Mike Frysinger | f1ba7ad | 2022-09-12 05:42:57 -0400 | [diff] [blame] | 1 | # Copyright 2018 The ChromiumOS Authors |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Create a manifest snapshot of a repo checkout. |
| 6 | |
| 7 | This starts with the output of `repo manifest -r` and updates the manifest to |
| 8 | account for local changes that may not already be available remotely; for any |
| 9 | commits that aren't already reachable from the upstream tracking branch, push |
| 10 | refs to the remotes so that this snapshot can be reproduced remotely. |
| 11 | """ |
| 12 | |
Chris McDonald | 59650c3 | 2021-07-20 15:29:28 -0600 | [diff] [blame] | 13 | import logging |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 14 | import os |
| 15 | import sys |
| 16 | |
| 17 | from chromite.lib import commandline |
| 18 | from chromite.lib import cros_build_lib |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 19 | from chromite.lib import git |
| 20 | from chromite.lib import parallel |
| 21 | from chromite.lib import repo_util |
| 22 | |
| 23 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 24 | BRANCH_REF_PREFIX = "refs/heads/" |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 25 | |
| 26 | |
| 27 | def GetParser(): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 28 | """Creates the argparse parser.""" |
Mike Frysinger | 66ac6e3 | 2023-02-02 09:03:31 -0500 | [diff] [blame^] | 29 | parser = commandline.ArgumentParser(description=__doc__, dryrun=True) |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 30 | parser.add_argument( |
| 31 | "--repo-path", |
| 32 | type="path", |
| 33 | default=".", |
| 34 | help="Path to the repo to snapshot.", |
| 35 | ) |
| 36 | parser.add_argument( |
| 37 | "--snapshot-ref", |
| 38 | help="Remote ref to create for projects whose HEAD is " |
| 39 | "not reachable from its current upstream branch. " |
| 40 | "Projects with multiple checkouts may have a " |
| 41 | "unique suffix appended to this ref.", |
| 42 | ) |
| 43 | parser.add_argument( |
| 44 | "--output-file", |
| 45 | type="path", |
| 46 | help="Path to write the manifest snapshot XML to.", |
| 47 | ) |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 48 | # This is for limiting network traffic to the git remote(s). |
| 49 | parser.add_argument( |
| 50 | "--jobs", |
| 51 | type=int, |
| 52 | default=16, |
| 53 | help="The number of parallel processes to run for " |
| 54 | "git push operations.", |
| 55 | ) |
| 56 | return parser |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 57 | |
| 58 | |
| 59 | def _GetUpstreamBranch(project): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 60 | """Return a best guess at the project's upstream branch name.""" |
| 61 | branch = project.upstream |
| 62 | if branch and branch.startswith(BRANCH_REF_PREFIX): |
| 63 | branch = branch[len(BRANCH_REF_PREFIX) :] |
| 64 | return branch |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 65 | |
| 66 | |
| 67 | def _NeedsSnapshot(repo_root, project): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 68 | """Test if project's revision is reachable from its upstream ref.""" |
| 69 | # Some projects don't have an upstream set. Try 'main' anyway. |
| 70 | branch = _GetUpstreamBranch(project) or "main" |
| 71 | upstream_ref = "refs/remotes/%s/%s" % (project.Remote().GitName(), branch) |
| 72 | project_path = os.path.join(repo_root, project.Path()) |
| 73 | try: |
| 74 | if git.IsReachable(project_path, project.revision, upstream_ref): |
| 75 | return False |
| 76 | except cros_build_lib.RunCommandError as e: |
| 77 | logging.debug("Reachability check failed: %s", e) |
| 78 | logging.info( |
| 79 | "Project %s revision %s not reachable from upstream %r.", |
| 80 | project.name, |
| 81 | project.revision, |
| 82 | upstream_ref, |
| 83 | ) |
| 84 | return True |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 85 | |
| 86 | |
| 87 | def _MakeUniqueRef(project, base_ref, used_refs): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 88 | """Return a git ref for project that isn't in used_refs. |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 89 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 90 | Args: |
| 91 | project: The Project object to create a ref for. |
| 92 | base_ref: A base ref name; this may be appended to to generate a unique ref. |
| 93 | used_refs: A set of ref names to uniquify against. It is updated with the |
| 94 | newly generated ref. |
| 95 | """ |
| 96 | ref = base_ref |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 97 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 98 | # If the project upstream is a non-main branch, append it to the ref. |
| 99 | branch = _GetUpstreamBranch(project) |
| 100 | if branch and branch != "main": |
| 101 | ref = "%s/%s" % (ref, branch) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 102 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 103 | if ref in used_refs: |
| 104 | # Append incrementing numbers until we find an unused ref. |
| 105 | for i in range(1, len(used_refs) + 2): |
| 106 | numbered = "%s/%d" % (ref, i) |
| 107 | if numbered not in used_refs: |
| 108 | ref = numbered |
| 109 | break |
| 110 | else: |
| 111 | raise AssertionError( |
| 112 | "failed to make unique ref (ref=%s used_refs=%r)" |
| 113 | % (ref, used_refs) |
| 114 | ) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 115 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 116 | used_refs.add(ref) |
| 117 | return ref |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 118 | |
| 119 | |
Mike Frysinger | 66ac6e3 | 2023-02-02 09:03:31 -0500 | [diff] [blame^] | 120 | def _GitPushProjectUpstream(repo_root, project, dryrun): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 121 | """Push the project revision to its remote upstream.""" |
| 122 | git.GitPush( |
| 123 | os.path.join(repo_root, project.Path()), |
| 124 | project.revision, |
| 125 | git.RemoteRef(project.Remote().GitName(), project.upstream), |
Mike Frysinger | 66ac6e3 | 2023-02-02 09:03:31 -0500 | [diff] [blame^] | 126 | dry_run=dryrun, |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 127 | ) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 128 | |
| 129 | |
| 130 | def main(argv): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 131 | parser = GetParser() |
| 132 | options = parser.parse_args(argv) |
| 133 | options.Freeze() |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 134 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 135 | snapshot_ref = options.snapshot_ref |
| 136 | if snapshot_ref and not snapshot_ref.startswith("refs/"): |
| 137 | snapshot_ref = BRANCH_REF_PREFIX + snapshot_ref |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 138 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 139 | repo = repo_util.Repository.Find(options.repo_path) |
| 140 | if repo is None: |
| 141 | cros_build_lib.Die( |
| 142 | "No repo found in --repo_path %r.", options.repo_path |
| 143 | ) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 144 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 145 | manifest = repo.Manifest(revision_locked=True) |
| 146 | projects = list(manifest.Projects()) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 147 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 148 | # Check if projects need snapshots (in parallel). |
| 149 | needs_snapshot_results = parallel.RunTasksInProcessPool( |
| 150 | _NeedsSnapshot, [(repo.root, x) for x in projects] |
| 151 | ) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 152 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 153 | # Group snapshot-needing projects by project name. |
| 154 | snapshot_projects = {} |
| 155 | for project, needs_snapshot in zip(projects, needs_snapshot_results): |
| 156 | if needs_snapshot: |
| 157 | snapshot_projects.setdefault(project.name, []).append(project) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 158 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 159 | if snapshot_projects and not snapshot_ref: |
| 160 | cros_build_lib.Die( |
| 161 | "Some project(s) need snapshot refs but no " |
| 162 | "--snapshot-ref specified." |
| 163 | ) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 164 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 165 | # Push snapshot refs (in parallel). |
| 166 | with parallel.BackgroundTaskRunner( |
| 167 | _GitPushProjectUpstream, |
| 168 | repo.root, |
Mike Frysinger | 66ac6e3 | 2023-02-02 09:03:31 -0500 | [diff] [blame^] | 169 | dryrun=options.dryrun, |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 170 | processes=options.jobs, |
| 171 | ) as queue: |
| 172 | for projects in snapshot_projects.values(): |
| 173 | # Since some projects (e.g. chromiumos/third_party/kernel) are checked out |
| 174 | # multiple places, we may need to push each checkout to a unique ref. |
| 175 | need_unique_refs = len(projects) > 1 |
| 176 | used_refs = set() |
| 177 | for project in projects: |
| 178 | if need_unique_refs: |
| 179 | ref = _MakeUniqueRef(project, snapshot_ref, used_refs) |
| 180 | else: |
| 181 | ref = snapshot_ref |
| 182 | # Update the upstream ref both for the push and the output XML. |
| 183 | project.upstream = ref |
| 184 | queue.put([project]) |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 185 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 186 | dest = options.output_file |
| 187 | if dest is None or dest == "-": |
| 188 | dest = sys.stdout |
Lann Martin | 4fbdf20 | 2018-08-30 12:02:52 -0600 | [diff] [blame] | 189 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 190 | manifest.Write(dest) |