mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
- actions/checkout v4.2.2 → v6.0.2 (pr-gate, auto-branch) - actions/github-script v7.0.1/v8 → v9.0.0 (all workflows) - actions/stale v9.0.0 → v10.2.0 Eliminates Node.js 20 deprecation warnings. Node 20 actions will be forced to Node 24 on June 2, 2026 and removed Sept 16, 2026. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
124 lines
4.3 KiB
YAML
124 lines
4.3 KiB
YAML
name: Branch Cleanup
|
|
|
|
on:
|
|
pull_request:
|
|
types: [closed]
|
|
schedule:
|
|
- cron: '0 4 * * 0' # Sunday 4am UTC — weekly orphan sweep
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: read
|
|
|
|
jobs:
|
|
# Runs immediately when a PR is merged — deletes the head branch.
|
|
# Belt-and-suspenders alongside the repo's delete_branch_on_merge setting,
|
|
# which handles web/API merges but may be bypassed by some CLI paths.
|
|
delete-merged-branch:
|
|
name: Delete merged PR branch
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 2
|
|
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true
|
|
steps:
|
|
- name: Delete head branch
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
script: |
|
|
const branch = context.payload.pull_request.head.ref;
|
|
const protectedBranches = ['main', 'develop', 'release'];
|
|
if (protectedBranches.includes(branch)) {
|
|
core.info(`Skipping protected branch: ${branch}`);
|
|
return;
|
|
}
|
|
try {
|
|
await github.rest.git.deleteRef({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
ref: `heads/${branch}`,
|
|
});
|
|
core.info(`Deleted branch: ${branch}`);
|
|
} catch (e) {
|
|
// 422 = branch already deleted (e.g. by delete_branch_on_merge setting)
|
|
if (e.status === 422) {
|
|
core.info(`Branch already deleted: ${branch}`);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
# Runs weekly to catch any orphaned branches whose PRs were merged
|
|
# before this workflow existed, or that slipped through edge cases.
|
|
sweep-orphaned-branches:
|
|
name: Weekly orphaned branch sweep
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
|
steps:
|
|
- name: Delete branches from merged PRs
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
script: |
|
|
const protectedBranches = new Set(['main', 'develop', 'release']);
|
|
const deleted = [];
|
|
const skipped = [];
|
|
|
|
// Paginate through all branches (100 per page)
|
|
let page = 1;
|
|
let allBranches = [];
|
|
while (true) {
|
|
const { data } = await github.rest.repos.listBranches({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
per_page: 100,
|
|
page,
|
|
});
|
|
allBranches = allBranches.concat(data);
|
|
if (data.length < 100) break;
|
|
page++;
|
|
}
|
|
|
|
core.info(`Scanning ${allBranches.length} branches...`);
|
|
|
|
for (const branch of allBranches) {
|
|
if (protectedBranches.has(branch.name)) continue;
|
|
|
|
// Find the most recent closed PR for this branch
|
|
const { data: prs } = await github.rest.pulls.list({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
head: `${context.repo.owner}:${branch.name}`,
|
|
state: 'closed',
|
|
per_page: 1,
|
|
sort: 'updated',
|
|
direction: 'desc',
|
|
});
|
|
|
|
if (prs.length === 0 || !prs[0].merged_at) {
|
|
skipped.push(branch.name);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
await github.rest.git.deleteRef({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
ref: `heads/${branch.name}`,
|
|
});
|
|
deleted.push(branch.name);
|
|
} catch (e) {
|
|
if (e.status !== 422) {
|
|
core.warning(`Failed to delete ${branch.name}: ${e.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const summary = [
|
|
`Deleted ${deleted.length} orphaned branch(es).`,
|
|
deleted.length > 0 ? ` Removed: ${deleted.join(', ')}` : '',
|
|
skipped.length > 0 ? ` Skipped (no merged PR): ${skipped.length} branch(es)` : '',
|
|
].filter(Boolean).join('\n');
|
|
|
|
core.info(summary);
|
|
await core.summary.addRaw(summary).write();
|