Skip to content

Commit 0d7e5d3

Browse files
committed
upload: Push a dummy commit to work around reordering issues
We've had a long running issue where github may mark prs as merged or closed when trying to reorder relative series. This is caused by push + update not being atomic (see comments for more details). To work around this we'll - detect it in a dumb way, if 2 or more prs had their base changed this upload. this will result in some false positives, but the only cost is an additional ~1s push that people likely won't notice, and will save us from needing to write more complex logic to detect it correctly. - push a dummy commit to every single branch initially that prevents github from marking any of the prs as merged. - update as normal - do an extra push to remove the dummy commit Topic: reorder4 Reviewers: aaron, brian-k Fixes: #170
1 parent d5e14f4 commit 0d7e5d3

File tree

2 files changed

+40
-1
lines changed

2 files changed

+40
-1
lines changed

revup/topic_stack.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from revup.types import (
1919
GitCommitHash,
2020
GitConflictException,
21+
GitTreeHash,
2122
RevupConflictException,
2223
RevupUsageException,
2324
)
@@ -256,6 +257,9 @@ class TopicStack:
256257
# Whether populate() was successfully called
257258
populated: bool = False
258259

260+
# Whether to work around github issues with reordering by pushing a dummy commit
261+
use_reordering_workaround = False
262+
259263
def all_reviews_iter(self) -> Iterator[Tuple[str, Topic, str, Review]]:
260264
"""
261265
One liner for common iteration pattern to reduce indentation a bit.
@@ -699,6 +703,7 @@ async def mark_rebases(self, skip_rebase: bool) -> None:
699703
changes that are already merged, or where push can be skipped due to being rebases or
700704
being identical.
701705
"""
706+
num_reordered_changes = 0
702707
for _, topic, base_branch, review in self.all_reviews_iter():
703708
# If the relative branch already merged, reset the remote base directly to the base
704709
# branch.
@@ -744,6 +749,19 @@ async def mark_rebases(self, skip_rebase: bool) -> None:
744749
# recreate the pr (but warn anyway).
745750
review.status = PrStatus.NEW
746751

752+
if review.pr_info is not None and review.remote_base != review.pr_info.baseRef:
753+
logging.debug(
754+
f"Retargeting pr {review.remote_head} from {review.pr_info.baseRef}"
755+
f" to {review.remote_base}"
756+
)
757+
num_reordered_changes += 1
758+
if num_reordered_changes > 1:
759+
# The logic for correctly detecting whether changes have been reordered can be
760+
# complex. To simplify, we'll apply the workaround if at least 2 PRs had a base
761+
# change. This is relatively rare, and the only cost is another 1s spent on an
762+
# extra git push operation.
763+
self.use_reordering_workaround = True
764+
747765
if review.pr_info is None:
748766
# This is a new pr, no need to check patch ids
749767
review.is_pure_rebase = False
@@ -1003,7 +1021,23 @@ async def push_git_refs(self, uploader: str, create_local_branches: bool) -> Non
10031021
if review.push_status != PushStatus.PUSHED or review.status == PrStatus.MERGED:
10041022
continue
10051023

1006-
push_targets.append(f"{review.new_commits[-1]}:refs/heads/{review.remote_head}")
1024+
commit_to_push = review.new_commits[-1]
1025+
if self.use_reordering_workaround:
1026+
# When reordering a relative series of PRs, github isn't able to handle the push,
1027+
# which happens via git, atomically with the api update, which happens through
1028+
# http. As a result github always sees the push happen first with the old relative
1029+
# structure. When reordering, a PR that is being moved forward might briefly look
1030+
# like it contains no new commits, causing github to either mark it merged or
1031+
# closed. To prevent this, we add an empty dummy commit onto the branch before
1032+
# pushing, do the update, then push once more to remove the dummy commit. This
1033+
# only happens when we detect that it is needed, so does not add much overhead.
1034+
dummy_commit = git.CommitHeader(
1035+
GitTreeHash(f"{commit_to_push}^{{tree}}"), [commit_to_push]
1036+
)
1037+
dummy_commit.commit_msg = "Revup dummy commit to work around reordering issues"
1038+
commit_to_push = await self.git_ctx.commit_tree(dummy_commit)
1039+
1040+
push_targets.append(f"{commit_to_push}:refs/heads/{review.remote_head}")
10071041

10081042
if create_local_branches:
10091043
await self.git_ctx.git(

revup/upload.py

+5
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ async def main(
110110
# Review graph requires populated PR urls from creation
111111
topics.populate_review_graph()
112112
await topics.update_prs()
113+
114+
if topics.use_reordering_workaround:
115+
topics.use_reordering_workaround = False
116+
with get_console().status("Pushing again to work around reordering issues…"):
117+
await topics.push_git_refs(git_ctx.author, create_local_branches=False)
113118
finally:
114119
topics.print(not args.verbose)
115120
return 0

0 commit comments

Comments
 (0)