@@ -86,6 +86,16 @@ class Pr:
86
86
labels : list [str ]
87
87
88
88
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
+
89
99
#
90
100
# Some data passed from the main process to each individual child process call
91
101
# within "git rebase -i", for each commit in the stack.
@@ -227,6 +237,8 @@ class Main:
227
237
commit_with_no_url = commit
228
238
break
229
239
else :
240
+ # Push this branch and see whether GitHub says whether it
241
+ # was up to date or not.
230
242
commit_hashes_to_push_branch .append (commit .hash )
231
243
232
244
# Some commits have no related PRs (no GitHub URLs in the message)?
@@ -270,23 +282,36 @@ class Main:
270
282
271
283
self .print_header (f"Processing commit: { self .clean_title (commit .title )} " )
272
284
285
+ to_push : list [Branch ] = []
286
+
273
287
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 :
275
299
self .print_branch_result (
276
300
type = "base" ,
277
- branch = str ( prev_commit .branch ) ,
278
- result = result ,
301
+ branch = prev_commit .branch ,
302
+ result = push_results [ prev_commit . branch ] ,
279
303
)
280
304
else :
281
305
self .print_branch_result (
282
306
type = "base" ,
283
307
branch = self .remote_base_branch ,
284
308
result = "up-to-date" ,
285
309
)
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
+ )
290
315
291
316
new_pr_title = commit .title
292
317
new_pr_body = None
@@ -342,11 +367,11 @@ class Main:
342
367
commits : list [Commit ],
343
368
prs_by_url : dict [str , Pr ],
344
369
):
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 :]:
347
372
pr = prs_by_url .get (commit .url , None ) if commit .url else None
348
373
if pr and pr .auto_merge_status == "ENABLED" :
349
- prev_commit = commits_chronological [i - 1 ]
374
+ prev_commit = commits_old_to_new [i - 1 ]
350
375
prev_pr = (
351
376
prs_by_url .get (prev_commit .url , None ) if prev_commit .url else None
352
377
)
@@ -426,25 +451,35 @@ class Main:
426
451
):
427
452
# We must iterate from the oldest commit to the newest one, because
428
453
# 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 ):
431
470
self .print_header (f"Updating PR: { self .clean_title (commit .title )} " )
432
471
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 ]
435
474
if result == "pushed" :
436
475
self .print_branch_result (
437
476
type = "head" ,
438
- branch = str ( commit .branch ) ,
477
+ branch = commit .branch ,
439
478
result = result ,
440
479
)
441
- commits_chronological [i ] = commit
442
480
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"
446
481
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 ,
448
483
commit = commit ,
449
484
commits = commits ,
450
485
)
@@ -453,27 +488,26 @@ class Main:
453
488
result = result ,
454
489
review_decision = pr .review_decision ,
455
490
)
456
- commits_chronological [i ].branch = pr .head_branch
491
+ commits_old_to_new [i ].branch = pr .head_branch
457
492
458
493
#
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.
461
497
#
462
- def process_commit_push_branch (
498
+ def process_commit_infer_branch (
463
499
self ,
464
500
* ,
465
501
commit : Commit ,
466
- ) -> tuple [ Commit , BranchPushResult ] :
502
+ ) -> str :
467
503
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
470
506
else :
471
- commit . branch = self .build_branch_name (
507
+ return self .build_branch_name (
472
508
title = commit .title ,
473
509
commit_hash = commit .hash ,
474
510
)
475
- pushed = self .git_push_branch (branch = commit .branch , hash = commit .hash )
476
- return commit , pushed
477
511
478
512
#
479
513
# Updates PR fields:
@@ -864,9 +898,14 @@ class Main:
864
898
return commits
865
899
866
900
#
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.
868
903
#
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 ]:
870
909
# Git push is a quick no-op on GitHub end if the branch isn't changed
871
910
# (it prints "Everything up-to-date"), so we always push and then verify
872
911
# the output for the status (instead of fetching from the remote and
@@ -877,15 +916,26 @@ class Main:
877
916
"push" ,
878
917
"-f" ,
879
918
self .remote ,
880
- f"{ hash } :refs/heads/{ branch } " ,
919
+ * [ f"{ branch . hash } :refs/heads/{ branch . branch } " for branch in branches ] ,
881
920
],
882
921
stderr_to_stdout = True ,
883
922
)
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
889
939
890
940
#
891
941
# Runs an interactive rebase with the provided shell command.
@@ -1147,7 +1197,7 @@ class Main:
1147
1197
# Prints a status after a commit message was updated locally.
1148
1198
#
1149
1199
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 " )
1151
1201
sys .stdout .flush ()
1152
1202
1153
1203
#
0 commit comments