#!/usr/bin/env bash # prune-branches.sh — Delete merged remote branches older than N days. # Usage: ./prune-branches.sh [--days 14] [--remote forgejo] [--execute] # Default: dry-run (shows what would be deleted). Pass --execute to actually delete. set -euo pipefail DAYS=14 REMOTE="forgejo" EXECUTE=false while [ $# -gt 0 ]; do case "$1" in --days) DAYS="$2"; shift 2 ;; --remote) REMOTE="$2"; shift 2 ;; --execute) EXECUTE=true; shift ;; --help|-h) echo "Usage: $0 [--days N] [--remote name] [--execute]"; exit 0 ;; *) echo "Unknown arg: $1"; exit 1 ;; esac done CUTOFF=$(date -v-${DAYS}d +%Y-%m-%d 2>/dev/null || date -d "-${DAYS} days" +%Y-%m-%d) PROTECTED="main|HEAD" echo "Scanning $REMOTE for merged branches older than $CUTOFF..." echo "" git fetch "$REMOTE" --prune --quiet COUNT=0 DELETE_COUNT=0 while IFS= read -r branch; do branch=$(echo "$branch" | sed 's/^[[:space:]]*//') [ -z "$branch" ] && continue short="${branch#$REMOTE/}" echo "$short" | grep -qE "^($PROTECTED)$" && continue last_date=$(git log -1 --format='%ai' "$branch" 2>/dev/null | cut -d' ' -f1) [ -z "$last_date" ] && continue COUNT=$((COUNT + 1)) if [[ "$last_date" < "$CUTOFF" ]]; then if ! git merge-base --is-ancestor "$branch" "$REMOTE/main" 2>/dev/null; then echo " SKIP (unmerged): $short ($last_date)" continue fi if $EXECUTE; then echo " DELETE: $short ($last_date)" git push "$REMOTE" --delete "$short" 2>&1 && DELETE_COUNT=$((DELETE_COUNT + 1)) || echo " FAILED: $short" else echo " WOULD DELETE: $short ($last_date)" DELETE_COUNT=$((DELETE_COUNT + 1)) fi fi done < <(git branch -r | grep "^ $REMOTE/") echo "" if $EXECUTE; then echo "Deleted $DELETE_COUNT of $COUNT branches." else echo "Would delete $DELETE_COUNT of $COUNT branches. Run with --execute to proceed." fi