1
1
mirror of https://github.com/orhun/git-cliff.git synced 2024-09-11 15:05:30 +03:00

Compare commits

...

5 Commits

Author SHA1 Message Date
dependabot[bot]
ea800afd14
Merge f4643755de into 8430c5c539 2024-08-06 20:08:36 +00:00
dependabot[bot]
f4643755de
chore(deps): bump serde from 1.0.203 to 1.0.204
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.203 to 1.0.204.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.203...v1.0.204)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-06 20:08:35 +00:00
Amin Yahyaabadi
8430c5c539
perf(changelog): cache commit retain checks (258 times faster generation) (#772)
* perf: cache commit retain checks + 87 times faster generation

This caches the commit retain checks whenever include/exclude paths are specified.

This speeds up changelog generation by `87` times in a big repository I have:
```
Now: 0.237 s
Before:  20.633 s
```

* perf: use separate retain_commit cache for each repository

* fix: use cacache for parallel git-local cache - 258 times faster

Sled fails to open the cache if multiple git-cliff processes run on the same repository. This fixes the issue by using cacache which stores the cache locally under .git/git-cliff

* fix: cache changed files of a commit directly

This slows down the retain_commit check by ~2 times, but allows reusing the cache in case the include/exclude patterns change.
This is beneficial for running in monorepos where git-cliff runs multiple times to generate changelog for different subsets of the repo

* fix: consider include/exclude patterns together

* fix: handle first commit for changed_files

* fix: normalize glob patterns to ignore ./

* docs: add docs for retain_commit_check and cache algorithms

* fix: add ** if a pattern represents a directory

* test: add tests for should retain commit checks

* fix: correctly get the files list for the first commit

* test: add test email/name for git commits

* fix: use / for path joining in entries to match the git behaviour

* fix: revert change of include/exclude patterns to slices

The slices are normalized, so an owned pattern is needed to avoid type mismatch between new normalized/unchanged patterns

* refactor: polish implementation

* refactor: apply clippy suggestion

---------

Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-06 23:04:17 +03:00
Orhun Parmaksız
2471745e11
fix(config): allow using environment variables without config file present (#783) 2024-08-06 15:58:35 +03:00
Orhun Parmaksız
4b33e7e986
feat(remote): activate integration if remote is set manually (#782) 2024-08-04 22:35:32 +03:00
7 changed files with 488 additions and 75 deletions

44
Cargo.lock generated
View File

@ -188,6 +188,25 @@ dependencies = [
"serde",
]
[[package]]
name = "bincode"
version = "2.0.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95"
dependencies = [
"bincode_derive",
"serde",
]
[[package]]
name = "bincode_derive"
version = "2.0.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c"
dependencies = [
"virtue",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -839,6 +858,8 @@ dependencies = [
name = "git-cliff-core"
version = "2.4.0"
dependencies = [
"bincode 2.0.0-rc.3",
"cacache",
"config",
"dirs",
"document-features",
@ -864,6 +885,7 @@ dependencies = [
"serde",
"serde_json",
"serde_regex",
"temp-dir",
"tera",
"thiserror",
"tokio",
@ -1033,7 +1055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6ffb12b95bb2a369fe47ca8924016c72c2fa0e6059ba98bd1516f558696c5a8"
dependencies = [
"async-trait",
"bincode",
"bincode 1.3.3",
"cacache",
"http 1.1.0",
"http-cache-semantics",
@ -2400,18 +2422,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
@ -2668,6 +2690,12 @@ dependencies = [
"libc",
]
[[package]]
name = "temp-dir"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f227968ec00f0e5322f9b8173c7a0cbcff6181a0a5b28e9892491c286277231"
[[package]]
name = "tempfile"
version = "3.10.1"
@ -3139,6 +3167,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "virtue"
version = "0.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"
[[package]]
name = "walkdir"
version = "2.5.0"

View File

@ -50,8 +50,9 @@ secrecy.workspace = true
dirs.workspace = true
lazy_static.workspace = true
thiserror = "1.0.61"
serde = { version = "1.0.203", features = ["derive"] }
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.118"
bincode = "2.0.0-rc.3"
serde_regex = "1.1.0"
tera = "1.20.0"
indexmap = { version = "2.2.6", optional = true }
@ -75,6 +76,7 @@ futures = { version = "0.3.30", optional = true }
url = "2.5.2"
dyn-clone = "1.0.17"
urlencoding = "2.1.3"
cacache = { version = "13.0.0", features = ["mmap"], default-features = false }
[dependencies.git2]
version = "0.19.0"
@ -97,6 +99,7 @@ features = ["debug-embed", "compression"]
[dev-dependencies]
pretty_assertions = "1.4.0"
expect-test = "1.5.0"
temp-dir = "0.1.13"
[package.metadata.docs.rs]
all-features = true

View File

@ -206,9 +206,9 @@ impl<'a> Changelog<'a> {
#[cfg(feature = "github")]
fn get_github_metadata(&self) -> Result<crate::remote::RemoteMetadata> {
use crate::remote::github;
if self
.body_template
.contains_variable(github::TEMPLATE_VARIABLES) ||
if self.config.remote.github.is_custom ||
self.body_template
.contains_variable(github::TEMPLATE_VARIABLES) ||
self.footer_template
.as_ref()
.map(|v| v.contains_variable(github::TEMPLATE_VARIABLES))
@ -262,9 +262,9 @@ impl<'a> Changelog<'a> {
#[cfg(feature = "gitlab")]
fn get_gitlab_metadata(&self) -> Result<crate::remote::RemoteMetadata> {
use crate::remote::gitlab;
if self
.body_template
.contains_variable(gitlab::TEMPLATE_VARIABLES) ||
if self.config.remote.gitlab.is_custom ||
self.body_template
.contains_variable(gitlab::TEMPLATE_VARIABLES) ||
self.footer_template
.as_ref()
.map(|v| v.contains_variable(gitlab::TEMPLATE_VARIABLES))
@ -326,9 +326,9 @@ impl<'a> Changelog<'a> {
#[cfg(feature = "gitea")]
fn get_gitea_metadata(&self) -> Result<crate::remote::RemoteMetadata> {
use crate::remote::gitea;
if self
.body_template
.contains_variable(gitea::TEMPLATE_VARIABLES) ||
if self.config.remote.gitea.is_custom ||
self.body_template
.contains_variable(gitea::TEMPLATE_VARIABLES) ||
self.footer_template
.as_ref()
.map(|v| v.contains_variable(gitea::TEMPLATE_VARIABLES))
@ -379,9 +379,9 @@ impl<'a> Changelog<'a> {
#[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) ||
if self.config.remote.bitbucket.is_custom ||
self.body_template
.contains_variable(bitbucket::TEMPLATE_VARIABLES) ||
self.footer_template
.as_ref()
.map(|v| v.contains_variable(bitbucket::TEMPLATE_VARIABLES))
@ -791,24 +791,28 @@ mod test {
},
remote: RemoteConfig {
github: Remote {
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
is_custom: false,
},
gitlab: Remote {
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
is_custom: false,
},
gitea: Remote {
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
is_custom: false,
},
bitbucket: Remote {
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
owner: String::from("coolguy"),
repo: String::from("awesome"),
token: None,
is_custom: false,
},
},
bump: Bump::default(),

View File

@ -138,12 +138,20 @@ pub struct RemoteConfig {
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Remote {
/// Owner of the remote.
pub owner: String,
pub owner: String,
/// Repository name.
pub repo: String,
pub repo: String,
/// Access token.
#[serde(skip_serializing)]
pub token: Option<SecretString>,
pub token: Option<SecretString>,
/// Whether if the remote is set manually.
#[serde(skip_deserializing, default = "default_true")]
pub is_custom: bool,
}
/// Returns `true` for serde's `default` attribute.
fn default_true() -> bool {
true
}
impl fmt::Display for Remote {
@ -162,9 +170,10 @@ impl Remote {
/// Constructs a new instance.
pub fn new<S: Into<String>>(owner: S, repo: S) -> Self {
Self {
owner: owner.into(),
repo: repo.into(),
token: None,
owner: owner.into(),
repo: repo.into(),
token: None,
is_custom: false,
}
}

View File

@ -30,7 +30,7 @@ impl EmbeddedConfig {
///
/// [`Config`]: Config
pub fn parse() -> Result<Config> {
Ok(toml::from_str(&Self::get_config()?)?)
Config::parse_from_str(&Self::get_config()?)
}
}

View File

@ -11,6 +11,7 @@ use git2::{
Oid,
Repository as GitRepository,
Sort,
TreeWalkMode,
};
use glob::Pattern;
use indexmap::IndexMap;
@ -29,22 +30,33 @@ static TAG_SIGNATURE_REGEX: Lazy<Regex> = lazy_regex!(
r"(?s)-----BEGIN (PGP|SSH|SIGNED) (SIGNATURE|MESSAGE)-----(.*?)-----END (PGP|SSH|SIGNED) (SIGNATURE|MESSAGE)-----"
);
/// Name of the cache file for changed files.
const CHANGED_FILES_CACHE: &str = "changed_files_cache";
/// Wrapper for [`Repository`] type from git2.
///
/// [`Repository`]: GitRepository
pub struct Repository {
inner: GitRepository,
inner: GitRepository,
/// Repository path.
pub path: PathBuf,
pub path: PathBuf,
/// Cache path for the changed files of the commits.
changed_files_cache_path: PathBuf,
}
impl Repository {
/// Initializes (opens) the repository.
pub fn init(path: PathBuf) -> Result<Self> {
if path.exists() {
let inner = GitRepository::open(&path)?;
let changed_files_cache_path = inner
.path()
.join(env!("CARGO_PKG_NAME"))
.join(CHANGED_FILES_CACHE);
Ok(Self {
inner: GitRepository::open(&path)?,
inner,
path,
changed_files_cache_path,
})
} else {
Err(Error::IoError(io::Error::new(
@ -60,8 +72,8 @@ impl Repository {
pub fn commits(
&self,
range: Option<&str>,
include_path: Option<&[Pattern]>,
exclude_path: Option<&[Pattern]>,
include_path: Option<Vec<Pattern>>,
exclude_path: Option<Vec<Pattern>>,
) -> Result<Vec<Commit>> {
let mut revwalk = self.inner.revwalk()?;
revwalk.set_sorting(Sort::TOPOLOGICAL)?;
@ -75,37 +87,182 @@ impl Repository {
.filter_map(|id| self.inner.find_commit(id).ok())
.collect();
if include_path.is_some() || exclude_path.is_some() {
let include_patterns = include_path.map(|patterns| {
patterns.into_iter().map(Self::normalize_pattern).collect()
});
let exclude_patterns = exclude_path.map(|patterns| {
patterns.into_iter().map(Self::normalize_pattern).collect()
});
commits.retain(|commit| {
let Ok(prev_commit) = commit.parent(0) else {
return false;
};
let Ok(diff) = self.inner.diff_tree_to_tree(
commit.tree().ok().as_ref(),
prev_commit.tree().ok().as_ref(),
None,
) else {
return false;
};
diff.deltas()
.filter_map(|delta| delta.new_file().path())
.any(|new_file_path| {
if let Some(include_path) = include_path {
return include_path
.iter()
.any(|glob| glob.matches_path(new_file_path));
}
if let Some(exclude_path) = exclude_path {
return !exclude_path
.iter()
.any(|glob| glob.matches_path(new_file_path));
}
unreachable!()
})
self.should_retain_commit(
commit,
&include_patterns,
&exclude_patterns,
)
});
}
Ok(commits)
}
/// Normalizes the glob pattern to match the git diff paths.
///
/// It removes the leading `./` and adds `**` to the end if the pattern is a
/// directory.
fn normalize_pattern(pattern: Pattern) -> Pattern {
let star_added = match pattern.as_str().chars().last() {
Some('/') | Some('\\') => Pattern::new(&format!("{pattern}**"))
.expect("failed to add '**' to the end of glob"),
_ => pattern,
};
let pattern_normal = match star_added.as_str().strip_prefix("./") {
Some(stripped) => Pattern::new(stripped)
.expect("failed to remove leading ./ from glob"),
None => star_added,
};
pattern_normal
}
/// Calculates whether the commit should be retained or not.
///
/// This function is used to filter the commits based on the changed files,
/// and include/exclude patterns.
fn should_retain_commit(
&self,
commit: &Commit,
include_patterns: &Option<Vec<Pattern>>,
exclude_patterns: &Option<Vec<Pattern>>,
) -> bool {
let changed_files = self.commit_changed_files(commit);
match (include_patterns, exclude_patterns) {
(Some(include_pattern), Some(exclude_pattern)) => {
// check if the commit has any changed files that match any of the
// include patterns and non of the exclude patterns.
return changed_files.iter().any(|path| {
include_pattern
.iter()
.any(|pattern| pattern.matches_path(path)) &&
!exclude_pattern
.iter()
.any(|pattern| pattern.matches_path(path))
});
}
(Some(include_pattern), None) => {
// check if the commit has any changed files that match the include
// patterns.
return changed_files.iter().any(|path| {
include_pattern
.iter()
.any(|pattern| pattern.matches_path(path))
});
}
(None, Some(exclude_pattern)) => {
// check if the commit has at least one changed file that does not
// match all exclude patterns.
return changed_files.iter().any(|path| {
!exclude_pattern
.iter()
.any(|pattern| pattern.matches_path(path))
});
}
(None, None) => true,
}
}
/// Returns the changed files of the commit.
///
/// It uses a cache to speed up checks to store the changed files of the
/// commits under `./.git/git-cliff-core/changed_files_cache`. The speed-up
/// was measured to be around 260x for large repositories.
///
/// If the cache is not found, it calculates the changed files and adds them
/// to the cache via [`Self::commit_changed_files_no_cache`].
fn commit_changed_files(&self, commit: &Commit) -> Vec<PathBuf> {
// Cache key is generated from the repository path and commit id
let cache_key = format!("commit_id:{}", commit.id());
// Check the cache first.
{
if let Ok(result) =
cacache::read_sync(&self.changed_files_cache_path, &cache_key)
{
if let Ok((files, _)) =
bincode::decode_from_slice(&result, bincode::config::standard())
{
return files;
}
}
}
// If the cache is not found, calculate the result and set it to the cache.
let result = self.commit_changed_files_no_cache(commit);
match bincode::encode_to_vec(
self.commit_changed_files_no_cache(commit),
bincode::config::standard(),
) {
Ok(v) => {
if let Err(e) = cacache::write_sync_with_algo(
cacache::Algorithm::Xxh3,
&self.changed_files_cache_path,
cache_key,
v,
) {
error!("Failed to set cache for repo {:?}: {e}", self.path);
}
}
Err(e) => {
error!("Failed to serialize cache for repo {:?}: {e}", self.path);
}
}
result
}
/// Calculate the changed files of the commit.
///
/// This function does not use the cache (directly calls git2).
fn commit_changed_files_no_cache(&self, commit: &Commit) -> Vec<PathBuf> {
let mut changed_files = Vec::new();
if let Ok(prev_commit) = commit.parent(0) {
// Compare the current commit with the previous commit to get the
// changed files.
// libgit2 does not provide a way to get the changed files directly, so
// the full diff is calculated here.
if let Ok(diff) = self.inner.diff_tree_to_tree(
commit.tree().ok().as_ref(),
prev_commit.tree().ok().as_ref(),
None,
) {
changed_files.extend(
diff.deltas().filter_map(|delta| {
delta.new_file().path().map(PathBuf::from)
}),
);
}
} else {
// If there is no parent, it is the first commit.
// So get all the files in the tree.
if let Ok(tree) = commit.tree() {
tree.walk(TreeWalkMode::PreOrder, |dir, entry| {
if entry.kind().expect("failed to get entry kind") !=
git2::ObjectType::Blob
{
return 0;
}
let name = entry.name().expect("failed to get entry name");
let entry_path = if dir != "," {
format!("{dir}/{name}")
} else {
name.to_string()
};
changed_files.push(entry_path.into());
0
})
.expect("failed to get the changed files of the first commit");
}
}
changed_files
}
/// Returns the current tag.
///
/// It is the same as running `git describe --tags`
@ -244,9 +401,10 @@ impl Repository {
(segments.get(1), segments.first())
{
return Ok(Remote {
owner: owner.to_string(),
repo: repo.trim_end_matches(".git").to_string(),
token: None,
owner: owner.to_string(),
repo: repo.trim_end_matches(".git").to_string(),
token: None,
is_custom: false,
});
}
}
@ -262,6 +420,7 @@ mod test {
use std::env;
use std::process::Command;
use std::str;
use temp_dir::TempDir;
fn get_last_commit_hash() -> Result<String> {
Ok(str::from_utf8(
@ -360,9 +519,10 @@ mod test {
let remote = repository.upstream_remote()?;
assert_eq!(
Remote {
owner: String::from("orhun"),
repo: String::from("git-cliff"),
token: None,
owner: remote.owner.clone(),
repo: String::from("git-cliff"),
token: None,
is_custom: false,
},
remote
);
@ -394,4 +554,195 @@ mod test {
assert_eq!(tag.message, None);
Ok(())
}
fn create_temp_repo() -> (Repository, TempDir) {
let temp_dir =
TempDir::with_prefix("git-cliff-").expect("failed to create temp dir");
let output = Command::new("git")
.args(["init"])
.current_dir(temp_dir.path())
.output()
.expect("failed to execute git init");
assert!(output.status.success(), "git init failed {:?}", output);
let repo = Repository::init(temp_dir.path().to_path_buf())
.expect("failed to init repo");
let output = Command::new("git")
.args(["config", "user.email", "test@gmail.com"])
.current_dir(temp_dir.path())
.output()
.expect("failed to execute git config user.email");
assert!(
output.status.success(),
"git config user.email failed {:?}",
output
);
let output = Command::new("git")
.args(["config", "user.name", "test"])
.current_dir(temp_dir.path())
.output()
.expect("failed to execute git config user.name");
assert!(
output.status.success(),
"git config user.name failed {:?}",
output
);
(repo, temp_dir)
}
fn create_commit_with_files<'a>(
repo: &'a Repository,
files: Vec<(&'a str, &'a str)>,
) -> Commit<'a> {
for (path, content) in files {
if let Some(parent) = repo.path.join(path).parent() {
std::fs::create_dir_all(parent).expect("failed to create dir");
}
std::fs::write(repo.path.join(path), content)
.expect("failed to write file");
}
let output = Command::new("git")
.args(["add", "."])
.current_dir(&repo.path)
.output()
.expect("failed to execute git add");
assert!(output.status.success(), "git add failed {:?}", output);
let output = Command::new("git")
.args(["commit", "--no-gpg-sign", "-m", "test commit"])
.current_dir(&repo.path)
.output()
.expect("failed to execute git commit");
assert!(output.status.success(), "git commit failed {:?}", output);
let last_commit = repo
.inner
.head()
.and_then(|head| head.peel_to_commit())
.expect("failed to get the last commit");
last_commit
}
#[test]
fn test_should_retain_commit() {
let (repo, _temp_dir) = create_temp_repo();
let new_pattern = |input: &str| {
Repository::normalize_pattern(
Pattern::new(input).expect("valid pattern"),
)
};
let first_commit = create_commit_with_files(&repo, vec![
("initial.txt", "initial content"),
("dir/initial.txt", "initial content"),
]);
{
let retain = repo.should_retain_commit(
&first_commit,
&Some(vec![new_pattern("dir/")]),
&None,
);
assert!(retain, "include: dir/");
}
let commit = create_commit_with_files(&repo, vec![
("file1.txt", "content1"),
("file2.txt", "content2"),
("dir/file3.txt", "content3"),
("dir/subdir/file4.txt", "content4"),
]);
{
let retain = repo.should_retain_commit(&commit, &None, &None);
assert!(retain, "no include/exclude patterns");
}
{
let retain = repo.should_retain_commit(
&commit,
&Some(vec![new_pattern("./")]),
&None,
);
assert!(retain, "include: ./");
}
{
let retain = repo.should_retain_commit(
&commit,
&Some(vec![new_pattern("**")]),
&None,
);
assert!(retain, "include: **");
}
{
let retain = repo.should_retain_commit(
&commit,
&Some(vec![new_pattern("*")]),
&None,
);
assert!(retain, "include: *");
}
{
let retain = repo.should_retain_commit(
&commit,
&Some(vec![new_pattern("dir/")]),
&None,
);
assert!(retain, "include: dir/");
}
{
let retain = repo.should_retain_commit(
&commit,
&Some(vec![new_pattern("dir/*")]),
&None,
);
assert!(retain, "include: dir/*");
}
{
let retain = repo.should_retain_commit(
&commit,
&Some(vec![new_pattern("file1.txt")]),
&None,
);
assert!(retain, "include: file1.txt");
}
{
let retain = repo.should_retain_commit(
&commit,
&None,
&Some(vec![new_pattern("file1.txt")]),
);
assert!(retain, "exclude: file1.txt");
}
{
let retain = repo.should_retain_commit(
&commit,
&Some(vec![new_pattern("file1.txt")]),
&Some(vec![new_pattern("file2.txt")]),
);
assert!(retain, "include: file1.txt, exclude: file2.txt");
}
{
let retain = repo.should_retain_commit(
&commit,
&None,
&Some(vec![new_pattern("**/*.txt")]),
);
assert!(!retain, "exclude: **/*.txt");
}
}
}

View File

@ -115,6 +115,7 @@ fn process_repository<'a>(
debug!("No GitHub remote is set, using remote: {}", remote);
config.remote.github.owner = remote.owner;
config.remote.github.repo = remote.repo;
config.remote.github.is_custom = remote.is_custom;
}
Err(e) => {
debug!("Failed to get remote from GitHub repository: {:?}", e);
@ -126,6 +127,7 @@ fn process_repository<'a>(
debug!("No GitLab remote is set, using remote: {}", remote);
config.remote.gitlab.owner = remote.owner;
config.remote.gitlab.repo = remote.repo;
config.remote.gitlab.is_custom = remote.is_custom;
}
Err(e) => {
debug!("Failed to get remote from GitLab repository: {:?}", e);
@ -137,6 +139,7 @@ fn process_repository<'a>(
debug!("No Gitea remote is set, using remote: {}", remote);
config.remote.gitea.owner = remote.owner;
config.remote.gitea.repo = remote.repo;
config.remote.gitea.is_custom = remote.is_custom;
}
Err(e) => {
debug!("Failed to get remote from Gitea repository: {:?}", e);
@ -148,6 +151,7 @@ fn process_repository<'a>(
debug!("No Bitbucket remote is set, using remote: {}", remote);
config.remote.bitbucket.owner = remote.owner;
config.remote.bitbucket.repo = remote.repo;
config.remote.bitbucket.is_custom = remote.is_custom;
}
Err(e) => {
debug!("Failed to get remote from Bitbucket repository: {:?}", e);
@ -209,8 +213,8 @@ fn process_repository<'a>(
}
let mut commits = repository.commits(
commit_range.as_deref(),
args.include_path.as_deref(),
args.exclude_path.as_deref(),
args.include_path.clone(),
args.exclude_path.clone(),
)?;
if let Some(commit_limit_value) = config.git.limit_commits {
commits.truncate(commit_limit_value);
@ -465,14 +469,22 @@ pub fn run(mut args: Opt) -> Result<()> {
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();
config.remote.github.is_custom = true;
}
if let Some(ref remote) = args.gitlab_repo {
config.remote.gitlab.owner = remote.0.owner.to_string();
config.remote.gitlab.repo = remote.0.repo.to_string();
config.remote.gitlab.is_custom = true;
}
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();
config.remote.bitbucket.is_custom = true;
}
if let Some(ref remote) = args.gitea_repo {
config.remote.gitea.owner = remote.0.owner.to_string();
config.remote.gitea.repo = remote.0.repo.to_string();
config.remote.gitea.is_custom = true;
}
if args.no_exec {
if let Some(ref mut preprocessors) = config.git.commit_preprocessors {