diff --git a/.github/fixtures/test-regex-label-grouping/cliff.toml b/.github/fixtures/test-regex-label-grouping/cliff.toml new file mode 100644 index 0000000000..49ce37f2a1 --- /dev/null +++ b/.github/fixtures/test-regex-label-grouping/cliff.toml @@ -0,0 +1,25 @@ +[changelog] +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +body = """ +### What's changed +{% for group, commits in commits | group_by(attribute="group") %} + #### {{ group }} + {% for commit in commits -%} + - {{ commit.message }} + {% endfor -%}\n +{% endfor %}\n +""" +footer = """ + +""" +trim = true + +[git] +commit_parsers = [ + { field = "author.name", pattern = "testa", group = "TEST A" }, + { field = "author.name", pattern = "testb", group = "TEST B" }, + { field = "author.name", pattern = "testc", group = "TEST C" }, +] diff --git a/.github/fixtures/test-regex-label-grouping/commit.sh b/.github/fixtures/test-regex-label-grouping/commit.sh new file mode 100755 index 0000000000..3a190fd5b4 --- /dev/null +++ b/.github/fixtures/test-regex-label-grouping/commit.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -e + +GIT_COMMITTER_DATE="2022-04-06 01:25:08" git commit --allow-empty -m "Initial commit" +GIT_COMMITTER_DATE="2022-04-06 01:25:09" git commit --allow-empty --author="testa " -m "feat: add feature 1" +GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty --author="testa " -m "feat: add feature 2" +GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty --author="testb " -m "feat: add feature 3" +GIT_COMMITTER_DATE="2022-04-06 01:25:12" git commit --allow-empty --author="testb " -m "feat: add feature 4" +GIT_COMMITTER_DATE="2022-04-06 01:25:13" git commit --allow-empty --author="testc " -m "feat: add feature 5" +GIT_COMMITTER_DATE="2022-04-06 01:25:14" git commit --allow-empty --author="testc " -m "feat: add feature 6" diff --git a/.github/fixtures/test-regex-label-grouping/expected.md b/.github/fixtures/test-regex-label-grouping/expected.md new file mode 100644 index 0000000000..9320ce2f94 --- /dev/null +++ b/.github/fixtures/test-regex-label-grouping/expected.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +### What's changed + +#### TEST A +- add feature 1 +- add feature 2 + +#### TEST B +- add feature 3 +- add feature 4 + +#### TEST C +- add feature 5 +- add feature 6 + + \ No newline at end of file diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index c6a84d9986..3b2bc8da39 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -62,9 +62,9 @@ impl<'a> Changelog<'a> { config, additional_context: HashMap::new(), }; + changelog.add_remote_data()?; changelog.process_commits(); changelog.process_releases(); - changelog.add_remote_data()?; Ok(changelog) } diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index f60928492f..047477250a 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -264,6 +264,11 @@ impl Commit<'_> { protect_breaking: bool, filter: bool, ) -> Result { + let lookup_context = serde_json::to_value(&self).map_err(|e| { + AppError::FieldError(format!( + "failed to convert context into value: {e}", + )) + })?; for parser in parsers { let mut regex_checks = Vec::new(); if let Some(message_regex) = parser.message.as_ref() { @@ -287,25 +292,22 @@ impl Commit<'_> { if let (Some(field_name), Some(pattern_regex)) = (parser.field.as_ref(), parser.pattern.as_ref()) { - regex_checks.push(( - pattern_regex, - match field_name.as_str() { - "id" => Some(self.id.clone()), - "message" => Some(self.message.clone()), - "body" => body, - "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, + let value = if field_name == "body" { + body.clone() + } else { + tera::dotted_pointer(&lookup_context, field_name) + .map(|v| v.to_string()) + }; + match value { + Some(value) => { + regex_checks.push((pattern_regex, value)); } - .ok_or_else(|| { - AppError::FieldError(format!( - "field {} does not have a value", - field_name - )) - })?, - )); + None => { + return Err(AppError::FieldError(format!( + "field {field_name} does not have a value", + ))); + } + } } if parser.sha.clone().map(|v| v.to_lowercase()).as_deref() == Some(&self.id) @@ -728,4 +730,55 @@ mod test { Ok(()) } + + #[test] + fn field_name_regex() -> Result<()> { + let commit = Commit { + message: String::from("feat: do something"), + author: Signature { + name: Some("John Doe".to_string()), + email: None, + timestamp: 0x0, + }, + ..Default::default() + }; + let parsed_commit = commit.clone().parse( + &[CommitParser { + sha: None, + message: None, + body: None, + footer: None, + group: Some(String::from("Test group")), + default_scope: None, + scope: None, + skip: None, + field: Some(String::from("author.name")), + pattern: Regex::new("Something else").ok(), + }], + false, + true, + ); + + assert!(parsed_commit.is_err()); + + let parsed_commit = commit.parse( + &[CommitParser { + sha: None, + message: None, + body: None, + footer: 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/src/error.rs b/git-cliff-core/src/error.rs index 5f96b589d3..c9342fce64 100644 --- a/git-cliff-core/src/error.rs +++ b/git-cliff-core/src/error.rs @@ -68,7 +68,7 @@ pub enum Error { #[error("Failed to parse integer: `{0}`")] IntParseError(#[from] std::num::TryFromIntError), /// Error that may occur while processing parsers that define field and - /// value matchers + /// value matchers. #[error("Field error: `{0}`")] FieldError(String), /// Error that may occur while parsing a SemVer version or version diff --git a/website/docs/configuration/git.md b/website/docs/configuration/git.md index 51dadd5e64..3c3722c490 100644 --- a/website/docs/configuration/git.md +++ b/website/docs/configuration/git.md @@ -165,14 +165,17 @@ Examples: - `{ sha = "f6f2472bdf0bbb5f9fcaf2d72c1fa9f98f772bb2", group = "Stuff" }` - Set the group of the commit by using its SHA1. - `{ 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: + - If the author's name attribute of the commit matches the pattern "John Doe" (as a regex), override the scope with "John's stuff". + - All values that are part of the commit context can be used. Nested fields can be accessed via the [dot notation](https://keats.github.io/tera/docs/#dot-notation). Some commonly used ones are: - `id` - `message` - - `body` - `author.name` - `author.email` - `committer.email` - `committer.name` + - `body` is a special field which contains the body of a convetional commit, if applicable. + - Be aware that all fields are converted to JSON strings before they are parsed by the given regex, especially when dealing with arrays. + ### protect_breaking_commits diff --git a/website/docs/tips-and-tricks.md b/website/docs/tips-and-tricks.md index 5c135927e5..693bcc2b32 100644 --- a/website/docs/tips-and-tricks.md +++ b/website/docs/tips-and-tricks.md @@ -43,14 +43,6 @@ Then strip the tags in the template with the series of filters: {% for group, commits in commits | filter(attribute="merge_commit", value=false) %} ``` -## Skip commits by PR label - -```jinja2 -{% if commit.github.pr_labels is containing("skip-release-notes") %} - {% continue %} -{% endif %} -``` - ## Remove gitmoji ```toml @@ -69,3 +61,26 @@ commit_parsers = [ { body = "$^", skip = true }, ] ``` + +## Skip commits by GitHub PR label + +```jinja2 +{% if commit.github.pr_labels is containing("skip-release-notes") %} + {% continue %} +{% endif %} +``` + +## Use GitHub PR labels as groups + +```toml +[git] +commit_parsers = [ + { field = "github.pr_labels", pattern = "breaking-change", group = " ๐Ÿ—๏ธ Breaking changes" }, + { field = "github.pr_labels", pattern = "type/enhancement", group = " ๐Ÿš€ Features" }, + { field = "github.pr_labels", pattern = "type/bug", group = " ๐Ÿ› Fixes" }, + { field = "github.pr_labels", pattern = "type/update", group = " ๐Ÿงช Dependencies" }, + { field = "github.pr_labels", pattern = "type/refactor", group = " ๐Ÿญ Refactor" }, + { field = "github.pr_labels", pattern = "area/documentation", group = " ๐Ÿ“ Documentation" }, + { field = "github.pr_labels", pattern = ".*", group = " ๐ŸŒ€ Miscellaneous" }, +] +```