diff --git a/.github/shared-files.sha256 b/.github/shared-files.sha256 index 9af6918..de628f5 100644 --- a/.github/shared-files.sha256 +++ b/.github/shared-files.sha256 @@ -68,5 +68,5 @@ fcb6cd0e2a71205b918f545f2680d378c790b774b9852129c5f9c7f3f8eb9860 .github/workfl 09230b04c5f14bdda50a0c862152ff197a656cbc3fd0d110899c5f8150f8aa9a .github/workflows/publish.yml#check-snapshot 51e987d59efd9887b43e345edc0e0f3db21686879e3ca6ea9f0fdbc493d37121 .github/workflows/publish.yml#check-tag a8637f88765da759a6eaba243d0d0d695053174df581ccbbcc5e5f3a475fc9e9 .github/workflows/publish.yml#verify-signing-key-gradle -f20f0a991ce68fa1dcc8ef4daffc5e5dea2093d9b578aa41d17f18a56555d79f .github/workflows/publish.yml#github-snapshot +83a1288c55d35e4d3972e013009577fb594a9a5fb8b9b7c96a7f6a19db20d8d1 .github/workflows/publish.yml#github-snapshot ce90d90fcb1f2de35075b91c74411e9eb7d3b43e7965fd592744e914fd04c5db .github/workflows/publish.yml#github-release diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 57c1925..7967479 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -528,23 +528,58 @@ jobs: # upload on purpose: a failed upload must not leave the release emptier than it was. And only # here: a versioned v* release is history, github-release never deletes anything. Every # deletion is printed by name, so the job log is the audit trail. + # + # Asset by asset, never the release. The first form of this job (2026-05-07) deleted the whole + # `snapshot` release with its tag and recreated it; on 2026-05-10 the runs of several main + # pushes minutes apart deleted it under each other, and every job of each burst but the last + # failed in that step with its artifacts present. The job then went with the Central + # migration and came back as `view || create` + `upload --clobber` without any cleanup, which + # is how the backlog grew (workspace/policies/fat-jar-release-assets.md has the runs). Hence + # three guards: an empty upload is refused (every asset would count as stale; the download + # and upload steps above fail first, the guard keeps it so), an asset that is already gone + # counts as removed (two overlapping dispatches may both pick it), and a failed deletion is + # reported and the rest still tried -- the assets of this upload are on the release either + # way, so this is "attach first, then red" like the signature check. env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail keep="$(ls -1 snapshot-assets | sort)" - current="$(gh release view snapshot --repo ${{ github.repository }} --json assets --jq '.assets[].name' | sort)" + if [ -z "$keep" ]; then + echo "::error::refusing to prune: this run uploaded nothing, so every asset of the release would count as stale" + exit 1 + fi + list_assets() { + gh release view snapshot --repo ${{ github.repository }} --json assets --jq '.assets[].name' | sort + } + current="$(list_assets)" stale="$(comm -13 <(printf '%s\n' "$keep") <(printf '%s\n' "$current"))" if [ -z "$stale" ]; then echo "nothing to remove: the release holds exactly the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload" exit 0 fi echo "removing $(printf '%s\n' "$stale" | wc -l) asset(s) that are not part of this upload:" + failed=0 while IFS= read -r name; do [ -n "$name" ] || continue echo " deleting $name" - gh release delete-asset snapshot "$name" --repo ${{ github.repository }} --yes + if gh release delete-asset snapshot "$name" --repo ${{ github.repository }} --yes; then + continue + fi + if ! remaining="$(list_assets)"; then + echo "::error::could not delete $name, and could not re-read the release to tell whether it is gone" + failed=$((failed + 1)) + elif printf '%s\n' "$remaining" | grep -qxF -- "$name"; then + echo "::error::could not delete $name" + failed=$((failed + 1)) + else + echo " $name was already gone" + fi done <<< "$stale" + if [ "$failed" -ne 0 ]; then + echo "::error::$failed asset(s) could not be removed; the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload are on the release" + exit 1 + fi echo "kept the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload" - name: Fail if anything was attached unsigned # After the upload on purpose: the assets must exist even when the signature does not. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4baf127..f4b128e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,8 +79,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 signature check existed) and three SpotBugs plugin jars (`fb-contrib-7.6.4.jar`, `fb-contrib-7.7.4.jar`, `findsecbugs-plugin-1.14.0.jar`) an old collect step uploaded from `target/spotbugs/` in 2026-05, 5 of its 11 assets. `github-snapshot` now deletes every asset - the run did not upload, after the upload and each printed by name; versioned releases are never - pruned, and the next snapshot run clears the backlog by itself. The collect steps copy + the run did not upload, after the upload and each printed by name; it refuses an empty upload, + treats an asset that is already gone as removed and fails only after trying every deletion (asset + by asset on purpose: the job's first form deleted and recreated the release, and failed in every + burst of main pushes in May 2026); versioned releases are never pruned, and the next snapshot run + clears the backlog by itself. The collect steps copy `-.pom` + `.pom.asc` as well (Central had them, the release did not), and the signature check counts every attachable type (`*.jar`, `*.aar`, `*.pom`) instead of jars alone. Shared job text with the two sibling repositories (`shared-files.sha256`), the convention diff --git a/CLAUDE.md b/CLAUDE.md index 4302c48..6bcd510 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -251,8 +251,10 @@ exists here. The attach jobs are shared text with the two sibling Maven-only rep signature check counts every attachable type (`*.jar`, `*.aar`, `*.pom`), and `github-snapshot` ends by deleting every asset of the `snapshot` pre-release that the run did not upload, each printed by name -- `gh release upload --clobber` never removed anything, so the release accumulated every past -version. Versioned releases are never pruned. See the policy's "Attach first, then go red" and "The -`snapshot` pre-release holds exactly the current upload". +version. The prune refuses an empty upload, treats an asset that is already gone as removed and +fails only after trying every deletion; versioned releases are never pruned. See the policy's +"Attach first, then go red" and "The `snapshot` pre-release holds exactly the current upload", which +also records the delete-and-recreate form of this job (May 2026) and why it is gone. ## Shared files and the release gate (`shared-files` job)