Cleanup Stale Branches #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Cleanup Stale Branches | |
| on: | |
| schedule: | |
| - cron: '0 8 * * 1' # Every Monday at 08:00 UTC | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'List stale branches without deleting' | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: write | |
| jobs: | |
| cleanup: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Delete stale branches | |
| # To preserve your branch from cleanup, do ONE of: | |
| # 1. Rename it under archive/: git branch -m my-branch archive/my-branch | |
| # 2. Keep an open PR (including drafts) pointing to it | |
| # 3. Add the branch pattern to PROTECTED below | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| DRY_RUN="${{ github.event_name == 'schedule' && 'true' || inputs.dry_run }}" | |
| COPILOT_DAYS=90 | |
| DEFAULT_DAYS=180 | |
| COPILOT_CUTOFF=$(date -d "$COPILOT_DAYS days ago" +%s) | |
| DEFAULT_CUTOFF=$(date -d "$DEFAULT_DAYS days ago" +%s) | |
| PROTECTED='^(main|master|develop|release/|hotfix/|archive/|[0-9]+\.[0-9]+-stable|preview-)' | |
| echo "Stale threshold: copilot/* = $COPILOT_DAYS days, others = $DEFAULT_DAYS days | Dry run: $DRY_RUN" | |
| git branch -r --format='%(refname:short) %(committerdate:unix)' | while read -r REF DATE; do | |
| BRANCH="${REF#origin/}" | |
| [ "$BRANCH" = "HEAD" ] && continue | |
| echo "$BRANCH" | grep -qE "$PROTECTED" && continue | |
| # copilot branches: 90 days, all others: 180 days | |
| if echo "$BRANCH" | grep -q '^copilot'; then | |
| [ "$DATE" -ge "$COPILOT_CUTOFF" ] 2>/dev/null && continue | |
| else | |
| [ "$DATE" -ge "$DEFAULT_CUTOFF" ] 2>/dev/null && continue | |
| fi | |
| # Skip branches with open PRs (including drafts) | |
| PR_COUNT=$(gh pr list --head "$BRANCH" --state open --json number --jq 'length' 2>/dev/null || echo "0") | |
| if [ "$PR_COUNT" -gt 0 ]; then | |
| echo "[skipped] $BRANCH (has open PR)" | |
| continue | |
| fi | |
| LAST=$(date -d "@$DATE" +%Y-%m-%d) | |
| if [ "$DRY_RUN" = "true" ]; then | |
| echo "[stale] $BRANCH (last commit: $LAST)" | |
| else | |
| echo "Deleting $BRANCH (last commit: $LAST)" | |
| git push origin --delete "$BRANCH" || echo " Failed to delete $BRANCH" | |
| fi | |
| done |