Skip to content

Commit

Permalink
feat(changelog): support parsing commits by footer (#569)
Browse files Browse the repository at this point in the history
* 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
conventional-commits/parser#47 for details.
As a future-proof workaround, you can use `key: ?value` in the regex.

* chore: fix typo

---------

Co-authored-by: Orhun Parmaksız <[email protected]>
  • Loading branch information
jan-ferdinand and orhun authored Jun 16, 2024
1 parent dabe716 commit 50c240c
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .github/fixtures/test-footer-filter/cliff.toml
Original file line number Diff line number Diff line change
@@ -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 = """
<!-- generated by git-cliff -->
"""
# 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 },
]
11 changes: 11 additions & 0 deletions .github/fixtures/test-footer-filter/commit.sh
Original file line number Diff line number Diff line change
@@ -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"
23 changes: 23 additions & 0 deletions .github/fixtures/test-footer-filter/expected.md
Original file line number Diff line number Diff line change
@@ -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

<!-- generated by git-cliff -->
41 changes: 41 additions & 0 deletions git-cliff-core/src/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -643,6 +644,7 @@ mod test {
sha: Some(String::from("coffee")),
message: None,
body: None,
footer: None,
group: None,
default_scope: None,
scope: None,
Expand All @@ -654,6 +656,7 @@ mod test {
sha: Some(String::from("coffee2")),
message: None,
body: None,
footer: None,
group: None,
default_scope: None,
scope: None,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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")),
Expand All @@ -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")),
Expand All @@ -720,17 +728,31 @@ 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,
skip: None,
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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -957,6 +983,10 @@ mod test {
- update docs
- add some documentation
### Footer
#### footer
- use footer
### I love tea
#### app
- damn right
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -1164,6 +1201,10 @@ chore(deps): fix broken deps
- update docs
- add some documentation
### Footer
#### footer
- use footer
### I love tea
#### app
- damn right
Expand Down
11 changes: 11 additions & 0 deletions git-cliff-core/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -684,6 +693,7 @@ mod test {
)),
message: None,
body: None,
footer: None,
group: None,
default_scope: None,
scope: None,
Expand All @@ -703,6 +713,7 @@ mod test {
)),
message: None,
body: None,
footer: None,
group: Some(String::from("Test group")),
default_scope: None,
scope: None,
Expand Down
3 changes: 3 additions & 0 deletions git-cliff-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ pub struct CommitParser {
/// Regex for matching the commit body.
#[serde(with = "serde_regex", default)]
pub body: Option<Regex>,
/// Regex for matching the commit footer.
#[serde(with = "serde_regex", default)]
pub footer: Option<Regex>,
/// Group of the commit.
pub group: Option<String>,
/// Default scope of the commit.
Expand Down
5 changes: 5 additions & 0 deletions git-cliff-core/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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")),
Expand All @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions website/docs/configuration/git.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".
<!-- Conventional commits parser is out of sync with spec, parsing only separator ":", not ": "; see: -->
<!-- https://github.com/conventional-commits/parser/issues/47 -->
- `{ 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" }`
Expand Down

0 comments on commit 50c240c

Please sign in to comment.