Skip to content

Commit dcb387e

Browse files
macdiceCommitfest Bot
authored andcommitted
git push --delete unreferenced branches.
This avoids cluttering the github account up with thousands of dead branches. Our retention period of 90 days matches Github's retention period for logs and results, so all the links would become useless anyway. XXX Should we tell the cf app to forget the links it holds?
1 parent 740317f commit dcb387e

4 files changed

Lines changed: 121 additions & 4 deletions

File tree

cfbot_github.py

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ def split_task_id(task_id):
142142
# as a Python object.
143143
#
144144
# repo includes the user, like "postgres/postgres".
145-
def get_github_api(repo, action, params=None, none_for_404=False):
146-
url = "https://api.github.com/repos/" + repo + "/actions/" + action
145+
def get_github_api(repo, action, params=None, none_for_404=False, prefix="actions/"):
146+
url = "https://api.github.com/repos/" + repo + "/" + prefix + action
147147
headers = {
148148
"Accept": "application/vnd.github+json",
149149
"X-GitHub-Api-Version": "2026-03-10",
@@ -768,6 +768,88 @@ def refresh_build_status_statistics(conn):
768768
group by 1, 2""")
769769

770770

771+
def gc_remote_branches(conn):
772+
# This horrible big lock prevents branches from being created or deleted
773+
# while we are paginating through them...
774+
#
775+
# XXX Could do better than this... one problem is that we only create
776+
# branch records after pushing, creating a window for deleting a branch
777+
# we'd just pushed...
778+
with cfbot_util.lock():
779+
# File the branches that still have build records
780+
cursor = conn.cursor()
781+
782+
# Find all the branches we still have builds for.
783+
live_branches = set()
784+
cursor.execute(
785+
"""select distinct branch_name
786+
from build
787+
where build_id like %s || ':%%'
788+
order by 1""",
789+
(cfbot_config.GITHUB_FULL_REPO,),
790+
)
791+
for (branch,) in cursor:
792+
live_branches.add(branch)
793+
794+
# There could also be very recently pushed branches that we don't have
795+
# a build for yet.
796+
#
797+
# XXX Maybe branch needs a branch_name column? This is not nice...
798+
cursor.execute("""select distinct 'cf/' || submission_id
799+
from branch
800+
where created > now() - interval '24 hours'""")
801+
for (branch,) in cursor:
802+
live_branches.add(branch)
803+
804+
# We can delete branches in batches (this is limited by command line
805+
# length and by index key length, but 512 has worked...).
806+
BATCH_SIZE = 128
807+
MAX_BATCHES = 4
808+
batches = 0
809+
batch = []
810+
811+
# Since we don't have a stable snapshot due to pagination via the API,
812+
# deduplicate the branch names we see.
813+
seen_branches = set()
814+
815+
page = 0
816+
while batches < MAX_BATCHES:
817+
remote_branches = get_github_api(
818+
cfbot_config.GITHUB_FULL_REPO,
819+
"branches",
820+
prefix="",
821+
params={"per_page": "100", "page": str(page)},
822+
)
823+
# print("page %s got %s branches" % (page, len(remote_branches)))
824+
if len(remote_branches) == 0:
825+
break
826+
for remote_branch in remote_branches:
827+
branch = remote_branch["name"]
828+
if branch in seen_branches:
829+
continue
830+
seen_branches.add(branch)
831+
if re.match(cfbot_config.GITHUB_MIRROR_BRANCH_PATTERN, branch):
832+
continue
833+
if branch in live_branches:
834+
continue
835+
logging.info("will drop unreferenced remote branch " + branch)
836+
batch.append(branch)
837+
if len(batch) == BATCH_SIZE:
838+
cfbot_work_queue.insert_work_queue(
839+
cursor, "push-delete-branch", " ".join(batch)
840+
)
841+
batch = []
842+
batches += 1
843+
if batches == MAX_BATCHES:
844+
break
845+
page += 1
846+
847+
if len(batch) > 0:
848+
cfbot_work_queue.insert_work_queue(
849+
cursor, "push-delete-branch", " ".join(batch)
850+
)
851+
852+
771853
# ======================================================================
772854
# Functions called by flask when our webhook is called by Github.
773855
# ======================================================================
@@ -849,6 +931,10 @@ def poll_github_run(conn, key):
849931
# ======================================================================
850932

851933
if __name__ == "__main__":
934+
with cfbot_util.db() as conn:
935+
gc_remote_branches(conn)
936+
conn.commit()
937+
exit(0)
852938
# print(json.dumps(get_builds_for_commit("3c970e3e544bb17a894854c027d3d3bc285fb072"), indent=4))
853939
# print(json.dumps(get_tasks_for_build("26492296536"), indent=4))
854940
# print(json.dumps(get_github_api("runs/" + "264922965360", none_for_404=True), indent=4))

cfbot_patch.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,17 +342,39 @@ def mirror_branch(branch):
342342
)
343343

344344

345+
def delete_branch(branch):
346+
template_repo_path = patchburner_ctl("template-repo-path").strip()
347+
348+
# XXX Not sure if we really need this lock since it's entirely operating on
349+
# the remote repo...
350+
with cfbot_util.lock():
351+
if cfbot_config.GIT_REMOTE_NAME:
352+
logging.info("deleting branch %s", branch)
353+
my_env = os.environ.copy()
354+
my_env["GIT_SSH_COMMAND"] = cfbot_config.GIT_SSH_COMMAND
355+
subprocess.check_call(
356+
"cd %s && git push -q --delete %s %s"
357+
% (template_repo_path, cfbot_config.GIT_REMOTE_NAME, branch),
358+
env=my_env,
359+
shell=True,
360+
# stderr=subprocess.DEVNULL,
361+
)
362+
363+
345364
def reset_repo_to_good_master_commit(conn, repo_path):
346365
# find the most recent commit ID that succeeded on master (our
347366
# mirror of it, in cfbot's repo, for best cache-sharing)
348367
cursor = conn.cursor()
349-
cursor.execute("""SELECT commit_id
368+
cursor.execute(
369+
"""SELECT commit_id
350370
FROM build
351371
WHERE branch_name = 'master'
352372
AND build_id LIKE '%s:%%'
353373
AND status = 'COMPLETED'
354374
ORDER BY created DESC
355-
LIMIT 1""" % cfbot_config.GITHUB_FULL_REPO)
375+
LIMIT 1"""
376+
% cfbot_config.GITHUB_FULL_REPO
377+
)
356378
result = cursor.fetchone()
357379
if result:
358380
(good_commit_id,) = result

cfbot_periodic_daily.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
#!/usr/bin/env python3
22

33
import cfbot_gc
4+
import cfbot_github
45
import cfbot_util
56

67
if __name__ == "__main__":
78
with cfbot_util.db() as conn:
89
cfbot_gc.gc(conn)
910
conn.commit()
11+
12+
# Now that we've deleted build records older than RETENTION_ALL, we can
13+
# delete unreferenced remote branches to avoid leaving junk in our
14+
# Github account.
15+
cfbot_github.gc_remote_branches(conn)
16+
conn.commit()

cfbot_work_queue.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ def process_one_job(conn, fetch_only):
143143
# Mirroring master, REL_*_STABLE
144144
elif type == "push-mirror-branch":
145145
cfbot_patch.mirror_branch(key)
146+
elif type == "push-delete-branch":
147+
cfbot_patch.delete_branch(key)
146148
# Notifying the Commitfest app
147149
elif type == "post-task-status":
148150
cfbot_commitfest.post_task_status(conn, key)

0 commit comments

Comments
 (0)