next_version-0.2.26/.cargo_vcs_info.json 0000644 00000000161 00000000001 0013644 0 ustar {
"git": {
"sha1": "7301a1468f3100413e5c49cf300358beaada1bf3"
},
"path_in_vcs": "crates/next_version"
} next_version-0.2.26/CHANGELOG.md 0000644 0000000 0000000 00000014672 10461020230 0014261 0 ustar 0000000 0000000 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.2.26](https://github.com/release-plz/release-plz/compare/next_version-v0.2.25...next_version-v0.2.26) - 2025-11-24
### Other
- address code quality issues ([#2498](https://github.com/release-plz/release-plz/pull/2498))
## [0.2.25](https://github.com/release-plz/release-plz/compare/next_version-v0.2.24...next_version-v0.2.25) - 2025-09-13
### Other
- update Cargo.toml dependencies
## [0.2.24](https://github.com/release-plz/release-plz/compare/next_version-v0.2.23...next_version-v0.2.24) - 2025-03-17
### Other
- add more rust lints ([#2135](https://github.com/release-plz/release-plz/pull/2135))
## [0.2.23](https://github.com/release-plz/release-plz/compare/next_version-v0.2.22...next_version-v0.2.23) - 2025-03-01
### Other
- add new line to changelog header (#2098)
## [0.2.22](https://github.com/release-plz/release-plz/compare/next_version-v0.2.21...next_version-v0.2.22) - 2025-02-02
### Other
- *(next-version)* add test for scope with whitespace (#1997)
- use `git-conventional` library to parse commits (#2021)
## [0.2.21](https://github.com/release-plz/release-plz/compare/next_version-v0.2.20...next_version-v0.2.21) - 2024-12-07
### Other
- update Cargo.toml dependencies
## [0.2.20](https://github.com/release-plz/release-plz/compare/next_version-v0.2.19...next_version-v0.2.20) - 2024-11-21
### Other
- move `MarcoIeni/release-plz` to `release-plz/release-plz` ([#1850](https://github.com/release-plz/release-plz/pull/1850))
## [0.2.19](https://github.com/release-plz/release-plz/compare/next_version-v0.2.18...next_version-v0.2.19) - 2024-08-04
### Other
- update Cargo.toml dependencies
## [0.2.18](https://github.com/release-plz/release-plz/compare/next_version-v0.2.17...next_version-v0.2.18) - 2024-06-18
### Added
- support bumping version based on configurable custom pattern ([#1511](https://github.com/release-plz/release-plz/pull/1511))
## [0.2.17](https://github.com/release-plz/release-plz/compare/next_version-v0.2.16...next_version-v0.2.17) - 2024-05-27
### Other
- update Cargo.toml dependencies
## [0.2.16](https://github.com/release-plz/release-plz/compare/next_version-v0.2.15...next_version-v0.2.16) - 2024-05-05
### Other
- add `if_not_else` clippy lint ([#1438](https://github.com/release-plz/release-plz/pull/1438))
## [0.2.15](https://github.com/release-plz/release-plz/compare/next_version-v0.2.14...next_version-v0.2.15) - 2024-03-08
### Other
- use edition and license workspace ([#1329](https://github.com/release-plz/release-plz/pull/1329))
## [0.2.14](https://github.com/release-plz/release-plz/compare/next_version-v0.2.13...next_version-v0.2.14) - 2024-02-23
### Other
- update Cargo.toml dependencies
## [0.2.13](https://github.com/release-plz/release-plz/compare/next_version-v0.2.12...next_version-v0.2.13) - 2024-01-16
### Other
- update Cargo.toml dependencies
## [0.2.12](https://github.com/release-plz/release-plz/compare/next_version-v0.2.11...next_version-v0.2.12) - 2023-12-30
### Added
- *(next_version)* support configuration ([#1168](https://github.com/release-plz/release-plz/pull/1168))
## [0.2.11](https://github.com/release-plz/release-plz/compare/next_version-v0.2.10...next_version-v0.2.11) - 2023-12-13
### Other
- better document bump strategy ([#1132](https://github.com/release-plz/release-plz/pull/1132))
## [0.2.10](https://github.com/release-plz/release-plz/compare/next_version-v0.2.9...next_version-v0.2.10) - 2023-11-08
### Other
- *(next-version)* clarify patch increment ([#1065](https://github.com/release-plz/release-plz/pull/1065))
## [0.2.9](https://github.com/release-plz/release-plz/compare/next_version-v0.2.8...next_version-v0.2.9) - 2023-10-15
### Other
- update dependencies
## [0.2.8](https://github.com/release-plz/release-plz/compare/next_version-v0.2.7...next_version-v0.2.8) - 2023-09-30
### Other
- update dependencies
## [0.2.7](https://github.com/release-plz/release-plz/compare/next_version-v0.2.6...next_version-v0.2.7) - 2023-09-16
### Other
- add additional clippy lints ([#965](https://github.com/release-plz/release-plz/pull/965))
## [0.2.6](https://github.com/release-plz/release-plz/compare/next_version-v0.2.5...next_version-v0.2.6) - 2023-09-08
### Other
- *(ci)* check links ([#941](https://github.com/release-plz/release-plz/pull/941))
## [0.2.5](https://github.com/release-plz/release-plz/compare/next_version-v0.2.4...next_version-v0.2.5) - 2023-07-25
### Other
- update dependencies
## [0.2.4](https://github.com/release-plz/release-plz/compare/next_version-v0.2.3...next_version-v0.2.4) - 2023-04-16
### Other
- add test for breaking change in footer (#690)
## [0.2.3](https://github.com/release-plz/release-plz/compare/next_version-v0.2.2...next_version-v0.2.3) - 2023-03-13
### Other
- update dependencies
## [0.2.2](https://github.com/release-plz/release-plz/compare/next_version-v0.2.1...next_version-v0.2.2) - 2023-03-04
### Fixed
- always bump patch in 0.0.x versions (#578)
## [0.2.1](https://github.com/release-plz/release-plz/compare/next_version-v0.2.0...next_version-v0.2.1) - 2023-02-26
### Added
- add function to force breaking changes (#568)
## [0.2.0](https://github.com/release-plz/release-plz/compare/next_version-v0.1.10...next_version-v0.2.0) - 2023-01-11
### Added
- [**breaking**] don't remove build metadata (#433)
- [**breaking**] handle pre-releases (#425)
## [0.1.10](https://github.com/release-plz/release-plz/compare/next_version-v0.1.9...next_version-v0.1.10) - 2022-12-16
### Other
- Add functions to increment major, minor and patch version (#375)
## [0.1.9] - 2022-11-04
### Other
- use workspace dependencies (#333)
## [0.1.8] - 2022-10-24
### Other
- *(deps)* bump semver from 1.0.12 to 1.0.14 (#283)
## [0.1.7] - 2022-07-25
### Other
- inline the increment* functions to cut down dependencies (#220)
## [0.1.6] - 2022-07-16
### Other
- update to cargo edit 10 (#211)
- *(deps)* bump semver from 1.0.10 to 1.0.12 (#204)
## [0.1.5] - 2022-06-18
### Other
- *(deps)* bump semver from 1.0.9 to 1.0.10 (#180)
## [0.1.4] - 2022-05-19
### Other
- upgrade dependencies (#133)
## [0.1.3] - 2022-05-01
### Other
- remove default features from cargo-edit dependency (#101)
## [0.1.2] - 2022-04-27
### Other
- update dependencies (#91)
## [0.1.1] - 2022-03-27
### Fixed
- fix repository link
next_version-0.2.26/Cargo.lock 0000644 00000004244 00000000001 0011625 0 ustar # This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "git-conventional"
version = "0.12.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6a949b7fcc81df22526032dcddb006e78c8575e47b0e7ba57d9960570a57bc4"
dependencies = [
"unicase",
"winnow",
]
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "next_version"
version = "0.2.26"
dependencies = [
"git-conventional",
"regex",
"semver",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "winnow"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
next_version-0.2.26/Cargo.toml 0000644 00000007731 00000000001 0011654 0 ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "next_version"
version = "0.2.26"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Determine next semantic version based on conventional commits"
readme = "README.md"
keywords = [
"semver",
"semantic",
"versioning",
"conventional",
"commits",
]
categories = [
"development-tools",
"parsing",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/release-plz/release-plz/tree/main/crates/next_version"
[lib]
name = "next_version"
path = "src/lib.rs"
[[test]]
name = "all"
path = "tests/all/main.rs"
[dependencies.git-conventional]
version = "0.12.9"
[dependencies.regex]
version = "1.11.2"
[dependencies.semver]
version = "1.0.26"
[lints.clippy]
arbitrary_source_item_ordering = "allow"
as_underscore = "warn"
await_holding_lock = "warn"
await_holding_refcell_ref = "warn"
cargo_common_metadata = "warn"
cast_lossless = "warn"
cast_possible_wrap = "warn"
cast_precision_loss = "warn"
cast_sign_loss = "warn"
decimal_literal_representation = "allow"
default_trait_access = "warn"
doc_markdown = "warn"
exit = "warn"
expl_impl_clone_on_copy = "warn"
explicit_into_iter_loop = "warn"
explicit_iter_loop = "warn"
filetype_is_file = "warn"
filter_map_next = "warn"
float_cmp_const = "warn"
fn_params_excessive_bools = "warn"
get_unwrap = "warn"
if_not_else = "warn"
if_then_some_else_none = "warn"
implicit_clone = "warn"
large_digit_groups = "warn"
large_stack_arrays = "warn"
large_types_passed_by_value = "warn"
let_underscore_must_use = "warn"
let_unit_value = "warn"
linkedlist = "warn"
lossy_float_literal = "warn"
macro_use_imports = "warn"
manual_ignore_case_cmp = "warn"
manual_let_else = "warn"
manual_ok_or = "warn"
map_all_any_identity = "warn"
map_err_ignore = "warn"
map_flatten = "warn"
map_unwrap_or = "allow"
map_with_unused_argument_over_ranges = "warn"
match_bool = "warn"
match_same_arms = "warn"
match_wildcard_for_single_variants = "warn"
mem_forget = "warn"
mut_mut = "warn"
needless_as_bytes = "warn"
needless_borrow = "warn"
needless_continue = "warn"
needless_pass_by_value = "warn"
needless_raw_string_hashes = "allow"
option_option = "warn"
ptr_as_ptr = "warn"
range_minus_one = "warn"
range_plus_one = "warn"
rc_buffer = "warn"
redundant_else = "allow"
ref_option_ref = "warn"
regex_creation_in_loops = "warn"
rest_pat_in_fully_bound_structs = "warn"
same_functions_in_if_condition = "warn"
semicolon_if_nothing_returned = "warn"
trait_duplication_in_bounds = "warn"
trivially_copy_pass_by_ref = "warn"
type_repetition_in_bounds = "warn"
unicode_not_nfc = "warn"
uninlined_format_args = "warn"
unnecessary_literal_bound = "warn"
unnecessary_map_or = "warn"
unneeded_field_pattern = "warn"
unreadable_literal = "warn"
unsafe_derive_deserialize = "warn"
unseparated_literal_suffix = "warn"
unused_self = "warn"
use_self = "warn"
used_underscore_binding = "warn"
verbose_file_reads = "warn"
zero_sized_map_values = "warn"
[lints.rust]
ambiguous_negative_literals = "warn"
closure_returning_async_block = "warn"
explicit_outlives_requirements = "warn"
if_let_rescope = "warn"
impl_trait_overcaptures = "warn"
impl_trait_redundant_captures = "warn"
let_underscore_drop = "warn"
macro_use_extern_crate = "warn"
missing_debug_implementations = "warn"
non_ascii_idents = "warn"
redundant_imports = "warn"
redundant_lifetimes = "warn"
single_use_lifetimes = "warn"
trivial_casts = "warn"
trivial_numeric_casts = "warn"
unit_bindings = "warn"
unsafe_attr_outside_unsafe = "warn"
unused = "warn"
unused_import_braces = "warn"
next_version-0.2.26/Cargo.toml.orig 0000644 0000000 0000000 00000000777 10461020230 0015340 0 ustar 0000000 0000000 [package]
name = "next_version"
version = "0.2.26"
edition.workspace = true
description = "Determine next semantic version based on conventional commits"
license.workspace = true
repository = "https://github.com/release-plz/release-plz/tree/main/crates/next_version"
keywords = ["semver", "semantic", "versioning", "conventional", "commits"]
categories = ["development-tools", "parsing"]
[lints]
workspace = true
[dependencies]
regex.workspace = true
semver.workspace = true
git-conventional.workspace = true
next_version-0.2.26/README.md 0000644 0000000 0000000 00000000476 10461020230 0013724 0 ustar 0000000 0000000 # next_version
Library to calculate next semantic version based on
[conventional commits](https://www.conventionalcommits.org/).
It does not analyze git history, the list of commits must be provided by the user.
## Credits
Parts of the codebase are inspired by [cocogitto](https://github.com/cocogitto/cocogitto).
next_version-0.2.26/src/lib.rs 0000644 0000000 0000000 00000010346 10461020230 0014345 0 ustar 0000000 0000000 //! Library to calculate next semantic version based on
//! [conventional commits](https://www.conventionalcommits.org/).
//!
//! It does not analyze git history, the list of commits must be provided by the user.
//!
//! # Version changes
//!
//! ## Non conventional commits
//!
//! If conventional commits are not used, the patch is incremented.
//!
//! ```rust
//! use semver::Version;
//! use next_version::NextVersion;
//!
//! let commits = ["my change"];
//! assert_eq!(Version::new(1, 2, 3).next(commits), Version::new(1, 2, 4));
//! ```
//!
//! ## `0.0.x` versions
//!
//! In `0.0.x` versions the patch is always incremented:
//!
//! ```rust
//! use semver::Version;
//! use next_version::NextVersion;
//!
//! let commits = ["my change"];
//! assert_eq!(Version::new(0, 0, 4).next(&commits), Version::new(0, 0, 5));
//!
//! let commits = ["feat!: break user"];
//! assert_eq!(Version::new(0, 0, 1).next(&commits), Version::new(0, 0, 2));
//! ```
//!
//!
We don't increase the minor version because the bump
//! from 0.0.x to 0.1.x
//! should be intentional (not automated) because the author communicates an higher level of
//! API stability to the user.
//!
//! ## Features
//!
//! If a feature comment is present:
//! - If the major number is `0`: the patch is incremented.
//! - Otherwise: the minor is incremented.
//!
//! ```rust
//! use semver::Version;
//! use next_version::NextVersion;
//!
//! let commits = ["my change", "feat: make coffe"];
//! assert_eq!(Version::new(1, 2, 4).next(&commits), Version::new(1, 3, 0));
//!
//! assert_eq!(Version::new(0, 2, 4).next(&commits), Version::new(0, 2, 5));
//! ```
//!
//! When the major number is 0,
//! we don't increase the minor version because the bump from 0.x.y to 0.(x+1).0
//! indicates a breaking change.
//!
//! ## Breaking changes
//!
//! Breaking changes will increment:
//! - major if major is not `0`.
//! - minor if major is `0`.
//!
//! ```rust
//! use semver::Version;
//! use next_version::NextVersion;
//!
//! let commits = ["feat!: break user"];
//! assert_eq!(Version::new(1, 2, 4).next(&commits), Version::new(2, 0, 0));
//!
//! assert_eq!(Version::new(0, 4, 4).next(&commits), Version::new(0, 5, 0));
//! ```
//!
//! According to the [conventional commits specification](https://www.conventionalcommits.org/),
//! breaking changes can also be specified in the footer:
//!
//! ```rust
//! use semver::Version;
//! use next_version::NextVersion;
//!
//! let breaking_commit = r#"feat: make coffe
//!
//! my change
//!
//! BREAKING CHANGE: user will be broken
//! "#;
//!
//! let commits = [breaking_commit];
//! assert_eq!(Version::new(1, 2, 4).next(&commits), Version::new(2, 0, 0));
//! ```
//!
//! ## Pre-release
//!
//! Pre-release versions are incremented in the same way, independently
//! by the type of commits:
//!
//! ```rust
//! use semver::Version;
//! use next_version::NextVersion;
//!
//! let commits = ["feat!: break user"];
//! let version = Version::parse("1.0.0-alpha.1.2").unwrap();
//! let expected = Version::parse("1.0.0-alpha.1.3").unwrap();
//! assert_eq!(version.next(commits.clone()), expected);
//!
//! // If the pre-release doesn't contain a version, `.1` is appended.
//! let version = Version::parse("1.0.0-beta").unwrap();
//! let expected = Version::parse("1.0.0-beta.1").unwrap();
//! assert_eq!(version.next(commits), expected);
//!
//! ```
//!
//! ## Build metadata
//!
//! Build metadata isn't modified.
//!
//! ```rust
//! use semver::Version;
//! use next_version::NextVersion;
//!
//! let commits = ["my change"];
//! let version = Version::parse("1.0.0-beta.1+1.1.0").unwrap();
//! let expected = Version::parse("1.0.0-beta.2+1.1.0").unwrap();
//! assert_eq!(version.next(commits.clone()), expected);
//!
//! let version = Version::parse("1.0.0+abcd").unwrap();
//! let expected = Version::parse("1.0.1+abcd").unwrap();
//! assert_eq!(version.next(commits.clone()), expected);
//! ```
//!
//! # Custom version increment
//!
//! If you don't like the default increment rules of the crate,
//! you can customize them by using [`VersionUpdater`].
mod next_version;
mod version_increment;
mod version_updater;
pub use crate::{next_version::*, version_increment::*, version_updater::*};
next_version-0.2.26/src/next_version.rs 0000644 0000000 0000000 00000006412 10461020230 0016321 0 ustar 0000000 0000000 use semver::Version;
use crate::VersionUpdater;
pub trait NextVersion {
fn next(&self, commits: I) -> Self
where
I: IntoIterator,
I::Item: AsRef;
/// Increments the major version number.
fn increment_major(&self) -> Self;
/// Increments the minor version number.
fn increment_minor(&self) -> Self;
/// Increments the patch version number.
fn increment_patch(&self) -> Self;
/// Increments the prerelease version number.
fn increment_prerelease(&self) -> Self;
}
impl NextVersion for Version {
/// Analyze commits and determine the next version based on
/// [conventional commits](https://www.conventionalcommits.org/) and
/// [semantic versioning](https://semver.org/):
/// - If no commits are passed, the version is unchanged.
/// - If some commits are present, but none of them match conventional commits specification,
/// the version is incremented as a Patch.
/// - If some commits match conventional commits, then the next version is calculated by using
/// [these](https://www.conventionalcommits.org/en/v1.0.0/#how-does-this-relate-to-semverare) rules.
///
/// ```rust
/// use next_version::NextVersion;
/// use semver::Version;
///
/// let commits = ["feat: make coffe"];
/// let version = Version::new(0, 3, 3);
/// assert_eq!(version.next(commits), Version::new(0, 3, 4));
/// ```
fn next(&self, commits: I) -> Self
where
I: IntoIterator,
I::Item: AsRef,
{
VersionUpdater::default().increment(self, commits)
}
// taken from https://github.com/killercup/cargo-edit/blob/643e9253a84db02c52a7fa94f07d786d281362ab/src/version.rs#L38
fn increment_major(&self) -> Self {
Self {
major: self.major + 1,
minor: 0,
patch: 0,
pre: semver::Prerelease::EMPTY,
build: self.build.clone(),
}
}
// taken from https://github.com/killercup/cargo-edit/blob/643e9253a84db02c52a7fa94f07d786d281362ab/src/version.rs#L46
fn increment_minor(&self) -> Self {
Self {
minor: self.minor + 1,
patch: 0,
pre: semver::Prerelease::EMPTY,
..self.clone()
}
}
// taken from https://github.com/killercup/cargo-edit/blob/643e9253a84db02c52a7fa94f07d786d281362ab/src/version.rs#L53
fn increment_patch(&self) -> Self {
Self {
patch: self.patch + 1,
pre: semver::Prerelease::EMPTY,
..self.clone()
}
}
fn increment_prerelease(&self) -> Self {
let next_pre = increment_last_identifier(self.pre.as_str());
let next_pre = semver::Prerelease::new(&next_pre).expect("pre release increment failed. Please report this issue to https://github.com/release-plz/release-plz/issues");
Self {
pre: next_pre,
..self.clone()
}
}
}
fn increment_last_identifier(release: &str) -> String {
match release.rsplit_once('.') {
Some((left, right)) => {
if let Ok(right_num) = right.parse::() {
format!("{left}.{}", right_num + 1)
} else {
format!("{release}.1")
}
}
None => format!("{release}.1"),
}
}
next_version-0.2.26/src/version_increment.rs 0000644 0000000 0000000 00000014022 10461020230 0017323 0 ustar 0000000 0000000 use git_conventional::Commit;
use regex::Regex;
use semver::Version;
use crate::{NextVersion, VersionUpdater};
#[derive(Debug, PartialEq, Eq)]
pub enum VersionIncrement {
Major,
Minor,
Patch,
Prerelease,
}
fn is_there_a_custom_match(regex: Option<&Regex>, commits: &[Commit]) -> bool {
regex.is_some_and(|r| commits.iter().any(|commit| r.is_match(&commit.type_())))
}
impl VersionIncrement {
/// Analyze commits and determine which part of version to increment based on
/// [conventional commits](https://www.conventionalcommits.org/) and
/// [Semantic versioning](https://semver.org/).
/// - If no commits are present, [`Option::None`] is returned, because the version should not be incremented.
/// - If some commits are present and [`semver::Prerelease`] is not empty, the version increment is
/// [`VersionIncrement::Prerelease`].
/// - If some commits are present, but none of them match conventional commits specification,
/// the version increment is [`VersionIncrement::Patch`].
/// - If some commits match conventional commits, then the next version is calculated by using
/// [these](https://www.conventionalcommits.org/en/v1.0.0/#how-does-this-relate-to-semverare) rules.
pub fn from_commits(current_version: &Version, commits: I) -> Option
where
I: IntoIterator,
I::Item: AsRef,
{
let updater = VersionUpdater::default();
Self::from_commits_with_updater(&updater, current_version, commits)
}
pub(crate) fn from_commits_with_updater(
updater: &VersionUpdater,
current_version: &Version,
commits: I,
) -> Option
where
I: IntoIterator,
I::Item: AsRef,
{
let mut commits = commits.into_iter().peekable();
let are_commits_present = commits.peek().is_some();
if are_commits_present {
if !current_version.pre.is_empty() {
return Some(Self::Prerelease);
}
// Parse commits and keep only the ones that follow conventional commits specification.
let commit_messages: Vec = commits.map(|c| c.as_ref().to_string()).collect();
let commits: Vec = commit_messages
.iter()
.filter_map(|c| Commit::parse(c).ok())
.collect();
Some(Self::from_conventional_commits(
current_version,
&commits,
updater,
))
} else {
None
}
}
/// Increments the version to take into account breaking changes.
/// ```rust
/// use next_version::VersionIncrement;
/// use semver::Version;
///
/// let increment = VersionIncrement::breaking(&Version::new(0, 3, 3));
/// assert_eq!(increment, VersionIncrement::Minor);
///
/// let increment = VersionIncrement::breaking(&Version::new(1, 3, 3));
/// assert_eq!(increment, VersionIncrement::Major);
///
/// let increment = VersionIncrement::breaking(&Version::parse("1.3.3-alpha.1").unwrap());
/// assert_eq!(increment, VersionIncrement::Prerelease);
/// ```
pub fn breaking(current_version: &Version) -> Self {
if !current_version.pre.is_empty() {
Self::Prerelease
} else if current_version.major == 0 && current_version.minor == 0 {
Self::Patch
} else if current_version.major == 0 {
Self::Minor
} else {
Self::Major
}
}
/// If no conventional commits are present, the version is incremented as a Patch
fn from_conventional_commits(
current: &Version,
commits: &[Commit],
updater: &VersionUpdater,
) -> Self {
let is_there_a_feature = || {
commits
.iter()
.any(|commit| commit.type_() == git_conventional::Type::FEAT)
};
let is_there_a_breaking_change = commits.iter().any(|commit| commit.breaking());
let is_major_bump = || {
(is_there_a_breaking_change
|| is_there_a_custom_match(updater.custom_major_increment_regex.as_ref(), commits))
&& (current.major != 0 || updater.breaking_always_increment_major)
};
let is_minor_bump = || {
let is_feat_bump = || {
is_there_a_feature()
&& (current.major != 0 || updater.features_always_increment_minor)
};
let is_breaking_bump =
|| current.major == 0 && current.minor != 0 && is_there_a_breaking_change;
is_feat_bump()
|| is_breaking_bump()
|| is_there_a_custom_match(updater.custom_minor_increment_regex.as_ref(), commits)
};
if is_major_bump() {
Self::Major
} else if is_minor_bump() {
Self::Minor
} else {
Self::Patch
}
}
}
impl VersionIncrement {
pub fn bump(&self, version: &Version) -> Version {
match self {
Self::Major => version.increment_major(),
Self::Minor => version.increment_minor(),
Self::Patch => version.increment_patch(),
Self::Prerelease => version.increment_prerelease(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_true_for_matching_custom_type() {
let regex = Regex::new(r"custom").unwrap();
let commits = vec![Commit::parse("custom: A custom commit").unwrap()];
assert!(is_there_a_custom_match(Some(®ex), &commits));
}
#[test]
fn returns_false_for_non_custom_commit_types() {
let regex = Regex::new(r"custom").unwrap();
let commits = vec![Commit::parse("feat: A feature commit").unwrap()];
assert!(!is_there_a_custom_match(Some(®ex), &commits));
}
#[test]
fn returns_false_for_empty_commits_list() {
let regex = Regex::new(r"custom").unwrap();
let commits: Vec = Vec::new();
assert!(!is_there_a_custom_match(Some(®ex), &commits));
}
}
next_version-0.2.26/src/version_updater.rs 0000644 0000000 0000000 00000017502 10461020230 0017011 0 ustar 0000000 0000000 use regex::Regex;
use semver::Version;
use crate::VersionIncrement;
/// This struct allows to increment a version by
/// specifying a configuration.
///
/// Useful if you don't like the default increment rules of the crate.
///
/// # Example
///
/// ```
/// use next_version::VersionUpdater;
/// use semver::Version;
///
/// let updated_version = VersionUpdater::new()
/// .with_features_always_increment_minor(false)
/// .with_breaking_always_increment_major(true)
/// .increment(&Version::new(1, 2, 3), ["feat: commit 1", "fix: commit 2"]);
///
/// assert_eq!(Version::new(1, 3, 0), updated_version);
/// ```
#[derive(Debug)]
pub struct VersionUpdater {
pub(crate) features_always_increment_minor: bool,
pub(crate) breaking_always_increment_major: bool,
pub(crate) custom_major_increment_regex: Option,
pub(crate) custom_minor_increment_regex: Option,
}
impl Default for VersionUpdater {
fn default() -> Self {
Self::new()
}
}
impl VersionUpdater {
/// Constructs a new instance with the default rules of the crate.
///
/// If you don't customize the struct further, it is equivalent to
/// calling [`crate::NextVersion::next`].
///
/// ```
/// use next_version::{NextVersion, VersionUpdater};
/// use semver::Version;
///
/// let version = Version::new(1, 2, 3);
/// let commits = ["feat: commit 1", "fix: commit 2"];
/// let updated_version1 = VersionUpdater::new()
/// .increment(&version, &commits);
/// let updated_version2 = version.next(&commits);
///
/// assert_eq!(updated_version1, updated_version2);
/// ```
pub fn new() -> Self {
Self {
features_always_increment_minor: false,
breaking_always_increment_major: false,
custom_major_increment_regex: None,
custom_minor_increment_regex: None,
}
}
/// Configures automatic minor version increments for feature changes.
///
/// - When `true` is passed, a feature will always trigger a minor version update.
/// - When `false` is passed, a feature will trigger:
/// - a patch version update if the major version is 0.
/// - a minor version update otherwise.
///
/// Default: `false`.
///
/// ```rust
/// use semver::Version;
/// use next_version::VersionUpdater;
///
/// let commits = ["feat: make coffee"];
/// let version = Version::new(0, 2, 3);
/// assert_eq!(
/// VersionUpdater::new()
/// .with_features_always_increment_minor(true)
/// .increment(&version, &commits),
/// Version::new(0, 3, 0)
/// );
/// assert_eq!(
/// VersionUpdater::new()
/// .increment(&version, &commits),
/// Version::new(0, 2, 4)
/// );
/// ```
pub fn with_features_always_increment_minor(
mut self,
features_always_increment_minor: bool,
) -> Self {
self.features_always_increment_minor = features_always_increment_minor;
self
}
/// Configures `0 -> 1` major version increments for breaking changes.
///
/// - When `true` is passed, a breaking change commit will always trigger a major version update
/// (including the transition from version 0 to 1)
/// - When `false` is passed, a breaking change commit will trigger:
/// - a minor version update if the major version is 0.
/// - a major version update otherwise.
///
/// Default: `false`.
///
/// ```rust
/// use semver::Version;
/// use next_version::VersionUpdater;
///
/// let commits = ["feat!: incompatible change"];
/// let version = Version::new(0, 2, 3);
/// assert_eq!(
/// VersionUpdater::new()
/// .with_breaking_always_increment_major(true)
/// .increment(&version, &commits),
/// Version::new(1, 0, 0)
/// );
/// assert_eq!(
/// VersionUpdater::new()
/// .increment(&version, &commits),
/// Version::new(0, 3, 0)
/// );
/// ```
pub fn with_breaking_always_increment_major(
mut self,
breaking_always_increment_major: bool,
) -> Self {
self.breaking_always_increment_major = breaking_always_increment_major;
self
}
/// Configure a custom regex pattern for major version increments.
/// This will check only the type of the commit against the given pattern.
///
/// Default: `None`.
///
/// ### Note
/// `commit type` according to the spec is only `[a-zA-Z]+`
///
/// ### Example
///
/// ```rust
/// use semver::Version;
/// use next_version::VersionUpdater;
///
/// let commits = ["abc: incompatible change"];
/// let version = Version::new(1, 2, 3);
/// assert_eq!(
/// VersionUpdater::new()
/// .with_custom_major_increment_regex("abc")
/// .expect("invalid regex")
/// .increment(&version, &commits),
/// Version::new(2, 0, 0)
/// );
/// assert_eq!(
/// VersionUpdater::new()
/// .increment(&version, &commits),
/// Version::new(1, 2, 4)
/// );
/// ```
///
/// ### Overriding default behavior
///
/// You can also override the default behavior of conventional commits types.
/// For example, you can make a `feat` commit trigger a _major_ version increment
/// instead of _minor_:
///
/// ```rust
/// use semver::Version;
/// use next_version::VersionUpdater;
///
/// let commits = ["feat: incompatible change"];
/// let version = Version::new(1, 2, 3);
/// assert_eq!(
/// VersionUpdater::new()
/// .with_custom_major_increment_regex("feat")
/// .expect("invalid regex")
/// .increment(&version, &commits),
/// Version::new(2, 0, 0)
/// );
/// assert_eq!(
/// VersionUpdater::new()
/// .increment(&version, &commits),
/// Version::new(1, 3, 0)
/// );
/// ```
pub fn with_custom_major_increment_regex(
mut self,
custom_major_increment_regex: &str,
) -> Result {
let regex = Regex::new(custom_major_increment_regex)?;
self.custom_major_increment_regex = Some(regex);
Ok(self)
}
/// Configures a custom regex pattern for minor version increments.
/// This will check only the type of the commit against the given pattern.
///
/// Default: `None`.
///
/// ### Note
/// `commit type` according to the spec is only `[a-zA-Z]+`
///
/// ### Example
///
/// ```rust
/// use semver::Version;
/// use next_version::VersionUpdater;
///
/// let commits = ["bbb: make coffee"];
/// let version = Version::new(0, 2, 3);
/// assert_eq!(
/// VersionUpdater::new()
/// .with_custom_minor_increment_regex("abc|bbb")
/// .expect("invalid regex")
/// .increment(&version, &commits),
/// Version::new(0, 3, 0)
/// );
/// assert_eq!(
/// VersionUpdater::new()
/// .increment(&version, &commits),
/// Version::new(0, 2, 4)
/// );
/// ```
pub fn with_custom_minor_increment_regex(
mut self,
custom_minor_increment_regex: &str,
) -> Result {
let regex = Regex::new(custom_minor_increment_regex)?;
self.custom_minor_increment_regex = Some(regex);
Ok(self)
}
/// Analyze commits and determine the next version.
pub fn increment(self, version: &Version, commits: I) -> Version
where
I: IntoIterator,
I::Item: AsRef,
{
let increment = VersionIncrement::from_commits_with_updater(&self, version, commits);
match increment {
Some(increment) => increment.bump(version),
None => version.clone(),
}
}
}
next_version-0.2.26/tests/all/main.rs 0000644 0000000 0000000 00000000035 10461020230 0015640 0 ustar 0000000 0000000 mod normal;
mod pre_release;
next_version-0.2.26/tests/all/normal.rs 0000644 0000000 0000000 00000007026 10461020230 0016213 0 ustar 0000000 0000000 use next_version::{NextVersion, VersionUpdater};
use semver::Version;
#[test]
fn commit_without_semver_prefix_increments_patch_version() {
let commits = ["my change"];
let version = Version::new(1, 2, 3);
assert_eq!(version.next(commits), Version::new(1, 2, 4));
}
#[test]
fn commit_with_fix_semver_prefix_increments_patch_version() {
let commits = ["my change", "fix: serious bug"];
let version = Version::new(1, 2, 3);
assert_eq!(version.next(commits), Version::new(1, 2, 4));
}
#[test]
fn commit_with_feat_semver_prefix_increments_patch_version() {
let commits = ["feat: make coffe"];
let version = Version::new(1, 3, 3);
assert_eq!(version.next(commits), Version::new(1, 4, 0));
}
#[test]
fn commit_with_feat_semver_prefix_increments_patch_version_when_major_is_zero() {
let commits = ["feat: make coffee"];
let version = Version::new(0, 2, 3);
assert_eq!(version.next(commits), Version::new(0, 2, 4));
}
#[test]
fn commit_with_feat_semver_prefix_increments_minor_version_when_major_is_zero() {
let commits = ["feat: make coffee"];
let version = Version::new(0, 2, 3);
assert_eq!(
VersionUpdater::new()
.with_features_always_increment_minor(true)
.with_breaking_always_increment_major(false)
.increment(&version, commits),
Version::new(0, 3, 0)
);
}
#[test]
fn commit_with_breaking_change_increments_major_version() {
let commits = ["feat!: break user"];
let version = Version::new(1, 2, 3);
assert_eq!(version.next(commits), Version::new(2, 0, 0));
}
#[test]
fn commit_with_breaking_change_increments_minor_version_when_major_is_zero() {
let commits = ["feat!: break user"];
let version = Version::new(0, 2, 3);
assert_eq!(version.next(commits), Version::new(0, 3, 0));
}
#[test]
fn commit_with_breaking_change_increments_major_version_when_major_is_zero() {
let commits = ["feat!: break user"];
let version = Version::new(0, 2, 3);
assert_eq!(
VersionUpdater::new()
.with_features_always_increment_minor(false)
.with_breaking_always_increment_major(true)
.increment(&version, commits),
Version::new(1, 0, 0)
);
}
#[test]
fn commit_with_custom_major_increment_regex_increments_major_version() {
let commits = ["major: some changes"];
let version = Version::new(1, 2, 3);
assert_eq!(
VersionUpdater::new()
.with_custom_major_increment_regex("another|major")
.unwrap()
.increment(&version, commits),
Version::new(2, 0, 0)
);
}
#[test]
fn commit_with_custom_minor_increment_regex_increments_minor_version() {
let commits = ["minor: some changes"];
let version = Version::new(1, 2, 3);
assert_eq!(
VersionUpdater::new()
.with_custom_minor_increment_regex("minor")
.unwrap()
.increment(&version, commits),
Version::new(1, 3, 0)
);
}
#[test]
fn commit_with_scope() {
let commits = ["feat(my_scope)!: this is a test commit"];
let version = Version::new(1, 0, 0);
assert_eq!(version.next(commits), Version::new(2, 0, 0));
}
#[test]
fn commit_with_scope_whitespace() {
let commits = ["feat(my scope)!: this is a test commit"];
let version = Version::new(1, 0, 0);
assert_eq!(version.next(commits), Version::new(2, 0, 0));
}
#[test]
fn commit_with_scope_minor() {
let commits = ["feat(my scope): this is a test commit"];
let version = Version::new(1, 0, 0);
assert_eq!(version.next(commits), Version::new(1, 1, 0));
}
next_version-0.2.26/tests/all/pre_release.rs 0000644 0000000 0000000 00000002661 10461020230 0017211 0 ustar 0000000 0000000 use next_version::NextVersion;
use semver::Version;
#[test]
fn commit_without_semver_prefix_increments_pre_release_version() {
let commits = ["my change"];
let version = Version::parse("1.0.0-alpha.2").unwrap();
let expected = Version::parse("1.0.0-alpha.3").unwrap();
assert_eq!(version.next(commits), expected);
}
#[test]
fn commit_with_breaking_change_increments_pre_release_version() {
let commits = ["feat!: break user"];
let version = Version::parse("1.0.0-alpha.2").unwrap();
let expected = Version::parse("1.0.0-alpha.3").unwrap();
assert_eq!(version.next(commits), expected);
}
#[test]
fn dot_1_is_added_to_unversioned_pre_release() {
let commits = ["feat!: break user"];
let version = Version::parse("1.0.0-alpha").unwrap();
let expected = Version::parse("1.0.0-alpha.1").unwrap();
assert_eq!(version.next(commits), expected);
}
#[test]
fn dot_1_is_added_to_last_identifier_in_pre_release() {
let commits = ["feat!: break user"];
let version = Version::parse("1.0.0-beta.1.2").unwrap();
let expected = Version::parse("1.0.0-beta.1.3").unwrap();
assert_eq!(version.next(commits), expected);
}
#[test]
fn dot_1_is_added_to_character_identifier_in_pre_release() {
let commits = ["feat!: break user"];
let version = Version::parse("1.0.0-beta.1.a").unwrap();
let expected = Version::parse("1.0.0-beta.1.a.1").unwrap();
assert_eq!(version.next(commits), expected);
}