diff --git a/.gitea/workflows/arch.yml b/.gitea/workflows/arch.yml index 38a84f46..a21b1764 100644 --- a/.gitea/workflows/arch.yml +++ b/.gitea/workflows/arch.yml @@ -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 diff --git a/scripts/ci/gitea-release.sh b/scripts/ci/gitea-release.sh index 37927965..6c890b72 100644 --- a/scripts/ci/gitea-release.sh +++ b/scripts/ci/gitea-release.sh @@ -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) -> " " 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/.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/.md (the source of truth), if that file