diff --git a/.github/fixtures/test-split-commits/cliff.toml b/.github/fixtures/test-split-commits/cliff.toml new file mode 100644 index 0000000000..9379e8b1c8 --- /dev/null +++ b/.github/fixtures/test-split-commits/cliff.toml @@ -0,0 +1,31 @@ +[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://tera.netlify.app/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 %} + - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# remove the leading and trailing whitespaces from the template +trim = true +# changelog footer +footer = """ +<!-- generated by git-cliff --> +""" + +[git] +# process each line of a commit as an individual commit +split_commits = true diff --git a/.github/fixtures/test-split-commits/commit.sh b/.github/fixtures/test-split-commits/commit.sh new file mode 100755 index 0000000000..ca71ac0e45 --- /dev/null +++ b/.github/fixtures/test-split-commits/commit.sh @@ -0,0 +1,20 @@ +#!/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 -m \ +"feat: add feature 1 +feat: add feature 2 +fix: fix feature 1" + +GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty -m \ +"chore: bump deps +style: apply formatting +fix: fix feature 2" + +GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty -m \ +"test: add initial tests +test: add more tests +test: update assert statements" + +git tag v0.1.0 diff --git a/.github/fixtures/test-split-commits/expected.md b/.github/fixtures/test-split-commits/expected.md new file mode 100644 index 0000000000..505ac8624d --- /dev/null +++ b/.github/fixtures/test-split-commits/expected.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.1.0] - 2022-04-05 + +### Chore + +- Bump deps + +### Feat + +- Add feature 1 +- Add feature 2 + +### Fix + +- Fix feature 1 +- Fix feature 2 + +### Style + +- Apply formatting + +### Test + +- Add initial tests +- Add more tests +- Update assert statements + +<!-- generated by git-cliff --> diff --git a/README.md b/README.md index 6934e9f1ff..0bdc3e27ba 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ - [git](#git) - [conventional_commits](#conventional_commits) - [filter_unconventional](#filter_unconventional) + - [split_commits](#split_commits) - [commit_preprocessors](#commit_preprocessors) - [commit_parsers](#commit_parsers) - [filter_commits](#filter_commits) @@ -76,6 +77,7 @@ - [Templating](#templating) - [Context](#context) - [Conventional Commits](#conventional-commits) + - [Footers](#footers) - [Breaking Changes](#breaking-changes) - [Non-Conventional Commits](#non-conventional-commits) - [Syntax](#syntax) @@ -86,7 +88,7 @@ - [Scoped (Sorted)](#scoped-sorted) - [Keep a Changelog](#keep-a-changelog) - [Unconventional](#unconventional) -- [Similar Projects](#similar-projects) +- [Similar/Related Projects](#similarrelated-projects) - [License](#license) - [Copyright](#copyright) @@ -432,6 +434,7 @@ This section contains the parsing and git related configuration options. [git] conventional_commits = true filter_unconventional = true +split_commits = false commit_parsers = [ { message = "^feat", group = "Features"}, { message = "^fix", group = "Bug Fixes"}, @@ -500,6 +503,27 @@ conventional_commits = false filter_unconventional = false ``` +#### split_commits + +> This flag violates "conventional commits". It should remain off by default if conventional commits is to be respected. + +If set to `true`, each line of a commit is processed individually, as if it were its own commit message. This may cause +a commit to appear multiple times in a changelog, once for each match. + +```toml +conventional_commits = true +filter_unconventional = true +split_commits = true +commit_parsers = [ + { message = "^feat", group = "Features"}, +] +``` + +With the configuration above, lines are parsed as conventional commits and unconventional lines are omitted. + +If `filter_unconventional = false`, every line will be processes as an unconventional commit, resulting in each line of +a commit being treated as a changelog entry. + #### commit_preprocessors An array of commit preprocessors for manipulating the commit messages before parsing/grouping them. These regex-based preprocessors can be used for removing or selecting certain parts of the commit message/body to be used in the following processes. diff --git a/config/cliff.toml b/config/cliff.toml index 2025a9b6f0..a12f1af819 100644 --- a/config/cliff.toml +++ b/config/cliff.toml @@ -33,6 +33,8 @@ footer = """ conventional_commits = true # filter out the commits that are not conventional filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false # regex for preprocessing the commit messages commit_preprocessors = [ { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, diff --git a/examples/detailed.toml b/examples/detailed.toml index bd355c12b9..9891483158 100644 --- a/examples/detailed.toml +++ b/examples/detailed.toml @@ -42,6 +42,8 @@ footer = """ conventional_commits = true # filter out the commits that are not conventional filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false # regex for parsing and grouping commits commit_parsers = [ { message = "^feat", group = "Features"}, diff --git a/examples/keepachangelog.toml b/examples/keepachangelog.toml index d2002250f5..c95d083003 100644 --- a/examples/keepachangelog.toml +++ b/examples/keepachangelog.toml @@ -36,6 +36,8 @@ footer = """ conventional_commits = true # filter out the commits that are not conventional filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false # regex for parsing and grouping commits commit_parsers = [ { message = "^.*: add", group = "Added"}, diff --git a/examples/scoped.toml b/examples/scoped.toml index 996b2f4697..dd7d0b7f0f 100644 --- a/examples/scoped.toml +++ b/examples/scoped.toml @@ -36,6 +36,8 @@ footer = """ conventional_commits = true # filter out the commits that are not conventional filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false # regex for parsing and grouping commits commit_parsers = [ { message = "^feat", group = "Features"}, diff --git a/examples/scopesorted.toml b/examples/scopesorted.toml index 0f2f7dee3c..4ffcaaab00 100644 --- a/examples/scopesorted.toml +++ b/examples/scopesorted.toml @@ -48,6 +48,8 @@ footer = """ conventional_commits = true # filter out the commits that are not conventional filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false # regex for parsing and grouping commits commit_parsers = [ { message = "^feat", group = "Features"}, diff --git a/examples/unconventional.toml b/examples/unconventional.toml index 26cf18ef0a..f1be35b6a3 100644 --- a/examples/unconventional.toml +++ b/examples/unconventional.toml @@ -33,6 +33,8 @@ footer = """ conventional_commits = true # filter out the commits that are not conventional filter_unconventional = false +# process each line of a commit as an individual commit +split_commits = false # regex for parsing and grouping commits commit_parsers = [ { message = "^feat", group = "Features"}, diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs index 0510910fba..2f5e1a356b 100644 --- a/git-cliff-core/src/config.rs +++ b/git-cliff-core/src/config.rs @@ -42,26 +42,30 @@ pub struct GitConfig { pub conventional_commits: Option<bool>, /// Whether to filter out unconventional commits. pub filter_unconventional: Option<bool>, + /// Whether to split commits by line, processing each line as an individual + /// commit. + pub split_commits: Option<bool>, + /// Git commit preprocessors. - pub commit_preprocessors: Option<Vec<CommitPreprocessor>>, + pub commit_preprocessors: Option<Vec<CommitPreprocessor>>, /// Git commit parsers. - pub commit_parsers: Option<Vec<CommitParser>>, + pub commit_parsers: Option<Vec<CommitParser>>, /// Link parsers. - pub link_parsers: Option<Vec<LinkParser>>, + pub link_parsers: Option<Vec<LinkParser>>, /// Whether to filter out commits. - pub filter_commits: Option<bool>, + pub filter_commits: Option<bool>, /// Blob pattern for git tags. - pub tag_pattern: Option<String>, + pub tag_pattern: Option<String>, #[serde(with = "serde_regex", default)] /// Regex to skip matched tags. - pub skip_tags: Option<Regex>, + pub skip_tags: Option<Regex>, #[serde(with = "serde_regex", default)] /// Regex to ignore matched tags. - pub ignore_tags: Option<Regex>, + pub ignore_tags: Option<Regex>, /// Whether to sort tags chronologically. - pub date_order: Option<bool>, + pub date_order: Option<bool>, /// Sorting of the commits inside sections. - pub sort_commits: Option<String>, + pub sort_commits: Option<String>, } /// Parser for grouping commits. diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index a20c7f3ee3..14de2da07f 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -40,6 +40,7 @@ fn generate_changelog() -> Result<()> { let git_config = GitConfig { conventional_commits: Some(true), filter_unconventional: Some(true), + split_commits: Some(false), commit_preprocessors: Some(vec![CommitPreprocessor { pattern: Regex::new(r#"\(fixes (#[1-9]+)\)"#).unwrap(), replace: Some(String::from("[closes Issue${1}]")), diff --git a/git-cliff/src/changelog.rs b/git-cliff/src/changelog.rs index 55e7f67528..f56b2faf23 100644 --- a/git-cliff/src/changelog.rs +++ b/git-cliff/src/changelog.rs @@ -50,6 +50,22 @@ impl<'a> Changelog<'a> { release.commits = release .commits .iter() + .cloned() + .flat_map(|commit| { + if self.config.git.split_commits.unwrap_or(false) { + commit + .message + .lines() + .map(|line| { + let mut c = commit.clone(); + c.message = line.to_string(); + c + }) + .collect() + } else { + vec![commit] + } + }) .filter_map(|commit| match commit.process(&self.config.git) { Ok(commit) => Some(commit), Err(e) => { @@ -169,8 +185,8 @@ mod test { use git_cliff_core::regex::Regex; use pretty_assertions::assert_eq; use std::str; - #[test] - fn changelog_generator() -> Result<()> { + + fn get_test_data() -> (Config, Vec<Release<'static>>) { let config = Config { changelog: ChangelogConfig { header: Some(String::from("# Changelog")), @@ -191,6 +207,7 @@ mod test { git: GitConfig { conventional_commits: Some(true), filter_unconventional: Some(false), + split_commits: Some(false), commit_preprocessors: Some(vec![CommitPreprocessor { pattern: Regex::new("<preprocess>").unwrap(), replace: Some(String::from( @@ -208,7 +225,7 @@ mod test { skip: None, }, CommitParser { - message: Regex::new("fix*").ok(), + message: Regex::new("^fix*").ok(), body: None, group: Some(String::from("Bug Fixes")), default_scope: None, @@ -325,6 +342,103 @@ mod test { previous: Some(Box::new(test_release)), }, ]; + (config, releases) + } + + #[test] + fn changelog_generator() -> Result<()> { + let (config, releases) = get_test_data(); + let changelog = Changelog::new(releases, &config)?; + let mut out = Vec::new(); + changelog.generate(&mut out)?; + assert_eq!( + String::from( + r#"# Changelog + ## Unreleased + + ### Bug Fixes + #### app + - fix abc + + ### New features + #### app + - add xyz + + ### Other + #### app + - document zyx + + #### ui + - do boring stuff + + ## Release [v1.0.0] - 1971-08-02 + (0bc123) + + ### Bug Fixes + #### ui + - fix more stuff + + ### Documentation + #### documentation + - update docs + + ### New features + #### app + - add cool features + + #### other + - support unscoped commits + + ### Other + #### app + - do nothing + + #### other + - support unconventional commits + - this commit is preprocessed + + #### ui + - make good stuff + ------------"# + ) + .replace(" ", ""), + str::from_utf8(&out).unwrap() + ); + Ok(()) + } + + #[test] + fn changelog_generator_split_commits() -> Result<()> { + let (mut config, mut releases) = get_test_data(); + config.git.split_commits = Some(true); + config.git.filter_unconventional = Some(false); + releases[0].commits.push(Commit::new( + String::from("0bc123"), + String::from( + "feat(app): add some more cool features +feat(app): even more features +feat(app): feature #3 +", + ), + )); + releases[0].commits.push(Commit::new( + String::from("003934"), + String::from( + "feat: add awesome stuff +fix(backend): fix awesome stuff +style: make awesome stuff look better +", + ), + )); + releases[2].commits.push(Commit::new( + String::from("123abc"), + String::from( + "chore(deps): bump some deps +chore(deps): bump some more deps +chore(deps): fix broken deps +", + ), + )); let changelog = Changelog::new(releases, &config)?; let mut out = Vec::new(); changelog.generate(&mut out)?; @@ -345,6 +459,11 @@ mod test { #### app - document zyx + #### deps + - bump some deps + - bump some more deps + - fix broken deps + #### ui - do boring stuff @@ -352,6 +471,9 @@ mod test { (0bc123) ### Bug Fixes + #### backend + - fix awesome stuff + #### ui - fix more stuff @@ -362,9 +484,13 @@ mod test { ### New features #### app - add cool features + - add some more cool features + - even more features + - feature #3 #### other - support unscoped commits + - add awesome stuff ### Other #### app @@ -373,6 +499,7 @@ mod test { #### other - support unconventional commits - this commit is preprocessed + - make awesome stuff look better #### ui - make good stuff