1
1
mirror of https://github.com/orhun/git-cliff.git synced 2024-11-25 13:22:17 +03:00

feat(release): make the bump version rules configurable (#530)

closes #447

Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
This commit is contained in:
Jarosław Surkont 2024-03-11 14:08:07 +01:00 committed by GitHub
parent ccdc15217c
commit 152414cb81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 268 additions and 28 deletions

View File

@ -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://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 = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[bump]
features_always_bump_minor = false
breaking_always_bump_major = false

View File

@ -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"

View File

@ -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
<!-- generated by git-cliff -->

View File

@ -5,5 +5,5 @@ GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add
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 "feat!: add breaking feature"
GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "fix: fix feature 2"

View File

@ -2,11 +2,14 @@
All notable changes to this project will be documented in this file.
## [0.1.1]
## [1.0.0]
### Feat
- [**breaking**] Add breaking feature
### Fix
- Fix feature 1
- Fix feature 2
## [0.1.0]

View File

@ -200,7 +200,8 @@ impl<'a> Changelog<'a> {
pub fn bump_version(&mut self) -> Result<Option<String>> {
if let Some(ref mut last_release) = self.releases.iter_mut().next() {
if last_release.version.is_none() {
let next_version = last_release.calculate_next_version()?;
let next_version = last_release
.calculate_next_version_with_config(&self.config.bump)?;
debug!("Bumping the version to {next_version}");
last_release.version = Some(next_version.to_string());
last_release.timestamp = SystemTime::now()
@ -307,6 +308,7 @@ impl<'a> Changelog<'a> {
mod test {
use super::*;
use crate::config::{
Bump,
ChangelogConfig,
CommitParser,
GitConfig,
@ -485,6 +487,7 @@ mod test {
token: None,
},
},
bump: Bump::default(),
};
let test_release = Release {
version: Some(String::from("v1.0.0")),

View File

@ -33,6 +33,9 @@ pub struct Config {
/// Configuration values about remote.
#[serde(default)]
pub remote: RemoteConfig,
/// Configuration values about bump version.
#[serde(default)]
pub bump: Bump,
}
/// Changelog configuration.
@ -136,6 +139,29 @@ impl Remote {
}
}
/// Bump version configuration.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Bump {
/// Configures automatic minor version increments for feature changes.
///
/// When `true`, a feature will always trigger a minor version update.
/// When `false`, a feature will trigger:
///
/// - A patch version update if the major version is 0.
/// - A minor version update otherwise.
pub features_always_bump_minor: Option<bool>,
/// Configures 0 -> 1 major version increments for breaking changes.
///
/// When `true`, a breaking change commit will always trigger a major
/// version update (including the transition from version 0 to 1)
/// When `false`, a breaking change commit will trigger:
///
/// - A minor version update if the major version is 0.
/// - A major version update otherwise.
pub breaking_always_bump_major: Option<bool>,
}
/// Parser for grouping commits.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CommitParser {

View File

@ -1,4 +1,5 @@
use crate::commit::Commit;
use crate::config::Bump;
use crate::error::Result;
#[cfg(feature = "github")]
use crate::github::{
@ -98,7 +99,21 @@ impl<'a> Release<'a> {
}
/// Calculates the next version based on the commits.
///
/// It uses the default bump version configuration to calculate the next
/// version.
pub fn calculate_next_version(&self) -> Result<String> {
self.calculate_next_version_with_config(&Bump::default())
}
/// Calculates the next version based on the commits.
///
/// It uses the given bump version configuration to calculate the next
/// version.
pub(super) fn calculate_next_version_with_config(
&self,
config: &Bump,
) -> Result<String> {
match self
.previous
.as_ref()
@ -126,8 +141,12 @@ impl<'a> Release<'a> {
}
}
let next_version = VersionUpdater::new()
.with_features_always_increment_minor(true)
.with_breaking_always_increment_major(true)
.with_features_always_increment_minor(
config.features_always_bump_minor.unwrap_or(true),
)
.with_breaking_always_increment_major(
config.breaking_always_bump_major.unwrap_or(true),
)
.increment(
&semver?,
self.commits
@ -169,7 +188,27 @@ mod test {
use super::*;
#[test]
fn bump_version() -> Result<()> {
for (version, expected_version, commits) in [
fn build_release<'a>(version: &str, commits: &'a [&str]) -> Release<'a> {
Release {
version: None,
commits: commits
.iter()
.map(|v| Commit::from(v.to_string()))
.collect(),
commit_id: None,
timestamp: 0,
previous: Some(Box::new(Release {
version: Some(String::from(version)),
..Default::default()
})),
#[cfg(feature = "github")]
github: crate::github::GitHubReleaseMetadata {
contributors: vec![],
},
}
}
let test_shared = [
("1.0.0", "1.1.0", vec!["feat: add xyz", "fix: fix xyz"]),
("1.0.0", "1.0.1", vec!["fix: add xyz", "fix: aaaaaa"]),
("1.0.0", "2.0.0", vec!["feat!: add xyz", "feat: zzz"]),
@ -202,27 +241,87 @@ mod test {
"aaa#/@#$@9384!#%^#@#@!#!239432413-idk-9999.2200.5932-alpha.420",
vec!["feat: damn this is working"],
),
] {
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(version)),
..Default::default()
})),
#[cfg(feature = "github")]
github: crate::github::GitHubReleaseMetadata {
contributors: vec![],
},
};
];
for (version, expected_version, commits) in test_shared.iter().chain(
[
("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
("0.0.1", "0.1.0", vec!["feat: add xyz", "fix: fix xyz"]),
("0.0.1", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
("0.1.0", "0.2.0", vec!["feat: add xyz", "fix: fix xyz"]),
("0.1.0", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
]
.iter(),
) {
let release = build_release(version, commits);
let next_version = release.calculate_next_version()?;
assert_eq!(expected_version, next_version);
assert_eq!(expected_version, &next_version);
let next_version =
release.calculate_next_version_with_config(&Bump::default())?;
assert_eq!(expected_version, &next_version);
}
for (version, expected_version, commits) in test_shared.iter().chain(
[
("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
("0.0.1", "0.0.2", vec!["feat: add xyz", "fix: fix xyz"]),
("0.0.1", "0.0.2", vec!["feat!: add xyz", "feat: zzz"]),
("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
("0.1.0", "0.1.1", vec!["feat: add xyz", "fix: fix xyz"]),
("0.1.0", "0.2.0", vec!["feat!: add xyz", "feat: zzz"]),
]
.iter(),
) {
let release = build_release(version, commits);
let next_version =
release.calculate_next_version_with_config(&Bump {
features_always_bump_minor: Some(false),
breaking_always_bump_major: Some(false),
})?;
assert_eq!(expected_version, &next_version);
}
for (version, expected_version, commits) in test_shared.iter().chain(
[
("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
("0.0.1", "0.1.0", vec!["feat: add xyz", "fix: fix xyz"]),
("0.0.1", "0.1.0", vec!["feat!: add xyz", "feat: zzz"]),
("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
("0.1.0", "0.2.0", vec!["feat: add xyz", "fix: fix xyz"]),
("0.1.0", "0.2.0", vec!["feat!: add xyz", "feat: zzz"]),
]
.iter(),
) {
let release = build_release(version, commits);
let next_version =
release.calculate_next_version_with_config(&Bump {
features_always_bump_minor: Some(true),
breaking_always_bump_major: Some(false),
})?;
assert_eq!(expected_version, &next_version);
}
for (version, expected_version, commits) in test_shared.iter().chain(
[
("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
("0.0.1", "0.0.2", vec!["feat: add xyz", "fix: fix xyz"]),
("0.0.1", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
("0.1.0", "0.1.1", vec!["feat: add xyz", "fix: fix xyz"]),
("0.1.0", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
]
.iter(),
) {
let release = build_release(version, commits);
let next_version =
release.calculate_next_version_with_config(&Bump {
features_always_bump_minor: Some(false),
breaking_always_bump_major: Some(true),
})?;
assert_eq!(expected_version, &next_version);
}
let empty_release = Release {
previous: Some(Box::new(Release {
version: None,
@ -230,8 +329,18 @@ mod test {
})),
..Default::default()
};
let next_version = empty_release.calculate_next_version()?;
assert_eq!("0.1.0", next_version);
assert_eq!("0.1.0", empty_release.calculate_next_version()?);
for (features_always_bump_minor, breaking_always_bump_major) in
[(true, true), (true, false), (false, true), (false, false)]
{
assert_eq!(
"0.1.0",
empty_release.calculate_next_version_with_config(&Bump {
features_always_bump_minor: Some(features_always_bump_minor),
breaking_always_bump_major: Some(breaking_always_bump_major),
})?
);
}
Ok(())
}

View File

@ -0,0 +1,28 @@
# `bump`
This section contains the bump version related configuration options.
```toml
[bump]
features_always_bump_minor = true
breaking_always_bump_major = true
```
### features_always_bump_minor
Configures automatic minor version increments for feature changes.
When `true`, a feature will always trigger a minor version update.
When `false`, a feature will trigger:
- A patch version update if the major version is 0.
- A minor version update otherwise.
### breaking_always_bump_major
Configures 0 -> 1 major version increments for breaking changes.
When `true`, a breaking change commit will always trigger a major version update
(including the transition from version 0 to 1)
When `false`, a breaking change commit will trigger:
- A minor version update if the major version is 0.
- A major version update otherwise.

View File

@ -29,3 +29,12 @@ Tip: you can also get the bumped version [from the context](/docs/usage/print-co
```bash
git cliff --unreleased --bump --context | jq -r .[0].version
```
## Zero-based versioning scheme
When working with a zero-based versioning scheme (i.e., `0.x.y` or `0.0.x`),
it is often desirable to preserve the leading zero even when introducing a breaking change.
A switch from `0` to `1` should indicate a higher API stability level.
You can modify the bumping rules to preserve the zero-based versioning scheme in the
[configuration file](/docs/configuration/bump).