Skip to content

Commit 0608b24

Browse files
committed
Push multiple branches in the stack atomically: it's faster, plus possible improves CODEOWNERS processing by GitHub
1 parent d13947f commit 0608b24

File tree

1 file changed

+89
-39
lines changed

1 file changed

+89
-39
lines changed

git-grok

+89-39
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ class Pr:
8686
labels: list[str]
8787

8888

89+
#
90+
# A pair of commit hash and branch name.
91+
#
92+
@dataclass
93+
class Branch:
94+
hash: str
95+
branch: str
96+
push_result: BranchPushResult | None = None
97+
98+
8999
#
90100
# Some data passed from the main process to each individual child process call
91101
# within "git rebase -i", for each commit in the stack.
@@ -227,6 +237,8 @@ class Main:
227237
commit_with_no_url = commit
228238
break
229239
else:
240+
# Push this branch and see whether GitHub says whether it
241+
# was up to date or not.
230242
commit_hashes_to_push_branch.append(commit.hash)
231243

232244
# Some commits have no related PRs (no GitHub URLs in the message)?
@@ -270,23 +282,36 @@ class Main:
270282

271283
self.print_header(f"Processing commit: {self.clean_title(commit.title)}")
272284

285+
to_push: list[Branch] = []
286+
273287
if prev_commit.hash != remote_commit.hash:
274-
prev_commit, result = self.process_commit_push_branch(commit=prev_commit)
288+
prev_commit.branch = self.process_commit_infer_branch(commit=prev_commit)
289+
to_push.append(Branch(hash=prev_commit.hash, branch=prev_commit.branch))
290+
else:
291+
prev_commit.branch = None
292+
293+
commit.branch = self.process_commit_infer_branch(commit=commit)
294+
to_push.append(Branch(hash=commit.hash, branch=commit.branch))
295+
296+
push_results = self.git_push_branches(branches=to_push)
297+
298+
if prev_commit.branch:
275299
self.print_branch_result(
276300
type="base",
277-
branch=str(prev_commit.branch),
278-
result=result,
301+
branch=prev_commit.branch,
302+
result=push_results[prev_commit.branch],
279303
)
280304
else:
281305
self.print_branch_result(
282306
type="base",
283307
branch=self.remote_base_branch,
284308
result="up-to-date",
285309
)
286-
prev_commit.branch = None
287-
288-
commit, result = self.process_commit_push_branch(commit=commit)
289-
self.print_branch_result(type="head", branch=str(commit.branch), result=result)
310+
self.print_branch_result(
311+
type="head",
312+
branch=commit.branch,
313+
result=push_results[commit.branch],
314+
)
290315

291316
new_pr_title = commit.title
292317
new_pr_body = None
@@ -342,11 +367,11 @@ class Main:
342367
commits: list[Commit],
343368
prs_by_url: dict[str, Pr],
344369
):
345-
commits_chronological = list(reversed(commits))
346-
for i, commit in list(enumerate(commits_chronological))[1:]:
370+
commits_old_to_new = list(reversed(commits))
371+
for i, commit in list(enumerate(commits_old_to_new))[1:]:
347372
pr = prs_by_url.get(commit.url, None) if commit.url else None
348373
if pr and pr.auto_merge_status == "ENABLED":
349-
prev_commit = commits_chronological[i - 1]
374+
prev_commit = commits_old_to_new[i - 1]
350375
prev_pr = (
351376
prs_by_url.get(prev_commit.url, None) if prev_commit.url else None
352377
)
@@ -426,25 +451,35 @@ class Main:
426451
):
427452
# We must iterate from the oldest commit to the newest one, because
428453
# previous commit PR's branch becomes the next commit PR's base branch.
429-
commits_chronological = list(reversed(commits))
430-
for i, commit in enumerate(commits_chronological):
454+
commits_old_to_new = list(reversed(commits))
455+
456+
# Push all branches atomically, in bulk. This is meant to prevent
457+
# useless CODEOWNERS reviewers addition in case the commits were
458+
# reordered or rebased, and is faster in general.
459+
to_push: list[Branch] = []
460+
for commit in commits_old_to_new:
461+
if (
462+
self.debug_force_push_branches
463+
or commit.hash in commit_hashes_to_push_branch
464+
):
465+
commit.branch = self.process_commit_infer_branch(commit=commit)
466+
to_push.append(Branch(hash=commit.hash, branch=commit.branch))
467+
push_results = self.git_push_branches(branches=to_push)
468+
469+
for i, commit in enumerate(commits_old_to_new):
431470
self.print_header(f"Updating PR: {self.clean_title(commit.title)}")
432471

