Skip to content

Auto Delete Merged Branches #192

Auto Delete Merged Branches

Auto Delete Merged Branches #192

name: Auto Delete Merged Branches
on:
pull_request:
types: [closed] # Triggers when a PR is closed
schedule:
- cron: '0 0 * * 0' # Runs weekly on Sundays at midnight UTC
workflow_dispatch: # Allows manual triggering
permissions:
contents: write # Required to delete branches
pull-requests: read # Required to check PR status
jobs:
delete-merged-branches:
runs-on: ubuntu-latest
name: Delete Merged Branches
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch all branches and history
- name: Delete Merged Branches with GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set +e # Don't exit on error
# Protected branches that should never be deleted
PROTECTED_BRANCHES=("main" "saas" "cli-tool")
# AI assistant branch prefixes to check for old closed/draft PRs
AI_PREFIXES=("cursor/" "claude/" "jules/")
# Calculate timestamp for 3 months ago
THREE_MONTHS_AGO=$(date -u -d '3 months ago' +%s 2>/dev/null || date -u -v-3m +%s)
echo "🔍 Finding branches with merged PRs..."
# Get all remote branches
git fetch --all --prune
# Get the default branch
DEFAULT_BRANCH=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
echo "Default branch: $DEFAULT_BRANCH"
echo ""
DELETED_COUNT=0
SKIPPED_COUNT=0
NO_PR_COUNT=0
OLD_AI_BRANCH_COUNT=0
# Loop through all remote branches
for branch in $(git branch -r | grep -v '\->' | sed 's/origin\///'); do
# Skip protected branches
SKIP=false
for protected in "${PROTECTED_BRANCHES[@]}"; do
if [ "$branch" = "$protected" ]; then
SKIP=true
break
fi
done
if [ "$SKIP" = true ]; then
echo "⏭️ Skipping protected branch: $branch"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
continue
fi
# Check if branch has AI assistant prefix
IS_AI_BRANCH=false
for prefix in "${AI_PREFIXES[@]}"; do
if [[ "$branch" == $prefix* ]]; then
IS_AI_BRANCH=true
break
fi
done
# Check if there's a PR for this branch using GitHub CLI
PR_DATA=$(gh pr list --head "$branch" --state all --json state,isDraft,createdAt --jq '.[0]' 2>/dev/null || echo "")
if [ -z "$PR_DATA" ] || [ "$PR_DATA" = "null" ]; then
# No PR found - check git history as fallback
MERGE_BASE=$(git merge-base $DEFAULT_BRANCH origin/$branch 2>/dev/null || echo "")
BRANCH_COMMIT=$(git rev-parse origin/$branch 2>/dev/null || echo "")
if [ -n "$MERGE_BASE" ] && [ -n "$BRANCH_COMMIT" ] && [ "$MERGE_BASE" = "$BRANCH_COMMIT" ]; then
echo "🗑️ Deleting merged branch (no PR): $branch"
if git push origin --delete "$branch" 2>&1; then
DELETED_COUNT=$((DELETED_COUNT + 1))
else
echo "⚠️ Failed to delete $branch"
fi
else
echo "⏭️ No PR found, branch not merged: $branch"
NO_PR_COUNT=$((NO_PR_COUNT + 1))
fi
else
PR_STATE=$(echo "$PR_DATA" | jq -r '.state')
IS_DRAFT=$(echo "$PR_DATA" | jq -r '.isDraft')
CREATED_AT=$(echo "$PR_DATA" | jq -r '.createdAt')
# Convert PR creation date to timestamp
PR_TIMESTAMP=$(date -u -d "$CREATED_AT" +%s 2>/dev/null || date -j -u -f "%Y-%m-%dT%H:%M:%SZ" "$CREATED_AT" +%s 2>/dev/null || echo "0")
if [ "$PR_STATE" = "MERGED" ]; then
echo "🗑️ Deleting branch with merged PR: $branch"
if git push origin --delete "$branch" 2>&1; then
DELETED_COUNT=$((DELETED_COUNT + 1))
else
echo "⚠️ Failed to delete $branch"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
fi
elif [ "$IS_AI_BRANCH" = true ] && [ "$PR_TIMESTAMP" -lt "$THREE_MONTHS_AGO" ] && { [ "$PR_STATE" = "CLOSED" ] || [ "$IS_DRAFT" = "true" ]; }; then
echo "🗑️ Deleting old AI branch (>3 months, closed/draft): $branch"
if git push origin --delete "$branch" 2>&1; then
DELETED_COUNT=$((DELETED_COUNT + 1))
OLD_AI_BRANCH_COUNT=$((OLD_AI_BRANCH_COUNT + 1))
else
echo "⚠️ Failed to delete $branch"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
fi
else
echo "⏭️ Skipping branch with $PR_STATE PR (draft: $IS_DRAFT): $branch"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
fi
fi
done
echo ""
echo "✅ Cleanup complete!"
echo " - Deleted: $DELETED_COUNT branches"
echo " - Old AI branches (>3mo, closed/draft): $OLD_AI_BRANCH_COUNT"
echo " - Skipped: $SKIPPED_COUNT branches (open/closed PRs or protected)"
echo " - No PR: $NO_PR_COUNT branches (no PR found, not merged)"
exit 0