diff --git a/Cargo.lock b/Cargo.lock index d8c8974..f88f7eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,7 @@ dependencies = [ "clap", "dotenv", "reqwest", + "semver", "serde", "serde_yaml", "tokio", @@ -928,6 +929,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + [[package]] name = "serde" version = "1.0.177" diff --git a/Cargo.toml b/Cargo.toml index 3ac9718..17d7b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ url = {version = "*"} serde_yaml = {version = "*"} serde = {version = "*", features = ["derive"]} +semver = "1.0.18" reqwest = {version = "*"} diff --git a/crates/cuddle-please/Cargo.toml b/crates/cuddle-please/Cargo.toml index 699c807..de68749 100644 --- a/crates/cuddle-please/Cargo.toml +++ b/crates/cuddle-please/Cargo.toml @@ -14,6 +14,7 @@ serde_yaml.workspace = true serde.workspace = true reqwest = {workspace = true, features = ["blocking", "json"]} url.workspace = true +semver.workspace = true [dev-dependencies] -tracing-test = {workspace = true, features = ["no-env-filter"]} \ No newline at end of file +tracing-test = {workspace = true, features = ["no-env-filter"]} diff --git a/crates/cuddle-please/src/gitea_client.rs b/crates/cuddle-please/src/gitea_client.rs index 9dc2a04..bf54f13 100644 --- a/crates/cuddle-please/src/gitea_client.rs +++ b/crates/cuddle-please/src/gitea_client.rs @@ -216,7 +216,7 @@ pub struct CommitDetails { pub message: String, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct Tag { pub id: String, pub message: String, @@ -224,11 +224,11 @@ pub struct Tag { pub commit: TagCommit, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct TagCommit { - created: String, + pub created: String, pub sha: String, - url: String, + pub url: String, } #[cfg(test)] diff --git a/crates/cuddle-please/src/lib.rs b/crates/cuddle-please/src/lib.rs index b67b1f0..cc6e3bb 100644 --- a/crates/cuddle-please/src/lib.rs +++ b/crates/cuddle-please/src/lib.rs @@ -3,3 +3,4 @@ pub mod environment; pub mod git_client; pub mod gitea_client; pub mod ui; +pub mod versioning; diff --git a/crates/cuddle-please/src/main.rs b/crates/cuddle-please/src/main.rs index 2ad3884..2641797 100644 --- a/crates/cuddle-please/src/main.rs +++ b/crates/cuddle-please/src/main.rs @@ -3,6 +3,7 @@ pub mod environment; pub mod git_client; pub mod gitea_client; pub mod ui; +pub mod versioning; use command::Command; diff --git a/crates/cuddle-please/src/versioning/mod.rs b/crates/cuddle-please/src/versioning/mod.rs new file mode 100644 index 0000000..b658cff --- /dev/null +++ b/crates/cuddle-please/src/versioning/mod.rs @@ -0,0 +1 @@ +pub mod semver; diff --git a/crates/cuddle-please/src/versioning/semver.rs b/crates/cuddle-please/src/versioning/semver.rs new file mode 100644 index 0000000..7d5cac8 --- /dev/null +++ b/crates/cuddle-please/src/versioning/semver.rs @@ -0,0 +1,153 @@ +use std::cmp::Reverse; + +use crate::gitea_client::{Commit, Tag}; +use semver::Version; + +pub fn get_most_significant_version<'a>(commits: Vec<&'a Tag>) -> Option<&'a Tag> { + let mut versions: Vec<(&'a Tag, Version)> = commits + .into_iter() + .filter_map(|c| { + if let Some(version) = c.name.trim_start_matches("v").parse::().ok() { + Some((c, version)) + } else { + None + } + }) + .collect(); + versions.sort_unstable_by_key(|(_, version)| Reverse(version.clone())); + + versions.first().map(|(tag, _)| *tag) +} + +#[cfg(test)] +mod test { + use tracing_test::traced_test; + + use crate::{ + gitea_client::{Tag, TagCommit}, + versioning::semver::get_most_significant_version, + }; + + fn create_tag(version: impl Into) -> Tag { + let version = version.into(); + Tag { + id: "some-id".into(), + message: version.clone(), + name: version, + commit: TagCommit { + created: "date".into(), + sha: "sha".into(), + url: "url".into(), + }, + } + } + + #[test] + #[traced_test] + fn gets_most_significant_version() { + let most_significant = create_tag("3.1.1"); + let tags = vec![ + create_tag("1.0.1"), + create_tag("1.2.1"), + most_significant.clone(), + create_tag("0.0.1"), + create_tag("0.0.2"), + ]; + + let actual = get_most_significant_version(tags.iter().collect()).unwrap(); + assert_eq!(&most_significant, actual) + } + + #[test] + #[traced_test] + fn gets_most_significant_version_patch() { + let most_significant = create_tag("0.0.8"); + let tags = vec![ + create_tag("0.0.1"), + create_tag("0.0.7"), + create_tag("0.0.2"), + most_significant.clone(), + create_tag("0.0.0"), + ]; + + let actual = get_most_significant_version(tags.iter().collect()).unwrap(); + assert_eq!(&most_significant, actual) + } + #[test] + #[traced_test] + fn gets_most_significant_version_minor() { + let most_significant = create_tag("0.8.0"); + let tags = vec![ + create_tag("0.1.1"), + create_tag("0.2.7"), + create_tag("0.7.2"), + most_significant.clone(), + create_tag("0.3.0"), + ]; + + let actual = get_most_significant_version(tags.iter().collect()).unwrap(); + assert_eq!(&most_significant, actual) + } + + #[test] + #[traced_test] + fn gets_most_significant_version_major() { + let most_significant = create_tag("7.8.0"); + let tags = vec![ + create_tag("6.1.1"), + create_tag("1.2.7"), + create_tag("2.7.2"), + most_significant.clone(), + create_tag("3.3.0"), + ]; + + let actual = get_most_significant_version(tags.iter().collect()).unwrap(); + assert_eq!(&most_significant, actual) + } + + #[test] + #[traced_test] + fn ignored_invalid_tags() { + let tags = vec![ + create_tag("something-3.3.0"), + create_tag("bla bla bla"), + create_tag("main"), + create_tag("master"), + create_tag("develop"), + ]; + + let actual = get_most_significant_version(tags.iter().collect()).is_none(); + assert!(actual) + } + + #[test] + #[traced_test] + fn mix_v_prefix() { + let most_significant = create_tag("v7.8.0"); + let tags = vec![ + create_tag("6.1.1"), + create_tag("v1.2.7"), + create_tag("2.7.2"), + most_significant.clone(), + create_tag("v3.3.0"), + ]; + + let actual = get_most_significant_version(tags.iter().collect()).unwrap(); + assert_eq!(&most_significant, actual) + } + #[test] + #[traced_test] + fn mix_v_prefix_2() { + let most_significant = create_tag("7.8.0"); + let tags = vec![ + create_tag("6.1.1"), + create_tag("v1.2.7"), + create_tag("2.7.2"), + most_significant.clone(), + create_tag("v3.3.0"), + ]; + + let actual = get_most_significant_version(tags.iter().collect()).unwrap(); + assert_eq!(&most_significant, actual) + } +}