Skip to content

🧹 Cleanup Lint Branches #8

🧹 Cleanup Lint Branches

🧹 Cleanup Lint Branches #8

name: "🧹 Cleanup Lint Branches"
# This workflow automatically cleans up lint/* branches that have been merged or closed.
# It helps maintain a clean repository by removing branches created by the linter workflow
# after their associated pull requests are no longer active.
on:
# Run daily at 00:00 UTC
schedule:
- cron: '0 0 * * *'
# Allow manual triggering with optional dry-run mode
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run mode (preview deletions without actually deleting)'
required: false
type: boolean
default: true
permissions:
contents: write
pull-requests: read
jobs:
cleanup:
name: Clean up merged/closed lint branches
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all branches and history
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup GitHub CLI
run: |
# Verify gh CLI is available (pre-installed on ubuntu-latest)
gh --version
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup lint branches
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Default to true (dry-run mode) unless explicitly set to false
# This ensures safe operation for both scheduled and manual triggers
DRY_RUN: ${{ github.event.inputs.dry_run == 'false' && 'false' || 'true' }}
BRANCH_AGE_DAYS: 7
run: |
set -e
set -o pipefail
echo "🧹 Starting lint branch cleanup..."
echo "Dry run mode: $DRY_RUN"
echo "Branch age threshold for orphaned branches: $BRANCH_AGE_DAYS days"
echo ""
# Get current date in seconds since epoch
current_date=$(date +%s)
age_threshold_seconds=$((BRANCH_AGE_DAYS * 86400))
# Track statistics
total_branches=0
deleted_branches=0
skipped_branches=0
error_branches=0
# Get all remote branches matching the pattern lint/style-fixes-*
echo "📋 Fetching lint branches..."
lint_branches=$(git for-each-ref --format='%(refname:short)' 'refs/remotes/origin/lint/style-fixes-*' | sed 's|^origin/||' || echo "")
if [ -z "$lint_branches" ]; then
echo "✅ No lint branches found matching 'lint/style-fixes-*'"
exit 0
fi
echo "Found $(echo "$lint_branches" | wc -l) lint branches"
echo ""
# Process each branch (using while read to handle special characters safely)
while IFS= read -r branch; do
[ -z "$branch" ] && continue
total_branches=$((total_branches + 1))
echo "🔍 Processing: $branch"
# Validate branch name matches expected pattern
if [[ ! "$branch" =~ ^lint/style-fixes-[0-9]+$ ]]; then
echo " ⚠️ Warning: Branch name doesn't match expected pattern, skipping"
skipped_branches=$((skipped_branches + 1))
echo ""
continue
fi
# Check if there's a PR for this branch
pr_number=$(gh pr list --state all --head "$branch" --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
should_delete=false
delete_reason=""
if [ -n "$pr_number" ]; then
# PR exists, check its state
echo " Found PR #$pr_number"
pr_state=$(gh pr view "$pr_number" --json state --jq '.state' 2>/dev/null || echo "")
pr_merged=$(gh pr view "$pr_number" --json merged --jq '.merged' 2>/dev/null || echo "false")
if [ "$pr_merged" = "true" ]; then
should_delete=true
delete_reason="PR #$pr_number was merged"
elif [ "$pr_state" = "CLOSED" ]; then
should_delete=true
delete_reason="PR #$pr_number was closed"
else
echo " ⏭️ Skipping: PR #$pr_number is still open"
skipped_branches=$((skipped_branches + 1))
fi
else
# No PR found - check if branch is old enough to delete
echo " No associated PR found (orphaned branch)"
# Get the last commit date on this branch
last_commit_date=$(git log -1 --format=%ct "origin/$branch" 2>/dev/null || echo "0")
if [ "$last_commit_date" != "0" ]; then
branch_age_seconds=$((current_date - last_commit_date))
branch_age_days=$((branch_age_seconds / 86400))
echo " Branch age: $branch_age_days days"
if [ $branch_age_seconds -gt $age_threshold_seconds ]; then
should_delete=true
delete_reason="Orphaned branch older than $BRANCH_AGE_DAYS days (age: $branch_age_days days)"
else
echo " ⏭️ Skipping: Orphaned branch is only $branch_age_days days old (threshold: $BRANCH_AGE_DAYS days)"
skipped_branches=$((skipped_branches + 1))
fi
else
echo " ⚠️ Warning: Could not determine branch age"
skipped_branches=$((skipped_branches + 1))
fi
fi
# Delete the branch if appropriate
if [ "$should_delete" = "true" ]; then
if [ "$DRY_RUN" = "true" ]; then
echo " 🔍 [DRY RUN] Would delete: $delete_reason"
deleted_branches=$((deleted_branches + 1))
else
echo " 🗑️ Deleting: $delete_reason"
# Use git push for safer deletion with validated branch name
delete_error=$(git push origin --delete "$branch" 2>&1)
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo " ✅ Successfully deleted"
deleted_branches=$((deleted_branches + 1))
else
echo " ❌ Failed to delete branch: $delete_error"
error_branches=$((error_branches + 1))
fi
fi
fi
echo ""
done <<< "$lint_branches"
# Print summary
echo "📊 Cleanup Summary"
echo "=================="
echo "Total lint branches processed: $total_branches"
if [ "$DRY_RUN" = "true" ]; then
echo "Branches that would be deleted: $deleted_branches"
else
echo "Branches deleted: $deleted_branches"
fi
echo "Branches skipped: $skipped_branches"
if [ $error_branches -gt 0 ]; then
echo "Branches with errors: $error_branches"
fi
echo ""
if [ "$DRY_RUN" = "true" ]; then
echo "✅ Dry run completed - no branches were actually deleted"
else
echo "✅ Cleanup completed"
fi