Skip to content

Commit f1f8e22

Browse files
feat: add cleanup-branches action (#1334)
* Add cleanup-branches action * clean up shellcheck issues * update cleanup-branches action * Add max-date option in action * fix protected_branches arr * Update actions/cleanup-branches/action.yml Co-authored-by: Doug Blinkhorn <[email protected]> * Fix branch list so that it works on a shallow clone * Update actions/cleanup-branches/action.yml Co-authored-by: Doug Blinkhorn <[email protected]> * fetch branches * prettier * Update actions/cleanup-branches/README.md Co-authored-by: Doug Blinkhorn <[email protected]> --------- Co-authored-by: Doug Blinkhorn <[email protected]>
1 parent 2ef9c70 commit f1f8e22

File tree

2 files changed

+121
-0
lines changed

2 files changed

+121
-0
lines changed

actions/cleanup-branches/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# cleanup-branches
2+
3+
Composite action (step) to query for branches that are not in an open PR, and delete them if 'dry-run' is 'false'. Protected branches are excluded as well.
4+
5+
## Inputs
6+
7+
| Name | Type | Description | Default Value | Required |
8+
| ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -------- |
9+
| `token` | `string` | GitHub token used to authenticate with `gh`. Requires permission to query for protected branches and delete branches (`contents: write`) and pull requests (`pull_requests: read`) | `${{ github.token }}` | true |
10+
| `dry-run` | `bool` | If `'true'`, then the action will print branches to be deleted, but will not delete them | `'true'` | true |
11+
| `max-date` | `string` | Value passed to `date -d`; a human readable date string. Maximum date of the head ref of a branch in order to be deleted. | `"2 weeks ago"` | false |
12+
13+
## Examples
14+
15+
### Clean up branches on a weekly cron schedule
16+
17+
<!-- x-release-please-start-version -->
18+
19+
```yaml
20+
name: Clean up orphaned branches
21+
on:
22+
schedule:
23+
- cron: "0 9 * * 1"
24+
25+
jobs:
26+
cleanup-branches::
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: write
30+
pull-requests: read
31+
steps:
32+
- uses: actions/checkout@v5
33+
- uses: grafana/shared-workflows/actions/cleanup-branches@cleanup-branches/v1.0.0
34+
with:
35+
dry-run: false
36+
```
37+
38+
<!-- x-release-please-end-version -->
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Clean up orphaned git branches
2+
description: |
3+
This action will query for branches that are not in an open PR, and will delete them if 'dry-run' is 'false'.
4+
Protected branches are excluded as well.
5+
inputs:
6+
dry-run:
7+
default: "true"
8+
required: true
9+
description: "If 'true', then the action will print branches to be deleted, but will not delete them"
10+
token:
11+
default: ${{ github.token }}
12+
required: true
13+
description: "GitHub token used to authenticate with `gh`. Requires permission to query for protected branches and delete branches (contents: write) and pull requests (pull_requests: read)"
14+
max-date:
15+
default: "2 weeks ago"
16+
required: false
17+
description: |
18+
Value provided to `date -d={}. From `man date`: "The --date=STRING is a mostly free format human readable date string such as "Sun, 29 Feb 2004 16:21:42 -0800" or "2004-02-29 16:21:42" or even "next Thursday". A date string may
19+
contain items indicating calendar date, time of day, time zone, day of week, relative time, relative date, and numbers. An empty string indicates the beginning of the day. The
20+
date string format is more complex than is easily documented here but is fully described in the info documentation."
21+
runs:
22+
using: composite
23+
steps:
24+
- name: List branches
25+
shell: bash
26+
env:
27+
GH_TOKEN: ${{ inputs.token }}
28+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
29+
MAX_DATE: ${{ inputs.max-date }}
30+
run: |
31+
#!/usr/bin/env bash
32+
# Fetch all branches from remote. This is basically `git fetch --unshallow` but also fetches branches.
33+
git fetch origin "+refs/heads/*:refs/remotes/origin/*"
34+
35+
# A limit of 1,000 open PRs is far beyond any repository that is in the grafana org
36+
readarray -t open_pr_branches < <(gh pr list --state open -L 1000 --json headRefName | jq -cr '.[].headRefName')
37+
38+
# For repositories that have exceeded 2,000+ branches, this could fail.
39+
readarray -t protected_branches < <(gh api --paginate "/repos/${GITHUB_REPOSITORY}/branches?protected=true" | jq -cr '.[].name')
40+
41+
branches=()
42+
while IFS= read -r line; do
43+
branches+=("$line")
44+
done < <(git ls-remote --heads origin | awk '{print $2}' | sed 's|refs/heads/||' | grep -Ev "^(origin/)?(${DEFAULT_BRANCH})$")
45+
46+
to_delete=()
47+
for branch in "${branches[@]}"; do
48+
found=0
49+
for pr_branch in "${open_pr_branches[@]}"; do
50+
if [[ "$branch" == "$pr_branch" ]]; then
51+
found=1
52+
break
53+
fi
54+
done
55+
if [ "$found" != 1 ]; then
56+
for protected_branch in "${protected_branches[@]}"; do
57+
if [[ "$branch" == "$protected_branch" ]]; then
58+
found=1
59+
break
60+
fi
61+
done
62+
fi
63+
if [ "$found" != 1 ]; then
64+
to_delete+=("$branch")
65+
fi
66+
done
67+
max_date=$(TZ=utc date -d "$MAX_DATE" +%s)
68+
for branch in "${to_delete[@]}"; do
69+
branch_ts=$(git log -1 --format=%ct "origin/$branch")
70+
if [[ "$branch_ts" -lt "$max_date" ]]; then
71+
echo "$branch" >> branches.txt
72+
fi
73+
done
74+
- name: Delete branches (dry run)
75+
shell: bash
76+
if: ${{ inputs.dry-run == "true" }}
77+
run: |
78+
cat branches.txt | xargs -I {} echo git push origin --delete "{}"
79+
- name: Delete branches
80+
shell: bash
81+
if: ${{ inputs.dry-run != "true" }}
82+
run: |
83+
cat branches.txt | xargs -I {} git push origin --delete "{}"

0 commit comments

Comments
 (0)