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