From 16285cd62e226d1d4c4a0ef22c3b0ad65753d363 Mon Sep 17 00:00:00 2001 From: smittie2000 Date: Sat, 24 Oct 2026 07:43:05 +0200 Subject: [PATCH] xtask local: order pre-releases by semver (rc.10 after rc.9) (#820) compare_versions compared pre-release tags as plain strings, so 1.0.0-rc.10 sorted before 1.0.0-rc.9. `cargo xtask local` uses it to pick the newest mirrored build for `status` and to choose what `prune` keeps, so a tenth release candidate would have been treated as older than the ninth and pruned first. Parse with the workspace's semver crate and compare by precedence, which orders numeric pre-release parts as numbers. Co-authored-by: Claude Opus 5.5 --- Cargo.lock | 1 + xtask/Cargo.toml | 1 + xtask/src/local.rs | 32 ++++++++++++-------------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9704e406..99bf1efe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5909,6 +5909,7 @@ name = "xtask" version = "0.7.0" dependencies = [ "jiff", + "semver", ] [[package]] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 941b3d56..2a36b604 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -10,3 +10,4 @@ workspace = true [dependencies] jiff = { workspace = true } +semver = { workspace = true } diff --git a/xtask/src/local.rs b/xtask/src/local.rs index c653978a..f655a116 100644 --- a/xtask/src/local.rs +++ b/xtask/src/local.rs @@ -514,38 +514,29 @@ fn rfc3339(secs: i64) -> String { // ---- versions ----------------------------------------------------------------------------------- -/// `1.2.3` or `1.2.3-pre.4`: the numeric core and the pre-release, if any. -fn parse_version(v: &str) -> Option<([u64; 3], Option)> { - let (core, pre) = match v.split_once('-') { - Some((c, p)) => (c, Some(p.to_string())), - None => (v, None), - }; - let mut parts = core.split('.').map(|p| p.parse::().ok()); - let n = [parts.next()??, parts.next()??, parts.next()??]; - parts.next().is_none().then_some((n, pre)) +/// `1.2.3` or `1.2.3-pre.4`. +fn parse_version(v: &str) -> Option { + semver::Version::parse(v).ok() } /// The version a build of an unreleased commit gets: after `checkout` and before whatever is /// released next, ordered by commit time. `0.4.0` → `0.4.1-dev.20261010183500`; /// `1.0.0-rc.1` → `1.0.0-rc.1.dev.20261010183500`. fn dev_version(checkout: &str, committed: i64) -> Result { - let ([major, minor, patch], pre) = parse_version(checkout).ok_or_else(|| format!("can't read version `{checkout}`"))?; + let v = parse_version(checkout).ok_or_else(|| format!("can't read version `{checkout}`"))?; + let (major, minor, patch) = (v.major, v.minor, v.patch); let stamp: String = rfc3339(committed).chars().filter(char::is_ascii_digit).collect(); - Ok(match pre { - None => format!("{major}.{minor}.{}-dev.{stamp}", patch.checked_add(1).ok_or("version too large")?), - Some(pre) => format!("{major}.{minor}.{patch}-{pre}.dev.{stamp}"), + Ok(if v.pre.is_empty() { + format!("{major}.{minor}.{}-dev.{stamp}", patch.checked_add(1).ok_or("version too large")?) + } else { + format!("{major}.{minor}.{patch}-{}.dev.{stamp}", v.pre) }) } +/// Semver precedence: numeric pre-release parts compare as numbers (`rc.10` is after `rc.9`). fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering { - use std::cmp::Ordering; match (parse_version(a), parse_version(b)) { - (Some((na, pa)), Some((nb, pb))) => na.cmp(&nb).then_with(|| match (pa, pb) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Greater, - (Some(_), None) => Ordering::Less, - (Some(x), Some(y)) => x.cmp(&y), - }), + (Some(x), Some(y)) => x.cmp_precedence(&y), _ => a.cmp(b), } } @@ -734,6 +725,7 @@ mod tests { assert_eq!(rfc3339(i64::MIN), "-292277022657-01-27T08:29:52Z"); assert_eq!(compare_versions("0.10.0", "0.9.0"), std::cmp::Ordering::Greater); assert_eq!(compare_versions("1.0.0", "1.0.0-rc.1"), std::cmp::Ordering::Greater); + assert_eq!(compare_versions("1.0.0-rc.10", "1.0.0-rc.9"), std::cmp::Ordering::Greater); assert!(parse_version("1.2").is_none() && parse_version(".building-1.2.3").is_none()); }