1- name : Merged stale branch deletion
2-
1+ name : Stale Branch Deletion Approval
2+ permissions :
3+ contents : write
4+ pull-requests : write
35on :
46 schedule :
5- # Runs every two weeks (on the 1st and 15th of each month)
6- - cron : " 0 0 1,15 * *"
7-
8- # Manual trigger
7+ - cron : ' 00 22 1 * *' # 10PM on 1st of every month
98 workflow_dispatch :
109 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"
1910 min_age_days :
2011 description : " Minimum age in days since merge"
2112 required : true
22- default : 14
13+ default : 27
2314 type : number
24-
2515jobs :
26- cleanup-branches :
27-
16+ identify-branches :
2817 if : github.repository_owner == 'mendix'
29-
3018 runs-on : ubuntu-latest
31-
3219 steps :
3320 - name : Checkout code
3421 uses : actions/checkout@v4
3522 with :
3623 fetch-depth : 0
3724 token : ${{ secrets.GITHUB_TOKEN }}
38-
39- - name : Set up Git
25+ persist-credentials : true
26+
27+ - name : Fetch all branches
4028 run : |
41- git config --global user.name 'GitHub Actions'
42- git config --global user.email '[email protected] ' 29+ echo "Fetching all branches from remote..."
30+ git fetch origin '+refs/heads/*:refs/remotes/origin/*' --prune
31+ echo "Fetched branches:"
32+ git branch -r
4333
4434 - name : Process branches
35+ id : branch-data
4536 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
37+ set -e
38+ echo "Finding all branches for processing..."
39+ ALL_BRANCHES=$(git branch -r | grep -v "origin/HEAD" | sed 's/origin\///')
40+
41+ echo "All branches found:"
42+ printf "%s\n" "${ALL_BRANCHES[@]}"
43+
44+ MIN_AGE_DAYS=${{ github.event.inputs.min_age_days || 27 }}
45+
46+ # Arrays to hold branches
47+ PROTECTED_BRANCHES=()
48+ MERGED_BRANCHES_TO_PROCESS=()
6949 BRANCHES_TO_DELETE=()
7050 BRANCHES_TOO_RECENT=()
71- BRANCHES_SKIPPED=()
7251
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")
52+ CURRENT_DATE=$(date +%Y%m%d)
53+ echo "CURRENT_DATE=$CURRENT_DATE" >> $GITHUB_ENV
54+
55+ # Check branches
56+ for BRANCH in $ALL_BRANCHES; do
57+ branch_lower=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]')
58+ # Identify protected branches
59+ if [[ $branch_lower =~ (backup|development|main|master|production) ]]; then
60+ if git branch -r --merged origin/development | grep -q "origin/$BRANCH"; then
61+ PROTECTED_BRANCHES+=("$BRANCH (protected name, merged)")
62+ else
63+ PROTECTED_BRANCHES+=("$BRANCH (protected name, not merged)")
64+ fi
65+ else
66+ # Process non-protected merged branches
67+ if git branch -r --merged origin/development | grep -q "origin/$BRANCH"; then
68+ MERGED_BRANCHES_TO_PROCESS+=("$BRANCH")
69+ else
70+ UNMERGED_BRANCHES+=("$BRANCH (not merged)")
71+ fi
9772 fi
98-
99- # Get the commit date
73+ done
74+
75+ # Process potential deletion
76+ for BRANCH in "${MERGED_BRANCHES_TO_PROCESS[@]}"; do
77+ MERGE_HASH=$(git log --grep="Merge branch.*$BRANCH" origin/development -n 1 --pretty=format:"%H" ||
78+ git log --grep="Merge pull request.*$BRANCH" origin/development -n 1 --pretty=format:"%H")
79+ [ -z "$MERGE_HASH" ] && MERGE_HASH=$(git log -n 1 origin/$BRANCH --pretty=format:"%H")
80+
10081 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
82+ DAYS_AGO=$(( ($(date +%s) - MERGE_DATE) / 86400 ))
83+
10984 if [[ $DAYS_AGO -ge $MIN_AGE_DAYS ]]; then
110- BRANCHES_TO_DELETE+=("$BRANCH ($DAYS_AGO days since merge )")
85+ BRANCHES_TO_DELETE+=("$BRANCH ($DAYS_AGO days)")
11186 else
112- BRANCHES_TOO_RECENT+=("$BRANCH ($DAYS_AGO days since merge )")
87+ BRANCHES_TOO_RECENT+=("$BRANCH ($DAYS_AGO days)")
11388 fi
11489 done
11590
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
91+ # Display non-deleted branches for logging
92+ ALL_NON_DELETED_BRANCHES=("${PROTECTED_BRANCHES[@]}" "${BRANCHES_TOO_RECENT[@]}" "${UNMERGED_BRANCHES[@]}")
93+ IFS=$'\n' ALL_NON_DELETED_BRANCHES=($(sort <<<"${ALL_NON_DELETED_BRANCHES[*]}"))
94+ unset IFS
12795
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"
96+ if [ ${#BRANCHES_TO_DELETE[@]} -eq 0 ]; then
97+ echo "No branches found for deletion."
98+ echo "NO_BRANCHES=true" >> $GITHUB_ENV
99+ exit 0
144100 else
145- for BRANCH_INFO in "${BRANCHES_SKIPPED[@]}"; do
146- echo "$BRANCH_INFO"
147- done
101+ echo "NO_BRANCHES=false" >> $GITHUB_ENV
148102 fi
149103
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 "=================================="
104+ # Create report
105+ echo "# Branch Cleanup Report - $(date +%Y-%m-%d)" > branch-report.branchreport
106+ echo "## Branches for deletion (merged >=${MIN_AGE_DAYS} days ago):" >> branch-report.branchreport
107+ echo '```' >> branch-report.branchreport
108+ printf "%s\n" "${BRANCHES_TO_DELETE[@]}" >> branch-report.branchreport
109+ echo '```' >> branch-report.branchreport
110+ echo "## Branches not eligible for deletion:" >> branch-report.branchreport
111+ echo '```' >> branch-report.branchreport
112+ printf "%s\n" "${ALL_NON_DELETED_BRANCHES[@]}" >> branch-report.branchreport
113+ echo '```' >> branch-report.branchreport
114+
115+ echo "BRANCHES_TO_DELETE<<EOF" >> $GITHUB_ENV
116+ printf "%s\n" "${BRANCHES_TO_DELETE[@]}" >> $GITHUB_ENV
117+ echo "EOF" >> $GITHUB_ENV
118+ echo "ALL_NON_DELETED_BRANCHES<<EOF" >> $GITHUB_ENV
119+ printf "%s\n" "${ALL_NON_DELETED_BRANCHES[@]}" >> $GITHUB_ENV
120+ echo "EOF" >> $GITHUB_ENV
121+
122+ - name : Create Deletion PR
123+ if : env.NO_BRANCHES != 'true'
124+ uses : peter-evans/create-pull-request@v6
125+ with :
126+ commit-message : " Branch cleanup proposal"
127+ title : " [AUTO] Branch Deletion Candidates - ${{ env.CURRENT_DATE }}"
128+ body : |
129+ ## Branches for deletion (merged to development)
130+ ```
131+ ${{ env.BRANCHES_TO_DELETE }}
132+ ```
156133
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
134+ ## Branches not eligible for deletion
135+ ```
136+ ${{ env.ALL_NON_DELETED_BRANCHES }}
137+ ```
138+ ## ⚠️ Warning
139+ Merging this PR will:
140+ 1. Delete the branches listed in the "Branches for deletion" section.
141+ 2. Remove the temporary branch-report.branchreport file.
142+ branch : branch-cleanup-${{ env.CURRENT_DATE }}
143+ assignees : MarkvanMents,OlufunkeMoronfolu
144+ reviewers : MarkvanMents,OlufunkeMoronfolu
145+ labels : Internal WIP
146+ add-paths : |
147+ branch-report.branchreport
0 commit comments