Compare prerelease versions correctly for updates
ci / test (push) Waiting to run
ci / fuzz-smoke (push) Waiting to run
ci / windows-client (push) Waiting to run
ci / package-release (linux-x86_64, ubuntu-latest) (push) Waiting to run
ci / package-release (macos-aarch64, macos-14) (push) Waiting to run
ci / package-release (macos-x86_64, macos-13) (push) Waiting to run
ci / package-release (windows-x86_64, windows-latest) (push) Waiting to run
ci / publish-gitea-release (push) Blocked by required conditions
ci / remote-bench (push) Waiting to run

This commit is contained in:
DuProcess
2026-07-12 22:05:21 -04:00
parent 5a85e6508d
commit a529061e58
4 changed files with 169 additions and 11 deletions
+83 -4
View File
@@ -4135,15 +4135,71 @@ fn update_binary_version_for_installer(local: &str, latest_tag: Option<&str>) ->
}
fn compare_dotted_versions(left: &str, right: &str) -> std::cmp::Ordering {
let left = parse_dotted_version(left);
let right = parse_dotted_version(right);
let width = left.len().max(right.len());
let left = parse_comparable_version(left);
let right = parse_comparable_version(right);
let width = left.core.len().max(right.core.len());
for index in 0..width {
let ordering = left
.core
.get(index)
.copied()
.unwrap_or(0)
.cmp(&right.get(index).copied().unwrap_or(0));
.cmp(&right.core.get(index).copied().unwrap_or(0));
if ordering != std::cmp::Ordering::Equal {
return ordering;
}
}
compare_prerelease(left.prerelease.as_deref(), right.prerelease.as_deref())
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ComparableVersion {
core: Vec<u64>,
prerelease: Option<String>,
}
fn parse_comparable_version(version: &str) -> ComparableVersion {
let version = version.strip_prefix('v').unwrap_or(version);
let version = version.split_once('+').map_or(version, |(base, _)| base);
let (core, prerelease) = version
.split_once('-')
.map_or((version, None), |(core, pre)| (core, Some(pre)));
ComparableVersion {
core: parse_dotted_version(core),
prerelease: prerelease
.filter(|value| !value.is_empty())
.map(|value| value.to_ascii_lowercase()),
}
}
fn compare_prerelease(left: Option<&str>, right: Option<&str>) -> std::cmp::Ordering {
match (left, right) {
(None, None) => std::cmp::Ordering::Equal,
(None, Some(_)) => std::cmp::Ordering::Greater,
(Some(_), None) => std::cmp::Ordering::Less,
(Some(left), Some(right)) => compare_prerelease_identifiers(left, right),
}
}
fn compare_prerelease_identifiers(left: &str, right: &str) -> std::cmp::Ordering {
let left_parts = left.split(['.', '-']).collect::<Vec<_>>();
let right_parts = right.split(['.', '-']).collect::<Vec<_>>();
let width = left_parts.len().max(right_parts.len());
for index in 0..width {
let Some(left) = left_parts.get(index).copied() else {
return std::cmp::Ordering::Less;
};
let Some(right) = right_parts.get(index).copied() else {
return std::cmp::Ordering::Greater;
};
let left_number = parse_numeric_identifier(left);
let right_number = parse_numeric_identifier(right);
let ordering = match (left_number, right_number) {
(Some(left), Some(right)) => left.cmp(&right),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => left.cmp(right),
};
if ordering != std::cmp::Ordering::Equal {
return ordering;
}
@@ -4151,6 +4207,12 @@ fn compare_dotted_versions(left: &str, right: &str) -> std::cmp::Ordering {
std::cmp::Ordering::Equal
}
fn parse_numeric_identifier(value: &str) -> Option<u64> {
(!value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()))
.then(|| value.parse::<u64>().ok())
.flatten()
}
fn parse_dotted_version(version: &str) -> Vec<u64> {
version
.split(|byte: char| !byte.is_ascii_digit())
@@ -11226,6 +11288,14 @@ mod tests {
update_binary_version_for_installer("1.0.0-rc41", Some("v1.0.0-rc42")),
None
);
assert_eq!(
update_binary_version_for_installer("1.0.0", Some("v1.0.0-rc42")).as_deref(),
Some("v1.0.0")
);
assert_eq!(
update_binary_version_for_installer("1.0.0-rc42", Some("v1.0.0")),
None
);
let config = ClientConfig::default();
let unix = unix_update_script(
&config,
@@ -11357,6 +11427,15 @@ mod tests {
update_version_status("1.0.0-rc40", "1.0.0-rc37"),
"local newer than latest release"
);
assert_eq!(
update_version_status("1.0.0-rc42", "1.0.0"),
"update available"
);
assert_eq!(
update_version_status("1.0.0", "1.0.0-rc42"),
"local newer than latest release"
);
assert_eq!(update_version_status("1.0.0", "1.0.0+build.7"), "current");
}
#[test]