🧹 Cleanup Lint Branches #21
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 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 |