Merge pull request 'The release-rebuild prune called a helper that cannot exist in a release rebuild' (#110) from worktree-arch-rebuild-prune into main
apple / swift (push) Successful in 1m30s
ci / bun-nix (push) Successful in 19s
ci / web (push) Successful in 1m40s
ci / docs-site (push) Successful in 3m12s
ci / rust-arm64 (push) Successful in 6m8s
apple / screenshots (push) Successful in 6m19s
android / android (push) Successful in 10m12s
arch / build-publish (push) Successful in 9m5s
decky / build-publish (push) Successful in 55s
deb / build-publish-client-arm64 (push) Successful in 1m58s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 18s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 15s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 52s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 32s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 28s
deb / build-publish (push) Successful in 9m25s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m28s
deb / build-publish-host (push) Successful in 7m54s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3m3s
docker / builders-arm64cross (push) Successful in 14s
ci / rust (push) Successful in 17m40s
docker / deploy-docs (push) Failing after 3m46s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m52s

Reviewed-on: #110
This commit was merged in pull request #110.
This commit is contained in:
2026-08-08 09:03:28 +00:00
2 changed files with 26 additions and 40 deletions
+21 -2
View File
@@ -420,10 +420,29 @@ jobs:
# A rebuild bumps pkgrel, so its FILENAMES differ from the ones already attached, and
# upsert_asset only replaces by name — the superseded set would survive untouched.
# Drop every pacman asset (and .sha256 sidecar) this upload did not just write.
#
# ⚠⚠ THIS MUST LIVE IN THE WORKFLOW, NOT IN scripts/ci/gitea-release.sh. The sourced
# script comes from the CHECKED-OUT TREE, which on a release rebuild is the OLD TAG —
# so it can only ever offer the helpers that existed when that tag was cut. A helper
# added for this feature is therefore guaranteed ABSENT in the one code path that
# calls it: the first attempt failed with `prune_release_assets: command not found`
# after publishing perfectly. Only the workflow file itself is taken from the ref you
# dispatch. Same reason a packaging fix made after a tag does NOT reach a rebuild of
# that tag — the PKGBUILD is the tag's too.
if [ -n "${RELEASE_TAG:-}" ]; then
KEEP="$(cd dist && printf '%s ' *.pkg.tar.zst)"
# An UNMATCHED glob would come through literally and match nothing in the keep set —
# i.e. "delete every pacman asset on the release". Empty out instead; prune refuses.
# i.e. "delete every pacman asset on the release". Skip entirely instead.
case "$KEEP" in *'*'*) KEEP="" ;; esac
prune_release_assets "$RID" .pkg.tar.zst "$KEEP"
API="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY"
if [ -n "$KEEP" ]; then
curl -fsS "$API/releases/$RID/assets" -H "Authorization: token $GITEA_TOKEN" \
| python3 -c "import json,sys;k=set(sys.argv[1].split());k|={n+'.sha256' for n in k};print('\n'.join('%s %s'%(a['id'],a['name']) for a in json.load(sys.stdin) if a.get('name','').endswith(('.pkg.tar.zst','.pkg.tar.zst.sha256')) and a['name'] not in k))" "$KEEP" \
| while read -r id name; do
[ -n "$id" ] || continue
echo "dropping superseded release asset: $name"
curl -fsS -o /dev/null -X DELETE "$API/releases/$RID/assets/$id" \
-H "Authorization: token $GITEA_TOKEN" || true
done
fi
fi
+5 -38
View File
@@ -38,18 +38,11 @@ for a in json.load(sys.stdin):
print(a.get("id",""));break' "$1" 2>/dev/null
}
_urlencode() { python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=""))' "$1"; }
# _json_stale_asset_ids SUFFIX KEEP_NAMES (assets JSON on stdin) -> "<id> <name>" lines
# The assets matching SUFFIX (or its .sha256 sidecar) that are NOT in the whitespace-separated
# KEEP_NAMES. See prune_release_assets.
_json_stale_asset_ids() {
python3 -c 'import json,sys
suffix, keep = sys.argv[1], set(sys.argv[2].split())
keep |= {n + ".sha256" for n in keep}
for a in json.load(sys.stdin):
n = a.get("name", "")
if n.endswith((suffix, suffix + ".sha256")) and n not in keep:
print(a.get("id", ""), n)' "$1" "$2" 2>/dev/null
}
# ⚠ Do NOT add helpers here for a workflow step that runs against a CHECKED-OUT RELEASE TAG
# (arch.yml's release-rebuild dispatch). Callers source this file from the working tree, so such a
# step gets the version of this file that shipped in that tag — never the one you just wrote. That
# logic belongs in the workflow, which is always read from the dispatched ref. Cost this once
# already: `prune_release_assets: command not found`, after the packages published fine.
# _release_notes_path TAG
# Print the path of the in-repo release notes for TAG (docs/releases/<TAG>.md) IFF it exists,
@@ -177,32 +170,6 @@ upsert_asset() {
if _put_asset "$rid" "$sums" "$name.sha256"; then rm -f "$sums"; else rm -f "$sums"; return 1; fi
}
# prune_release_assets RELEASE_ID SUFFIX KEEP_NAMES
# Delete every asset of the release whose name ends in SUFFIX (or SUFFIX.sha256) and is not one
# of KEEP_NAMES (whitespace-separated).
#
# WHY: upsert_asset replaces an asset BY NAME, which is idempotent only while the filename is
# stable. A REBUILD of an already-published release is exactly the case where it is not — a
# distro moved under the release, the artifact is rebuilt at a higher pkgrel, and
# `punktfunk-host-0.25.0-2-x86_64.pkg.tar.zst` collides with nothing, so the -1 build stays
# attached. A superseded package on a release page is not clutter; it is a live download of the
# very build the rebuild exists to replace. Scoped by SUFFIX because a release object is shared
# by ~8 packaging workflows running concurrently — each leg may only ever prune names it owns.
prune_release_assets() {
local rid="${1:?release id}" suffix="${2:?suffix}" keep="${3:-}"
local api
# An empty keep list means "delete every asset matching SUFFIX", which is never what a caller
# wants and is exactly what a mis-expanded glob looks like. Refuse rather than clear a release.
[ -n "$keep" ] || { echo "gitea-release: prune_release_assets got an empty keep list — refusing" >&2; return 0; }
api="$(_gitea_api)"
curl -fsS "$api/releases/$rid/assets" -H "Authorization: token ${GITEA_TOKEN:?}" \
| _json_stale_asset_ids "$suffix" "$keep" \
| while read -r id name; do
echo "gitea-release: dropping superseded asset '$name'"
curl -fsS -o /dev/null -X DELETE "$api/releases/$rid/assets/$id" \
-H "Authorization: token ${GITEA_TOKEN:?}" || true
done
}
# apply_release_notes RELEASE_ID TAG
# Force the release body to match docs/releases/<TAG>.md (the source of truth), if that file