diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index fe2e9bd68f..bdc6f62e5c 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -609,11 +609,15 @@ impl<'a> Changelog<'a> { } for release in &self.releases { + let mut release_for_changelog = release.clone(); + release_for_changelog + .commits + .retain(|commit| commit.should_include_in_changelog()); let write_result = write!( out, "{}", self.body_template.render( - &release, + &release_for_changelog, Some(&self.additional_context), &postprocessors )? @@ -837,6 +841,8 @@ mod test { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -849,6 +855,8 @@ mod test { default_scope: None, scope: None, skip: Some(true), + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -861,6 +869,8 @@ mod test { default_scope: None, scope: None, skip: Some(true), + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -873,6 +883,8 @@ mod test { default_scope: None, scope: None, skip: Some(true), + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -885,6 +897,8 @@ mod test { default_scope: Some(String::from("other")), scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -897,6 +911,8 @@ mod test { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -909,6 +925,8 @@ mod test { default_scope: None, scope: Some(String::from("documentation")), skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -921,6 +939,8 @@ mod test { default_scope: None, scope: Some(String::from("documentation")), skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -933,6 +953,8 @@ mod test { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -945,6 +967,8 @@ mod test { default_scope: None, scope: Some(String::from("footer")), skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -957,6 +981,8 @@ mod test { default_scope: Some(String::from("other")), scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index 6722c106e8..d5629ecbbb 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -170,6 +170,19 @@ pub struct Commit<'a> { /// In fact, it is pre-processed by [`Commit::preprocess`], and only be /// generated when serializing into `context` the first time. pub raw_message: Option, + + /// Whether this commit should contribute to version bumping. + /// + /// `None` means include (default). Set by matching [`CommitParser::bump`]. + #[serde(default)] + pub include_in_bump: Option, + + /// Whether this commit should appear in the changelog. + /// + /// `None` means include (default). Set by matching + /// [`CommitParser::include_in_changelog`]. + #[serde(default)] + pub include_in_changelog: Option, } impl From for Commit<'_> { @@ -306,6 +319,26 @@ impl Commit<'_> { !(self.conv.as_ref().is_some_and(ConventionalCommit::breaking) && protect_breaking) } + /// Apply bump / changelog inclusion flags from a matching parser. + fn apply_parser_inclusion(&mut self, parser: &CommitParser) { + if parser.bump.is_some() { + self.include_in_bump = parser.bump; + } + if parser.include_in_changelog.is_some() { + self.include_in_changelog = parser.include_in_changelog; + } + } + + /// Whether this commit should contribute to version bumping. + pub fn should_include_in_bump(&self) -> bool { + self.include_in_bump.unwrap_or(true) + } + + /// Whether this commit should appear in the changelog. + pub fn should_include_in_changelog(&self) -> bool { + self.include_in_changelog.unwrap_or(true) + } + /// Parses the commit using [`CommitParser`]s. /// /// Sets the [`group`] and [`scope`] of the commit. @@ -400,6 +433,7 @@ impl Commit<'_> { self.group = parser.group.clone().or(self.group); self.scope = parser.scope.clone().or(self.scope); self.default_scope = parser.default_scope.clone().or(self.default_scope); + self.apply_parser_inclusion(parser); return Ok(self); } } @@ -417,6 +451,7 @@ impl Commit<'_> { self.group = parser.group.clone().map(regex_replace); self.scope = parser.scope.clone().map(regex_replace); self.default_scope.clone_from(&parser.default_scope); + self.apply_parser_inclusion(parser); return Ok(self); } } @@ -604,6 +639,8 @@ mod test { default_scope: Some(String::from("test_scope")), scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }], @@ -761,6 +798,37 @@ mod test { ); } + #[test] + fn parse_commit_inclusion_flags() -> Result<()> { + let docs = Commit::new(String::from("abc"), String::from("docs: update readme")).parse( + &[CommitParser { + message: Regex::new("^docs").ok(), + include_in_changelog: Some(false), + ..Default::default() + }], + false, + true, + )?; + assert!(docs.should_include_in_bump()); + assert!(!docs.should_include_in_changelog()); + + let chore = Commit::new(String::from("def"), String::from("chore: tidy")).parse( + &[CommitParser { + message: Regex::new("^chore").ok(), + bump: Some(false), + group: Some(String::from("Other")), + ..Default::default() + }], + false, + true, + )?; + assert!(!chore.should_include_in_bump()); + assert!(chore.should_include_in_changelog()); + assert_eq!(chore.group.as_deref(), Some("Other")); + + Ok(()) + } + #[test] fn parse_body() -> Result<()> { let mut commit = Commit::new( @@ -812,6 +880,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }], @@ -874,6 +944,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("author.name")), pattern: Regex::new("John Doe").ok(), }], @@ -892,6 +964,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("remote.pr_title")), pattern: Regex::new("feat: do something").ok(), }], @@ -910,6 +984,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("body")), pattern: Regex::new("something great").ok(), }], @@ -928,6 +1004,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("remote.pr_labels")), pattern: Regex::new("feature|deprecation").ok(), }], @@ -946,6 +1024,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("links")), pattern: Regex::new(".*").ok(), }], @@ -964,6 +1044,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("remote")), pattern: Regex::new(".*").ok(), }], @@ -995,6 +1077,8 @@ Refs: #123 default_scope: None, scope: None, skip: Some(true), + bump: None, + include_in_changelog: None, field: None, pattern: None, }], @@ -1037,6 +1121,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("author.name")), pattern: Regex::new("^John Doe$").ok(), }], @@ -1055,6 +1141,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("remote.pr_title")), pattern: Regex::new("^feat(\\([^)]+\\))?").ok(), }], @@ -1073,6 +1161,8 @@ Refs: #123 default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("author.name")), pattern: Regex::new("Something else").ok(), }], diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs index 93f3be3e84..adf68f472f 100644 --- a/git-cliff-core/src/config.rs +++ b/git-cliff-core/src/config.rs @@ -446,6 +446,17 @@ pub struct CommitParser { pub scope: Option, /// Whether to skip this commit group. pub skip: Option, + /// Whether this matched commit should contribute to version bumping. + /// + /// Defaults to `true` when unset. Set to `false` to keep the commit in the + /// changelog while excluding it from `--bump` / `--bumped-version`. + pub bump: Option, + /// Whether this matched commit should appear in the changelog. + /// + /// Defaults to `true` when unset. Set to `false` to keep the commit for + /// version bumping while omitting it from changelog output. Matched + /// commits still satisfy `filter_commits`. + pub include_in_changelog: Option, /// Field name of the commit to match the regex against. pub field: Option, /// Regex for matching the field value. diff --git a/git-cliff-core/src/process.rs b/git-cliff-core/src/process.rs index 18c3b377c9..ee2ffdb6de 100644 --- a/git-cliff-core/src/process.rs +++ b/git-cliff-core/src/process.rs @@ -285,6 +285,8 @@ mod test { default_scope: None, scope: None, skip: Some(true), + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -297,6 +299,8 @@ mod test { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -337,6 +341,8 @@ mod test { default_scope: None, scope: None, skip: Some(true), + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -349,6 +355,8 @@ mod test { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, diff --git a/git-cliff-core/src/release.rs b/git-cliff-core/src/release.rs index bc41da426f..b28ecfd1a3 100644 --- a/git-cliff-core/src/release.rs +++ b/git-cliff-core/src/release.rs @@ -190,6 +190,7 @@ impl Release<'_> { &old_semver, self.commits .iter() + .filter(|commit| commit.should_include_in_bump()) .map(|commit| commit.raw_message().trim_end().to_string()) .collect::>(), ); diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index 8885221354..164ef575cf 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -59,6 +59,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -71,6 +73,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -83,6 +87,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -95,6 +101,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: Some(String::from("tests")), skip: None, + bump: None, + include_in_changelog: None, field: None, pattern: None, }, @@ -107,6 +115,8 @@ fn generate_changelog() -> Result<()> { default_scope: None, scope: None, skip: None, + bump: None, + include_in_changelog: None, field: Some(String::from("author.name")), pattern: Regex::new("John Doe").ok(), }, diff --git a/website/docs/configuration/git.md b/website/docs/configuration/git.md index e58327b00a..ba3c8b22bb 100644 --- a/website/docs/configuration/git.md +++ b/website/docs/configuration/git.md @@ -227,6 +227,8 @@ Examples: - Skip processing the commit if the commit message (description) starts with "revert". - `{ message = "^doc", group = "Documentation", default_scope = "other" },` - 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 = "^docs", include_in_changelog = false }` + - Match `docs` commits so they still satisfy `filter_commits` and contribute to version bumping, but omit them from the changelog. Use `bump = false` for the inverse (show in changelog, exclude from bump). - `{ 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. - `{ sha = "f6f2472bdf0bbb5f9fcaf2d72c1fa9f98f772bb2", skip = true }`