releaser/src/semantic.rs

230 lines
6.6 KiB
Rust
Raw Normal View History

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());
}
}