From 3ed74045de980ea68411a22257935656c46b9663 Mon Sep 17 00:00:00 2001 From: hukla <129692708+huklaa@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:48:33 +0300 Subject: [PATCH 1/3] fix(quake): parse semver build metadata in image tags --- crates/quake/src/cli_version.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/quake/src/cli_version.rs b/crates/quake/src/cli_version.rs index 695e97ba..5f8256ef 100644 --- a/crates/quake/src/cli_version.rs +++ b/crates/quake/src/cli_version.rs @@ -89,11 +89,12 @@ const V0_6_0: (u64, u64, u64) = (0, 6, 0); /// Extract a `(major, minor, patch)` tuple from an image tag. /// /// Returns `Some` only for explicit parseable versions such as `v0.6.0`, -/// `arc_consensus:v0.5.1-rc1`, or `0.7.0`. Returns `None` for missing tags, -/// the `"latest"` tag, or tags that do not fit the `MAJOR.MINOR.PATCH[-...]` -/// pattern. Callers decide how to interpret `None` — [`check_cli_version`] -/// distinguishes `latest` from unparsable, while [`apply_version_compat`] -/// treats every `None` uniformly as "assume the target supports every flag". +/// `arc_consensus:v0.5.1-rc1`, `v0.6.0+build.1`, or `0.7.0`. Returns `None` +/// for missing tags, the `"latest"` tag, or tags that do not fit the +/// `MAJOR.MINOR.PATCH[-...][+...]` pattern. Callers decide how to interpret +/// `None` — [`check_cli_version`] distinguishes `latest` from unparsable, while +/// [`apply_version_compat`] treats every `None` uniformly as "assume the target +/// supports every flag". fn parse_image_semver(image_tag: Option<&str>) -> Option<(u64, u64, u64)> { let tag = image_tag?; let version_str = tag.rsplit(':').next().unwrap_or(tag); @@ -107,7 +108,10 @@ fn parse_image_semver(image_tag: Option<&str>) -> Option<(u64, u64, u64)> { } let major = parts[0].parse::().ok()?; let minor = parts[1].parse::().ok()?; - let patch_str = parts[2].split('-').next().unwrap_or(parts[2]); + let patch_str = parts[2] + .split(|c| c == '-' || c == '+') + .next() + .unwrap_or(parts[2]); let patch = patch_str.parse::().ok()?; Some((major, minor, patch)) } @@ -246,7 +250,9 @@ mod tests { fn supports_cli_flags_handles_prerelease_versions() { assert!(supports_cli_flags(Some("v0.5.0-rc1"))); assert!(supports_cli_flags(Some("v0.5.0-beta"))); + assert!(supports_cli_flags(Some("v0.5.0+build.1"))); assert!(!supports_cli_flags(Some("v0.4.0-rc1"))); + assert!(!supports_cli_flags(Some("v0.4.0+build.1"))); } /// Run `apply_version_compat` over `input` for the given `image_tag` and @@ -294,6 +300,7 @@ mod tests { "v0.6.0", "0.6.0", "v0.6.0-rc1", + "v0.6.0+build.1", ] { let result = compat_flags(Some(tag)); assert!( @@ -315,6 +322,7 @@ mod tests { "v0.6.1", "0.6.1", "v0.6.1-rc1", + "v0.6.1+build.1", "v0.7.0-beta", ] { let result = compat_flags(Some(tag)); From ab6b6e6972d565b862e87a84eeea6c93b059fafe Mon Sep 17 00:00:00 2001 From: Hukla <129692708+huklaa@users.noreply.github.com> Date: Wed, 26 Aug 2026 01:59:22 +0300 Subject: [PATCH 2/3] test(quake): cover prerelease tags with build metadata --- crates/quake/src/cli_version.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/quake/src/cli_version.rs b/crates/quake/src/cli_version.rs index 5f8256ef..c557134a 100644 --- a/crates/quake/src/cli_version.rs +++ b/crates/quake/src/cli_version.rs @@ -103,6 +103,7 @@ fn parse_image_semver(image_tag: Option<&str>) -> Option<(u64, u64, u64)> { } let version_str = version_str.strip_prefix('v').unwrap_or(version_str); let parts: Vec<&str> = version_str.split('.').collect(); + // Build metadata can contain dots (e.g. +build.1), so extra segments are valid. if parts.len() < 3 { return None; } @@ -251,8 +252,10 @@ mod tests { assert!(supports_cli_flags(Some("v0.5.0-rc1"))); assert!(supports_cli_flags(Some("v0.5.0-beta"))); assert!(supports_cli_flags(Some("v0.5.0+build.1"))); + assert!(supports_cli_flags(Some("v0.5.0-rc1+build.1"))); assert!(!supports_cli_flags(Some("v0.4.0-rc1"))); assert!(!supports_cli_flags(Some("v0.4.0+build.1"))); + assert!(!supports_cli_flags(Some("v0.4.0-rc1+build.1"))); } /// Run `apply_version_compat` over `input` for the given `image_tag` and @@ -301,6 +304,7 @@ mod tests { "0.6.0", "v0.6.0-rc1", "v0.6.0+build.1", + "v0.6.0-rc1+build.1", ] { let result = compat_flags(Some(tag)); assert!( @@ -323,6 +327,7 @@ mod tests { "0.6.1", "v0.6.1-rc1", "v0.6.1+build.1", + "v0.6.1-rc1+build.1", "v0.7.0-beta", ] { let result = compat_flags(Some(tag)); From 3ec4fdd9a47f5526f058aea1872c2187bf2d7559 Mon Sep 17 00:00:00 2001 From: Hukla <129692708+huklaa@users.noreply.github.com> Date: Wed, 26 Aug 2026 03:02:58 +0300 Subject: [PATCH 3/3] test(quake): cover semver metadata edge cases --- crates/quake/src/cli_version.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/quake/src/cli_version.rs b/crates/quake/src/cli_version.rs index c557134a..0009622d 100644 --- a/crates/quake/src/cli_version.rs +++ b/crates/quake/src/cli_version.rs @@ -103,7 +103,8 @@ fn parse_image_semver(image_tag: Option<&str>) -> Option<(u64, u64, u64)> { } let version_str = version_str.strip_prefix('v').unwrap_or(version_str); let parts: Vec<&str> = version_str.split('.').collect(); - // Build metadata can contain dots (e.g. +build.1), so extra segments are valid. + // Prerelease and build metadata may contain dot-separated identifiers (e.g. -rc.1 or + // +build.1), so extra segments after MAJOR.MINOR.PATCH are valid. if parts.len() < 3 { return None; } @@ -250,10 +251,13 @@ mod tests { #[test] fn supports_cli_flags_handles_prerelease_versions() { assert!(supports_cli_flags(Some("v0.5.0-rc1"))); + assert!(supports_cli_flags(Some("v0.5.0-rc.1"))); assert!(supports_cli_flags(Some("v0.5.0-beta"))); assert!(supports_cli_flags(Some("v0.5.0+build.1"))); + assert!(supports_cli_flags(Some("v0.5.0+2026-08-26"))); assert!(supports_cli_flags(Some("v0.5.0-rc1+build.1"))); assert!(!supports_cli_flags(Some("v0.4.0-rc1"))); + assert!(!supports_cli_flags(Some("v0.4.0-rc.1"))); assert!(!supports_cli_flags(Some("v0.4.0+build.1"))); assert!(!supports_cli_flags(Some("v0.4.0-rc1+build.1"))); } @@ -303,7 +307,9 @@ mod tests { "v0.6.0", "0.6.0", "v0.6.0-rc1", + "v0.6.0-rc.1", "v0.6.0+build.1", + "v0.6.0+2026-08-26", "v0.6.0-rc1+build.1", ] { let result = compat_flags(Some(tag)); @@ -326,7 +332,9 @@ mod tests { "v0.6.1", "0.6.1", "v0.6.1-rc1", + "v0.6.1-rc.1", "v0.6.1+build.1", + "v0.6.1+2026-08-26", "v0.6.1-rc1+build.1", "v0.7.0-beta", ] {