From 86d342284557a7aa6b2bdd7d5b39d12b90e22a38 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Fri, 19 Dec 2025 18:06:55 +0100 Subject: [PATCH] Delete of old releases --- .github/workflows/delete-old-releases.yml | 129 ++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 .github/workflows/delete-old-releases.yml diff --git a/.github/workflows/delete-old-releases.yml b/.github/workflows/delete-old-releases.yml new file mode 100644 index 0000000..44dd291 --- /dev/null +++ b/.github/workflows/delete-old-releases.yml @@ -0,0 +1,129 @@ +name: Delete old releases + +on: + schedule: + - cron: "0 3 * * *" # daily 03:00 UTC + workflow_dispatch: + inputs: + keep_full: + description: "How many full (non-prerelease) releases to keep" + required: false + default: "3" + keep_pre: + description: "How many prereleases to keep" + required: false + default: "3" + delete_tags: + description: "Also delete the git tag for deleted releases" + type: boolean + required: false + default: true + dry_run: + description: "Do not delete anything; just print what would happen" + type: boolean + required: false + default: false + protect_tag_regex: + description: "Regex of tag names that must never be deleted (empty = none)" + required: false + default: "^$" + +jobs: + clean_releases: + name: "Clean releases" + runs-on: ubuntu-latest + permissions: + contents: write # required to delete releases (and tags if enabled) + + steps: + - name: Delete old releases (and optionally tags) + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + KEEP_FULL: ${{ inputs.keep_full || '3' }} + KEEP_PRE: ${{ inputs.keep_pre || '3' }} + DELETE_TAGS: ${{ inputs.delete_tags || 'true' }} + DRY_RUN: ${{ inputs.dry_run || 'false' }} + PROTECT_TAG_REGEX: ${{ inputs.protect_tag_regex || '^$' }} + shell: bash + run: | + set -euo pipefail + + echo "Repo: $REPO" + echo "Keep full releases: $KEEP_FULL" + echo "Keep prereleases: $KEEP_PRE" + echo "Delete tags: $DELETE_TAGS" + echo "Dry-run: $DRY_RUN" + echo "Protect regex: $PROTECT_TAG_REGEX" + echo + + # Fetch all releases (paginated) + releases_json="$(gh api --paginate "repos/$REPO/releases")" + + delete_release_and_tag() { + local rid="$1" + local tag="$2" + local name="$3" + local kind="$4" + + # Protect tag patterns if requested + if [[ -n "${PROTECT_TAG_REGEX}" ]] && [[ "$tag" =~ ${PROTECT_TAG_REGEX} ]]; then + echo "SKIP (protected tag) [$kind] $name tag=$tag id=$rid" + return 0 + fi + + if [[ "$DRY_RUN" == "true" ]]; then + echo "DRY-RUN delete [$kind] $name tag=$tag id=$rid" + return 0 + fi + + echo "Deleting release [$kind] $name tag=$tag id=$rid" + gh api --method DELETE "repos/$REPO/releases/$rid" + + if [[ "$DELETE_TAGS" == "true" && -n "$tag" ]]; then + # Delete the git ref for the tag. If the tag ref doesn't exist, don't fail the job. + echo "Deleting tag ref: refs/tags/$tag" + gh api --method DELETE "repos/$REPO/git/refs/tags/$tag" || echo "Tag ref not found (already deleted?): $tag" + fi + } + + # Helper to select, sort newest-first, and delete beyond keep count + process_group() { + local jq_filter="$1" + local keep="$2" + local kind="$3" + + # Build array of candidates sorted newest first by created_at + # Keep fields we need (id, tag_name, name) + candidates="$(echo "$releases_json" | jq -c "$jq_filter + | sort_by(.created_at) | reverse + | map({id, tag_name, name})")" + + total="$(echo "$candidates" | jq 'length')" + echo "Found $total $kind releases" + + # Nothing to delete? + if (( total <= keep )); then + echo "Nothing to delete for $kind (keep=$keep)" + echo + return 0 + fi + + # Select items beyond keep + echo "$candidates" | jq -c ".[${keep}:][]" | while read -r item; do + rid="$(echo "$item" | jq -r '.id')" + tag="$(echo "$item" | jq -r '.tag_name // ""')" + name="$(echo "$item" | jq -r '.name // .tag_name // "(no name)"')" + delete_release_and_tag "$rid" "$tag" "$name" "$kind" + done + + echo + } + + # Full releases + process_group '[.[] | select(.prerelease == false)]' "$KEEP_FULL" "full" + + # Pre-releases + process_group '[.[] | select(.prerelease == true)]' "$KEEP_PRE" "pre" + + echo "Done."