Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions utils/update_checkout/tests/test_cross_repo_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ def setUp(self):
"swift": "main",
},
},
# Pins repo1 to a tag rather than a branch.
"tagged": {
"aliases": ["tagged"],
"repos": {
"repo1": "1.0.0",
"repo2": "main",
"swift": "main",
},
},
},
}

Expand Down Expand Up @@ -505,3 +514,43 @@ def test_no_branch_ambiguity_in_staleness_check(self):
self.verify_head_for_stale_pr_merge_ref(
repo_name=repo_name, rev_scheme_name=rev_scheme, pr_id=pr_id
)

def check_pr_for_repo_pinned_to_tag(self, *, stale: bool):
repo_name = "repo1"
pr_id = 1
repo_path = os.path.join(self.local_path, repo_name)

# The scheme pins repo1 to a tag, while the PR itself targets main.
tag = self.get_branch(rev_scheme_name="tagged", repo_name=repo_name)
self.call(["git", "tag", tag, "main"], cwd=repo_path)
self.call(["git", "push", "origin", tag], cwd=repo_path)
self.set_up_pr_merge_ref(
repo_name=repo_name, rev_scheme_name="main", pr_id=pr_id, stale=stale
)

self.call(
self.update_checkout_base_args
+ [
"--clone",
"--scheme",
"tagged",
"--github-comment",
f"""
https://github.com/apple/{repo_name}/pull/{pr_id}
@swift-ci please test
""",
]
)
# There is no base branch to check staleness against or to merge, so
# the PR merge ref is used as-is, even if its actual base branch has
# since moved on.
self.verify_head_for_up_to_date_pr_merge_ref(repo_name=repo_name, pr_id=pr_id)

# A scheme can pin a repository to a tag instead of a branch. A PR for such a
# repository should still be checked out, rather than failing because the
# tag is not a branch on the remote.
def test_checkout_pr_merge_ref_for_repo_pinned_to_tag(self):
self.check_pr_for_repo_pinned_to_tag(stale=False)

def test_checkout_stale_pr_merge_ref_for_repo_pinned_to_tag(self):
self.check_pr_for_repo_pinned_to_tag(stale=True)
62 changes: 43 additions & 19 deletions utils/update_checkout/update_checkout/update_checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,39 @@ def get_pr_branch(
prefix=prefix,
)

def get_remote_base_branch_object_id() -> Optional[str]:
"""Return the object ID of the remote tip of the PR base branch, or
`None` if the base branch is not a branch on the remote. That happens
when the scheme pins the repository to a tag or a commit rather than a
branch."""
try:
remote_base_branch_object_id_and_ref, _, _ = Git.run(
repo_path,
[
"ls-remote",
# This command should fail if no matching refs are found.
"--exit-code",
"--heads",
"origin",
# Important for disambiguation. Just 'base_branch' matches
# both 'base_branch' and '*/base_branch'.
f"refs/heads/{base_branch}",
],
echo=True,
prefix=prefix,
)
except GitException as e:
# Exit code 2 means that no matching refs were found.
if e.returncode == 2:
return None
raise # Pass the error up the chain.

remote_base_branch_object_id, _ = remote_base_branch_object_id_and_ref.split()
return remote_base_branch_object_id

# A PR merge ref is up to date if its first parent matches the remote tip
# of the PR base branch.
def is_pr_merge_ref_up_to_date():
def is_pr_merge_ref_up_to_date(remote_base_branch_object_id: str):
# The PR merge ref's first parent is the base branch tip at the time
# GitHub last computed the ref. If that still matches the current remote
# base branch tip, the merge ref is up to date and we can skip the
Expand All @@ -153,28 +183,22 @@ def is_pr_merge_ref_up_to_date():
echo=True,
prefix=prefix,
)
remote_base_branch_object_id_and_ref, _, _ = Git.run(
repo_path,
[
"ls-remote",
# This command should fail if no matching refs are found.
"--exit-code",
"--heads",
"origin",
# Important for disambiguation. Just 'base_branch' matches
# both 'base_branch' and '*/base_branch'.
f"refs/heads/{base_branch}",
],
echo=True,
prefix=prefix,
)

remote_base_branch_object_id, _ = remote_base_branch_object_id_and_ref.split()

return base_parent_object_id == remote_base_branch_object_id

# 3. Check whether the merge ref is stale. If up to date, that's it.
if is_pr_merge_ref_up_to_date():
remote_base_branch_object_id = get_remote_base_branch_object_id()
if remote_base_branch_object_id is None:
# The scheme pins this repository to a tag or a commit, which says
# nothing about the branch the PR targets, so there is no base branch to
# check staleness against or to merge. Use the PR merge ref as GitHub
# computed it against the PR's actual base branch.
print(
f"{prefix}'{base_branch}' is not a branch on the remote; using the "
f"PR merge ref as-is"
)
return pr_branch
if is_pr_merge_ref_up_to_date(remote_base_branch_object_id):
return pr_branch

# The PR merge ref is out of date. This path exists because GitHub is not
Expand Down