Skip to content

Commit

Permalink
feat(release): make the bump version rules configurable
Browse files Browse the repository at this point in the history
closes #447
  • Loading branch information
jsurkont committed Mar 11, 2024
1 parent 830528c commit 47bd294
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 28 deletions.
31 changes: 31 additions & 0 deletions .github/fixtures/test-bump-version-keep-zerover/cliff.toml
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions .github/fixtures/test-bump-version-keep-zerover/commit.sh
Original file line number Diff line number Diff line change
@@ -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"
22 changes: 22 additions & 0 deletions .github/fixtures/test-bump-version-keep-zerover/expected.md
Original file line number Diff line number Diff line change
@@ -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 -->
2 changes: 1 addition & 1 deletion .github/fixtures/test-bump-version/commit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
7 changes: 5 additions & 2 deletions .github/fixtures/test-bump-version/expected.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
5 changes: 4 additions & 1 deletion git-cliff-core/src/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -307,6 +308,7 @@ impl<'a> Changelog<'a> {
mod test {
use super::*;
use crate::config::{
Bump,
ChangelogConfig,
CommitParser,
GitConfig,
Expand Down Expand Up @@ -485,6 +487,7 @@ mod test {
token: None,
},
},
bump: Bump::default(),
};
let test_release = Release {
version: Some(String::from("v1.0.0")),
Expand Down
26 changes: 26 additions & 0 deletions git-cliff-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
157 changes: 133 additions & 24 deletions git-cliff-core/src/release.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::commit::Commit;
use crate::config::Bump;
use crate::error::Result;
#[cfg(feature = "github")]
use crate::github::{
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"]),
Expand Down Expand Up @@ -202,36 +241,106 @@ 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,
..Default::default()
})),
..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(())
}

Expand Down
28 changes: 28 additions & 0 deletions website/docs/configuration/bump.md
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 9 additions & 0 deletions website/docs/usage/bump-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

0 comments on commit 47bd294

Please sign in to comment.