Skip to content

Commit 32ce22f

Browse files
[Github] Add initial workflow to prune unused user branches
This patch starts implementing the long requested feature of a workflow to automatically prune user branches that are not tied to a PR. For now this just consists of a script that finds user branches not attached to a PR and prints them out. Future patches will add support for dumping the diff between the branches and main (to persist to artifact storage so people can recover if they intended to use the branch) and actually deleting the branches. Reviewers: cmtice, tstellar, petrhosek, vbvictor Pull Request: llvm#175693
1 parent c28c8a0 commit 32ce22f

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Prune Unused Branches
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
paths:
9+
- .github/workflows/prune-branches.yml
10+
schedule:
11+
- cron: '0 8 * * *' # Runs daily at 08:00 UTC.
12+
13+
jobs:
14+
prune-branches:
15+
name: Prune Branches
16+
if: github.repository_owner == 'llvm'
17+
runs-on: ubuntu-24.04
18+
steps:
19+
- name: Fetch LLVM sources
20+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
21+
with:
22+
fetch-depth: 0
23+
- name: Install dependencies
24+
run: |
25+
pip install --require-hashes -r ./llvm/utils/git/requirements.txt
26+
- name: Run Script
27+
env:
28+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29+
run: |
30+
python3 .github/workflows/prune-unused-branches.py
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import subprocess
2+
import os
3+
4+
import github
5+
6+
7+
def get_branches() -> list[str]:
8+
git_process = subprocess.run(
9+
["git", "branch", "--all"], stdout=subprocess.PIPE, check=True
10+
)
11+
branches = [
12+
branch.strip() for branch in git_process.stdout.decode("utf-8").split("\n")
13+
]
14+
15+
def branch_filter(branch_name):
16+
return "users/" in branch_name or "revert-" in branch_name
17+
18+
filtered_branches = list(filter(branch_filter, branches))
19+
return [branch.replace("remotes/origin/", "") for branch in filtered_branches]
20+
21+
22+
def get_branches_from_open_prs(github_token) -> list[str]:
23+
gh = github.Github(auth=github.Auth.Token(github_token))
24+
query = """
25+
query ($after: String) {
26+
search(query: "is:pr repo:llvm/llvm-project is:open head:users/", type: ISSUE, first: 100, after: $after) {
27+
nodes {
28+
... on PullRequest {
29+
baseRefName
30+
headRefName
31+
isCrossRepository
32+
number
33+
}
34+
}
35+
pageInfo {
36+
hasNextPage
37+
endCursor
38+
}
39+
}
40+
}"""
41+
pr_data = []
42+
has_next_page = True
43+
variables = {"after": None}
44+
while has_next_page:
45+
_, res_data = gh._Github__requester.graphql_query(query, variables=variables)
46+
page_info = res_data["data"]["search"]["pageInfo"]
47+
has_next_page = page_info["hasNextPage"]
48+
if has_next_page:
49+
variables["after"] = page_info["endCursor"]
50+
prs = res_data["data"]["search"]["nodes"]
51+
pr_data.extend(prs)
52+
print(f"Processed {len(prs)} PRs")
53+
54+
user_branches = []
55+
for pr in pr_data:
56+
if not pr["isCrossRepository"]:
57+
if pr["baseRefName"] != "main":
58+
user_branches.append(pr["baseRefName"])
59+
user_branches.append(pr["headRefName"])
60+
return user_branches
61+
62+
63+
def get_user_branches_to_remove(
64+
user_branches: list[str], user_branches_from_prs: list[str]
65+
) -> list[str]:
66+
user_branches_to_remove = set(user_branches)
67+
for pr_user_branch in set(user_branches_from_prs):
68+
user_branches_to_remove.remove(pr_user_branch)
69+
return list(user_branches_to_remove)
70+
71+
72+
def main(github_token):
73+
user_branches = get_branches()
74+
user_branches_from_prs = get_branches_from_open_prs(github_token)
75+
print(f"Found {len(user_branches)} user branches in the repository")
76+
print(f"Found {len(user_branches_from_prs)} user branches associated with PRs")
77+
user_branches_to_remove = get_user_branches_to_remove(
78+
user_branches, user_branches_from_prs
79+
)
80+
print(f"Deleting {len(user_branches_to_remove)} user branches.")
81+
print(user_branches_to_remove)
82+
83+
84+
if __name__ == "__main__":
85+
main(os.environ["GITHUB_TOKEN"])

0 commit comments

Comments
 (0)