2023-04-07 01:43:47 +02:00
|
|
|
use regex::Regex;
|
|
|
|
use semver::{BuildMetadata, Prerelease, Version};
|
|
|
|
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
|
|
pub enum BumpType {
|
|
|
|
Major,
|
|
|
|
Minor,
|
|
|
|
Patch,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_most_significant_bump(commits: &[String]) -> Option<BumpType> {
|
|
|
|
let bump_regex = Regex::new(
|
|
|
|
r"^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test|breaking)(\([a-zA-Z-_ ]+\))?: .*",
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let mut major_bump = None;
|
|
|
|
let mut minor_bump = None;
|
|
|
|
let mut patch_bump = None;
|
|
|
|
|
|
|
|
for commit in commits {
|
|
|
|
if let Some(captures) = bump_regex.captures(commit) {
|
|
|
|
let bump_type = captures.get(1).unwrap().as_str();
|
|
|
|
match bump_type {
|
|
|
|
"breaking" => {
|
|
|
|
if major_bump.is_none() {
|
|
|
|
major_bump = Some(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"feat" => {
|
|
|
|
if minor_bump.is_none() && major_bump.is_none() {
|
|
|
|
minor_bump = Some(1);
|
|
|
|
} else if let Some(mut count) = minor_bump {
|
|
|
|
count += 1;
|
|
|
|
minor_bump = Some(count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"chore" | "docs" | "ci" | "test" => {}
|
|
|
|
_ => {
|
|
|
|
if patch_bump.is_none() && minor_bump.is_none() && major_bump.is_none() {
|
|
|
|
patch_bump = Some(1);
|
|
|
|
} else if let Some(mut count) = patch_bump {
|
|
|
|
count += 1;
|
|
|
|
patch_bump = Some(count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let most_significant_bump = match (major_bump, minor_bump, patch_bump) {
|
|
|
|
(Some(count), _, _) if count > 0 => Some(BumpType::Major),
|
|
|
|
(_, Some(count), _) if count > 0 => Some(BumpType::Minor),
|
|
|
|
(_, _, Some(count)) if count > 0 => Some(BumpType::Patch),
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
most_significant_bump
|
|
|
|
}
|
|
|
|
|
|
|
|
fn increment_patch(v: &mut Version) {
|
|
|
|
v.patch += 1;
|
|
|
|
v.pre = Prerelease::EMPTY;
|
|
|
|
v.build = BuildMetadata::EMPTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn increment_minor(v: &mut Version) {
|
|
|
|
v.minor += 1;
|
|
|
|
v.patch = 0;
|
|
|
|
v.pre = Prerelease::EMPTY;
|
|
|
|
v.build = BuildMetadata::EMPTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn increment_major(v: &mut Version) {
|
|
|
|
v.major += 1;
|
|
|
|
v.minor = 0;
|
|
|
|
v.patch = 0;
|
|
|
|
v.pre = Prerelease::EMPTY;
|
|
|
|
v.build = BuildMetadata::EMPTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bump_semver(semver: &str, bump_type: BumpType) -> Result<String, semver::Error> {
|
2023-04-07 02:19:01 +02:00
|
|
|
let mut contains_start_v = false;
|
|
|
|
|
|
|
|
let semver = if semver.starts_with("v") {
|
|
|
|
contains_start_v = true;
|
|
|
|
semver.replace("v", "")
|
|
|
|
} else {
|
|
|
|
semver.to_string()
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut version = Version::parse(&semver)?;
|
2023-04-07 01:43:47 +02:00
|
|
|
|
|
|
|
match bump_type {
|
|
|
|
BumpType::Major => increment_major(&mut version),
|
|
|
|
BumpType::Minor => increment_minor(&mut version),
|
|
|
|
BumpType::Patch => increment_patch(&mut version),
|
|
|
|
}
|
|
|
|
|
2023-04-07 02:19:01 +02:00
|
|
|
if contains_start_v {
|
|
|
|
Ok(format!("v{version}"))
|
|
|
|
} else {
|
|
|
|
Ok(version.to_string())
|
|
|
|
}
|
2023-04-07 01:43:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::{get_most_significant_bump, BumpType};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_commits() {
|
|
|
|
let commits = vec![];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_major_bump() {
|
|
|
|
let commits = vec![
|
|
|
|
"breaking: new feature".to_string(),
|
|
|
|
"fix: bug fix".to_string(),
|
|
|
|
"chore: some chore".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Major));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_minor_bump() {
|
|
|
|
let commits = vec!["feat: bug fix".to_string(), "chore: some chore".to_string()];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Minor));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_patch_bump() {
|
|
|
|
let commits = vec![
|
|
|
|
"chore: some chore".to_string(),
|
|
|
|
"style: code style change".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Patch));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_significant_bumps() {
|
|
|
|
let commits = vec![
|
|
|
|
"docs: documentation update".to_string(),
|
|
|
|
"chore: another chore".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_combined_bumps() {
|
|
|
|
let commits = vec![
|
|
|
|
"breaking: new breaking change".to_string(),
|
|
|
|
"feat: new feature".to_string(),
|
|
|
|
"fix: bug fix".to_string(),
|
|
|
|
"style: code style change".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Major));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_major_bump_with_scope() {
|
|
|
|
let commits = vec![
|
|
|
|
"breaking(some-system): new feature".to_string(),
|
|
|
|
"fix(another-system): bug fix".to_string(),
|
|
|
|
"chore(third-system): some chore".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Major));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_minor_bump_with_scope() {
|
|
|
|
let commits = vec![
|
|
|
|
"feat(some-system): bug fix".to_string(),
|
|
|
|
"chore(another-system): some chore".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Minor));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_patch_bump_with_scope() {
|
|
|
|
let commits = vec![
|
|
|
|
"chore(some-system): some chore".to_string(),
|
|
|
|
"style(another-system): code style change".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Patch));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_combined_bumps_with_scope() {
|
|
|
|
let commits = vec![
|
|
|
|
"breaking(some-system): new feature".to_string(),
|
|
|
|
"feat(another-system): bug fix".to_string(),
|
|
|
|
"style(third-system): code style change".to_string(),
|
|
|
|
];
|
|
|
|
assert_eq!(get_most_significant_bump(&commits), Some(BumpType::Major));
|
|
|
|
}
|
|
|
|
|
|
|
|
use super::bump_semver;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bump_semver_major() {
|
|
|
|
let semver = "1.2.3";
|
|
|
|
let bumped_version = bump_semver(semver, BumpType::Major).unwrap();
|
|
|
|
assert_eq!(bumped_version, "2.0.0");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bump_semver_minor() {
|
|
|
|
let semver = "1.2.3";
|
|
|
|
let bumped_version = bump_semver(semver, BumpType::Minor).unwrap();
|
|
|
|
assert_eq!(bumped_version, "1.3.0");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bump_semver_patch() {
|
|
|
|
let semver = "1.2.3";
|
|
|
|
let bumped_version = bump_semver(semver, BumpType::Patch).unwrap();
|
|
|
|
assert_eq!(bumped_version, "1.2.4");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_bump_semver_invalid() {
|
|
|
|
let semver = "1.2.invalid";
|
|
|
|
let bumped_version = bump_semver(semver, BumpType::Patch);
|
|
|
|
assert!(bumped_version.is_err());
|
|
|
|
}
|
|
|
|
}
|