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
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:
+37
-2
@@ -100,8 +100,43 @@ function Release-LatestTagDownloadUrl {
|
|||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Version-Core($Value) {
|
||||||
|
$base = $Value.TrimStart("v").Split("+")[0]
|
||||||
|
$base.Split("-")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function Version-Prerelease($Value) {
|
||||||
|
$base = $Value.TrimStart("v").Split("+")[0]
|
||||||
|
$dash = $base.IndexOf("-")
|
||||||
|
if ($dash -lt 0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
$base.Substring($dash + 1).ToLowerInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
function Version-Parts($Value) {
|
function Version-Parts($Value) {
|
||||||
[regex]::Matches($Value, "\d+") | ForEach-Object { [int64]$_.Value }
|
[regex]::Matches((Version-Core $Value), "\d+") | ForEach-Object { [int64]$_.Value }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Compare-Prerelease($Left, $Right) {
|
||||||
|
if (-not $Left -and -not $Right) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (-not $Left) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (-not $Right) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if ($Left -eq $Right) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if ($Left -match "(?i)^rc(\d+)$" -and $Right -match "(?i)^rc(\d+)$") {
|
||||||
|
$leftRc = [int64]([regex]::Match($Left, "(?i)^rc(\d+)$").Groups[1].Value)
|
||||||
|
$rightRc = [int64]([regex]::Match($Right, "(?i)^rc(\d+)$").Groups[1].Value)
|
||||||
|
return $leftRc.CompareTo($rightRc)
|
||||||
|
}
|
||||||
|
return [string]::CompareOrdinal($Left, $Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Compare-DoshVersion($Left, $Right) {
|
function Compare-DoshVersion($Left, $Right) {
|
||||||
@@ -114,7 +149,7 @@ function Compare-DoshVersion($Left, $Right) {
|
|||||||
if ($l -lt $r) { return -1 }
|
if ($l -lt $r) { return -1 }
|
||||||
if ($l -gt $r) { return 1 }
|
if ($l -gt $r) { return 1 }
|
||||||
}
|
}
|
||||||
return 0
|
return Compare-Prerelease (Version-Prerelease $Left) (Version-Prerelease $Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Current-SourceVersion {
|
function Current-SourceVersion {
|
||||||
|
|||||||
+45
-5
@@ -222,20 +222,60 @@ release_latest_tag_download_url() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
version_part() {
|
version_core() {
|
||||||
|
value="${1#v}"
|
||||||
|
value="${value%%+*}"
|
||||||
|
printf '%s\n' "${value%%-*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
version_prerelease() {
|
||||||
|
value="${1#v}"
|
||||||
|
value="${value%%+*}"
|
||||||
|
case "$value" in
|
||||||
|
*-*) printf '%s\n' "${value#*-}" ;;
|
||||||
|
*) printf '\n' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
version_core_part() {
|
||||||
version="$1"
|
version="$1"
|
||||||
index="$2"
|
index="$2"
|
||||||
printf '%s\n' "$version" \
|
version_core "$version" \
|
||||||
| sed 's/[^0-9][^0-9]*/ /g' \
|
| sed 's/[^0-9][^0-9]*/ /g' \
|
||||||
| awk -v index="$index" '{ value=$index; if (value == "") value=0; print value }'
|
| awk -v index="$index" '{ value=$index; if (value == "") value=0; print value }'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prerelease_less_than() {
|
||||||
|
left="$1"
|
||||||
|
right="$2"
|
||||||
|
if [ -z "$left" ] && [ -z "$right" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ -z "$left" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ -z "$right" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$left" = "$right" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
left_rc="$(printf '%s\n' "$left" | sed -n 's/^[Rr][Cc]\([0-9][0-9]*\)$/\1/p')"
|
||||||
|
right_rc="$(printf '%s\n' "$right" | sed -n 's/^[Rr][Cc]\([0-9][0-9]*\)$/\1/p')"
|
||||||
|
if [ -n "$left_rc" ] && [ -n "$right_rc" ]; then
|
||||||
|
[ "$left_rc" -lt "$right_rc" ]
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
first="$(printf '%s\n%s\n' "$left" "$right" | LC_ALL=C sort | sed -n '1p')"
|
||||||
|
[ "$first" = "$left" ]
|
||||||
|
}
|
||||||
|
|
||||||
version_less_than() {
|
version_less_than() {
|
||||||
left="$1"
|
left="$1"
|
||||||
right="$2"
|
right="$2"
|
||||||
for index in 1 2 3 4 5 6 7 8; do
|
for index in 1 2 3 4 5 6 7 8; do
|
||||||
lpart="$(version_part "$left" "$index")"
|
lpart="$(version_core_part "$left" "$index")"
|
||||||
rpart="$(version_part "$right" "$index")"
|
rpart="$(version_core_part "$right" "$index")"
|
||||||
if [ "$lpart" -lt "$rpart" ]; then
|
if [ "$lpart" -lt "$rpart" ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -243,7 +283,7 @@ version_less_than() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return 1
|
prerelease_less_than "$(version_prerelease "$left")" "$(version_prerelease "$right")"
|
||||||
}
|
}
|
||||||
|
|
||||||
current_source_version() {
|
current_source_version() {
|
||||||
|
|||||||
+83
-4
@@ -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 {
|
fn compare_dotted_versions(left: &str, right: &str) -> std::cmp::Ordering {
|
||||||
let left = parse_dotted_version(left);
|
let left = parse_comparable_version(left);
|
||||||
let right = parse_dotted_version(right);
|
let right = parse_comparable_version(right);
|
||||||
let width = left.len().max(right.len());
|
let width = left.core.len().max(right.core.len());
|
||||||
for index in 0..width {
|
for index in 0..width {
|
||||||
let ordering = left
|
let ordering = left
|
||||||
|
.core
|
||||||
.get(index)
|
.get(index)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(0)
|
.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 {
|
if ordering != std::cmp::Ordering::Equal {
|
||||||
return ordering;
|
return ordering;
|
||||||
}
|
}
|
||||||
@@ -4151,6 +4207,12 @@ fn compare_dotted_versions(left: &str, right: &str) -> std::cmp::Ordering {
|
|||||||
std::cmp::Ordering::Equal
|
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> {
|
fn parse_dotted_version(version: &str) -> Vec<u64> {
|
||||||
version
|
version
|
||||||
.split(|byte: char| !byte.is_ascii_digit())
|
.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")),
|
update_binary_version_for_installer("1.0.0-rc41", Some("v1.0.0-rc42")),
|
||||||
None
|
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 config = ClientConfig::default();
|
||||||
let unix = unix_update_script(
|
let unix = unix_update_script(
|
||||||
&config,
|
&config,
|
||||||
@@ -11357,6 +11427,15 @@ mod tests {
|
|||||||
update_version_status("1.0.0-rc40", "1.0.0-rc37"),
|
update_version_status("1.0.0-rc40", "1.0.0-rc37"),
|
||||||
"local newer than latest release"
|
"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]
|
#[test]
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ fn installers_skip_stale_latest_release_prebuilts() {
|
|||||||
assert!(install.contains("latest_release_is_stale"));
|
assert!(install.contains("latest_release_is_stale"));
|
||||||
assert!(install.contains("current_source_version"));
|
assert!(install.contains("current_source_version"));
|
||||||
assert!(install.contains("latest release $latest is older than source $current"));
|
assert!(install.contains("latest release $latest is older than source $current"));
|
||||||
|
assert!(install.contains("version_prerelease"));
|
||||||
|
assert!(install.contains("prerelease_less_than"));
|
||||||
assert!(
|
assert!(
|
||||||
install.contains("if latest_release_is_stale; then"),
|
install.contains("if latest_release_is_stale; then"),
|
||||||
"unix installer must skip stale latest before downloading a prebuilt"
|
"unix installer must skip stale latest before downloading a prebuilt"
|
||||||
@@ -53,6 +55,8 @@ fn installers_skip_stale_latest_release_prebuilts() {
|
|||||||
assert!(ps1.contains("Latest-ReleaseIsStale"));
|
assert!(ps1.contains("Latest-ReleaseIsStale"));
|
||||||
assert!(ps1.contains("Current-SourceVersion"));
|
assert!(ps1.contains("Current-SourceVersion"));
|
||||||
assert!(ps1.contains("latest release $latestVersion is older than source $current"));
|
assert!(ps1.contains("latest release $latestVersion is older than source $current"));
|
||||||
|
assert!(ps1.contains("Version-Prerelease"));
|
||||||
|
assert!(ps1.contains("Compare-Prerelease"));
|
||||||
assert!(
|
assert!(
|
||||||
ps1.contains("if (Latest-ReleaseIsStale)"),
|
ps1.contains("if (Latest-ReleaseIsStale)"),
|
||||||
"windows installer must skip stale latest before downloading a prebuilt"
|
"windows installer must skip stale latest before downloading a prebuilt"
|
||||||
|
|||||||
Reference in New Issue
Block a user