From 322117b15970696b9cf0de5f252d7ad8572a3e7d Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Wed, 11 Oct 2023 11:54:07 +0200 Subject: [PATCH 1/7] feat(config): add field and value matchers to commit parser (#194) --- git-cliff-core/src/changelog.rs | 14 ++++++ git-cliff-core/src/commit.rs | 63 ++++++++++++++++++++++++ git-cliff-core/src/config.rs | 5 ++ git-cliff-core/src/error.rs | 4 ++ git-cliff-core/tests/integration_test.rs | 6 +++ 5 files changed, 92 insertions(+) diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index 56275eed58..fc1553c5bb 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -238,6 +238,8 @@ mod test { default_scope: None, scope: None, skip: Some(true), + field: None, + pattern: None, }, CommitParser { message: Regex::new("feat*").ok(), @@ -246,6 +248,8 @@ mod test { default_scope: Some(String::from("other")), scope: None, skip: None, + field: None, + pattern: None, }, CommitParser { message: Regex::new("^fix*").ok(), @@ -254,6 +258,8 @@ mod test { default_scope: None, scope: None, skip: None, + field: None, + pattern: None, }, CommitParser { message: Regex::new("doc:").ok(), @@ -262,6 +268,8 @@ mod test { default_scope: None, scope: Some(String::from("documentation")), skip: None, + field: None, + pattern: None, }, CommitParser { message: Regex::new("docs:").ok(), @@ -270,6 +278,8 @@ mod test { default_scope: None, scope: Some(String::from("documentation")), skip: None, + field: None, + pattern: None, }, CommitParser { message: Regex::new(r"match\((.*)\):.*").ok(), @@ -278,6 +288,8 @@ mod test { default_scope: None, scope: None, skip: None, + field: None, + pattern: None, }, CommitParser { message: Regex::new(".*").ok(), @@ -286,6 +298,8 @@ mod test { default_scope: Some(String::from("other")), scope: None, skip: None, + field: None, + pattern: None, }, ]), protect_breaking_commits: None, diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index 96a8c2d4e6..6371bd29ca 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -258,6 +258,67 @@ impl Commit<'_> { ) { regex_checks.push((body_regex, body.to_string())) } + if let (Some(field_name), Some(pattern_regex)) = + (parser.field.as_ref(), parser.pattern.as_ref()) + { + match field_name.as_str() { + "id" => regex_checks.push((pattern_regex, self.id.clone())), + "message" => { + regex_checks.push((pattern_regex, self.message.clone())) + } + "body" => regex_checks.push(( + pattern_regex, + self.conv + .as_ref() + .and_then(|v| v.body()) + .unwrap_or_default() + .to_string(), + )), + "author.name" => regex_checks.push(( + pattern_regex, + self.author.name.clone().ok_or_else(|| { + AppError::FieldError(format!( + "field {} does not have a value", + field_name + )) + })?, + )), + "author.email" => regex_checks.push(( + pattern_regex, + self.author.email.clone().ok_or_else(|| { + AppError::FieldError(format!( + "field {} does not have a value", + field_name + )) + })?, + )), + "commiter.name" => regex_checks.push(( + pattern_regex, + self.committer.name.clone().ok_or_else(|| { + AppError::FieldError(format!( + "field {} does not have a value", + field_name + )) + })?, + )), + "commiter.email" => regex_checks.push(( + pattern_regex, + self.committer.email.clone().ok_or_else(|| { + AppError::FieldError(format!( + "field {} does not have a value", + field_name + )) + })?, + )), + _ => { + return Err(AppError::FieldError(format!( + "field does not exist: {}", + field_name + ))) + } + } + } + for (regex, text) in regex_checks { if regex.is_match(&text) { if self.skip_commit(parser, protect_breaking) { @@ -420,6 +481,8 @@ mod test { default_scope: Some(String::from("test_scope")), scope: None, skip: None, + field: None, + pattern: None, }], false, false, diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs index 1502a47bcd..7e2e41a68a 100644 --- a/git-cliff-core/src/config.rs +++ b/git-cliff-core/src/config.rs @@ -100,6 +100,11 @@ pub struct CommitParser { pub scope: Option, /// Whether to skip this commit group. pub skip: Option, + /// Field name of the commit to match the regex against + pub field: Option, + /// Regex for matching the field value + #[serde(with = "serde_regex", default)] + pub pattern: Option, } /// TextProcessor, e.g. for modifying commit messages. diff --git a/git-cliff-core/src/error.rs b/git-cliff-core/src/error.rs index 85f68a30d9..bd04639722 100644 --- a/git-cliff-core/src/error.rs +++ b/git-cliff-core/src/error.rs @@ -57,6 +57,10 @@ pub enum Error { /// Error that may occur while parsing integers. #[error("Failed to parse integer: `{0}`")] IntParseError(#[from] std::num::TryFromIntError), + /// Error that may occur while processing parsers that define field and + /// value matchers + #[error("Field error: `{0}`")] + FieldError(String), } /// Result type of the core library. diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index 775afb0610..d53ad4ee29 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -55,6 +55,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: None, skip: None, + field: None, + pattern: None, }, CommitParser { message: Regex::new("^fix").ok(), @@ -63,6 +65,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: None, skip: None, + field: None, + pattern: None, }, CommitParser { message: Regex::new("^test").ok(), @@ -71,6 +75,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: Some(String::from("tests")), skip: None, + field: None, + pattern: None, }, ]), protect_breaking_commits: None, From 7b07e537471a15debb2a2bbe9f7a78d991933ffc Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Wed, 11 Oct 2023 12:38:47 +0200 Subject: [PATCH 2/7] docs(config): matchers docs updated with new type of parser based on field and pattern --- website/docs/configuration.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/docs/configuration.md b/website/docs/configuration.md index a1d180b501..0e81a54de5 100644 --- a/website/docs/configuration.md +++ b/website/docs/configuration.md @@ -220,6 +220,15 @@ Examples: - If the commit starts with "doc", group the commit as "Documentation" and set the default scope to "other". (e.g. `docs: xyz` will be processed as `docs(other): xyz`) - `{ message = "(www)", scope = "Application"}` - If the commit contains "(www)", override the scope with "Application". Scoping order is: scope specification, conventional commit's scope and default scope. +- `{ field = "author.name" pattern = "John Doe" group = "John' stuff"}` + - If the author's name attribute of the commit matches the pattern "John Doe" (as a regex), override the scope with "John' stuff". Supported commit attributes are: + - `id` + - `message` + - `body` + - `author.name` + - `author.email` + - `commiter.email` + - `commiter.name` ### protect_breaking_commits From a2c79f5127e30fa00a011457cd5c860882c1cad4 Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Wed, 11 Oct 2023 15:07:28 +0200 Subject: [PATCH 3/7] test(config): tests covering new parsers --- git-cliff-core/src/commit.rs | 32 ++++++++++++++++++++++++ git-cliff-core/tests/integration_test.rs | 30 +++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index 6371bd29ca..96376655da 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -631,4 +631,36 @@ mod test { Commit::from(String::from("thisisinvalidsha1 style: add formatting")) ); } + + #[test] + fn parse_commit_field() -> Result<()> { + let mut commit = Commit::new( + String::from("8f55e69eba6e6ce811ace32bd84cc82215673cb6"), + String::from("feat: do something"), + ); + + commit.author = Signature { + name: Some("John Doe".to_string()), + email: None, + timestamp: 0x0, + }; + + let parsed_commit = commit.parse( + &[CommitParser { + message: None, + body: None, + group: Some(String::from("Test group")), + default_scope: None, + scope: None, + skip: None, + field: Some(String::from("author.name")), + pattern: Regex::new("John Doe").ok(), + }], + false, + false, + )?; + + assert_eq!(Some(String::from("Test group")), parsed_commit.group); + Ok(()) + } } diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index d53ad4ee29..cc8c638c52 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -1,4 +1,7 @@ -use git_cliff_core::commit::Commit; +use git_cliff_core::commit::{ + Commit, + Signature, +}; use git_cliff_core::config::{ ChangelogConfig, CommitParser, @@ -78,6 +81,16 @@ fn generate_changelog() -> Result<()> { field: None, pattern: None, }, + CommitParser { + message: None, + body: None, + group: Some(String::from("docs")), + default_scope: None, + scope: None, + skip: None, + field: Some(String::from("author.name")), + pattern: Regex::new("John Doe").ok(), + }, ]), protect_breaking_commits: None, filter_commits: Some(true), @@ -101,6 +114,17 @@ fn generate_changelog() -> Result<()> { limit_commits: None, }; + let mut commit_with_author = Commit::new( + String::from("hjdfas32"), + String::from("docs(cool): testing author filtering"), + ); + + commit_with_author.author = Signature { + name: Some("John Doe".to_string()), + email: None, + timestamp: 0x0, + }; + let releases = vec![ Release { version: Some(String::from("v2.0.0")), @@ -139,6 +163,7 @@ fn generate_changelog() -> Result<()> { String::from("1234"), String::from("fix: support preprocessing (fixes #99)"), ), + commit_with_author ] .iter() .filter_map(|c| c.process(&git_config).ok()) @@ -187,6 +212,9 @@ fn generate_changelog() -> Result<()> { ## Release v2.0.0 +### docs +- *(cool)* testing author filtering + ### fix bugs - fix abc - support preprocessing [closes Issue#99] From 2beb45a86cf0f0e351d7c8e7da8ab72c776c632a Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Sun, 15 Oct 2023 12:19:42 +0200 Subject: [PATCH 4/7] chore(docs): fix typos in website/docs/configuration.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Orhun Parmaksız --- website/docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/configuration.md b/website/docs/configuration.md index 0e81a54de5..62d7764d4d 100644 --- a/website/docs/configuration.md +++ b/website/docs/configuration.md @@ -220,7 +220,7 @@ Examples: - If the commit starts with "doc", group the commit as "Documentation" and set the default scope to "other". (e.g. `docs: xyz` will be processed as `docs(other): xyz`) - `{ message = "(www)", scope = "Application"}` - If the commit contains "(www)", override the scope with "Application". Scoping order is: scope specification, conventional commit's scope and default scope. -- `{ field = "author.name" pattern = "John Doe" group = "John' stuff"}` +- `{ field = "author.name", pattern = "John Doe", group = "John's stuff"}` - If the author's name attribute of the commit matches the pattern "John Doe" (as a regex), override the scope with "John' stuff". Supported commit attributes are: - `id` - `message` From f8a4f54b5544415481776d04ec2d6b760a5ee52c Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Sun, 15 Oct 2023 12:20:10 +0200 Subject: [PATCH 5/7] chore(docs): fix more typos website/docs/configuration.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Orhun Parmaksız --- website/docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/configuration.md b/website/docs/configuration.md index 62d7764d4d..d0d0d0c65c 100644 --- a/website/docs/configuration.md +++ b/website/docs/configuration.md @@ -227,8 +227,8 @@ Examples: - `body` - `author.name` - `author.email` - - `commiter.email` - - `commiter.name` + - `committer.email` + - `committer.name` ### protect_breaking_commits From eb38458c58116c87549411a378e031d29577e408 Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Sun, 15 Oct 2023 12:21:02 +0200 Subject: [PATCH 6/7] chore(docs): update docs for git-cliff-core/src/config.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Orhun Parmaksız --- git-cliff-core/src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs index 7e2e41a68a..63f97192b2 100644 --- a/git-cliff-core/src/config.rs +++ b/git-cliff-core/src/config.rs @@ -100,9 +100,9 @@ pub struct CommitParser { pub scope: Option, /// Whether to skip this commit group. pub skip: Option, - /// Field name of the commit to match the regex against + /// Field name of the commit to match the regex against. pub field: Option, - /// Regex for matching the field value + /// Regex for matching the field value. #[serde(with = "serde_regex", default)] pub pattern: Option, } From e7c0f96b4a988f76a39bc78ec16fcbc9b7abfd49 Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Sun, 15 Oct 2023 12:22:18 +0200 Subject: [PATCH 7/7] chore(config): code matching field name refactored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Orhun Parmaksız --- git-cliff-core/src/commit.rs | 74 ++++++++++-------------------------- 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index 96376655da..43019c815d 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -261,64 +261,30 @@ impl Commit<'_> { if let (Some(field_name), Some(pattern_regex)) = (parser.field.as_ref(), parser.pattern.as_ref()) { - match field_name.as_str() { - "id" => regex_checks.push((pattern_regex, self.id.clone())), - "message" => { - regex_checks.push((pattern_regex, self.message.clone())) - } - "body" => regex_checks.push(( - pattern_regex, - self.conv + regex_checks.push(( + pattern_regex, + match field_name.as_str() { + "id" => Some(self.id.clone()), + "message" => Some(self.message.clone()), + "body" => self + .conv .as_ref() .and_then(|v| v.body()) - .unwrap_or_default() - .to_string(), - )), - "author.name" => regex_checks.push(( - pattern_regex, - self.author.name.clone().ok_or_else(|| { - AppError::FieldError(format!( - "field {} does not have a value", - field_name - )) - })?, - )), - "author.email" => regex_checks.push(( - pattern_regex, - self.author.email.clone().ok_or_else(|| { - AppError::FieldError(format!( - "field {} does not have a value", - field_name - )) - })?, - )), - "commiter.name" => regex_checks.push(( - pattern_regex, - self.committer.name.clone().ok_or_else(|| { - AppError::FieldError(format!( - "field {} does not have a value", - field_name - )) - })?, - )), - "commiter.email" => regex_checks.push(( - pattern_regex, - self.committer.email.clone().ok_or_else(|| { - AppError::FieldError(format!( - "field {} does not have a value", - field_name - )) - })?, - )), - _ => { - return Err(AppError::FieldError(format!( - "field does not exist: {}", - field_name - ))) + .map(|v| v.to_string()), + "author.name" => self.author.name.clone(), + "author.email" => self.author.email.clone(), + "committer.name" => self.committer.name.clone(), + "committer.email" => self.committer.email.clone(), + _ => None, } - } + .ok_or_else(|| { + AppError::FieldError(format!( + "field {} does not have a value", + field_name + )) + })?, + )); } - for (regex, text) in regex_checks { if regex.is_match(&text) { if self.skip_commit(parser, protect_breaking) {