Skip to content

Commit 148c861

Browse files
authored
Merge pull request #62004 from apple/maxd/fix-checkout-cross-repo
update_checkout: re-read config for cross-repo testing When updating versions of some dependencies, it's important to be able to re-read the checkout config to get the new versions to actually check them out. This not working prevents us from making CI green in cross-repo PRs that update these dependencies. I also used this as an opportunity to refactor out some common code and add Python docstrings to some functions for easier maintenance.
2 parents 0f47da7 + 9d29995 commit 148c861

File tree

1 file changed

+113
-25
lines changed

1 file changed

+113
-25
lines changed

utils/update_checkout/update_checkout/update_checkout.py

Lines changed: 113 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ def check_parallel_results(results, op):
7575

7676

7777
def confirm_tag_in_repo(tag, repo_name):
78+
# type: (str, str) -> str | None
79+
"""Confirm that a given tag exists in a git repository. This function
80+
assumes that the repository is already a current working directory before
81+
it's called.
82+
83+
Args:
84+
tag (str): tag to look up in the repository
85+
repo_name (str): name the repository for the look up, used for logging
86+
87+
Returns:
88+
str | None: returns `tag` argument value or `None` if the tag doesn't
89+
exist.
90+
"""
91+
7892
tag_exists = shell.capture(['git', 'ls-remote', '--tags',
7993
'origin', tag], echo=False)
8094
if not tag_exists:
@@ -97,6 +111,21 @@ def find_rev_by_timestamp(timestamp, repo_name, refspec):
97111

98112
def get_branch_for_repo(config, repo_name, scheme_name, scheme_map,
99113
cross_repos_pr):
114+
"""Infer, fetch, and return a branch corresponding to a given PR, otherwise
115+
return a branch found in the config for this repository name.
116+
117+
Args:
118+
config (Dict[str, Any]): deserialized `update-checkout-config.json`
119+
repo_name (str): name of the repository for checking out the branch
120+
scheme_name (str): name of the scheme to look up in the config
121+
scheme_map (Dict[str, str]): map of repo names to branches to check out
122+
cross_repos_pr (Dict[str, str]): map of repo ids to PRs to check out
123+
124+
Returns:
125+
Tuple[str, bool]: a pair of a checked out branch and a boolean
126+
indicating whether this repo matched any `cross_repos_pr`.
127+
"""
128+
100129
cross_repo = False
101130
repo_branch = scheme_name
102131
if scheme_map:
@@ -240,28 +269,54 @@ def update_single_repository(pool_args):
240269
return value
241270

242271

