mirror of
https://github.com/orhun/git-cliff.git
synced 2024-10-03 18:27:14 +03:00
feat(bitbucket): add Bitbucket support (#663)
* Draft: feat(bitbucket): Closes #566 Signed-off-by: dark0dave <dark0dave@mykolab.com> * refactor: improve bitbucket integration --------- Signed-off-by: dark0dave <dark0dave@mykolab.com> Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
This commit is contained in:
parent
2e1ed71876
commit
8ffc0548fe
4
.github/ISSUE_TEMPLATE/integration.yml
vendored
4
.github/ISSUE_TEMPLATE/integration.yml
vendored
@ -1,5 +1,5 @@
|
||||
name: Integration ⚙️
|
||||
description: Report a bug or request a feature about GitHub/GitLab integration
|
||||
description: Report a bug or request a feature about an integration (e.g GitHub/GitLab/Bitbucket)
|
||||
labels: ["integration"]
|
||||
assignees:
|
||||
- orhun
|
||||
@ -8,7 +8,7 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
Please see https://git-cliff.org/docs/category/integration for more information about GitHub/GitLab integration.
|
||||
Please see https://git-cliff.org/docs/category/integration for more information about integrations.
|
||||
- type: checkboxes
|
||||
id: new-bug
|
||||
attributes:
|
||||
|
46
.github/fixtures/test-bitbucket-integration/cliff.toml
vendored
Normal file
46
.github/fixtures/test-bitbucket-integration/cliff.toml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
[remote.bitbucket]
|
||||
owner = "orhunp"
|
||||
repo = "git-cliff-readme-example"
|
||||
|
||||
[changelog]
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
## What's Changed
|
||||
{%- if version %} in {{ version }}{%- endif -%}
|
||||
{% for commit in commits %}
|
||||
* {{ commit.message | split(pat="\n") | first | trim }}\
|
||||
{% if commit.bitbucket.username %} by @{{ commit.bitbucket.username }}{%- endif -%}
|
||||
{% if commit.bitbucket.pr_number %} in #{{ commit.bitbucket.pr_number }}{%- endif %}
|
||||
{%- endfor -%}
|
||||
|
||||
{% if bitbucket.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||
{% raw %}\n{% endraw -%}
|
||||
## New Contributors
|
||||
{%- endif %}\
|
||||
{% for contributor in bitbucket.contributors | filter(attribute="is_first_time", value=true) %}
|
||||
* @{{ contributor.username }} made their first contribution
|
||||
{%- if contributor.pr_number %} in \
|
||||
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||
{%- endif %}
|
||||
{%- endfor -%}
|
||||
{% raw %}\n\n{% endraw -%}
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
<!-- generated by -cliff -->
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = false
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# process each line of a commit as an individual commit
|
||||
split_commits = false
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }]
|
||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||
protect_breaking_commits = false
|
6
.github/fixtures/test-bitbucket-integration/commit.sh
vendored
Executable file
6
.github/fixtures/test-bitbucket-integration/commit.sh
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
git remote add origin https://bitbucket.org/orhunp/git-cliff-readme-example
|
||||
git pull origin master
|
||||
git fetch --tags
|
16
.github/fixtures/test-bitbucket-integration/expected.md
vendored
Normal file
16
.github/fixtures/test-bitbucket-integration/expected.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
## What's Changed
|
||||
* feat(config): support multiple file formats by @orhun <orhun@archlinux.org>
|
||||
* feat(cache): use cache while fetching pages by @orhun <orhun@archlinux.org>
|
||||
|
||||
## What's Changed in v1.0.1
|
||||
* refactor(parser): expose string functions by @orhun <orhun@archlinux.org>
|
||||
* chore(release): add release script by @orhun <orhun@archlinux.org>
|
||||
|
||||
## What's Changed in v1.0.0
|
||||
* Initial commit by @orhun <orhun@archlinux.org>
|
||||
* docs(project): add README.md by @orhun <orhun@archlinux.org>
|
||||
* feat(parser): add ability to parse arrays by @orhun <orhun@archlinux.org>
|
||||
* fix(args): rename help argument due to conflict by @orhun <orhun@archlinux.org>
|
||||
* docs(example)!: add tested usage example by @orhun <orhun@archlinux.org>
|
||||
|
||||
<!-- generated by -cliff -->
|
@ -11,7 +11,7 @@ COPY --from=planner /app/recipe.json recipe.json
|
||||
ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
COPY . .
|
||||
RUN cargo build --release --locked --no-default-features --features github --features gitlab
|
||||
RUN cargo build --release --locked --no-default-features --features github --features gitlab --features bitbucket
|
||||
RUN rm -f target/release/deps/git_cliff*
|
||||
|
||||
FROM debian:buster-slim as runner
|
||||
|
@ -37,6 +37,16 @@ gitlab = [
|
||||
"dep:tokio",
|
||||
"dep:futures",
|
||||
]
|
||||
## Enable integration with Bitbucket.
|
||||
## You can turn this off if you don't use Bitbucket and don't want
|
||||
## to make network requests to the Bitbucket API.
|
||||
bitbucket = [
|
||||
"dep:reqwest",
|
||||
"dep:http-cache-reqwest",
|
||||
"dep:reqwest-middleware",
|
||||
"dep:tokio",
|
||||
"dep:futures",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
glob = { workspace = true, optional = true }
|
||||
|
@ -8,6 +8,8 @@ use crate::release::{
|
||||
Release,
|
||||
Releases,
|
||||
};
|
||||
#[cfg(feature = "bitbucket")]
|
||||
use crate::remote::bitbucket::BitbucketClient;
|
||||
#[cfg(feature = "github")]
|
||||
use crate::remote::github::GitHubClient;
|
||||
#[cfg(feature = "gitlab")]
|
||||
@ -297,6 +299,62 @@ impl<'a> Changelog<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Bitbucket metadata needed for the changelog.
|
||||
///
|
||||
/// This function creates a multithread async runtime for handling the
|
||||
///
|
||||
/// requests. The following are fetched from the bitbucket REST API:
|
||||
///
|
||||
/// - Commits
|
||||
/// - Pull requests
|
||||
///
|
||||
/// Each of these are paginated requests so they are being run in parallel
|
||||
/// for speedup.
|
||||
///
|
||||
///
|
||||
/// If no bitbucket related variable is used in the template then this
|
||||
/// function returns empty vectors.
|
||||
#[cfg(feature = "bitbucket")]
|
||||
fn get_bitbucket_metadata(&self) -> Result<crate::remote::RemoteMetadata> {
|
||||
use crate::remote::bitbucket;
|
||||
if self
|
||||
.body_template
|
||||
.contains_variable(bitbucket::TEMPLATE_VARIABLES) ||
|
||||
self.footer_template
|
||||
.as_ref()
|
||||
.map(|v| v.contains_variable(bitbucket::TEMPLATE_VARIABLES))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
warn!("You are using an experimental feature! Please report bugs at <https://git-cliff.org/issues>");
|
||||
let bitbucket_client =
|
||||
BitbucketClient::try_from(self.config.remote.bitbucket.clone())?;
|
||||
info!(
|
||||
"{} ({})",
|
||||
bitbucket::START_FETCHING_MSG,
|
||||
self.config.remote.bitbucket
|
||||
);
|
||||
let data = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(async {
|
||||
let (commits, pull_requests) = tokio::try_join!(
|
||||
bitbucket_client.get_commits(),
|
||||
bitbucket_client.get_pull_requests()
|
||||
)?;
|
||||
debug!("Number of Bitbucket commits: {}", commits.len());
|
||||
debug!(
|
||||
"Number of Bitbucket pull requests: {}",
|
||||
pull_requests.len()
|
||||
);
|
||||
Ok((commits, pull_requests))
|
||||
});
|
||||
info!("{}", bitbucket::FINISHED_FETCHING_MSG);
|
||||
data
|
||||
} else {
|
||||
Ok((vec![], vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Increments the version for the unreleased changes based on semver.
|
||||
pub fn bump_version(&mut self) -> Result<Option<String>> {
|
||||
if let Some(ref mut last_release) = self.releases.iter_mut().next() {
|
||||
@ -337,6 +395,14 @@ impl<'a> Changelog<'a> {
|
||||
} else {
|
||||
(vec![], vec![])
|
||||
};
|
||||
#[cfg(feature = "bitbucket")]
|
||||
let (bitbucket_commits, bitbucket_pull_request) =
|
||||
if self.config.remote.bitbucket.is_set() {
|
||||
self.get_bitbucket_metadata()
|
||||
.expect("Could not get bitbucket metadata")
|
||||
} else {
|
||||
(vec![], vec![])
|
||||
};
|
||||
let postprocessors = self
|
||||
.config
|
||||
.changelog
|
||||
@ -363,6 +429,11 @@ impl<'a> Changelog<'a> {
|
||||
gitlab_commits.clone(),
|
||||
gitlab_merge_request.clone(),
|
||||
)?;
|
||||
#[cfg(feature = "bitbucket")]
|
||||
release.update_bitbucket_metadata(
|
||||
bitbucket_commits.clone(),
|
||||
bitbucket_pull_request.clone(),
|
||||
)?;
|
||||
let write_result = write!(
|
||||
out,
|
||||
"{}",
|
||||
@ -601,12 +672,17 @@ mod test {
|
||||
limit_commits: None,
|
||||
},
|
||||
remote: RemoteConfig {
|
||||
github: Remote {
|
||||
github: Remote {
|
||||
owner: String::from("coolguy"),
|
||||
repo: String::from("awesome"),
|
||||
token: None,
|
||||
},
|
||||
gitlab: Remote {
|
||||
gitlab: Remote {
|
||||
owner: String::from("coolguy"),
|
||||
repo: String::from("awesome"),
|
||||
token: None,
|
||||
},
|
||||
bitbucket: Remote {
|
||||
owner: String::from("coolguy"),
|
||||
repo: String::from("awesome"),
|
||||
token: None,
|
||||
@ -685,6 +761,10 @@ mod test {
|
||||
gitlab: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
#[cfg(feature = "bitbucket")]
|
||||
bitbucket: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
};
|
||||
let releases = vec![
|
||||
test_release.clone(),
|
||||
@ -736,6 +816,10 @@ mod test {
|
||||
gitlab: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
#[cfg(feature = "bitbucket")]
|
||||
bitbucket: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
},
|
||||
];
|
||||
(config, releases)
|
||||
|
@ -128,6 +128,9 @@ pub struct Commit<'a> {
|
||||
/// GitLab metadata of the commit.
|
||||
#[cfg(feature = "gitlab")]
|
||||
pub gitlab: crate::remote::RemoteContributor,
|
||||
/// Bitbucket metadata of the commit.
|
||||
#[cfg(feature = "bitbucket")]
|
||||
pub bitbucket: crate::remote::RemoteContributor,
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Commit<'a> {
|
||||
@ -443,6 +446,8 @@ impl Serialize for Commit<'_> {
|
||||
commit.serialize_field("github", &self.github)?;
|
||||
#[cfg(feature = "gitlab")]
|
||||
commit.serialize_field("gitlab", &self.gitlab)?;
|
||||
#[cfg(feature = "bitbucket")]
|
||||
commit.serialize_field("bitbucket", &self.bitbucket)?;
|
||||
commit.end()
|
||||
}
|
||||
}
|
||||
|
@ -122,10 +122,13 @@ pub struct GitConfig {
|
||||
pub struct RemoteConfig {
|
||||
/// GitHub remote.
|
||||
#[serde(default)]
|
||||
pub github: Remote,
|
||||
pub github: Remote,
|
||||
/// GitLab remote.
|
||||
#[serde(default)]
|
||||
pub gitlab: Remote,
|
||||
pub gitlab: Remote,
|
||||
/// Bitbucket remote.
|
||||
#[serde(default)]
|
||||
pub bitbucket: Remote,
|
||||
}
|
||||
|
||||
/// A single remote.
|
||||
|
@ -77,17 +77,17 @@ pub enum Error {
|
||||
SemverError(#[from] semver::Error),
|
||||
/// The errors that may occur when processing a HTTP request.
|
||||
#[error("HTTP client error: `{0}`")]
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
HttpClientError(#[from] reqwest::Error),
|
||||
/// The errors that may occur while constructing the HTTP client with
|
||||
/// middleware.
|
||||
#[error("HTTP client with middleware error: `{0}`")]
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
HttpClientMiddlewareError(#[from] reqwest_middleware::Error),
|
||||
/// A possible error when converting a HeaderValue from a string or byte
|
||||
/// slice.
|
||||
#[error("HTTP header error: `{0}`")]
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
HttpHeaderError(#[from] reqwest::header::InvalidHeaderValue),
|
||||
/// Error that may occur during handling pages.
|
||||
#[error("Pagination error: `{0}`")]
|
||||
|
@ -27,7 +27,7 @@ pub mod error;
|
||||
/// Common release type.
|
||||
pub mod release;
|
||||
/// Remote handler.
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub mod remote;
|
||||
/// Git repository.
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commit::Commit;
|
||||
use crate::config::Bump;
|
||||
use crate::error::Result;
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
use crate::remote::{
|
||||
RemoteCommit,
|
||||
RemoteContributor,
|
||||
@ -36,6 +36,9 @@ pub struct Release<'a> {
|
||||
/// Contributors.
|
||||
#[cfg(feature = "gitlab")]
|
||||
pub gitlab: RemoteReleaseMetadata,
|
||||
/// Contributors.
|
||||
#[cfg(feature = "bitbucket")]
|
||||
pub bitbucket: RemoteReleaseMetadata,
|
||||
}
|
||||
|
||||
#[cfg(feature = "github")]
|
||||
@ -44,6 +47,9 @@ crate::update_release_metadata!(github, update_github_metadata);
|
||||
#[cfg(feature = "gitlab")]
|
||||
crate::update_release_metadata!(gitlab, update_gitlab_metadata);
|
||||
|
||||
#[cfg(feature = "bitbucket")]
|
||||
crate::update_release_metadata!(bitbucket, update_bitbucket_metadata);
|
||||
|
||||
impl<'a> Release<'a> {
|
||||
/// Calculates the next version based on the commits.
|
||||
///
|
||||
@ -156,6 +162,10 @@ mod test {
|
||||
gitlab: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
#[cfg(feature = "bitbucket")]
|
||||
bitbucket: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,6 +351,9 @@ mod test {
|
||||
gitlab: RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
bitbucket: RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
};
|
||||
release.update_github_metadata(
|
||||
vec![
|
||||
@ -617,6 +630,9 @@ mod test {
|
||||
gitlab: RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
bitbucket: RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
};
|
||||
release.update_gitlab_metadata(
|
||||
vec![
|
||||
|
222
git-cliff-core/src/remote/bitbucket.rs
Normal file
222
git-cliff-core/src/remote/bitbucket.rs
Normal file
@ -0,0 +1,222 @@
|
||||
use crate::config::Remote;
|
||||
use crate::error::*;
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
use std::env;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Bitbucket REST API url.
|
||||
const BITBUCKET_API_URL: &str = "https://api.bitbucket.org/2.0/repositories";
|
||||
|
||||
/// Environment variable for overriding the Bitbucket REST API url.
|
||||
const BITBUCKET_API_URL_ENV: &str = "BITBUCKET_API_URL";
|
||||
|
||||
/// Log message to show while fetching data from Bitbucket.
|
||||
pub const START_FETCHING_MSG: &str = "Retrieving data from Bitbucket...";
|
||||
|
||||
/// Log message to show when done fetching from Bitbucket.
|
||||
pub const FINISHED_FETCHING_MSG: &str = "Done fetching Bitbucket data.";
|
||||
|
||||
/// Template variables related to this remote.
|
||||
pub(crate) const TEMPLATE_VARIABLES: &[&str] = &["bitbucket", "commit.bitbucket"];
|
||||
|
||||
/// Maximum number of entries to fetch for bitbucket pull requests.
|
||||
pub(crate) const BITBUCKET_MAX_PAGE_PRS: usize = 50;
|
||||
|
||||
/// Representation of a single commit.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BitbucketCommit {
|
||||
/// SHA.
|
||||
pub hash: String,
|
||||
/// Author of the commit.
|
||||
pub author: Option<BitbucketCommitAuthor>,
|
||||
}
|
||||
|
||||
impl RemoteCommit for BitbucketCommit {
|
||||
fn id(&self) -> String {
|
||||
self.hash.clone()
|
||||
}
|
||||
|
||||
fn username(&self) -> Option<String> {
|
||||
self.author.clone().and_then(|v| v.login)
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-commits-get>
|
||||
impl RemoteEntry for BitbucketPagination<BitbucketCommit> {
|
||||
fn url(_id: i64, api_url: &str, remote: &Remote, page: i32) -> String {
|
||||
let commit_page = page + 1;
|
||||
format!(
|
||||
"{}/{}/{}/commits?pagelen={MAX_PAGE_SIZE}&page={commit_page}",
|
||||
api_url, remote.owner, remote.repo
|
||||
)
|
||||
}
|
||||
|
||||
fn buffer_size() -> usize {
|
||||
10
|
||||
}
|
||||
|
||||
fn early_exit(&self) -> bool {
|
||||
self.values.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Bitbucket Pagination Header
|
||||
///
|
||||
/// <https://developer.atlassian.com/cloud/bitbucket/rest/intro/#pagination>
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BitbucketPagination<T> {
|
||||
/// Total number of objects in the response.
|
||||
pub size: Option<i64>,
|
||||
/// Page number of the current results.
|
||||
pub page: Option<i64>,
|
||||
/// Current number of objects on the existing page. Globally, the minimum
|
||||
/// length is 10 and the maximum is 100.
|
||||
pub pagelen: Option<i64>,
|
||||
/// Link to the next page if it exists.
|
||||
pub next: Option<String>,
|
||||
/// Link to the previous page if it exists.
|
||||
pub previous: Option<String>,
|
||||
/// List of Objects.
|
||||
pub values: Vec<T>,
|
||||
}
|
||||
|
||||
/// Author of the commit.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BitbucketCommitAuthor {
|
||||
/// Username.
|
||||
#[serde(rename = "raw")]
|
||||
pub login: Option<String>,
|
||||
}
|
||||
|
||||
/// Label of the pull request.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PullRequestLabel {
|
||||
/// Name of the label.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Representation of a single pull request's merge commit
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BitbucketPullRequestMergeCommit {
|
||||
/// SHA of the merge commit.
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
/// Representation of a single pull request.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BitbucketPullRequest {
|
||||
/// Pull request number.
|
||||
pub id: i64,
|
||||
/// Pull request title.
|
||||
pub title: Option<String>,
|
||||
/// Bitbucket Pull Request Merge Commit
|
||||
pub merge_commit_sha: BitbucketPullRequestMergeCommit,
|
||||
/// Author of Pull Request
|
||||
pub author: BitbucketCommitAuthor,
|
||||
}
|
||||
|
||||
impl RemotePullRequest for BitbucketPullRequest {
|
||||
fn number(&self) -> i64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn title(&self) -> Option<String> {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
fn labels(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn merge_commit(&self) -> Option<String> {
|
||||
Some(self.merge_commit_sha.hash.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-get>
|
||||
impl RemoteEntry for BitbucketPagination<BitbucketPullRequest> {
|
||||
fn url(_id: i64, api_url: &str, remote: &Remote, page: i32) -> String {
|
||||
let pr_page = page + 1;
|
||||
format!(
|
||||
"{}/{}/{}/pullrequests?&pagelen={BITBUCKET_MAX_PAGE_PRS}&\
|
||||
page={pr_page}&state=MERGED",
|
||||
api_url, remote.owner, remote.repo
|
||||
)
|
||||
}
|
||||
|
||||
fn buffer_size() -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
fn early_exit(&self) -> bool {
|
||||
self.values.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP client for handling Bitbucket REST API requests.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BitbucketClient {
|
||||
/// Remote.
|
||||
remote: Remote,
|
||||
/// HTTP client.
|
||||
client: ClientWithMiddleware,
|
||||
}
|
||||
|
||||
/// Constructs a Bitbucket client from the remote configuration.
|
||||
impl TryFrom<Remote> for BitbucketClient {
|
||||
type Error = Error;
|
||||
fn try_from(remote: Remote) -> Result<Self> {
|
||||
Ok(Self {
|
||||
client: create_remote_client(&remote, "application/json")?,
|
||||
remote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteClient for BitbucketClient {
|
||||
fn api_url() -> String {
|
||||
env::var(BITBUCKET_API_URL_ENV)
|
||||
.ok()
|
||||
.unwrap_or_else(|| BITBUCKET_API_URL.to_string())
|
||||
}
|
||||
|
||||
fn remote(&self) -> Remote {
|
||||
self.remote.clone()
|
||||
}
|
||||
|
||||
fn client(&self) -> ClientWithMiddleware {
|
||||
self.client.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl BitbucketClient {
|
||||
/// Fetches the Bitbucket API and returns the commits.
|
||||
pub async fn get_commits(&self) -> Result<Vec<Box<dyn RemoteCommit>>> {
|
||||
Ok(self
|
||||
.fetch_with_early_exit::<BitbucketPagination<BitbucketCommit>>(0)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|v| v.values)
|
||||
.map(|v| Box::new(v) as Box<dyn RemoteCommit>)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Fetches the Bitbucket API and returns the pull requests.
|
||||
pub async fn get_pull_requests(
|
||||
&self,
|
||||
) -> Result<Vec<Box<dyn RemotePullRequest>>> {
|
||||
Ok(self
|
||||
.fetch_with_early_exit::<BitbucketPagination<BitbucketPullRequest>>(0)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|v| v.values)
|
||||
.map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
|
||||
.collect())
|
||||
}
|
||||
}
|
@ -53,6 +53,10 @@ impl RemoteEntry for GitHubCommit {
|
||||
fn buffer_size() -> usize {
|
||||
10
|
||||
}
|
||||
|
||||
fn early_exit(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Author of the commit.
|
||||
@ -112,6 +116,10 @@ impl RemoteEntry for GitHubPullRequest {
|
||||
fn buffer_size() -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
fn early_exit(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP client for handling GitHub REST API requests.
|
||||
|
@ -49,9 +49,14 @@ impl RemoteEntry for GitLabProject {
|
||||
fn url(_id: i64, api_url: &str, remote: &Remote, _page: i32) -> String {
|
||||
format!("{}/projects/{}%2F{}", api_url, remote.owner, remote.repo)
|
||||
}
|
||||
|
||||
fn buffer_size() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn early_exit(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a single commit.
|
||||
@ -109,6 +114,10 @@ impl RemoteEntry for GitLabCommit {
|
||||
fn buffer_size() -> usize {
|
||||
10
|
||||
}
|
||||
|
||||
fn early_exit(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a single pull request.
|
||||
@ -174,6 +183,10 @@ impl RemoteEntry for GitLabMergeRequest {
|
||||
fn buffer_size() -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
fn early_exit(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a GitLab User.
|
||||
@ -213,7 +226,7 @@ pub struct GitLabClient {
|
||||
client: ClientWithMiddleware,
|
||||
}
|
||||
|
||||
/// Constructs a GitHub client from the remote configuration.
|
||||
/// Constructs a GitLab client from the remote configuration.
|
||||
impl TryFrom<Remote> for GitLabClient {
|
||||
type Error = Error;
|
||||
fn try_from(remote: Remote) -> Result<Self> {
|
||||
@ -243,7 +256,7 @@ impl RemoteClient for GitLabClient {
|
||||
impl GitLabClient {
|
||||
/// Fetches the GitLab API and returns the pull requests.
|
||||
pub async fn get_project(&self) -> Result<GitLabProject> {
|
||||
self.get_entry::<GitLabProject>().await
|
||||
self.get_entry::<GitLabProject>(0, 1).await
|
||||
}
|
||||
|
||||
/// Fetches the GitLab API and returns the commits.
|
||||
|
@ -6,6 +6,10 @@ pub mod github;
|
||||
#[cfg(feature = "gitlab")]
|
||||
pub mod gitlab;
|
||||
|
||||
/// Bitbucket client.
|
||||
#[cfg(feature = "bitbucket")]
|
||||
pub mod bitbucket;
|
||||
|
||||
use crate::config::Remote;
|
||||
use crate::error::{
|
||||
Error,
|
||||
@ -66,6 +70,8 @@ pub trait RemoteEntry {
|
||||
fn url(project_id: i64, api_url: &str, remote: &Remote, page: i32) -> String;
|
||||
/// Returns the request buffer size.
|
||||
fn buffer_size() -> usize;
|
||||
/// Whether if exit early.
|
||||
fn early_exit(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Trait for handling remote commits.
|
||||
@ -178,6 +184,32 @@ pub trait RemoteClient {
|
||||
/// Returns the HTTP client for making requests.
|
||||
fn client(&self) -> ClientWithMiddleware;
|
||||
|
||||
/// Returns true if the client should early exit.
|
||||
fn early_exit<T: DeserializeOwned + RemoteEntry>(&self, page: &T) -> bool {
|
||||
page.early_exit()
|
||||
}
|
||||
|
||||
/// Retrieves a single object.
|
||||
async fn get_entry<T: DeserializeOwned + RemoteEntry>(
|
||||
&self,
|
||||
project_id: i64,
|
||||
page: i32,
|
||||
) -> Result<T> {
|
||||
let url = T::url(project_id, &Self::api_url(), &self.remote(), page);
|
||||
debug!("Sending request to: {url}");
|
||||
let response = self.client().get(&url).send().await?;
|
||||
let response_text = if response.status().is_success() {
|
||||
let text = response.text().await?;
|
||||
trace!("Response: {:?}", text);
|
||||
text
|
||||
} else {
|
||||
let text = response.text().await?;
|
||||
error!("Request error: {}", text);
|
||||
text
|
||||
};
|
||||
Ok(serde_json::from_str::<T>(&response_text)?)
|
||||
}
|
||||
|
||||
/// Retrieves a single page of entries.
|
||||
async fn get_entries_with_page<T: DeserializeOwned + RemoteEntry>(
|
||||
&self,
|
||||
@ -205,6 +237,8 @@ pub trait RemoteClient {
|
||||
}
|
||||
|
||||
/// Fetches the remote API and returns the given entry.
|
||||
///
|
||||
/// See `fetch_with_early_exit` for the early exit version of this method.
|
||||
async fn fetch<T: DeserializeOwned + RemoteEntry>(
|
||||
&self,
|
||||
project_id: i64,
|
||||
@ -230,21 +264,36 @@ pub trait RemoteClient {
|
||||
Ok(entries.into_iter().flatten().collect())
|
||||
}
|
||||
|
||||
/// Retrieves a single object.
|
||||
async fn get_entry<T: DeserializeOwned + RemoteEntry>(&self) -> Result<T> {
|
||||
let url = T::url(0, &Self::api_url(), &self.remote(), 1);
|
||||
debug!("Sending request to: {url}");
|
||||
let response = self.client().get(&url).send().await?;
|
||||
let response_text = if response.status().is_success() {
|
||||
let text = response.text().await?;
|
||||
trace!("Response: {:?}", text);
|
||||
text
|
||||
} else {
|
||||
let text = response.text().await?;
|
||||
error!("Request error: {}", text);
|
||||
text
|
||||
};
|
||||
Ok(serde_json::from_str::<T>(&response_text)?)
|
||||
/// Fetches the remote API and returns the given entry.
|
||||
///
|
||||
/// Early exits based on the response.
|
||||
async fn fetch_with_early_exit<T: DeserializeOwned + RemoteEntry>(
|
||||
&self,
|
||||
project_id: i64,
|
||||
) -> Result<Vec<T>> {
|
||||
let entries: Vec<T> = stream::iter(0..)
|
||||
.map(|i| self.get_entry::<T>(project_id, i))
|
||||
.buffered(T::buffer_size())
|
||||
.take_while(|page| {
|
||||
let status = match page {
|
||||
Ok(v) => !self.early_exit(v),
|
||||
Err(e) => {
|
||||
debug!("Error while fetching page: {:?}", e);
|
||||
true
|
||||
}
|
||||
};
|
||||
future::ready(status && page.is_ok())
|
||||
})
|
||||
.map(|page| match page {
|
||||
Ok(v) => v,
|
||||
Err(ref e) => {
|
||||
log::error!("{:#?}", e);
|
||||
page.expect("failed to fetch page: {}")
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
Ok(entries)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ impl Template {
|
||||
}
|
||||
|
||||
/// Returns `true` if the template contains one of the given variables.
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
pub(crate) fn contains_variable(&self, variables: &[&str]) -> bool {
|
||||
variables
|
||||
.iter()
|
||||
@ -232,6 +232,10 @@ mod test {
|
||||
gitlab: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
#[cfg(feature = "bitbucket")]
|
||||
bitbucket: crate::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
},
|
||||
Option::<HashMap<&str, String>>::None.as_ref(),
|
||||
&[TextProcessor {
|
||||
|
@ -196,6 +196,10 @@ fn generate_changelog() -> Result<()> {
|
||||
gitlab: git_cliff_core::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
#[cfg(feature = "bitbucket")]
|
||||
bitbucket: git_cliff_core::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
},
|
||||
Release {
|
||||
version: Some(String::from("v1.0.0")),
|
||||
@ -228,6 +232,10 @@ fn generate_changelog() -> Result<()> {
|
||||
gitlab: git_cliff_core::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
#[cfg(feature = "bitbucket")]
|
||||
bitbucket: git_cliff_core::remote::RemoteReleaseMetadata {
|
||||
contributors: vec![],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -23,13 +23,15 @@ path = "src/bin/mangen.rs"
|
||||
|
||||
[features]
|
||||
# check for new versions
|
||||
default = ["update-informer", "github", "gitlab"]
|
||||
default = ["update-informer", "github", "gitlab", "bitbucket"]
|
||||
# inform about new releases
|
||||
update-informer = ["dep:update-informer"]
|
||||
# enable GitHub integration
|
||||
github = ["git-cliff-core/github", "dep:indicatif"]
|
||||
# enable GitLab integration
|
||||
gitlab = ["git-cliff-core/gitlab", "dep:indicatif"]
|
||||
# enable Bitbucket integration
|
||||
bitbucket = ["git-cliff-core/bitbucket", "dep:indicatif"]
|
||||
|
||||
[dependencies]
|
||||
glob.workspace = true
|
||||
|
@ -64,7 +64,7 @@ pub struct Opt {
|
||||
help = "Prints help information",
|
||||
help_heading = "FLAGS"
|
||||
)]
|
||||
pub help: Option<bool>,
|
||||
pub help: Option<bool>,
|
||||
#[arg(
|
||||
short = 'V',
|
||||
long,
|
||||
@ -73,10 +73,10 @@ pub struct Opt {
|
||||
help = "Prints version information",
|
||||
help_heading = "FLAGS"
|
||||
)]
|
||||
pub version: Option<bool>,
|
||||
pub version: Option<bool>,
|
||||
/// Increases the logging verbosity.
|
||||
#[arg(short, long, action = ArgAction::Count, alias = "debug", help_heading = Some("FLAGS"))]
|
||||
pub verbose: u8,
|
||||
pub verbose: u8,
|
||||
/// Writes the default configuration file to cliff.toml
|
||||
#[arg(
|
||||
short,
|
||||
@ -85,7 +85,7 @@ pub struct Opt {
|
||||
num_args = 0..=1,
|
||||
required = false
|
||||
)]
|
||||
pub init: Option<Option<String>>,
|
||||
pub init: Option<Option<String>>,
|
||||
/// Sets the configuration file.
|
||||
#[arg(
|
||||
short,
|
||||
@ -95,7 +95,7 @@ pub struct Opt {
|
||||
default_value = DEFAULT_CONFIG,
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub config: PathBuf,
|
||||
pub config: PathBuf,
|
||||
/// Sets the working directory.
|
||||
#[arg(
|
||||
short,
|
||||
@ -104,7 +104,7 @@ pub struct Opt {
|
||||
value_name = "PATH",
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub workdir: Option<PathBuf>,
|
||||
pub workdir: Option<PathBuf>,
|
||||
/// Sets the git repository.
|
||||
#[arg(
|
||||
short,
|
||||
@ -114,7 +114,7 @@ pub struct Opt {
|
||||
num_args(1..),
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub repository: Option<Vec<PathBuf>>,
|
||||
pub repository: Option<Vec<PathBuf>>,
|
||||
/// Sets the path to include related commits.
|
||||
#[arg(
|
||||
long,
|
||||
@ -122,7 +122,7 @@ pub struct Opt {
|
||||
value_name = "PATTERN",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub include_path: Option<Vec<Pattern>>,
|
||||
pub include_path: Option<Vec<Pattern>>,
|
||||
/// Sets the path to exclude related commits.
|
||||
#[arg(
|
||||
long,
|
||||
@ -130,10 +130,10 @@ pub struct Opt {
|
||||
value_name = "PATTERN",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub exclude_path: Option<Vec<Pattern>>,
|
||||
pub exclude_path: Option<Vec<Pattern>>,
|
||||
/// Sets the regex for matching git tags.
|
||||
#[arg(long, env = "GIT_CLIFF_TAG_PATTERN", value_name = "PATTERN")]
|
||||
pub tag_pattern: Option<Regex>,
|
||||
pub tag_pattern: Option<Regex>,
|
||||
/// Sets custom commit messages to include in the changelog.
|
||||
#[arg(
|
||||
long,
|
||||
@ -141,7 +141,7 @@ pub struct Opt {
|
||||
value_name = "MSG",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub with_commit: Option<Vec<String>>,
|
||||
pub with_commit: Option<Vec<String>>,
|
||||
/// Sets commits that will be skipped in the changelog.
|
||||
#[arg(
|
||||
long,
|
||||
@ -149,7 +149,7 @@ pub struct Opt {
|
||||
value_name = "SHA1",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub skip_commit: Option<Vec<String>>,
|
||||
pub skip_commit: Option<Vec<String>>,
|
||||
/// Prepends entries to the given changelog file.
|
||||
#[arg(
|
||||
short,
|
||||
@ -158,7 +158,7 @@ pub struct Opt {
|
||||
value_name = "PATH",
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub prepend: Option<PathBuf>,
|
||||
pub prepend: Option<PathBuf>,
|
||||
/// Writes output to the given file.
|
||||
#[arg(
|
||||
short,
|
||||
@ -169,7 +169,7 @@ pub struct Opt {
|
||||
num_args = 0..=1,
|
||||
default_missing_value = DEFAULT_OUTPUT
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: Option<PathBuf>,
|
||||
/// Sets the tag for the latest version.
|
||||
#[arg(
|
||||
short,
|
||||
@ -178,13 +178,13 @@ pub struct Opt {
|
||||
value_name = "TAG",
|
||||
allow_hyphen_values = true
|
||||
)]
|
||||
pub tag: Option<String>,
|
||||
pub tag: Option<String>,
|
||||
/// Bumps the version for unreleased changes.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub bump: bool,
|
||||
pub bump: bool,
|
||||
/// Prints bumped version for unreleased changes.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub bumped_version: bool,
|
||||
pub bumped_version: bool,
|
||||
/// Sets the template for the changelog body.
|
||||
#[arg(
|
||||
short,
|
||||
@ -193,38 +193,38 @@ pub struct Opt {
|
||||
value_name = "TEMPLATE",
|
||||
allow_hyphen_values = true
|
||||
)]
|
||||
pub body: Option<String>,
|
||||
pub body: Option<String>,
|
||||
/// Processes the commits starting from the latest tag.
|
||||
#[arg(short, long, help_heading = Some("FLAGS"))]
|
||||
pub latest: bool,
|
||||
pub latest: bool,
|
||||
/// Processes the commits that belong to the current tag.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub current: bool,
|
||||
pub current: bool,
|
||||
/// Processes the commits that do not belong to a tag.
|
||||
#[arg(short, long, help_heading = Some("FLAGS"))]
|
||||
pub unreleased: bool,
|
||||
pub unreleased: bool,
|
||||
/// Sorts the tags topologically.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub topo_order: bool,
|
||||
pub topo_order: bool,
|
||||
/// Disables the external command execution.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub no_exec: bool,
|
||||
pub no_exec: bool,
|
||||
/// Prints changelog context as JSON.
|
||||
#[arg(short = 'x', long, help_heading = Some("FLAGS"))]
|
||||
pub context: bool,
|
||||
pub context: bool,
|
||||
/// Strips the given parts from the changelog.
|
||||
#[arg(short, long, value_name = "PART", value_enum)]
|
||||
pub strip: Option<Strip>,
|
||||
pub strip: Option<Strip>,
|
||||
/// Sets sorting of the commits inside sections.
|
||||
#[arg(
|
||||
long,
|
||||
value_enum,
|
||||
default_value_t = Sort::Oldest
|
||||
)]
|
||||
pub sort: Sort,
|
||||
pub sort: Sort,
|
||||
/// Sets the commit range to process.
|
||||
#[arg(value_name = "RANGE", help_heading = Some("ARGS"))]
|
||||
pub range: Option<String>,
|
||||
pub range: Option<String>,
|
||||
/// Sets the GitHub API token.
|
||||
#[arg(
|
||||
long,
|
||||
@ -233,7 +233,7 @@ pub struct Opt {
|
||||
hide_env_values = true,
|
||||
hide = !cfg!(feature = "github"),
|
||||
)]
|
||||
pub github_token: Option<SecretString>,
|
||||
pub github_token: Option<SecretString>,
|
||||
/// Sets the GitHub repository.
|
||||
#[arg(
|
||||
long,
|
||||
@ -242,7 +242,7 @@ pub struct Opt {
|
||||
value_name = "OWNER/REPO",
|
||||
hide = !cfg!(feature = "github"),
|
||||
)]
|
||||
pub github_repo: Option<RemoteValue>,
|
||||
pub github_repo: Option<RemoteValue>,
|
||||
/// Sets the GitLab API token.
|
||||
#[arg(
|
||||
long,
|
||||
@ -251,7 +251,7 @@ pub struct Opt {
|
||||
hide_env_values = true,
|
||||
hide = !cfg!(feature = "gitlab"),
|
||||
)]
|
||||
pub gitlab_token: Option<SecretString>,
|
||||
pub gitlab_token: Option<SecretString>,
|
||||
/// Sets the GitLab repository.
|
||||
#[arg(
|
||||
long,
|
||||
@ -260,7 +260,25 @@ pub struct Opt {
|
||||
value_name = "OWNER/REPO",
|
||||
hide = !cfg!(feature = "gitlab"),
|
||||
)]
|
||||
pub gitlab_repo: Option<RemoteValue>,
|
||||
pub gitlab_repo: Option<RemoteValue>,
|
||||
/// Sets the Bitbucket API token.
|
||||
#[arg(
|
||||
long,
|
||||
env = "BITBUCKET_TOKEN",
|
||||
value_name = "TOKEN",
|
||||
hide_env_values = true,
|
||||
hide = !cfg!(feature = "bitbucket"),
|
||||
)]
|
||||
pub bitbucket_token: Option<SecretString>,
|
||||
/// Sets the Bitbucket repository.
|
||||
#[arg(
|
||||
long,
|
||||
env = "BITBUCKET_REPO",
|
||||
value_parser = clap::value_parser!(RemoteValue),
|
||||
value_name = "OWNER/REPO",
|
||||
hide = !cfg!(feature = "bitbucket"),
|
||||
)]
|
||||
pub bitbucket_repo: Option<RemoteValue>,
|
||||
}
|
||||
|
||||
/// Custom type for the remote value.
|
||||
|
@ -134,6 +134,17 @@ fn process_repository<'a>(
|
||||
debug!("Failed to get remote from GitLab repository: {:?}", e);
|
||||
}
|
||||
}
|
||||
} else if !config.remote.bitbucket.is_set() {
|
||||
match repository.upstream_remote() {
|
||||
Ok(remote) => {
|
||||
debug!("No Bitbucket remote is set, using remote: {}", remote);
|
||||
config.remote.bitbucket.owner = remote.owner;
|
||||
config.remote.bitbucket.repo = remote.repo;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to get remote from Bitbucket repository: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print debug information about configuration and arguments.
|
||||
@ -426,6 +437,13 @@ pub fn run(mut args: Opt) -> Result<()> {
|
||||
if args.gitlab_token.is_some() {
|
||||
config.remote.gitlab.token.clone_from(&args.gitlab_token);
|
||||
}
|
||||
if args.bitbucket_token.is_some() {
|
||||
config
|
||||
.remote
|
||||
.bitbucket
|
||||
.token
|
||||
.clone_from(&args.bitbucket_token);
|
||||
}
|
||||
if let Some(ref remote) = args.github_repo {
|
||||
config.remote.github.owner = remote.0.owner.to_string();
|
||||
config.remote.github.repo = remote.0.repo.to_string();
|
||||
@ -434,6 +452,10 @@ pub fn run(mut args: Opt) -> Result<()> {
|
||||
config.remote.gitlab.owner = remote.0.owner.to_string();
|
||||
config.remote.gitlab.repo = remote.0.repo.to_string();
|
||||
}
|
||||
if let Some(ref remote) = args.bitbucket_repo {
|
||||
config.remote.bitbucket.owner = remote.0.owner.to_string();
|
||||
config.remote.bitbucket.repo = remote.0.repo.to_string();
|
||||
}
|
||||
if args.no_exec {
|
||||
if let Some(ref mut preprocessors) = config.git.commit_preprocessors {
|
||||
preprocessors
|
||||
|
@ -10,7 +10,7 @@ use git_cliff_core::error::{
|
||||
Error,
|
||||
Result,
|
||||
};
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
use indicatif::{
|
||||
ProgressBar,
|
||||
ProgressStyle,
|
||||
@ -66,7 +66,7 @@ fn colored_level(style: &mut Style, level: Level) -> StyledValue<'_, &'static st
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "github", feature = "gitlab"))]
|
||||
#[cfg(any(feature = "github", feature = "gitlab", feature = "bitbucket"))]
|
||||
lazy_static::lazy_static! {
|
||||
/// Lazily initialized progress bar.
|
||||
pub static ref PROGRESS_BAR: ProgressBar = {
|
||||
@ -144,6 +144,24 @@ pub fn init() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bitbucket")]
|
||||
{
|
||||
let message = record.args().to_string();
|
||||
if message
|
||||
.starts_with(git_cliff_core::remote::bitbucket::START_FETCHING_MSG)
|
||||
{
|
||||
PROGRESS_BAR
|
||||
.enable_steady_tick(std::time::Duration::from_millis(80));
|
||||
PROGRESS_BAR.set_message(message);
|
||||
return Ok(());
|
||||
} else if message.starts_with(
|
||||
git_cliff_core::remote::bitbucket::FINISHED_FETCHING_MSG,
|
||||
) {
|
||||
PROGRESS_BAR.finish_and_clear();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(f, " {} {} > {}", level, target, record.args())
|
||||
});
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
This section contains the Git remote related configuration options.
|
||||
|
||||
You can configure a remote for GitHub, GitLab or Bitbucket as follows:
|
||||
|
||||
```toml
|
||||
[remote.github]
|
||||
owner = "orhun"
|
||||
@ -9,24 +11,13 @@ repo = "git-cliff"
|
||||
token = ""
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
See the [GitHub integration](/docs/integration/github).
|
||||
|
||||
:::
|
||||
|
||||
Or if you are using GitLab:
|
||||
|
||||
```toml
|
||||
[remote.gitlab]
|
||||
owner = "orhun"
|
||||
repo = "git-cliff"
|
||||
token = ""
|
||||
```
|
||||
Change this to `remote.gitlab` or `remote.bitbucket` accordingly to your project.
|
||||
|
||||
:::tip
|
||||
|
||||
See the [GitLab integration](/docs/integration/gitlab).
|
||||
- See the [GitHub integration](/docs/integration/github).
|
||||
- See the [GitLab integration](/docs/integration/gitlab).
|
||||
- See the [Bitbucket integration](/docs/integration/bitbucket).
|
||||
|
||||
:::
|
||||
|
||||
@ -46,7 +37,7 @@ e.g.
|
||||
git cliff --github-repo orhun/git-cliff
|
||||
```
|
||||
|
||||
Same applies for GitLab with `--gitlab-repo` and `GITLAB_REPO` environment variables.
|
||||
Same applies for GitLab/Bitbucket with `--gitlab-repo`/`--bitbucket-repo` and `GITLAB_REPO`/`BITBUCKET_REPO` environment variables.
|
||||
|
||||
### token
|
||||
|
||||
@ -58,4 +49,4 @@ If you are using GitHub, then you can also pass this value via `--github-token`
|
||||
git cliff --github-token <TOKEN>
|
||||
```
|
||||
|
||||
Same applies for GitLab with `--gitlab-token` and `GITLAB_TOKEN` environment variables.
|
||||
Same applies for GitLab/Bitbucket with `--gitlab-token`/`--bitbucket-token` and `GITLAB_TOKEN`/`BITBUCKET_TOKEN` environment variables.
|
||||
|
@ -20,8 +20,12 @@ The minimum supported Rust version is `1.70.0`.
|
||||
|
||||
Also, **git-cliff** has the following feature flags which can be enabled via `--features` argument:
|
||||
|
||||
- `update-informer`: inform about the new releases of **git-cliff** (enabled as default)
|
||||
- `github`: enables the [GitHub integration](/docs/integration/github) (enabled as default)
|
||||
- `update-informer`: inform about the new releases of **git-cliff**
|
||||
- `github`: enables the [GitHub integration](/docs/integration/github)
|
||||
- `gitlab`: enables the [GitLab integration](/docs/integration/gitlab)
|
||||
- `bitbucket`: enables the [Bitbucket integration](/docs/integration/bitbucket)
|
||||
|
||||
All these features are enabled as default.
|
||||
|
||||
To install without these features:
|
||||
|
||||
|
179
website/docs/integration/bitbucket.md
Normal file
179
website/docs/integration/bitbucket.md
Normal file
@ -0,0 +1,179 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Bitbucket Integration 🆕
|
||||
|
||||
:::warning
|
||||
|
||||
This is still an experimental feature, please [report bugs](https://github.com/orhun/git-cliff/issues/new/choose).
|
||||
|
||||
:::
|
||||
|
||||
:::note
|
||||
|
||||
If you have built from source, enable the `bitbucket` feature flag for the integration to work.
|
||||
|
||||
:::
|
||||
|
||||
For projects hosted on Bitbucket, you can use **git-cliff** to add the following to your changelog:
|
||||
|
||||
- Bitbucket usernames
|
||||
- Contributors list (all contributors / first time)
|
||||
- Pull request links (associated with the commits)
|
||||
|
||||
## Setting up the remote
|
||||
|
||||
As default, remote upstream URL is automatically retrieved from the Git repository.
|
||||
|
||||
If that doesn't work or if you want to set a custom remote, there are a couple of ways of doing it:
|
||||
|
||||
- Use the [remote option](/docs/configuration/remote) in the configuration file:
|
||||
|
||||
```toml
|
||||
[remote.bitbucket]
|
||||
owner = "orhun"
|
||||
repo = "git-cliff"
|
||||
token = "***"
|
||||
```
|
||||
|
||||
- Use the `--bitbucket-repo` argument (takes values in `OWNER/REPO` format, e.g. "orhun/git-cliff")
|
||||
|
||||
- Use the `BITBUCKET_REPO` environment variable (same format as `--bitbucket-repo`)
|
||||
|
||||
## Authentication
|
||||
|
||||
:::tip
|
||||
|
||||
[Bitbucket REST API](https://developer.atlassian.com/cloud/bitbucket/rest/) is being used to retrieve data from Bitbucket and it has [rate limiting](https://support.atlassian.com/bitbucket-cloud/docs/api-request-limits/) rules.
|
||||
|
||||
You can follow [this guide](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#authentication) for creating an access token.
|
||||
|
||||
:::
|
||||
|
||||
To set an access token, you can use the [configuration file](/docs/configuration/remote) (not recommended), `--bitbucket-token` argument or `BITBUCKET_TOKEN` environment variable.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
BITBUCKET_TOKEN="***" git cliff --bitbucket-repo "orhun/git-cliff"
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
You can use the `BITBUCKET_API_URL` environment variable want to override the API URL. This is useful if you are using your own Bitbucket instance.
|
||||
|
||||
:::
|
||||
|
||||
## Templating
|
||||
|
||||
:::tip
|
||||
|
||||
See the [templating documentation](/docs/category/templating) for general information about how the template engine works.
|
||||
|
||||
:::
|
||||
|
||||
### Remote
|
||||
|
||||
You can use the following [context](/docs/templating/context) for adding the remote to the changelog:
|
||||
|
||||
```json
|
||||
{
|
||||
"bitbucket": {
|
||||
"owner": "orhun",
|
||||
"repo": "git-cliff"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```jinja2
|
||||
https://bitbucket.org/{{ remote.bitbucket.owner }}/{{ remote.bitbucket.repo }}/commits/tag/{{ version }}
|
||||
```
|
||||
|
||||
### Commit authors
|
||||
|
||||
For each commit, Bitbucket related values are added as a nested object (named `bitbucket`) to the [template context](/docs/templating/context):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "8edec7fd50f703811d55f14a3c5f0fd02b43d9e7",
|
||||
"message": "refactor(config): remove unnecessary newline from configs\n",
|
||||
"group": "🚜 Refactor",
|
||||
|
||||
"...": "<strip>",
|
||||
|
||||
"bitbucket": {
|
||||
"username": "orhun",
|
||||
"pr_title": "some things have changed",
|
||||
"pr_number": 420,
|
||||
"pr_labels": ["rust"],
|
||||
"is_first_time": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This can be used in the template as follows:
|
||||
|
||||
```
|
||||
{% for commit in commits %}
|
||||
* {{ commit.message | split(pat="\n") | first | trim }}\
|
||||
{% if commit.bitbucket.username %} by @{{ commit.bitbucket.username }}{%- endif %}\
|
||||
{% if commit.bitbucket.pr_number %} in #{{ commit.bitbucket.pr_number }}{%- endif %}
|
||||
{%- endfor -%}
|
||||
```
|
||||
|
||||
The will result in:
|
||||
|
||||
```md
|
||||
- feat(commit): add merge_commit flag to the context by @orhun in #389
|
||||
- feat(args): set `CHANGELOG.md` as default missing value for output option by @sh-cho in #354
|
||||
```
|
||||
|
||||
### Contributors
|
||||
|
||||
For each release, following contributors data is added to the [template context](/docs/templating/context) as a nested object:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "v1.4.0",
|
||||
"commits": [],
|
||||
"commit_id": "0af9eb24888d1a8c9b2887fbe5427985582a0f26",
|
||||
"timestamp": 0,
|
||||
"previous": null,
|
||||
"bitbucket": {
|
||||
"contributors": [
|
||||
{
|
||||
"username": "orhun",
|
||||
"pr_title": "some things have changed",
|
||||
"pr_number": 420,
|
||||
"pr_labels": ["rust"],
|
||||
"is_first_time": true
|
||||
},
|
||||
{
|
||||
"username": "cliffjumper",
|
||||
"pr_title": "I love jumping",
|
||||
"pr_number": 999,
|
||||
"pr_labels": ["rust"],
|
||||
"is_first_time": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This can be used in the template as follows:
|
||||
|
||||
```
|
||||
{% for contributor in bitbucket.contributors | filter(attribute="is_first_time", value=true) %}
|
||||
* @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }}
|
||||
{%- endfor -%}
|
||||
```
|
||||
|
||||
The will result in:
|
||||
|
||||
```md
|
||||
- @orhun made their first contribution in #420
|
||||
- @cliffjumper made their first contribution in #999
|
||||
```
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Python
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Rust/Cargo
|
||||
|
Loading…
Reference in New Issue
Block a user