From c4a1e824e09c3748f9994f80d7eb999826604e16 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 15 Sep 2026 15:59:04 +0200 Subject: [PATCH 1/4] fix(updater): skip CMake update when pinned hash not ancestor of latest tag Handle dependencies pinned to a commit hash that is not in the history of the latest matching tag by skipping the update instead of rolling forward. Escape latestTag in ls-remote tag matching, bypass semver comparison for hashes, and reset LASTEXITCODE on negative ancestry checks to avoid false script failures. --- updater/scripts/cmake-functions.ps1 | 4 ++++ updater/scripts/update-dependency.ps1 | 12 ++++++++++-- updater/tests/update-dependency-cmake.Tests.ps1 | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/updater/scripts/cmake-functions.ps1 b/updater/scripts/cmake-functions.ps1 index 8ef4fb3..ceb9061 100644 --- a/updater/scripts/cmake-functions.ps1 +++ b/updater/scripts/cmake-functions.ps1 @@ -122,6 +122,10 @@ function Test-HashAncestry { # Check if old hash is ancestor of new hash git merge-base --is-ancestor $oldHash $newHash 2>$null $isAncestor = $LastExitCode -eq 0 + if ($LastExitCode -eq 1) { + # a negative ancestry result is not a script failure + $global:LASTEXITCODE = 0 + } return $isAncestor } diff --git a/updater/scripts/update-dependency.ps1 b/updater/scripts/update-dependency.ps1 index b17b50a..ed8c599 100644 --- a/updater/scripts/update-dependency.ps1 +++ b/updater/scripts/update-dependency.ps1 @@ -221,9 +221,11 @@ if ("$Tag" -eq '') { if (("$originalTag" -ne '') -and ("$latestTag" -ne '') -and ("$latestTag" -ne "$originalTag")) { do { + $isHash = $isCMakeFile -and $originalTag -match '^[a-f0-9]{40}$' + # It's possible that the dependency was updated to a pre-release version manually in which case we don't want to # roll back, even though it's not the latest version matching the configured pattern. - if ((GetComparableVersion $originalTag) -ge (GetComparableVersion $latestTag)) { + if (-not $isHash -and (GetComparableVersion $originalTag) -ge (GetComparableVersion $latestTag)) { Write-Host "SemVer represented by the original tag '$originalTag' is newer than the latest tag '$latestTag'. Skipping update." $latestTag = $originalTag break @@ -232,7 +234,13 @@ if ("$Tag" -eq '') { # Verify that the latest tag actually points to a different commit. Otherwise, we don't need to update. $refs = $(git ls-remote --tags $url) $refOriginal = (($refs -match "refs/tags/$originalTag" ) -split '[ \t]') | Select-Object -First 1 - $refLatest = (($refs -match "refs/tags/$latestTag" ) -split '[ \t]') | Select-Object -First 1 + $refLatest = (($refs -match "refs/tags/$([regex]::Escape($latestTag))$" ) -split '[ \t]') | Select-Object -First 1 + if ($isHash -and -not (Test-HashAncestry $url $originalTag $refLatest)) { + Write-Host "Pinned hash '$originalTag' is not in history of the latest tag '$latestTag'. Skipping update." + $latestTag = $originalTag + break + } + if ($refOriginal -eq $refLatest) { Write-Host "Latest tag '$latestTag' points to the same commit as the original tag '$originalTag'. Skipping update." $latestTag = $originalTag diff --git a/updater/tests/update-dependency-cmake.Tests.ps1 b/updater/tests/update-dependency-cmake.Tests.ps1 index c4400e2..06fae2d 100644 --- a/updater/tests/update-dependency-cmake.Tests.ps1 +++ b/updater/tests/update-dependency-cmake.Tests.ps1 @@ -387,6 +387,20 @@ FetchContent_MakeAvailable(sentry-native) $content | Should -Not -Match 'a64d5bd8ee130f2cda196b6fa7d9b65bfa6d32e2' $content | Should -Not -Match '# 0.9.1' } + + It 'skips an unreleased pin' { + $hash = 'a92fd4a232d8010e3c8222a8406f420c61369691' + $hashTemplate.Replace('a64d5bd8ee130f2cda196b6fa7d9b65bfa6d32e2', $hash) | Out-File $hashTestFile + $original = Get-Content $hashTestFile -Raw + + $output = & "$PSScriptRoot/../scripts/update-dependency.ps1" -Path $hashTestFile -Pattern '^(0\.9\.1|0\.11\.0)$' -WarningVariable warnings + + $LASTEXITCODE | Should -Be 0 + $warnings | Should -BeNullOrEmpty + Get-Content $hashTestFile -Raw | Should -BeExactly $original + $output | Should -Contain "originalTag=$hash" + $output | Should -Contain "latestTag=$hash" + } } Context 'Complex formatting' { From 4c9b7817b1d775e722070c082546609832753042 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 15 Sep 2026 17:08:59 +0200 Subject: [PATCH 2/4] submodules --- updater/scripts/update-dependency.ps1 | 13 ++++++++++ updater/tests/update-dependency.Tests.ps1 | 30 +++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/updater/scripts/update-dependency.ps1 b/updater/scripts/update-dependency.ps1 index ed8c599..989d296 100644 --- a/updater/scripts/update-dependency.ps1 +++ b/updater/scripts/update-dependency.ps1 @@ -231,6 +231,19 @@ if ("$Tag" -eq '') { break } + if ($isSubmodule) { + git -C $Path merge-base --is-ancestor HEAD $latestTag + if ($LASTEXITCODE -eq 1) { + $global:LASTEXITCODE = 0 + Write-Host "Submodule '$Path' is not in history of the latest tag '$latestTag'. Skipping update." + $latestTag = $originalTag + break + } + if ($LASTEXITCODE -ne 0) { + throw "Could not validate submodule ancestry for $Path (git merge-base failed with exit code $LASTEXITCODE)" + } + } + # Verify that the latest tag actually points to a different commit. Otherwise, we don't need to update. $refs = $(git ls-remote --tags $url) $refOriginal = (($refs -match "refs/tags/$originalTag" ) -split '[ \t]') | Select-Object -First 1 diff --git a/updater/tests/update-dependency.Tests.ps1 b/updater/tests/update-dependency.Tests.ps1 index 4690804..73f3b3c 100644 --- a/updater/tests/update-dependency.Tests.ps1 +++ b/updater/tests/update-dependency.Tests.ps1 @@ -667,4 +667,34 @@ param([string] $originalVersion, [string] $newVersion) Remove-Item $postUpdateScript -ErrorAction SilentlyContinue } } + + It 'only updates submodules forward' { + git init $TestDrive | Out-Null + Push-Location $TestDrive + try { + git submodule add --quiet https://github.com/getsentry/sentry-native sentry-native + $hash = 'a92fd4a232d8010e3c8222a8406f420c61369691' + git -C sentry-native checkout --quiet $hash + git add sentry-native + $original = git -C sentry-native describe --tags + + $output = UpdateDependency 'sentry-native' '^0\.16\.[56]$' + + $LASTEXITCODE | Should -Be 0 + git -C sentry-native rev-parse HEAD | Should -Be $hash + $output | Should -Contain "originalTag=$original" + $output | Should -Contain "latestTag=$original" + + git -C sentry-native checkout --quiet 0.16.5 + git add sentry-native + + $output = UpdateDependency 'sentry-native' '^0\.16\.[56]$' + + $LASTEXITCODE | Should -Be 0 + git -C sentry-native describe --tags | Should -Be '0.16.6' + $output | Should -Contain 'latestTag=0.16.6' + } finally { + Pop-Location + } + } } From e11e422ea37e5e025456ccae87a636a42647a30b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 15 Sep 2026 17:09:48 +0200 Subject: [PATCH 3/4] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a7e05b..c95ee2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Danger - Harden `extra-install-packages` handling: pass the package list into the container via env var instead of host-shell string interpolation (defense in depth) ([#169](https://github.com/getsentry/github-workflows/pull/169)) +- Updater - Skip CMake and submodule updates to tags that do not contain the pinned commit, preventing exceptions and rollbacks ([#173](https://github.com/getsentry/github-workflows/pull/173)) ## 3.4.0 From f8c380e24499cb668cf62cd38cd5b5b227dcac09 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 15 Sep 2026 17:16:44 +0200 Subject: [PATCH 4/4] fix finding --- updater/scripts/update-dependency.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updater/scripts/update-dependency.ps1 b/updater/scripts/update-dependency.ps1 index 989d296..1cecde5 100644 --- a/updater/scripts/update-dependency.ps1 +++ b/updater/scripts/update-dependency.ps1 @@ -245,7 +245,7 @@ if ("$Tag" -eq '') { } # Verify that the latest tag actually points to a different commit. Otherwise, we don't need to update. - $refs = $(git ls-remote --tags $url) + [string[]]$refs = $(git ls-remote --tags $url) $refOriginal = (($refs -match "refs/tags/$originalTag" ) -split '[ \t]') | Select-Object -First 1 $refLatest = (($refs -match "refs/tags/$([regex]::Escape($latestTag))$" ) -split '[ \t]') | Select-Object -First 1 if ($isHash -and -not (Test-HashAncestry $url $originalTag $refLatest)) {