Repository navigation
255 lines (226 loc) · 8.76 KB
/
Copy pathbranch-cleanup.yml
File metadata and controls
255 lines (226 loc) · 8.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# Automated cleanup for merged and stale branches
#
# This workflow runs on a schedule (daily) to:
# 1. Delete merged branches older than 7 days
# 2. Delete branches for closed GitHub issues
# 3. Clean up stale worktrees
#
# Preserves:
# - Main development branches (main, Dev_new_gui)
# - Branches with active work or recent commits
# - Protected branches
#
# See: https://github.com/mrveiss/AutoBot-AI/issues/4110
name: Automated branch cleanup
on:
schedule:
# Run daily at 02:00 UTC
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run mode (no deletions)'
required: false
type: boolean
default: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
contents: write
issues: read
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up environment
id: env
env:
INPUT_DRY_RUN: ${{ inputs.dry_run }}
EVENT_NAME: ${{ github.event_name }}
run: |
DRY_RUN="${INPUT_DRY_RUN:-false}"
# For scheduled runs, always do real cleanup (not dry-run)
if [ "$EVENT_NAME" = "schedule" ]; then
DRY_RUN="false"
fi
echo "dry_run=$DRY_RUN" >> "$GITHUB_OUTPUT"
echo "Running with dry_run=$DRY_RUN"
- name: Fetch latest refs
run: |
git fetch --all --prune
git fetch origin --tags
- name: Delete merged branches (7+ days old)
id: merged
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ steps.env.outputs.dry_run }}
shell: bash
run: |
set -euo pipefail
# Shared safe-pruning guards (recency, open-PR) -- see #10035.
source scripts/lib/branch-guards.sh
BASE_BRANCH="Dev_new_gui"
DAYS_OLD=7
CUTOFF_DATE=$(date -d "$DAYS_OLD days ago" +%s)
deleted_count=0
skipped_count=0
echo "Looking for merged branches older than $DAYS_OLD days..."
# Get all branches merged into Dev_new_gui (ancestor-based, never
# patch-equivalence -- #10035).
merged_branches=$(git branch -r --merged "origin/$BASE_BRANCH" \
| grep -v "HEAD\|$BASE_BRANCH\|main\|master" \
| sed 's|^ *origin/||' || true)
if [ -z "$merged_branches" ]; then
echo " No merged branches found."
else
while IFS= read -r branch; do
[ -z "$branch" ] && continue
# Never delete a freshly-pushed branch or one with an open PR
# (#10035) -- symmetric with the closed-issue step below.
if branch_recently_pushed "origin/$branch"; then
echo " SKIP (pushed <${BRANCH_MIN_AGE_HOURS}h ago): origin/$branch"
skipped_count=$((skipped_count + 1))
continue
fi
if branch_has_open_pr "$branch"; then
echo " SKIP (open PR): origin/$branch"
skipped_count=$((skipped_count + 1))
continue
fi
# Get last commit timestamp
last_commit_ts=$(git log -1 --format=%ct "origin/$branch" 2>/dev/null || echo "0")
if [ "$last_commit_ts" -lt "$CUTOFF_DATE" ]; then
if [ "$DRY_RUN" = "true" ]; then
echo " WOULD DELETE: origin/$branch (merged, age > $DAYS_OLD days)"
else
if git push origin --delete "$branch" 2>/dev/null; then
echo " DELETED: origin/$branch (merged, age > $DAYS_OLD days)"
deleted_count=$((deleted_count + 1))
else
echo " SKIP (deletion failed): origin/$branch"
skipped_count=$((skipped_count + 1))
fi
fi
else
skipped_count=$((skipped_count + 1))
fi
done <<< "$merged_branches"
fi
{
echo "deleted=$deleted_count"
echo "skipped=$skipped_count"
} >> "$GITHUB_OUTPUT"
- name: Delete branches for closed issues
id: closed_issues
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ steps.env.outputs.dry_run }}
shell: bash
run: |
set -euo pipefail
# Shared safe-pruning guards -- see #10035.
source scripts/lib/branch-guards.sh
deleted_count=0
skipped_count=0
errors=0
echo "Looking for branches tied to closed issues..."
# Get all remote branches with issue numbers
remote_branches=$(git branch -r \
| sed 's|^ *origin/||' \
| grep -v "HEAD\|main\|master\|Dev_new_gui" || true)
if [ -z "$remote_branches" ]; then
echo " No candidate branches found."
else
while IFS= read -r branch; do
[ -z "$branch" ] && continue
# Extract the issue number from issue-/hotfix- branches only.
# Date tokens and MVA work-items must never match (#10035).
issue_number=$(extract_issue_number "$branch")
[ -z "$issue_number" ] && continue
# Never delete freshly-pushed work or a branch with an open PR.
if branch_recently_pushed "origin/$branch"; then
echo " SKIP (pushed <${BRANCH_MIN_AGE_HOURS}h ago): origin/$branch"
skipped_count=$((skipped_count + 1))
continue
fi
if branch_has_open_pr "$branch"; then
echo " SKIP (open PR): origin/$branch"
skipped_count=$((skipped_count + 1))
continue
fi
# Check issue state
issue_state=$(gh issue view "$issue_number" --json state --jq '.state' 2>/dev/null || echo "")
if [ "$issue_state" = "CLOSED" ]; then
# Verify a merged PR actually closes this issue (#10114)
merged_pr=$(merged_pr_for_issue "$issue_number")
if [ -n "$merged_pr" ]; then
if [ "$DRY_RUN" = "true" ]; then
echo " WOULD DELETE: origin/$branch (issue closed, PR merged)"
else
if git push origin --delete "$branch" 2>/dev/null; then
echo " DELETED: origin/$branch (issue closed, PR merged)"
deleted_count=$((deleted_count + 1))
else
echo " ERROR deleting: origin/$branch"
errors=$((errors + 1))
fi
fi
fi
fi
done <<< "$remote_branches"
fi
{
echo "deleted=$deleted_count"
echo "skipped=$skipped_count"
echo "errors=$errors"
} >> "$GITHUB_OUTPUT"
- name: Run cleanup script
if: always()
env:
DRY_RUN: ${{ steps.env.outputs.dry_run }}
run: |
if [ "$DRY_RUN" = "true" ]; then
bash scripts/cleanup-worktrees.sh --dry-run --branches-only
else
bash scripts/cleanup-worktrees.sh --branches-only
fi
- name: Final prune
if: ${{ steps.env.outputs.dry_run == 'false' }}
run: |
git fetch --all --prune
echo "Repository cleaned and pruned."
- name: Generate summary
if: always()
env:
MERGED_DELETED: ${{ steps.merged.outputs.deleted || '0' }}
MERGED_SKIPPED: ${{ steps.merged.outputs.skipped || '0' }}
CLOSED_DELETED: ${{ steps.closed_issues.outputs.deleted || '0' }}
CLOSED_SKIPPED: ${{ steps.closed_issues.outputs.skipped || '0' }}
CLOSED_ERRORS: ${{ steps.closed_issues.outputs.errors || '0' }}
DRY_RUN_MODE: ${{ steps.env.outputs.dry_run }}
run: |
cat >> "$GITHUB_STEP_SUMMARY" <<'EOF'
## Branch Cleanup Summary
**Merged branches (7+ days old):**
EOF
{
echo "- Deleted: $MERGED_DELETED"
echo "- Skipped: $MERGED_SKIPPED"
echo ""
echo "**Branches for closed issues:**"
echo "- Deleted: $CLOSED_DELETED"
echo "- Skipped: $CLOSED_SKIPPED"
echo "- Errors: $CLOSED_ERRORS"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
if [ "$DRY_RUN_MODE" = "true" ]; then
echo "**Mode:** Dry-run (no changes)" >> "$GITHUB_STEP_SUMMARY"
else
echo "**Mode:** Live (changes applied)" >> "$GITHUB_STEP_SUMMARY"
fi