Skip to content

Commit 99a7c0c

Browse files
GitHub action for deleting merged stale branches
1 parent c94d1ce commit 99a7c0c

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
name: Merged stale branch deletion
2+
3+
on:
4+
schedule:
5+
# Runs every two weeks (on the 1st and 15th of each month)
6+
- cron: "0 0 1,15 * *"
7+
8+
# Manual trigger
9+
workflow_dispatch:
10+
inputs:
11+
dry_run:
12+
description: "Dry run (no actual deletions)"
13+
required: true
14+
default: "true"
15+
type: choice
16+
options:
17+
- "true"
18+
- "false"
19+
min_age_days:
20+
description: "Minimum age in days since merge"
21+
required: true
22+
default: 14
23+
type: number
24+
25+
jobs:
26+
cleanup-branches:
27+
28+
if: github.repository_owner == 'mendix'
29+
30+
runs-on: ubuntu-latest
31+
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v4
35+
with:
36+
fetch-depth: 0
37+
token: ${{ secrets.GITHUB_TOKEN }}
38+
39+
- name: Set up Git
40+
run: |
41+
git config --global user.name 'GitHub Actions'
42+
git config --global user.email '[email protected]'
43+
44+
- name: Process branches
45+
run: |
46+
echo "FETCHING ALL BRANCHES"
47+
git fetch --all
48+
49+
# Checks if it is a dry run
50+
if [[ "${{ github.event_name }}" == "schedule" ]]; then
51+
IS_DRY_RUN="false" # Scheduled runs do actual deletions
52+
MIN_AGE_DAYS=14
53+
else
54+
IS_DRY_RUN="${{ github.event.inputs.dry_run }}"
55+
MIN_AGE_DAYS=${{ github.event.inputs.min_age_days }}
56+
fi
57+
58+
echo "Mode: $([ "$IS_DRY_RUN" == "true" ] && echo "DRY RUN (no deletions)" || echo "ACTUAL DELETION")"
59+
echo "Using minimum age of $MIN_AGE_DAYS days since merge"
60+
61+
echo "FINDING MERGED BRANCHES"
62+
# Gets list of merged branches
63+
MERGED_BRANCHES=$(git branch -r --merged origin/development | grep -v "origin/development" | grep -v "origin/main" | grep -v "origin/production" | grep -v "origin/master" | sed 's/origin\///')
64+
65+
CURRENT_DATE=$(date +%s)
66+
echo "Current date: $(date -d @$CURRENT_DATE)"
67+
68+
# Creates arrays to track results
69+
BRANCHES_TO_DELETE=()
70+
BRANCHES_TOO_RECENT=()
71+
BRANCHES_SKIPPED=()
72+
73+
echo "CHECKING BRANCH MERGE DATES:"
74+
echo "=================================="
75+
for BRANCH in $MERGED_BRANCHES; do
76+
# Skips branches with "backup" in their name
77+
if [[ $BRANCH == *backup* ]]; then
78+
BRANCHES_SKIPPED+=("$BRANCH (backup branch)")
79+
continue
80+
fi
81+
82+
# Finds when branch was merged to development
83+
# Gets the commit hash where the branch was merged
84+
MERGE_HASH=$(git log --grep="Merge branch.*$BRANCH" origin/development -n 1 --pretty=format:"%H" || git log --grep="Merge pull request.*$BRANCH" origin/development -n 1 --pretty=format:"%H")
85+
86+
# If no merge commit found, do:
87+
if [[ -z "$MERGE_HASH" ]]; then
88+
# Get the last commit of the branch
89+
LAST_COMMIT=$(git log -n 1 origin/$BRANCH --pretty=format:"%H")
90+
# Find when this commit or its equivalent appeared in development
91+
MERGE_HASH=$(git log origin/development --pretty=format:"%H" --grep="$(git log -n 1 $LAST_COMMIT --pretty=format:"%s")" -n 1)
92+
fi
93+
94+
# If still no merge info, use the last commit on the branch
95+
if [[ -z "$MERGE_HASH" ]]; then
96+
MERGE_HASH=$(git log -n 1 origin/$BRANCH --pretty=format:"%H")
97+
fi
98+
99+
# Get the commit date
100+
MERGE_DATE=$(git show -s --format=%ct $MERGE_HASH)
101+
MERGE_DATE_HUMAN=$(git show -s --format=%ci $MERGE_HASH)
102+
103+
# Calculate days since merge
104+
DAYS_AGO=$(( (CURRENT_DATE - MERGE_DATE) / 86400 ))
105+
106+
echo "Branch: $BRANCH - merged approximately $DAYS_AGO days ago ($MERGE_DATE_HUMAN)"
107+
108+
# Check if the branch is old enough to be deleted
109+
if [[ $DAYS_AGO -ge $MIN_AGE_DAYS ]]; then
110+
BRANCHES_TO_DELETE+=("$BRANCH ($DAYS_AGO days since merge)")
111+
else
112+
BRANCHES_TOO_RECENT+=("$BRANCH ($DAYS_AGO days since merge)")
113+
fi
114+
done
115+
116+
# Display results
117+
echo ""
118+
echo "BRANCHES RECOMMENDED FOR DELETION (MERGED >= $MIN_AGE_DAYS DAYS AGO):"
119+
echo "=================================="
120+
if [ ${#BRANCHES_TO_DELETE[@]} -eq 0 ]; then
121+
echo "No branches found that meet deletion criteria"
122+
else
123+
for BRANCH_INFO in "${BRANCHES_TO_DELETE[@]}"; do
124+
echo "$BRANCH_INFO"
125+
done
126+
fi
127+
128+
echo ""
129+
echo "BRANCHES TOO RECENT TO DELETE (MERGED < $MIN_AGE_DAYS DAYS AGO):"
130+
echo "=================================="
131+
if [ ${#BRANCHES_TOO_RECENT[@]} -eq 0 ]; then
132+
echo "No recently merged branches found"
133+
else
134+
for BRANCH_INFO in "${BRANCHES_TOO_RECENT[@]}"; do
135+
echo "$BRANCH_INFO"
136+
done
137+
fi
138+
139+
echo ""
140+
echo "BRANCHES SKIPPED:"
141+
echo "=================================="
142+
if [ ${#BRANCHES_SKIPPED[@]} -eq 0 ]; then
143+
echo "No branches skipped"
144+
else
145+
for BRANCH_INFO in "${BRANCHES_SKIPPED[@]}"; do
146+
echo "$BRANCH_INFO"
147+
done
148+
fi
149+
150+
# Perform actual deletion if not a dry run
151+
if [[ "$IS_DRY_RUN" == "false" ]]; then
152+
echo ""
153+
echo "=================================="
154+
echo "PROCEEDING WITH ACTUAL DELETION"
155+
echo "=================================="
156+
157+
# Extract just the branch names from BRANCHES_TO_DELETE
158+
for BRANCH_INFO in "${BRANCHES_TO_DELETE[@]}"; do
159+
BRANCH=$(echo $BRANCH_INFO | cut -d' ' -f1)
160+
echo "Deleting branch: $BRANCH"
161+
git push origin --delete $BRANCH
162+
done
163+
else
164+
echo ""
165+
echo "=================================="
166+
echo "This is just a report - no branches were actually deleted."
167+
fi

0 commit comments

Comments
 (0)