243-
def get_timestamp_to_match(args):
244-
if not args.match_timestamp:
272+
def get_timestamp_to_match(match_timestamp, source_root):
273+
# type: (str | None, str) -> str | None
274+
"""Computes a timestamp of the last commit on the current branch in
275+
the `swift` repository.
276+
277+
Args:
278+
match_timestamp (str | None): value of `--match-timestamp` to check.
279+
source_root (str): directory that contains sources of the Swift project.
280+
281+
Returns:
282+
str | None: a timestamp of the last commit of `swift` repository if
283+
`match_timestamp` argument has a value, `None` if `match_timestamp` is
284+
falsy.
285+
"""
286+
if not match_timestamp:
245287
return None
246-
with shell.pushd(os.path.join(args.source_root, "swift"),
288+
with shell.pushd(os.path.join(source_root, "swift"),
247289
dry_run=False, echo=False):
248290
return shell.capture(["git", "log", "-1", "--format=%cI"],
249291
echo=False).strip()
250292

251293

252-
def update_all_repositories(args, config, scheme_name, cross_repos_pr):
253-
scheme_map = None
294+
def get_scheme_map(config, scheme_name):
295+
"""Find a mapping from repository IDs to branches in the config.
296+
297+
Args:
298+
config (Dict[str, Any]): deserialized `update-checkout-config.json`
299+
scheme_name (str): name of the scheme to look up in `config`
300+
301+
Returns:
302+
Dict[str, str]: a mapping from repos to branches for the given scheme.
303+
"""
304+
254305
if scheme_name:
255306
# This loop is only correct, since we know that each alias set has
256307
# unique contents. This is checked by validate_config. Thus the first
257308
# branch scheme data that has scheme_name as one of its aliases is
258309
# the only possible correct answer.
259310
for v in config['branch-schemes'].values():
260311
if scheme_name in v['aliases']:
261-
scheme_map = v['repos']
262-
break
312+
return v['repos']
313+
314+
return None
315+
316+
317+
def update_all_repositories(args, config, scheme_name, scheme_map, cross_repos_pr):
263318
pool_args = []
264-
timestamp = get_timestamp_to_match(args)
319+
timestamp = get_timestamp_to_match(args.match_timestamp, args.source_root)
265320
for repo_name in config['repos'].keys():
266321
if repo_name in args.skip_repository_list:
267322
print("Skipping update of '" + repo_name + "', requested by user")
@@ -471,6 +526,18 @@ def full_target_name(repository, target):
471526

472527

473528
def skip_list_for_platform(config, all_repos):
529+
"""Computes a list of repositories to skip when updating or cloning, if not
530+
overriden by `--all-repositories` CLI argument.
531+
532+
Args:
533+
config (Dict[str, Any]): deserialized `update-checkout-config.json`
534+
all_repos (List[str]): repositories not required for current platform.
535+
536+
Returns:
537+
List[str]: a resulting list of repositories to skip or empty list if
538+
`all_repos` is not empty.
539+
"""
540+
474541
if all_repos:
475542
return [] # Do not skip any platform-specific repositories
476543

@@ -598,22 +665,14 @@ def main():
598665
clone_with_ssh = args.clone_with_ssh
599666
skip_history = args.skip_history
600667
skip_tags = args.skip_tags
601-
scheme = args.scheme
668+
scheme_name = args.scheme
602669
github_comment = args.github_comment
603670
all_repos = args.all_repositories
604671

605672
with open(args.config) as f:
606673
config = json.load(f)
607674
validate_config(config)
608675

609-
if args.dump_hashes:
610-
dump_repo_hashes(args, config)
611-
return (None, None)
612-
613-
if args.dump_hashes_config:
614-
dump_repo_hashes(args, config, args.dump_hashes_config)
615-
return (None, None)
616-
617676
cross_repos_pr = {}
618677
if github_comment:
619678
regex_pr = r'(apple/[-a-zA-Z0-9_]+/pull/\d+|apple/[-a-zA-Z0-9_]+#\d+)'
@@ -622,22 +681,51 @@ def main():
622681
repos_with_pr = [pr.replace('/pull/', '#') for pr in repos_with_pr]
623682
cross_repos_pr = dict(pr.split('#') for pr in repos_with_pr)
624683

684+
# If branch is None, default to using the default branch alias
685+
# specified by our configuration file.
686+
if scheme_name is None:
687+
scheme_name = config['default-branch-scheme']
688+
689+
scheme_map = get_scheme_map(config, scheme_name)
690+
625691
clone_results = None
692+
skip_repo_list = []
626693
if clone or clone_with_ssh:
627-
# If branch is None, default to using the default branch alias
628-
# specified by our configuration file.
629-
if scheme is None:
630-
scheme = config['default-branch-scheme']
631-
632694
skip_repo_list = skip_list_for_platform(config, all_repos)
633695
skip_repo_list.extend(args.skip_repository_list)
634696
clone_results = obtain_all_additional_swift_sources(args, config,
635697
clone_with_ssh,
636-
scheme,
698+
scheme_name,
637699
skip_history,
638700
skip_tags,
639701
skip_repo_list)
640702

703+
swift_repo_path = os.path.join(args.source_root, 'swift')
704+
if 'swift' not in skip_repo_list and os.path.exists(swift_repo_path):
705+
with shell.pushd(swift_repo_path, dry_run=False, echo=True):
706+
# Check if `swift` repo itself needs to switch to a cross-repo branch.
707+
branch_name, cross_repo = get_branch_for_repo(config, 'swift',
708+
scheme_name,
709+
scheme_map,
710+
cross_repos_pr)
711+
712+
if cross_repo:
713+
shell.run(['git', 'checkout', branch_name], echo=True,
714+
prefix="[swift] ")
715+
716+
# Re-read the config after checkout.
717+
with open(args.config) as f:
718+
config = json.load(f)
719+
validate_config(config)
720+
721+
if args.dump_hashes:
722+
dump_repo_hashes(args, config)
723+
return (None, None)
724+
725+
if args.dump_hashes_config:
726+
dump_repo_hashes(args, config, args.dump_hashes_config)
727+
return (None, None)
728+
641729
# Quick check whether somebody is calling update in an empty directory
642730
directory_contents = os.listdir(args.source_root)
643731
if not ('cmark' in directory_contents or
@@ -646,8 +734,8 @@ def main():
646734
print("You don't have all swift sources. "
647735
"Call this script with --clone to get them.")
648736

649-
update_results = update_all_repositories(args, config, scheme,
650-
cross_repos_pr)
737+
update_results = update_all_repositories(args, config, scheme_name,
738+
scheme_map, cross_repos_pr)
651739
fail_count = 0
652740
fail_count += check_parallel_results(clone_results, "CLONE")
653741
fail_count += check_parallel_results(update_results, "UPDATE")

0 commit comments

Comments
 (0)