diff --git a/.github/fixtures/test-bump-version-major/cliff.toml b/.github/fixtures/test-bump-version-major/cliff.toml new file mode 100644 index 0000000000..c48526d6f7 --- /dev/null +++ b/.github/fixtures/test-bump-version-major/cliff.toml @@ -0,0 +1,27 @@ +[changelog] +# template for the changelog footer +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") }}] +{% 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 +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true diff --git a/.github/fixtures/test-bump-version-major/commit.sh b/.github/fixtures/test-bump-version-major/commit.sh new file mode 100755 index 0000000000..3d7ef4718b --- /dev/null +++ b/.github/fixtures/test-bump-version-major/commit.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 1" +GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 2" +git tag v0.1.0 + +GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "fix: fix feature 1" +GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "fix: fix feature 2" diff --git a/.github/fixtures/test-bump-version-major/expected.md b/.github/fixtures/test-bump-version-major/expected.md new file mode 100644 index 0000000000..84bb0ad594 --- /dev/null +++ b/.github/fixtures/test-bump-version-major/expected.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.0.0] + +### Fix + +- Fix feature 1 +- Fix feature 2 + +## [0.1.0] + +### Feat + +- Add feature 1 +- Add feature 2 + + diff --git a/.github/fixtures/test-bump-version-minor/cliff.toml b/.github/fixtures/test-bump-version-minor/cliff.toml new file mode 100644 index 0000000000..c48526d6f7 --- /dev/null +++ b/.github/fixtures/test-bump-version-minor/cliff.toml @@ -0,0 +1,27 @@ +[changelog] +# template for the changelog footer +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") }}] +{% 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 +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true diff --git a/.github/fixtures/test-bump-version-minor/commit.sh b/.github/fixtures/test-bump-version-minor/commit.sh new file mode 100755 index 0000000000..0fd84c20f6 --- /dev/null +++ b/.github/fixtures/test-bump-version-minor/commit.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 1" +GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 2" +git tag v0.1.0 + +GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "feat!: add breaking feature" +GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "fix: fix feature 2" diff --git a/.github/fixtures/test-bump-version-minor/expected.md b/.github/fixtures/test-bump-version-minor/expected.md new file mode 100644 index 0000000000..b222642be6 --- /dev/null +++ b/.github/fixtures/test-bump-version-minor/expected.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.2.0] + +### Feat + +- [**breaking**] Add breaking feature + +### Fix + +- Fix feature 2 + +## [0.1.0] + +### Feat + +- Add feature 1 +- Add feature 2 + + diff --git a/.github/fixtures/test-bump-version-patch/cliff.toml b/.github/fixtures/test-bump-version-patch/cliff.toml new file mode 100644 index 0000000000..c48526d6f7 --- /dev/null +++ b/.github/fixtures/test-bump-version-patch/cliff.toml @@ -0,0 +1,27 @@ +[changelog] +# template for the changelog footer +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") }}] +{% 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 +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true diff --git a/.github/fixtures/test-bump-version-patch/commit.sh b/.github/fixtures/test-bump-version-patch/commit.sh new file mode 100755 index 0000000000..0fd84c20f6 --- /dev/null +++ b/.github/fixtures/test-bump-version-patch/commit.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 1" +GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 2" +git tag v0.1.0 + +GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "feat!: add breaking feature" +GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "fix: fix feature 2" diff --git a/.github/fixtures/test-bump-version-patch/expected.md b/.github/fixtures/test-bump-version-patch/expected.md new file mode 100644 index 0000000000..9a8c825495 --- /dev/null +++ b/.github/fixtures/test-bump-version-patch/expected.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.1.1] + +### Feat + +- [**breaking**] Add breaking feature + +### Fix + +- Fix feature 2 + +## [0.1.0] + +### Feat + +- Add feature 1 +- Add feature 2 + + diff --git a/.github/workflows/test-fixtures.yml b/.github/workflows/test-fixtures.yml index 77e30cab4d..f732dc1e3d 100644 --- a/.github/workflows/test-fixtures.yml +++ b/.github/workflows/test-fixtures.yml @@ -38,6 +38,12 @@ jobs: - fixtures-name: test-split-commits - fixtures-name: test-bump-version command: --bump + - fixtures-name: test-bump-version-major + command: --bump major + - fixtures-name: test-bump-version-minor + command: --bump minor + - fixtures-name: test-bump-version-patch + command: --bump patch - fixtures-name: test-bump-version-custom-minor command: --bump - fixtures-name: test-bumped-version diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs index eea73925e2..0f34ef4e9d 100644 --- a/git-cliff-core/src/config.rs +++ b/git-cliff-core/src/config.rs @@ -174,6 +174,17 @@ impl Remote { } } +/// Version bump type. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] +pub enum BumpType { + /// Bump major version. + Major, + /// Bump minor version. + Minor, + /// Bump patch version. + Patch, +} + /// Bump version configuration. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Bump { @@ -218,6 +229,9 @@ pub struct Bump { /// /// `commit type` according to the spec is only `[a-zA-Z]+` pub custom_minor_increment_regex: Option, + + /// Force to always bump in major, minor or patch. + pub bump_type: Option, } /// Parser for grouping commits. diff --git a/git-cliff-core/src/release.rs b/git-cliff-core/src/release.rs index 9ee23835fa..2e681bf51b 100644 --- a/git-cliff-core/src/release.rs +++ b/git-cliff-core/src/release.rs @@ -1,5 +1,3 @@ -use crate::commit::Commit; -use crate::config::Bump; use crate::error::Result; #[cfg(feature = "remote")] use crate::remote::{ @@ -8,7 +6,16 @@ use crate::remote::{ RemotePullRequest, RemoteReleaseMetadata, }; -use next_version::VersionUpdater; +use crate::{ + commit::Commit, + config::Bump, + config::BumpType, +}; + +use next_version::{ + NextVersion, + VersionUpdater, +}; use semver::Version; use serde::{ Deserialize, @@ -124,15 +131,23 @@ impl<'a> Release<'a> { custom_minor_increment_regex, )?; } - let next_version = next_version - .increment( - &semver?, - self.commits - .iter() - .map(|commit| commit.message.trim_end().to_string()) - .collect::>(), - ) - .to_string(); + let next_version = if let Some(bump_type) = &config.bump_type { + match bump_type { + BumpType::Major => semver?.increment_major().to_string(), + BumpType::Minor => semver?.increment_minor().to_string(), + BumpType::Patch => semver?.increment_patch().to_string(), + } + } else { + next_version + .increment( + &semver?, + self.commits + .iter() + .map(|commit| commit.message.trim_end().to_string()) + .collect::>(), + ) + .to_string() + }; if let Some(prefix) = prefix { Ok(format!("{prefix}{next_version}")) } else { @@ -282,6 +297,7 @@ mod test { initial_tag: None, custom_major_increment_regex: None, custom_minor_increment_regex: None, + bump_type: None, })?; assert_eq!(expected_version, &next_version); } @@ -305,6 +321,7 @@ mod test { initial_tag: None, custom_major_increment_regex: None, custom_minor_increment_regex: None, + bump_type: None, })?; assert_eq!(expected_version, &next_version); } @@ -328,6 +345,7 @@ mod test { initial_tag: None, custom_major_increment_regex: None, custom_minor_increment_regex: None, + bump_type: None, })?; assert_eq!(expected_version, &next_version); } @@ -351,6 +369,7 @@ mod test { initial_tag: None, custom_major_increment_regex: None, custom_minor_increment_regex: None, + bump_type: None, })? ); } diff --git a/git-cliff/src/args.rs b/git-cliff/src/args.rs index 4c7698567d..f0decb7944 100644 --- a/git-cliff/src/args.rs +++ b/git-cliff/src/args.rs @@ -13,6 +13,7 @@ use clap::{ ValueEnum, }; use git_cliff_core::{ + config::BumpType, config::Remote, DEFAULT_CONFIG, DEFAULT_OUTPUT, @@ -190,9 +191,16 @@ pub struct Opt { allow_hyphen_values = true )] pub tag: Option, - /// Bumps the version for unreleased changes. - #[arg(long, help_heading = Some("FLAGS"))] - pub bump: bool, + /// Bumps the version for unreleased changes. Optionally with specified + /// version. + #[arg( + long, + value_name = "BUMP", + value_enum, + num_args = 0..=1, + default_missing_value = "auto", + value_parser = clap::value_parser!(BumpOption))] + pub bump: Option, /// Prints bumped version for unreleased changes. #[arg(long, help_heading = Some("FLAGS"))] pub bumped_version: bool, @@ -352,6 +360,54 @@ impl TypedValueParser for RemoteValueParser { } } +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum BumpOption { + Auto, + Specific(BumpType), +} + +impl ValueParserFactory for BumpOption { + type Parser = BumpOptionParser; + fn value_parser() -> Self::Parser { + BumpOptionParser + } +} + +/// Parser for bump type. +#[derive(Clone, Debug)] +pub struct BumpOptionParser; + +impl TypedValueParser for BumpOptionParser { + type Value = BumpOption; + fn parse_ref( + &self, + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &std::ffi::OsStr, + ) -> Result { + let inner = clap::builder::StringValueParser::new(); + let value = inner.parse_ref(cmd, arg, value)?; + match value.as_str() { + "auto" => Ok(BumpOption::Auto), + "major" => Ok(BumpOption::Specific(BumpType::Major)), + "minor" => Ok(BumpOption::Specific(BumpType::Minor)), + "patch" => Ok(BumpOption::Specific(BumpType::Patch)), + _ => { + let mut err = + clap::Error::new(ErrorKind::ValueValidation).with_cmd(cmd); + if let Some(arg) = arg { + err.insert( + ContextKind::InvalidArg, + ContextValue::String(arg.to_string()), + ); + } + err.insert(ContextKind::InvalidValue, ContextValue::String(value)); + Err(err) + } + } + } +} + impl Opt { /// Custom string parser for directories. /// @@ -409,4 +465,29 @@ mod tests { .is_err()); Ok(()) } + + #[test] + fn bump_option_parser() -> Result<(), clap::Error> { + let bump_option_parser = BumpOptionParser; + assert_eq!( + BumpOption::Auto, + bump_option_parser.parse_ref( + &Opt::command(), + None, + OsStr::new("auto") + )? + ); + assert!(bump_option_parser + .parse_ref(&Opt::command(), None, OsStr::new("test")) + .is_err()); + assert_eq!( + BumpOption::Specific(BumpType::Major), + bump_option_parser.parse_ref( + &Opt::command(), + None, + OsStr::new("major") + )? + ); + Ok(()) + } } diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs index a42da54993..d9541a02c6 100644 --- a/git-cliff/src/lib.rs +++ b/git-cliff/src/lib.rs @@ -14,6 +14,7 @@ pub mod logger; extern crate log; use args::{ + BumpOption, Opt, Sort, Strip, @@ -546,10 +547,13 @@ pub fn run(mut args: Opt) -> Result<()> { } // Process commits and releases for the changelog. + if let Some(BumpOption::Specific(bump_type)) = args.bump { + config.bump.bump_type = Some(bump_type) + } let mut changelog = Changelog::new(releases, &config)?; // Print the result. - if args.bump || args.bumped_version { + if args.bump.is_some() || args.bumped_version { let next_version = if let Some(next_version) = changelog.bump_version()? { next_version } else if let Some(last_version) = diff --git a/website/docs/configuration/bump.md b/website/docs/configuration/bump.md index 5f0f5bc3e9..3ef9881cb3 100644 --- a/website/docs/configuration/bump.md +++ b/website/docs/configuration/bump.md @@ -81,3 +81,14 @@ git-cliff --bumped-version 0.2.0 ``` + +### bump_type + +When set, it forces to always bump in major, minor or patch. + +e.g. + +```toml +[bump] +bump_type = "minor" +``` diff --git a/website/docs/usage/args.md b/website/docs/usage/args.md index efd9b3ee4b..c4926f325e 100644 --- a/website/docs/usage/args.md +++ b/website/docs/usage/args.md @@ -14,7 +14,6 @@ git-cliff [FLAGS] [OPTIONS] [--] [RANGE] -h, --help Prints help information -V, --version Prints version information -v, --verbose... Increases the logging verbosity - --bump Bumps the version for unreleased changes --bumped-version Prints bumped version for unreleased changes -l, --latest Processes the commits starting from the latest tag --current Processes the commits that belong to the current tag @@ -28,6 +27,7 @@ git-cliff [FLAGS] [OPTIONS] [--] [RANGE] ``` -i, --init [] Writes the default configuration file to cliff.toml + --bump Bumps the version for unreleased changes [default: auto] [possible values: auto, major, minor, patch] -c, --config Sets the configuration file [env: GIT_CLIFF_CONFIG=] [default: cliff.toml] -w, --workdir Sets the working directory [env: GIT_CLIFF_WORKDIR=] -r, --repository ... Sets the git repository [env: GIT_CLIFF_REPOSITORY=] diff --git a/website/docs/usage/bump-version.md b/website/docs/usage/bump-version.md index 6478af93d5..6feb82a7e4 100644 --- a/website/docs/usage/bump-version.md +++ b/website/docs/usage/bump-version.md @@ -22,18 +22,32 @@ How it works is that for a semantic versioning such as `..` - "feat:" -> increments `MINOR` - "scope!" (breaking changes) -> increments `MAJOR` +## Get version + You can also calculate and print the next semantic version to `stdout`: ```bash git cliff --bumped-version ``` -Tip: you can also get the bumped version [from the context](/docs/usage/print-context) as follows: +:::tip + +You can also get the bumped version [from the context](/docs/usage/print-context) as follows: ```bash git cliff --unreleased --bump --context | jq -r .[0].version ``` +::: + +## Bump to a specific version type + +Optionally, you can specify a bump type in `--bump`: + +```bash +git cliff --bump [major|minor|patch] +``` + ## Zero-based versioning scheme When working with a zero-based versioning scheme (i.e., `0.x.y` or `0.0.x`),