From df0279f4eea0a791ebcb4c1cc138f50297958a75 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Fri, 22 Mar 2024 13:10:30 +0100 Subject: [PATCH 1/2] feat: Enable footer-based commit parsing Enable defining parsers for a commit's footer, similar to the already- present commit parsers for a commit's message or body. For example: ```toml commit_parsers = [ { footer = "changelog: ignore", skip = true }, ] ``` Due to an inconsistency between the conventional commits specification and its reference parser, footers are currently interpreted as `key:value` instead of the (correct) `key: value`. See https://github.com/conventional-commits/parser/issues/47 for details. As a future-proof workaround, you can use `key: ?value` in the regex. --- .../fixtures/test-footer-filter/cliff.toml | 39 ++++++++++++++++++ .github/fixtures/test-footer-filter/commit.sh | 11 +++++ .../fixtures/test-footer-filter/expected.md | 23 +++++++++++ git-cliff-core/src/changelog.rs | 41 +++++++++++++++++++ git-cliff-core/src/commit.rs | 11 +++++ git-cliff-core/src/config.rs | 3 ++ git-cliff-core/tests/integration_test.rs | 5 +++ website/docs/configuration/git.md | 4 ++ 8 files changed, 137 insertions(+) create mode 100644 .github/fixtures/test-footer-filter/cliff.toml create mode 100755 .github/fixtures/test-footer-filter/commit.sh create mode 100644 .github/fixtures/test-footer-filter/expected.md diff --git a/.github/fixtures/test-footer-filter/cliff.toml b/.github/fixtures/test-footer-filter/cliff.toml new file mode 100644 index 0000000000..392bb0a7b3 --- /dev/null +++ b/.github/fixtures/test-footer-filter/cliff.toml @@ -0,0 +1,39 @@ +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true + +[git] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features", default_scope = "app" }, + { message = "^fix", group = "Bug Fixes", scope = "cli" }, + # Accept both separators, ": " and ":". + # Conventional commits require the separator to be ": ", but the reference implementation of the + # conventional commits parser currently does not adhere to the specification. See also: + # https://github.com/conventional-commits/parser/issues/47 + { footer = "^changelog: ?ignore", skip = true }, +] diff --git a/.github/fixtures/test-footer-filter/commit.sh b/.github/fixtures/test-footer-filter/commit.sh new file mode 100755 index 0000000000..61da0775e6 --- /dev/null +++ b/.github/fixtures/test-footer-filter/commit.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +GIT_COMMITTER_DATE="2022-04-06 00:00:00" git commit --allow-empty -m "Initial commit" +GIT_COMMITTER_DATE="2022-04-06 00:10:00" git commit --allow-empty -m "feat: add feature 1" +git tag v0.1.0 +GIT_COMMITTER_DATE="2022-04-06 01:00:00" git commit --allow-empty -m "refactor: change feature 1" -m "BREAKING CHANGE: feature 1 is now different" +GIT_COMMITTER_DATE="2022-04-06 01:10:00" git commit --allow-empty -m "chore: upgrade dependencies" -m "changelog: ignore" +git tag v0.2.0 +GIT_COMMITTER_DATE="2022-04-06 02:00:00" git commit --allow-empty -m "test: add tests" -m "footer: some more info" +GIT_COMMITTER_DATE="2022-04-06 02:10:00" git commit --allow-empty -m "test: add more tests" -m "changelog: ignore" diff --git a/.github/fixtures/test-footer-filter/expected.md b/.github/fixtures/test-footer-filter/expected.md new file mode 100644 index 0000000000..df28a3db98 --- /dev/null +++ b/.github/fixtures/test-footer-filter/expected.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [unreleased] + +### Test + +- Add tests + +## [0.2.0] - 2022-04-06 + +### Refactor + +- Change feature 1 + +## [0.1.0] - 2022-04-06 + +### Features + +- Add feature 1 + + diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index 7707f37596..1b6676be4d 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -632,6 +632,7 @@ mod test { sha: Some(String::from("tea")), message: None, body: None, + footer: None, group: Some(String::from("I love tea")), default_scope: None, scope: None, @@ -643,6 +644,7 @@ mod test { sha: Some(String::from("coffee")), message: None, body: None, + footer: None, group: None, default_scope: None, scope: None, @@ -654,6 +656,7 @@ mod test { sha: Some(String::from("coffee2")), message: None, body: None, + footer: None, group: None, default_scope: None, scope: None, @@ -665,6 +668,7 @@ mod test { sha: None, message: Regex::new(r".*merge.*").ok(), body: None, + footer: None, group: None, default_scope: None, scope: None, @@ -676,6 +680,7 @@ mod test { sha: None, message: Regex::new("feat*").ok(), body: None, + footer: None, group: Some(String::from("New features")), default_scope: Some(String::from("other")), scope: None, @@ -687,6 +692,7 @@ mod test { sha: None, message: Regex::new("^fix*").ok(), body: None, + footer: None, group: Some(String::from("Bug Fixes")), default_scope: None, scope: None, @@ -698,6 +704,7 @@ mod test { sha: None, message: Regex::new("doc:").ok(), body: None, + footer: None, group: Some(String::from("Documentation")), default_scope: None, scope: Some(String::from("documentation")), @@ -709,6 +716,7 @@ mod test { sha: None, message: Regex::new("docs:").ok(), body: None, + footer: None, group: Some(String::from("Documentation")), default_scope: None, scope: Some(String::from("documentation")), @@ -720,6 +728,7 @@ mod test { sha: None, message: Regex::new(r"match\((.*)\):.*").ok(), body: None, + footer: None, group: Some(String::from("Matched ($1)")), default_scope: None, scope: None, @@ -727,10 +736,23 @@ mod test { field: None, pattern: None, }, + CommitParser { + sha: None, + message: None, + body: None, + footer: Regex::new("Footer:.*").ok(), + group: Some(String::from("Footer")), + default_scope: None, + scope: Some(String::from("footer")), + skip: None, + field: None, + pattern: None, + }, CommitParser { sha: None, message: Regex::new(".*").ok(), body: None, + footer: None, group: Some(String::from("Other")), default_scope: Some(String::from("other")), scope: None, @@ -832,6 +854,10 @@ mod test { String::from("coffee"), String::from("revert(app): skip this commit"), ), + Commit::new( + String::from("footer"), + String::from("misc: use footer\n\nFooter: footer text"), + ), ], commit_id: Some(String::from("0bc123")), timestamp: 50000000, @@ -957,6 +983,10 @@ mod test { - update docs - add some documentation + ### Footer + #### footer + - use footer + ### I love tea #### app - damn right @@ -998,6 +1028,13 @@ mod test { config.git.split_commits = Some(true); config.git.filter_unconventional = Some(false); config.git.protect_breaking_commits = Some(true); + + if let Some(parsers) = config.git.commit_parsers.as_mut() { + for parser in parsers.iter_mut().filter(|p| p.footer.is_some()) { + parser.skip = Some(true) + } + } + releases[0].commits.push(Commit::new( String::from("0bc123"), String::from( @@ -1164,6 +1201,10 @@ chore(deps): fix broken deps - update docs - add some documentation + ### Footer + #### footer + - use footer + ### I love tea #### app - damn right diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index 1a74d1e0d4..f60928492f 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -277,6 +277,13 @@ impl Commit<'_> { if let Some(body_regex) = parser.body.as_ref() { regex_checks.push((body_regex, body.clone().unwrap_or_default())) } + if let (Some(footer_regex), Some(footers)) = ( + parser.footer.as_ref(), + self.conv.as_ref().map(|v| v.footers()), + ) { + regex_checks + .extend(footers.iter().map(|f| (footer_regex, f.to_string()))); + } if let (Some(field_name), Some(pattern_regex)) = (parser.field.as_ref(), parser.pattern.as_ref()) { @@ -483,6 +490,7 @@ mod test { sha: None, message: Regex::new("test*").ok(), body: None, + footer: None, group: Some(String::from("test_group")), default_scope: Some(String::from("test_scope")), scope: None, @@ -656,6 +664,7 @@ mod test { sha: None, message: None, body: None, + footer: None, group: Some(String::from("Test group")), default_scope: None, scope: None, @@ -684,6 +693,7 @@ mod test { )), message: None, body: None, + footer: None, group: None, default_scope: None, scope: None, @@ -703,6 +713,7 @@ mod test { )), message: None, body: None, + footer: None, group: Some(String::from("Test group")), default_scope: None, scope: None, diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs index 0d2a855328..1a142ab721 100644 --- a/git-cliff-core/src/config.rs +++ b/git-cliff-core/src/config.rs @@ -213,6 +213,9 @@ pub struct CommitParser { /// Regex for matching the commit body. #[serde(with = "serde_regex", default)] pub body: Option, + /// Regex for matching the commit footer. + #[serde(with = "serde_regex", default)] + pub footer: Option, /// Group of the commit. pub group: Option, /// Default scope of the commit. diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index 99d790dc2f..95fd991ab6 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -56,6 +56,7 @@ fn generate_changelog() -> Result<()> { sha: Some(String::from("coffee")), message: None, body: None, + footer: None, group: Some(String::from("I love coffee")), default_scope: None, scope: None, @@ -67,6 +68,7 @@ fn generate_changelog() -> Result<()> { sha: None, message: Regex::new("^feat").ok(), body: None, + footer: None, group: Some(String::from("shiny features")), default_scope: None, scope: None, @@ -78,6 +80,7 @@ fn generate_changelog() -> Result<()> { sha: None, message: Regex::new("^fix").ok(), body: None, + footer: None, group: Some(String::from("fix bugs")), default_scope: None, scope: None, @@ -89,6 +92,7 @@ fn generate_changelog() -> Result<()> { sha: None, message: Regex::new("^test").ok(), body: None, + footer: None, group: None, default_scope: None, scope: Some(String::from("tests")), @@ -100,6 +104,7 @@ fn generate_changelog() -> Result<()> { sha: None, message: None, body: None, + footer: None, group: Some(String::from("docs")), default_scope: None, scope: None, diff --git a/website/docs/configuration/git.md b/website/docs/configuration/git.md index b7d7145919..8e65b17bff 100644 --- a/website/docs/configuration/git.md +++ b/website/docs/configuration/git.md @@ -146,6 +146,10 @@ Examples: - Group the commit as "Features" if the commit message (description) starts with "feat". - `{ body = ".*security", group = "Security" }` - Group the commit as "Security" if the commit body contains "security". + + +- `{ footer = "^changelog: ?ignore", skip = true }` + - Skip processing the commit if the commit footer contains "changelog: ignore". - `{ message = '^fix\((.*)\)', group = 'Fix (${1})' }` - Use the matched scope value from the commit message in the group name. - `{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation" }` From ae51fc0f04cfd53d326e08550b668eae9e194bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orhun=20Parmaks=C4=B1z?= Date: Sun, 16 Jun 2024 19:23:02 +0300 Subject: [PATCH 2/2] chore: fix typo --- website/docs/configuration/git.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/configuration/git.md b/website/docs/configuration/git.md index 8e65b17bff..2c84f9aab7 100644 --- a/website/docs/configuration/git.md +++ b/website/docs/configuration/git.md @@ -146,8 +146,8 @@ Examples: - Group the commit as "Features" if the commit message (description) starts with "feat". - `{ body = ".*security", group = "Security" }` - Group the commit as "Security" if the commit body contains "security". - - + + - `{ footer = "^changelog: ?ignore", skip = true }` - Skip processing the commit if the commit footer contains "changelog: ignore". - `{ message = '^fix\((.*)\)', group = 'Fix (${1})' }`