diff --git a/Cargo.lock b/Cargo.lock index 1c008261c4..76f6366fe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,6 +301,16 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "conventional_commit_parser" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58660f9e1d5eeeeec9c33d1473ea8bba000c673a2189edaeedb4523ec7d6f7cb" +dependencies = [ + "pest", + "pest_derive", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -604,9 +614,11 @@ dependencies = [ "indexmap 2.0.2", "lazy-regex", "log", + "next_version", "pretty_assertions", "regex", "rust-embed", + "semver", "serde", "serde_json", "serde_regex", @@ -1032,6 +1044,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "next_version" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70bb5535b0e53d062c92ad0ad600a29b5fd4ea84e40dc42faecff21218239593" +dependencies = [ + "conventional_commit_parser", + "semver", +] + [[package]] name = "nom" version = "7.1.3" diff --git a/config/cliff.toml b/config/cliff.toml index f402fbd753..ce51682288 100644 --- a/config/cliff.toml +++ b/config/cliff.toml @@ -5,6 +5,7 @@ # Configuration options are organized into tables and keys. # See documentation for more information on available options. + [changelog] # changelog header header = """ diff --git a/git-cliff-core/Cargo.toml b/git-cliff-core/Cargo.toml index 43defd509a..8a18e22f42 100644 --- a/git-cliff-core/Cargo.toml +++ b/git-cliff-core/Cargo.toml @@ -26,6 +26,8 @@ tera = "1.19.1" indexmap = { version = "2.0.2", optional = true } toml = "0.8.2" lazy-regex = "3.0.2" +next_version = "0.2.8" +semver = "1.0.19" [dependencies.git2] version = "0.18.1" diff --git a/git-cliff-core/src/error.rs b/git-cliff-core/src/error.rs index 85f68a30d9..1e093c705b 100644 --- a/git-cliff-core/src/error.rs +++ b/git-cliff-core/src/error.rs @@ -57,6 +57,13 @@ pub enum Error { /// Error that may occur while parsing integers. #[error("Failed to parse integer: `{0}`")] IntParseError(#[from] std::num::TryFromIntError), + /// Error that may occur while parsing a SemVer version or version + /// requirement. + #[error("Semver error: `{0}`")] + SemverError(#[from] semver::Error), + /// Error that may occur when a version is not found for the next release. + #[error("Previous version is not found for calculating the next release.")] + PreviousVersionNotFound, } /// Result type of the core library. diff --git a/git-cliff-core/src/release.rs b/git-cliff-core/src/release.rs index f1964494af..a7d6d4bdd8 100644 --- a/git-cliff-core/src/release.rs +++ b/git-cliff-core/src/release.rs @@ -1,5 +1,10 @@ use crate::commit::Commit; -use crate::error::Result; +use crate::error::{ + Error, + Result, +}; +use next_version::NextVersion; +use semver::Version; use serde::{ Deserialize, Serialize, @@ -22,6 +27,26 @@ pub struct Release<'a> { pub previous: Option>>, } +impl<'a> Release<'a> { + /// Calculates the next version based on the commits. + pub fn calculate_next_version(&self) -> Result { + let version = self + .previous + .as_ref() + .and_then(|release| release.version.clone()) + .ok_or_else(|| Error::PreviousVersionNotFound)?; + let next_version = Version::parse(version.trim_start_matches('v'))? + .next( + self.commits + .iter() + .map(|commit| commit.message.to_string()) + .collect::>(), + ) + .to_string(); + Ok(next_version) + } +} + /// Representation of a list of releases. pub struct Releases<'a>(pub &'a Vec>); @@ -31,3 +56,33 @@ impl<'a> Releases<'a> { Ok(serde_json::to_string(self.0)?) } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn bump_version() -> Result<()> { + for (expected_version, commits) in [ + ("1.1.0", vec!["feat: add xyz", "fix: fix xyz"]), + ("1.0.1", vec!["fix: add xyz", "fix: aaaaaa"]), + ("2.0.0", vec!["feat!: add xyz", "feat: zzz"]), + ] { + let release = Release { + version: None, + commits: commits + .into_iter() + .map(|v| Commit::from(v.to_string())) + .collect(), + commit_id: None, + timestamp: 0, + previous: Some(Box::new(Release { + version: Some(String::from("1.0.0")), + ..Default::default() + })), + }; + let next_version = release.calculate_next_version()?; + assert_eq!(expected_version, next_version); + } + Ok(()) + } +} diff --git a/git-cliff/src/args.rs b/git-cliff/src/args.rs index f534bffdf3..9972fcf113 100644 --- a/git-cliff/src/args.rs +++ b/git-cliff/src/args.rs @@ -142,6 +142,9 @@ 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, /// Sets the template for the changelog body. #[arg( short, diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs index cf94f57405..2caeef85a2 100644 --- a/git-cliff/src/lib.rs +++ b/git-cliff/src/lib.rs @@ -229,6 +229,17 @@ fn process_repository<'a>( } } + // Bump the version. + if args.bump && releases[release_index].version.is_none() { + let next_version = releases[release_index].calculate_next_version()?; + debug!("Bumping the version to {next_version}"); + releases[release_index].version = Some(next_version.to_string()); + releases[release_index].timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH)? + .as_secs() + .try_into()?; + } + Ok(releases) } diff --git a/website/docs/usage/examples.md b/website/docs/usage/examples.md index 07c36a2d15..2d7980e646 100644 --- a/website/docs/usage/examples.md +++ b/website/docs/usage/examples.md @@ -17,13 +17,21 @@ Then simply create a changelog at your projects git root directory: git cliff ``` -Set a tag for the "unreleased" changes: +Set a tag for the unreleased changes: ```bash # it doesn't have to be an existing tag git cliff --tag 1.0.0 ``` +Calculate and set the next semantic version (i.e. _bump the version_) for the unreleased changes: + +```bash +# Semver: {MAJOR}.{MINOR}.{PATCH} +# "fix:" increments PATCH, "feat:" increments MINOR and "scope!" (breaking changes) increments MAJOR +git cliff --bump +``` + Generate a changelog for a certain part of git history: ```bash diff --git a/website/docs/usage/usage.md b/website/docs/usage/usage.md index f6fa1a6d71..53c28ffeb5 100644 --- a/website/docs/usage/usage.md +++ b/website/docs/usage/usage.md @@ -1,6 +1,7 @@ --- sidebar_position: 3 --- + # Usage ``` @@ -13,6 +14,7 @@ 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 -i, --init Writes the default configuration file to cliff.toml -l, --latest Processes the commits starting from the latest tag --current Processes the commits that belong to the current tag