433-
if commit.hash in commit_hashes_to_push_branch:
434-
commit, result = self.process_commit_push_branch(commit=commit)
472+
if commit.branch in push_results:
473+
result = push_results[commit.branch]
435474
if result == "pushed":
436475
self.print_branch_result(
437476
type="head",
438-
branch=str(commit.branch),
477+
branch=commit.branch,
439478
result=result,
440479
)
441-
commits_chronological[i] = commit
442480

443-
assert (
444-
commit.url is not None
445-
), f"commit {commit.hash} PR url is expected to be in the message at this point"
446481
pr, result = self.process_update_pr(
447-
prev_commit=commits_chronological[i - 1] if i > 0 else None,
482+
prev_commit=commits_old_to_new[i - 1] if i > 0 else None,
448483
commit=commit,
449484
commits=commits,
450485
)
@@ -453,27 +488,26 @@ class Main:
453488
result=result,
454489
review_decision=pr.review_decision,
455490
)
456-
commits_chronological[i].branch = pr.head_branch
491+
commits_old_to_new[i].branch = pr.head_branch
457492

458493
#
459-
# Pushes an existing branch (it we know this commit's PR URL by querying
460-
# GitHub), or creates a new branch based on commit title and pushes it.
494+
# For a commit, infers its corresponding remote branch name by either
495+
# querying it from the PR (when commit.url is set), or by building it from
496+
# the commit title and hash.
461497
#
462-
def process_commit_push_branch(
498+
def process_commit_infer_branch(
463499
self,
464500
*,
465501
commit: Commit,
466-
) -> tuple[Commit, BranchPushResult]:
502+
) -> str:
467503
if commit.url:
468-
pr = self.gh_get_pr(url=commit.url)
469-
commit.branch = pr.head_branch
504+
pr = self.gh_get_pr(url=commit.url) # likely a cache hit
505+
return pr.head_branch
470506
else:
471-
commit.branch = self.build_branch_name(
507+
return self.build_branch_name(
472508
title=commit.title,
473509
commit_hash=commit.hash,
474510
)
475-
pushed = self.git_push_branch(branch=commit.branch, hash=commit.hash)
476-
return commit, pushed
477511

478512
#
479513
# Updates PR fields:
@@ -864,9 +898,14 @@ class Main:
864898
return commits
865899

866900
#
867-
# Pushes a branch to remote GitHub.
901+
# Pushes multiple branches atomically to remote GitHub. Returns a dict
902+
# mapping branch names to their push results.
868903
#
869-
def git_push_branch(self, *, branch: str, hash: str) -> BranchPushResult:
904+
def git_push_branches(
905+
self,
906+
*,
907+
branches: list[Branch],
908+
) -> dict[str, BranchPushResult]:
870909
# Git push is a quick no-op on GitHub end if the branch isn't changed
871910
# (it prints "Everything up-to-date"), so we always push and then verify
872911
# the output for the status (instead of fetching from the remote and
@@ -877,15 +916,26 @@ class Main:
877916
"push",
878917
"-f",
879918
self.remote,
880-
f"{hash}:refs/heads/{branch}",
919+
*[f"{branch.hash}:refs/heads/{branch.branch}" for branch in branches],
881920
],
882921
stderr_to_stdout=True,
883922
)
884-
return (
885-
"up-to-date"
886-
if re.match(r"^[^\n]+up-to-date", out, flags=re.S)
887-
else "pushed"
888-
)
923+
# If the hash is NOT mentioned in the output, it's either a short
924+
# "Everything up-to-date" message (which means that ALL branches are
925+
# unchanged), or THIS particular branch is up-to-date. I.e. if a branch
926+
# is changed, git always prints its hash in the output, on one of the
927+
# following formats:
928+
# 1. * [new branch] 10dc4f6 -> grok/...
929+
# 2. + 10dc4f6...b28d03e 10dc4f6 -> grok/... (forced update)
930+
results: dict[str, BranchPushResult] = {
931+
branch.branch: "up-to-date" for branch in branches
932+
}
933+
for branch in branches:
934+
for line in out.splitlines():
935+
if branch.hash in line:
936+
results[branch.branch] = "pushed"
937+
break
938+
return results
889939

890940
#
891941
# Runs an interactive rebase with the provided shell command.
@@ -1147,7 +1197,7 @@ class Main:
11471197
# Prints a status after a commit message was updated locally.
11481198
#
11491199
def print_commit_message_updated(self):
1150-
print(f" ── updated commit message")
1200+
print(f" ── added PR URL to commit's message to bind them")
11511201
sys.stdout.flush()
11521202

11531203
#

0 commit comments

Comments
 (0)