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:
parent
ccdc15217c
commit
152414cb81
31
.github/fixtures/test-bump-version-keep-zerover/cliff.toml
vendored
Normal file
31
.github/fixtures/test-bump-version-keep-zerover/cliff.toml
vendored
Normal 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
|
9
.github/fixtures/test-bump-version-keep-zerover/commit.sh
vendored
Executable file
9
.github/fixtures/test-bump-version-keep-zerover/commit.sh
vendored
Executable 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"
|
22
.github/fixtures/test-bump-version-keep-zerover/expected.md
vendored
Normal file
22
.github/fixtures/test-bump-version-keep-zerover/expected.md
vendored
Normal 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 -->
|
2
.github/fixtures/test-bump-version/commit.sh
vendored
2
.github/fixtures/test-bump-version/commit.sh
vendored
@ -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"
|
||||
|
@ -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]
|
||||
|
@ -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")),
|
||||
|
@ -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 {
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
28
website/docs/configuration/bump.md
Normal file
28
website/docs/configuration/bump.md
Normal 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.
|
@ -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).
|
||||
|
Loading…
Reference in New Issue
Block a user