From a02475d6a38f50ba95de49895b0659a71bdd6794 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Fri, 7 Apr 2023 01:43:47 +0200 Subject: [PATCH] feat: add bump --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/bump.rs | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 4 files changed, 225 insertions(+) create mode 100644 src/bump.rs diff --git a/Cargo.lock b/Cargo.lock index e2b61f1..64e60d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1265,6 +1265,7 @@ dependencies = [ "eyre", "regex", "reqwest", + "semver", "serde", "serde_json", "tokio", @@ -1427,6 +1428,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.159" diff --git a/Cargo.toml b/Cargo.toml index ca5f405..e236137 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ dotenv = "0.15.0" eyre.workspace = true regex = "1.7.3" reqwest = "0.11.16" +semver = "1.0.17" serde.workspace = true serde_json = "1.0.95" tokio.workspace = true diff --git a/src/bump.rs b/src/bump.rs new file mode 100644 index 0000000..5a032e8 --- /dev/null +++ b/src/bump.rs @@ -0,0 +1,216 @@ +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 { + 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 { + let mut version = Version::parse(semver)?; + + match bump_type { + BumpType::Major => increment_major(&mut version), + BumpType::Minor => increment_minor(&mut version), + BumpType::Patch => increment_patch(&mut version), + } + + Ok(version.to_string()) +} + +#[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()); + } +} diff --git a/src/main.rs b/src/main.rs index b46b0bd..e010f2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod bump; mod conventional_commits; mod gitea;