diff options
author | LaMont Jones <lamontjones@google.com> | 2021-11-18 22:40:18 +0000 |
---|---|---|
committer | LaMont Jones <lamontjones@google.com> | 2022-02-17 21:57:55 +0000 |
commit | cc879a97c3e2614d19b15b4661c3cab4d33139c9 (patch) | |
tree | 69d225e9f0e9d79fec8f423d9c40c275f0bf3b8c | |
parent | 87cce68b28c34fa86895baa8d7f48307382e6c75 (diff) | |
download | git-repo-cc879a97c3e2614d19b15b4661c3cab4d33139c9.tar.gz |
Add multi-manifest support with <submanifest> elementv2.22
To be addressed in another change:
- a partial `repo sync` (with a list of projects/paths to sync)
requires `--this-tree-only`.
Change-Id: I6c7400bf001540e9d7694fa70934f8f204cb5f57
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322657
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
-rw-r--r-- | command.py | 84 | ||||
-rw-r--r-- | docs/internal-fs-layout.md | 4 | ||||
-rw-r--r-- | docs/manifest-format.md | 66 | ||||
-rw-r--r-- | git_superproject.py | 3 | ||||
-rwxr-xr-x | main.py | 43 | ||||
-rw-r--r-- | manifest_xml.py | 454 | ||||
-rw-r--r-- | project.py | 41 | ||||
-rw-r--r-- | subcmds/abandon.py | 7 | ||||
-rw-r--r-- | subcmds/branches.py | 14 | ||||
-rw-r--r-- | subcmds/checkout.py | 2 | ||||
-rw-r--r-- | subcmds/diff.py | 2 | ||||
-rw-r--r-- | subcmds/diffmanifests.py | 3 | ||||
-rw-r--r-- | subcmds/download.py | 10 | ||||
-rw-r--r-- | subcmds/forall.py | 12 | ||||
-rw-r--r-- | subcmds/gitc_init.py | 1 | ||||
-rw-r--r-- | subcmds/grep.py | 15 | ||||
-rw-r--r-- | subcmds/info.py | 16 | ||||
-rw-r--r-- | subcmds/init.py | 12 | ||||
-rw-r--r-- | subcmds/list.py | 7 | ||||
-rw-r--r-- | subcmds/manifest.py | 72 | ||||
-rw-r--r-- | subcmds/overview.py | 4 | ||||
-rw-r--r-- | subcmds/prune.py | 4 | ||||
-rw-r--r-- | subcmds/rebase.py | 9 | ||||
-rw-r--r-- | subcmds/stage.py | 11 | ||||
-rw-r--r-- | subcmds/start.py | 5 | ||||
-rw-r--r-- | subcmds/status.py | 9 | ||||
-rw-r--r-- | subcmds/sync.py | 8 | ||||
-rw-r--r-- | subcmds/upload.py | 44 |
28 files changed, 797 insertions, 165 deletions
@@ -61,13 +61,21 @@ class Command(object): | |||
61 | # it is the number of parallel jobs to default to. | 61 | # it is the number of parallel jobs to default to. |
62 | PARALLEL_JOBS = None | 62 | PARALLEL_JOBS = None |
63 | 63 | ||
64 | # Whether this command supports Multi-manifest. If False, then main.py will | ||
65 | # iterate over the manifests and invoke the command once per (sub)manifest. | ||
66 | # This is only checked after calling ValidateOptions, so that partially | ||
67 | # migrated subcommands can set it to False. | ||
68 | MULTI_MANIFEST_SUPPORT = True | ||
69 | |||
64 | def __init__(self, repodir=None, client=None, manifest=None, gitc_manifest=None, | 70 | def __init__(self, repodir=None, client=None, manifest=None, gitc_manifest=None, |
65 | git_event_log=None): | 71 | git_event_log=None, outer_client=None, outer_manifest=None): |
66 | self.repodir = repodir | 72 | self.repodir = repodir |
67 | self.client = client | 73 | self.client = client |
74 | self.outer_client = outer_client or client | ||
68 | self.manifest = manifest | 75 | self.manifest = manifest |
69 | self.gitc_manifest = gitc_manifest | 76 | self.gitc_manifest = gitc_manifest |
70 | self.git_event_log = git_event_log | 77 | self.git_event_log = git_event_log |
78 | self.outer_manifest = outer_manifest | ||
71 | 79 | ||
72 | # Cache for the OptionParser property. | 80 | # Cache for the OptionParser property. |
73 | self._optparse = None | 81 | self._optparse = None |
@@ -135,6 +143,18 @@ class Command(object): | |||
135 | type=int, default=self.PARALLEL_JOBS, | 143 | type=int, default=self.PARALLEL_JOBS, |
136 | help=f'number of jobs to run in parallel (default: {default})') | 144 | help=f'number of jobs to run in parallel (default: {default})') |
137 | 145 | ||
146 | m = p.add_option_group('Multi-manifest options') | ||
147 | m.add_option('--outer-manifest', action='store_true', | ||
148 | help='operate starting at the outermost manifest') | ||
149 | m.add_option('--no-outer-manifest', dest='outer_manifest', | ||
150 | action='store_false', default=None, | ||
151 | help='do not operate on outer manifests') | ||
152 | m.add_option('--this-manifest-only', action='store_true', default=None, | ||
153 | help='only operate on this (sub)manifest') | ||
154 | m.add_option('--no-this-manifest-only', '--all-manifests', | ||
155 | dest='this_manifest_only', action='store_false', | ||
156 | help='operate on this manifest and its submanifests') | ||
157 | |||
138 | def _Options(self, p): | 158 | def _Options(self, p): |
139 | """Initialize the option parser with subcommand-specific options.""" | 159 | """Initialize the option parser with subcommand-specific options.""" |
140 | 160 | ||
@@ -252,16 +272,19 @@ class Command(object): | |||
252 | return project | 272 | return project |
253 | 273 | ||
254 | def GetProjects(self, args, manifest=None, groups='', missing_ok=False, | 274 | def GetProjects(self, args, manifest=None, groups='', missing_ok=False, |
255 | submodules_ok=False): | 275 | submodules_ok=False, all_manifests=False): |
256 | """A list of projects that match the arguments. | 276 | """A list of projects that match the arguments. |
257 | """ | 277 | """ |
258 | if not manifest: | 278 | if all_manifests: |
259 | manifest = self.manifest | 279 | if not manifest: |
260 | all_projects_list = manifest.projects | 280 | manifest = self.manifest.outer_client |
281 | all_projects_list = manifest.all_projects | ||
282 | else: | ||
283 | if not manifest: | ||
284 | manifest = self.manifest | ||
285 | all_projects_list = manifest.projects | ||
261 | result = [] | 286 | result = [] |
262 | 287 | ||
263 | mp = manifest.manifestProject | ||
264 | |||
265 | if not groups: | 288 | if not groups: |
266 | groups = manifest.GetGroupsStr() | 289 | groups = manifest.GetGroupsStr() |
267 | groups = [x for x in re.split(r'[,\s]+', groups) if x] | 290 | groups = [x for x in re.split(r'[,\s]+', groups) if x] |
@@ -282,12 +305,19 @@ class Command(object): | |||
282 | for arg in args: | 305 | for arg in args: |
283 | # We have to filter by manifest groups in case the requested project is | 306 | # We have to filter by manifest groups in case the requested project is |
284 | # checked out multiple times or differently based on them. | 307 | # checked out multiple times or differently based on them. |
285 | projects = [project for project in manifest.GetProjectsWithName(arg) | 308 | projects = [project for project in manifest.GetProjectsWithName( |
309 | arg, all_manifests=all_manifests) | ||
286 | if project.MatchesGroups(groups)] | 310 | if project.MatchesGroups(groups)] |
287 | 311 | ||
288 | if not projects: | 312 | if not projects: |
289 | path = os.path.abspath(arg).replace('\\', '/') | 313 | path = os.path.abspath(arg).replace('\\', '/') |
290 | project = self._GetProjectByPath(manifest, path) | 314 | tree = manifest |
315 | if all_manifests: | ||
316 | # Look for the deepest matching submanifest. | ||
317 | for tree in reversed(list(manifest.all_manifests)): | ||
318 | if path.startswith(tree.topdir): | ||
319 | break | ||
320 | project = self._GetProjectByPath(tree, path) | ||
291 | 321 | ||
292 | # If it's not a derived project, update path->project mapping and | 322 | # If it's not a derived project, update path->project mapping and |
293 | # search again, as arg might actually point to a derived subproject. | 323 | # search again, as arg might actually point to a derived subproject. |
@@ -308,7 +338,8 @@ class Command(object): | |||
308 | 338 | ||
309 | for project in projects: | 339 | for project in projects: |
310 | if not missing_ok and not project.Exists: | 340 | if not missing_ok and not project.Exists: |
311 | raise NoSuchProjectError('%s (%s)' % (arg, project.relpath)) | 341 | raise NoSuchProjectError('%s (%s)' % ( |
342 | arg, project.RelPath(local=not all_manifests))) | ||
312 | if not project.MatchesGroups(groups): | 343 | if not project.MatchesGroups(groups): |
313 | raise InvalidProjectGroupsError(arg) | 344 | raise InvalidProjectGroupsError(arg) |
314 | 345 | ||
@@ -319,12 +350,22 @@ class Command(object): | |||
319 | result.sort(key=_getpath) | 350 | result.sort(key=_getpath) |
320 | return result | 351 | return result |
321 | 352 | ||
322 | def FindProjects(self, args, inverse=False): | 353 | def FindProjects(self, args, inverse=False, all_manifests=False): |
354 | """Find projects from command line arguments. | ||
355 | |||
356 | Args: | ||
357 | args: a list of (case-insensitive) strings, projects to search for. | ||
358 | inverse: a boolean, if True, then projects not matching any |args| are | ||
359 | returned. | ||
360 | all_manifests: a boolean, if True then all manifests and submanifests are | ||
361 | used. If False, then only the local (sub)manifest is used. | ||
362 | """ | ||
323 | result = [] | 363 | result = [] |
324 | patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args] | 364 | patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args] |
325 | for project in self.GetProjects(''): | 365 | for project in self.GetProjects('', all_manifests=all_manifests): |
366 | paths = [project.name, project.RelPath(local=not all_manifests)] | ||
326 | for pattern in patterns: | 367 | for pattern in patterns: |
327 | match = pattern.search(project.name) or pattern.search(project.relpath) | 368 | match = any(pattern.search(x) for x in paths) |
328 | if not inverse and match: | 369 | if not inverse and match: |
329 | result.append(project) | 370 | result.append(project) |
330 | break | 371 | break |
@@ -333,9 +374,24 @@ class Command(object): | |||
333 | else: | 374 | else: |
334 | if inverse: | 375 | if inverse: |
335 | result.append(project) | 376 | result.append(project) |
336 | result.sort(key=lambda project: project.relpath) | 377 | result.sort(key=lambda project: (project.manifest.path_prefix, |
378 | project.relpath)) | ||
337 | return result | 379 | return result |
338 | 380 | ||
381 | def ManifestList(self, opt): | ||
382 | """Yields all of the manifests to traverse. | ||
383 | |||
384 | Args: | ||
385 | opt: The command options. | ||
386 | """ | ||
387 | top = self.outer_manifest | ||
388 | if opt.outer_manifest is False or opt.this_manifest_only: | ||
389 | top = self.manifest | ||
390 | yield top | ||
391 | if not opt.this_manifest_only: | ||
392 | for child in top.all_children: | ||
393 | yield child | ||
394 | |||
339 | 395 | ||
340 | class InteractiveCommand(Command): | 396 | class InteractiveCommand(Command): |
341 | """Command which requires user interaction on the tty and | 397 | """Command which requires user interaction on the tty and |
diff --git a/docs/internal-fs-layout.md b/docs/internal-fs-layout.md index 0e830510..a9bd1d26 100644 --- a/docs/internal-fs-layout.md +++ b/docs/internal-fs-layout.md | |||
@@ -50,6 +50,10 @@ For example, if you want to change the manifest branch, you can simply run | |||
50 | For more documentation on the manifest format, including the local_manifests | 50 | For more documentation on the manifest format, including the local_manifests |
51 | support, see the [manifest-format.md] file. | 51 | support, see the [manifest-format.md] file. |
52 | 52 | ||
53 | * `submanifests/{submanifest.path}/`: The path prefix to the manifest state of | ||
54 | a submanifest included in a multi-manifest checkout. The outermost manifest | ||
55 | manifest state is found adjacent to `submanifests/`. | ||
56 | |||
53 | * `manifests/`: A git checkout of the manifest project. Its `.git/` state | 57 | * `manifests/`: A git checkout of the manifest project. Its `.git/` state |
54 | points to the `manifest.git` bare checkout (see below). It tracks the git | 58 | points to the `manifest.git` bare checkout (see below). It tracks the git |
55 | branch specified at `repo init` time via `--manifest-branch`. | 59 | branch specified at `repo init` time via `--manifest-branch`. |
diff --git a/docs/manifest-format.md b/docs/manifest-format.md index 8e0049b3..7c0a7da9 100644 --- a/docs/manifest-format.md +++ b/docs/manifest-format.md | |||
@@ -26,6 +26,7 @@ following DTD: | |||
26 | remote*, | 26 | remote*, |
27 | default?, | 27 | default?, |
28 | manifest-server?, | 28 | manifest-server?, |
29 | submanifest*?, | ||
29 | remove-project*, | 30 | remove-project*, |
30 | project*, | 31 | project*, |
31 | extend-project*, | 32 | extend-project*, |
@@ -57,6 +58,15 @@ following DTD: | |||
57 | <!ELEMENT manifest-server EMPTY> | 58 | <!ELEMENT manifest-server EMPTY> |
58 | <!ATTLIST manifest-server url CDATA #REQUIRED> | 59 | <!ATTLIST manifest-server url CDATA #REQUIRED> |
59 | 60 | ||
61 | <!ELEMENT submanifest EMPTY> | ||
62 | <!ATTLIST submanifest name ID #REQUIRED> | ||
63 | <!ATTLIST submanifest remote IDREF #IMPLIED> | ||
64 | <!ATTLIST submanifest project CDATA #IMPLIED> | ||
65 | <!ATTLIST submanifest manifest-name CDATA #IMPLIED> | ||
66 | <!ATTLIST submanifest revision CDATA #IMPLIED> | ||
67 | <!ATTLIST submanifest path CDATA #IMPLIED> | ||
68 | <!ATTLIST submanifest groups CDATA #IMPLIED> | ||
69 | |||
60 | <!ELEMENT project (annotation*, | 70 | <!ELEMENT project (annotation*, |
61 | project*, | 71 | project*, |
62 | copyfile*, | 72 | copyfile*, |
@@ -236,6 +246,60 @@ the specified tag. This is used by repo sync when the --smart-tag option | |||
236 | is given. | 246 | is given. |
237 | 247 | ||
238 | 248 | ||
249 | ### Element submanifest | ||
250 | |||
251 | One or more submanifest elements may be specified. Each element describes a | ||
252 | single manifest to be checked out as a child. | ||
253 | |||
254 | Attribute `name`: A unique name (within the current (sub)manifest) for this | ||
255 | submanifest. It acts as a default for `revision` below. The same name can be | ||
256 | used for submanifests with different parent (sub)manifests. | ||
257 | |||
258 | Attribute `remote`: Name of a previously defined remote element. | ||
259 | If not supplied the remote given by the default element is used. | ||
260 | |||
261 | Attribute `project`: The manifest project name. The project's name is appended | ||
262 | onto its remote's fetch URL to generate the actual URL to configure the Git | ||
263 | remote with. The URL gets formed as: | ||
264 | |||
265 | ${remote_fetch}/${project_name}.git | ||
266 | |||
267 | where ${remote_fetch} is the remote's fetch attribute and | ||
268 | ${project_name} is the project's name attribute. The suffix ".git" | ||
269 | is always appended as repo assumes the upstream is a forest of | ||
270 | bare Git repositories. If the project has a parent element, its | ||
271 | name will be prefixed by the parent's. | ||
272 | |||
273 | The project name must match the name Gerrit knows, if Gerrit is | ||
274 | being used for code reviews. | ||
275 | |||
276 | `project` must not be empty, and may not be an absolute path or use "." or ".." | ||
277 | path components. It is always interpreted relative to the remote's fetch | ||
278 | settings, so if a different base path is needed, declare a different remote | ||
279 | with the new settings needed. | ||
280 | |||
281 | If not supplied the remote and project for this manifest will be used: `remote` | ||
282 | cannot be supplied. | ||
283 | |||
284 | Attribute `manifest-name`: The manifest filename in the manifest project. If | ||
285 | not supplied, `default.xml` is used. | ||
286 | |||
287 | Attribute `revision`: Name of a Git branch (e.g. "main" or "refs/heads/main"), | ||
288 | tag (e.g. "refs/tags/stable"), or a commit hash. If not supplied, `name` is | ||
289 | used. | ||
290 | |||
291 | Attribute `path`: An optional path relative to the top directory | ||
292 | of the repo client where the submanifest repo client top directory | ||
293 | should be placed. If not supplied, `revision` is used. | ||
294 | |||
295 | `path` may not be an absolute path or use "." or ".." path components. | ||
296 | |||
297 | Attribute `groups`: List of additional groups to which all projects | ||
298 | in the included submanifest belong. This appends and recurses, meaning | ||
299 | all projects in submanifests carry all parent submanifest groups. | ||
300 | Same syntax as the corresponding element of `project`. | ||
301 | |||
302 | |||
239 | ### Element project | 303 | ### Element project |
240 | 304 | ||
241 | One or more project elements may be specified. Each element | 305 | One or more project elements may be specified. Each element |
@@ -471,7 +535,7 @@ These restrictions are not enforced for [Local Manifests]. | |||
471 | 535 | ||
472 | Attribute `groups`: List of additional groups to which all projects | 536 | Attribute `groups`: List of additional groups to which all projects |
473 | in the included manifest belong. This appends and recurses, meaning | 537 | in the included manifest belong. This appends and recurses, meaning |
474 | all projects in sub-manifests carry all parent include groups. | 538 | all projects in included manifests carry all parent include groups. |
475 | Same syntax as the corresponding element of `project`. | 539 | Same syntax as the corresponding element of `project`. |
476 | 540 | ||
477 | ## Local Manifests {#local-manifests} | 541 | ## Local Manifests {#local-manifests} |
diff --git a/git_superproject.py b/git_superproject.py index 237e57e1..299d2537 100644 --- a/git_superproject.py +++ b/git_superproject.py | |||
@@ -92,7 +92,8 @@ class Superproject(object): | |||
92 | self._branch = manifest.branch | 92 | self._branch = manifest.branch |
93 | self._repodir = os.path.abspath(repodir) | 93 | self._repodir = os.path.abspath(repodir) |
94 | self._superproject_dir = superproject_dir | 94 | self._superproject_dir = superproject_dir |
95 | self._superproject_path = os.path.join(self._repodir, superproject_dir) | 95 | self._superproject_path = manifest.SubmanifestInfoDir(manifest.path_prefix, |
96 | superproject_dir) | ||
96 | self._manifest_path = os.path.join(self._superproject_path, | 97 | self._manifest_path = os.path.join(self._superproject_path, |
97 | _SUPERPROJECT_MANIFEST_NAME) | 98 | _SUPERPROJECT_MANIFEST_NAME) |
98 | git_name = '' | 99 | git_name = '' |
@@ -127,6 +127,8 @@ global_options.add_option('--event-log', | |||
127 | help='filename of event log to append timeline to') | 127 | help='filename of event log to append timeline to') |
128 | global_options.add_option('--git-trace2-event-log', action='store', | 128 | global_options.add_option('--git-trace2-event-log', action='store', |
129 | help='directory to write git trace2 event log to') | 129 | help='directory to write git trace2 event log to') |
130 | global_options.add_option('--submanifest-path', action='store', | ||
131 | metavar='REL_PATH', help='submanifest path') | ||
130 | 132 | ||
131 | 133 | ||
132 | class _Repo(object): | 134 | class _Repo(object): |
@@ -217,7 +219,12 @@ class _Repo(object): | |||
217 | SetDefaultColoring(gopts.color) | 219 | SetDefaultColoring(gopts.color) |
218 | 220 | ||
219 | git_trace2_event_log = EventLog() | 221 | git_trace2_event_log = EventLog() |
220 | repo_client = RepoClient(self.repodir) | 222 | outer_client = RepoClient(self.repodir) |
223 | repo_client = outer_client | ||
224 | if gopts.submanifest_path: | ||
225 | repo_client = RepoClient(self.repodir, | ||
226 | submanifest_path=gopts.submanifest_path, | ||
227 | outer_client=outer_client) | ||
221 | gitc_manifest = None | 228 | gitc_manifest = None |
222 | gitc_client_name = gitc_utils.parse_clientdir(os.getcwd()) | 229 | gitc_client_name = gitc_utils.parse_clientdir(os.getcwd()) |
223 | if gitc_client_name: | 230 | if gitc_client_name: |
@@ -229,6 +236,8 @@ class _Repo(object): | |||
229 | repodir=self.repodir, | 236 | repodir=self.repodir, |
230 | client=repo_client, | 237 | client=repo_client, |
231 | manifest=repo_client.manifest, | 238 | manifest=repo_client.manifest, |
239 | outer_client=outer_client, | ||
240 | outer_manifest=outer_client.manifest, | ||
232 | gitc_manifest=gitc_manifest, | 241 | gitc_manifest=gitc_manifest, |
233 | git_event_log=git_trace2_event_log) | 242 | git_event_log=git_trace2_event_log) |
234 | except KeyError: | 243 | except KeyError: |
@@ -283,7 +292,37 @@ class _Repo(object): | |||
283 | try: | 292 | try: |
284 | cmd.CommonValidateOptions(copts, cargs) | 293 | cmd.CommonValidateOptions(copts, cargs) |
285 | cmd.ValidateOptions(copts, cargs) | 294 | cmd.ValidateOptions(copts, cargs) |
286 | result = cmd.Execute(copts, cargs) | 295 | |
296 | this_manifest_only = copts.this_manifest_only | ||
297 | # If not specified, default to using the outer manifest. | ||
298 | outer_manifest = copts.outer_manifest is not False | ||
299 | if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only: | ||
300 | result = cmd.Execute(copts, cargs) | ||
301 | elif outer_manifest and repo_client.manifest.is_submanifest: | ||
302 | # The command does not support multi-manifest, we are using a | ||
303 | # submanifest, and the command line is for the outermost manifest. | ||
304 | # Re-run using the outermost manifest, which will recurse through the | ||
305 | # submanifests. | ||
306 | gopts.submanifest_path = '' | ||
307 | result = self._Run(name, gopts, argv) | ||
308 | else: | ||
309 | # No multi-manifest support. Run the command in the current | ||
310 | # (sub)manifest, and then any child submanifests. | ||
311 | result = cmd.Execute(copts, cargs) | ||
312 | for submanifest in repo_client.manifest.submanifests.values(): | ||
313 | spec = submanifest.ToSubmanifestSpec(root=repo_client.outer_client) | ||
314 | gopts.submanifest_path = submanifest.repo_client.path_prefix | ||
315 | child_argv = argv[:] | ||
316 | child_argv.append('--no-outer-manifest') | ||
317 | # Not all subcommands support the 3 manifest options, so only add them | ||
318 | # if the original command includes them. | ||
319 | if hasattr(copts, 'manifest_url'): | ||
320 | child_argv.extend(['--manifest-url', spec.manifestUrl]) | ||
321 | if hasattr(copts, 'manifest_name'): | ||
322 | child_argv.extend(['--manifest-name', spec.manifestName]) | ||
323 | if hasattr(copts, 'manifest_branch'): | ||
324 | child_argv.extend(['--manifest-branch', spec.revision]) | ||
325 | result = self._Run(name, gopts, child_argv) or result | ||
287 | except (DownloadError, ManifestInvalidRevisionError, | 326 | except (DownloadError, ManifestInvalidRevisionError, |
288 | NoManifestException) as e: | 327 | NoManifestException) as e: |
289 | print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)), | 328 | print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)), |
diff --git a/manifest_xml.py b/manifest_xml.py index 7c5906da..7a4eb1e8 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -33,6 +33,9 @@ from wrapper import Wrapper | |||
33 | MANIFEST_FILE_NAME = 'manifest.xml' | 33 | MANIFEST_FILE_NAME = 'manifest.xml' |
34 | LOCAL_MANIFEST_NAME = 'local_manifest.xml' | 34 | LOCAL_MANIFEST_NAME = 'local_manifest.xml' |
35 | LOCAL_MANIFESTS_DIR_NAME = 'local_manifests' | 35 | LOCAL_MANIFESTS_DIR_NAME = 'local_manifests' |
36 | SUBMANIFEST_DIR = 'submanifests' | ||
37 | # Limit submanifests to an arbitrary depth for loop detection. | ||
38 | MAX_SUBMANIFEST_DEPTH = 8 | ||
36 | 39 | ||
37 | # Add all projects from local manifest into a group. | 40 | # Add all projects from local manifest into a group. |
38 | LOCAL_MANIFEST_GROUP_PREFIX = 'local:' | 41 | LOCAL_MANIFEST_GROUP_PREFIX = 'local:' |
@@ -197,10 +200,122 @@ class _XmlRemote(object): | |||
197 | self.annotations.append(Annotation(name, value, keep)) | 200 | self.annotations.append(Annotation(name, value, keep)) |
198 | 201 | ||
199 | 202 | ||
203 | class _XmlSubmanifest: | ||
204 | """Manage the <submanifest> element specified in the manifest. | ||
205 | |||
206 | Attributes: | ||
207 | name: a string, the name for this submanifest. | ||
208 | remote: a string, the remote.name for this submanifest. | ||
209 | project: a string, the name of the manifest project. | ||
210 | revision: a string, the commitish. | ||
211 | manifestName: a string, the submanifest file name. | ||
212 | groups: a list of strings, the groups to add to all projects in the submanifest. | ||
213 | path: a string, the relative path for the submanifest checkout. | ||
214 | annotations: (derived) a list of annotations. | ||
215 | present: (derived) a boolean, whether the submanifest's manifest file is present. | ||
216 | """ | ||
217 | def __init__(self, | ||
218 | name, | ||
219 | remote=None, | ||
220 | project=None, | ||
221 | revision=None, | ||
222 | manifestName=None, | ||
223 | groups=None, | ||
224 | path=None, | ||
225 | parent=None): | ||
226 | self.name = name | ||
227 | self.remote = remote | ||
228 | self.project = project | ||
229 | self.revision = revision | ||
230 | self.manifestName = manifestName | ||
231 | self.groups = groups | ||
232 | self.path = path | ||
233 | self.annotations = [] | ||
234 | outer_client = parent._outer_client or parent | ||
235 | if self.remote and not self.project: | ||
236 | raise ManifestParseError( | ||
237 | f'Submanifest {name}: must specify project when remote is given.') | ||
238 | rc = self.repo_client = RepoClient( | ||
239 | parent.repodir, manifestName, parent_groups=','.join(groups) or '', | ||
240 | submanifest_path=self.relpath, outer_client=outer_client) | ||
241 | |||
242 | self.present = os.path.exists(os.path.join(self.repo_client.subdir, | ||
243 | MANIFEST_FILE_NAME)) | ||
244 | |||
245 | def __eq__(self, other): | ||
246 | if not isinstance(other, _XmlSubmanifest): | ||
247 | return False | ||
248 | return ( | ||
249 | self.name == other.name and | ||
250 | self.remote == other.remote and | ||
251 | self.project == other.project and | ||
252 | self.revision == other.revision and | ||
253 | self.manifestName == other.manifestName and | ||
254 | self.groups == other.groups and | ||
255 | self.path == other.path and | ||
256 | sorted(self.annotations) == sorted(other.annotations)) | ||
257 | |||
258 | def __ne__(self, other): | ||
259 | return not self.__eq__(other) | ||
260 | |||
261 | def ToSubmanifestSpec(self, root): | ||
262 | """Return a SubmanifestSpec object, populating attributes""" | ||
263 | mp = root.manifestProject | ||
264 | remote = root.remotes[self.remote or root.default.remote.name] | ||
265 | # If a project was given, generate the url from the remote and project. | ||
266 | # If not, use this manifestProject's url. | ||
267 | if self.project: | ||
268 | manifestUrl = remote.ToRemoteSpec(self.project).url | ||
269 | else: | ||
270 | manifestUrl = mp.GetRemote(mp.remote.name).url | ||
271 | manifestName = self.manifestName or 'default.xml' | ||
272 | revision = self.revision or self.name | ||
273 | path = self.path or revision.split('/')[-1] | ||
274 | groups = self.groups or [] | ||
275 | |||
276 | return SubmanifestSpec(self.name, manifestUrl, manifestName, revision, path, | ||
277 | groups) | ||
278 | |||
279 | @property | ||
280 | def relpath(self): | ||
281 | """The path of this submanifest relative to the parent manifest.""" | ||
282 | revision = self.revision or self.name | ||
283 | return self.path or revision.split('/')[-1] | ||
284 | |||
285 | def GetGroupsStr(self): | ||
286 | """Returns the `groups` given for this submanifest.""" | ||
287 | if self.groups: | ||
288 | return ','.join(self.groups) | ||
289 | return '' | ||
290 | |||
291 | def AddAnnotation(self, name, value, keep): | ||
292 | """Add annotations to the submanifest.""" | ||
293 | self.annotations.append(Annotation(name, value, keep)) | ||
294 | |||
295 | |||
296 | class SubmanifestSpec: | ||
297 | """The submanifest element, with all fields expanded.""" | ||
298 | |||
299 | def __init__(self, | ||
300 | name, | ||
301 | manifestUrl, | ||
302 | manifestName, | ||
303 | revision, | ||
304 | path, | ||
305 | groups): | ||
306 | self.name = name | ||
307 | self.manifestUrl = manifestUrl | ||
308 | self.manifestName = manifestName | ||
309 | self.revision = revision | ||
310 | self.path = path | ||
311 | self.groups = groups or [] | ||
312 | |||
313 | |||
200 | class XmlManifest(object): | 314 | class XmlManifest(object): |
201 | """manages the repo configuration file""" | 315 | """manages the repo configuration file""" |
202 | 316 | ||
203 | def __init__(self, repodir, manifest_file, local_manifests=None): | 317 | def __init__(self, repodir, manifest_file, local_manifests=None, |
318 | outer_client=None, parent_groups='', submanifest_path=''): | ||
204 | """Initialize. | 319 | """Initialize. |
205 | 320 | ||
206 | Args: | 321 | Args: |
@@ -210,23 +325,37 @@ class XmlManifest(object): | |||
210 | be |repodir|/|MANIFEST_FILE_NAME|. | 325 | be |repodir|/|MANIFEST_FILE_NAME|. |
211 | local_manifests: Full path to the directory of local override manifests. | 326 | local_manifests: Full path to the directory of local override manifests. |
212 | This will usually be |repodir|/|LOCAL_MANIFESTS_DIR_NAME|. | 327 | This will usually be |repodir|/|LOCAL_MANIFESTS_DIR_NAME|. |
328 | outer_client: RepoClient of the outertree. | ||
329 | parent_groups: a string, the groups to apply to this projects. | ||
330 | submanifest_path: The submanifest root relative to the repo root. | ||
213 | """ | 331 | """ |
214 | # TODO(vapier): Move this out of this class. | 332 | # TODO(vapier): Move this out of this class. |
215 | self.globalConfig = GitConfig.ForUser() | 333 | self.globalConfig = GitConfig.ForUser() |
216 | 334 | ||
217 | self.repodir = os.path.abspath(repodir) | 335 | self.repodir = os.path.abspath(repodir) |
218 | self.topdir = os.path.dirname(self.repodir) | 336 | self._CheckLocalPath(submanifest_path) |
337 | self.topdir = os.path.join(os.path.dirname(self.repodir), submanifest_path) | ||
219 | self.manifestFile = manifest_file | 338 | self.manifestFile = manifest_file |
220 | self.local_manifests = local_manifests | 339 | self.local_manifests = local_manifests |
221 | self._load_local_manifests = True | 340 | self._load_local_manifests = True |
341 | self.parent_groups = parent_groups | ||
342 | |||
343 | if outer_client and self.isGitcClient: | ||
344 | raise ManifestParseError('Multi-manifest is incompatible with `gitc-init`') | ||
345 | |||
346 | if submanifest_path and not outer_client: | ||
347 | # If passing a submanifest_path, there must be an outer_client. | ||
348 | raise ManifestParseError(f'Bad call to {self.__class__.__name__}') | ||
349 | |||
350 | # If self._outer_client is None, this is not a checkout that supports | ||
351 | # multi-tree. | ||
352 | self._outer_client = outer_client or self | ||
222 | 353 | ||
223 | self.repoProject = MetaProject(self, 'repo', | 354 | self.repoProject = MetaProject(self, 'repo', |
224 | gitdir=os.path.join(repodir, 'repo/.git'), | 355 | gitdir=os.path.join(repodir, 'repo/.git'), |
225 | worktree=os.path.join(repodir, 'repo')) | 356 | worktree=os.path.join(repodir, 'repo')) |
226 | 357 | ||
227 | mp = MetaProject(self, 'manifests', | 358 | mp = self.SubmanifestProject(self.path_prefix) |
228 | gitdir=os.path.join(repodir, 'manifests.git'), | ||
229 | worktree=os.path.join(repodir, 'manifests')) | ||
230 | self.manifestProject = mp | 359 | self.manifestProject = mp |
231 | 360 | ||
232 | # This is a bit hacky, but we're in a chicken & egg situation: all the | 361 | # This is a bit hacky, but we're in a chicken & egg situation: all the |
@@ -311,6 +440,31 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
311 | ae.setAttribute('value', a.value) | 440 | ae.setAttribute('value', a.value) |
312 | e.appendChild(ae) | 441 | e.appendChild(ae) |
313 | 442 | ||
443 | def _SubmanifestToXml(self, r, doc, root): | ||
444 | """Generate XML <submanifest/> node.""" | ||
445 | e = doc.createElement('submanifest') | ||
446 | root.appendChild(e) | ||
447 | e.setAttribute('name', r.name) | ||
448 | if r.remote is not None: | ||
449 | e.setAttribute('remote', r.remote) | ||
450 | if r.project is not None: | ||
451 | e.setAttribute('project', r.project) | ||
452 | if r.manifestName is not None: | ||
453 | e.setAttribute('manifest-name', r.manifestName) | ||
454 | if r.revision is not None: | ||
455 | e.setAttribute('revision', r.revision) | ||
456 | if r.path is not None: | ||
457 | e.setAttribute('path', r.path) | ||
458 | if r.groups: | ||
459 | e.setAttribute('groups', r.GetGroupsStr()) | ||
460 | |||
461 | for a in r.annotations: | ||
462 | if a.keep == 'true': | ||
463 | ae = doc.createElement('annotation') | ||
464 | ae.setAttribute('name', a.name) | ||
465 | ae.setAttribute('value', a.value) | ||
466 | e.appendChild(ae) | ||
467 | |||
314 | def _ParseList(self, field): | 468 | def _ParseList(self, field): |
315 | """Parse fields that contain flattened lists. | 469 | """Parse fields that contain flattened lists. |
316 | 470 | ||
@@ -329,6 +483,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
329 | 483 | ||
330 | doc = xml.dom.minidom.Document() | 484 | doc = xml.dom.minidom.Document() |
331 | root = doc.createElement('manifest') | 485 | root = doc.createElement('manifest') |
486 | if self.is_submanifest: | ||
487 | root.setAttribute('path', self.path_prefix) | ||
332 | doc.appendChild(root) | 488 | doc.appendChild(root) |
333 | 489 | ||
334 | # Save out the notice. There's a little bit of work here to give it the | 490 | # Save out the notice. There's a little bit of work here to give it the |
@@ -383,6 +539,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
383 | root.appendChild(e) | 539 | root.appendChild(e) |
384 | root.appendChild(doc.createTextNode('')) | 540 | root.appendChild(doc.createTextNode('')) |
385 | 541 | ||
542 | for r in sorted(self.submanifests): | ||
543 | self._SubmanifestToXml(self.submanifests[r], doc, root) | ||
544 | if self.submanifests: | ||
545 | root.appendChild(doc.createTextNode('')) | ||
546 | |||
386 | def output_projects(parent, parent_node, projects): | 547 | def output_projects(parent, parent_node, projects): |
387 | for project_name in projects: | 548 | for project_name in projects: |
388 | for project in self._projects[project_name]: | 549 | for project in self._projects[project_name]: |
@@ -537,6 +698,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
537 | 'project', | 698 | 'project', |
538 | 'extend-project', | 699 | 'extend-project', |
539 | 'include', | 700 | 'include', |
701 | 'submanifest', | ||
540 | # These are children of 'project' nodes. | 702 | # These are children of 'project' nodes. |
541 | 'annotation', | 703 | 'annotation', |
542 | 'project', | 704 | 'project', |
@@ -575,12 +737,74 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
575 | """Manifests can modify e if they support extra project attributes.""" | 737 | """Manifests can modify e if they support extra project attributes.""" |
576 | 738 | ||
577 | @property | 739 | @property |
740 | def is_multimanifest(self): | ||
741 | """Whether this is a multimanifest checkout""" | ||
742 | return bool(self.outer_client.submanifests) | ||
743 | |||
744 | @property | ||
745 | def is_submanifest(self): | ||
746 | """Whether this manifest is a submanifest""" | ||
747 | return self._outer_client and self._outer_client != self | ||
748 | |||
749 | @property | ||
750 | def outer_client(self): | ||
751 | """The instance of the outermost manifest client""" | ||
752 | self._Load() | ||
753 | return self._outer_client | ||
754 | |||
755 | @property | ||
756 | def all_manifests(self): | ||
757 | """Generator yielding all (sub)manifests.""" | ||
758 | self._Load() | ||
759 | outer = self._outer_client | ||
760 | yield outer | ||
761 | for tree in outer.all_children: | ||
762 | yield tree | ||
763 | |||
764 | @property | ||
765 | def all_children(self): | ||
766 | """Generator yielding all child submanifests.""" | ||
767 | self._Load() | ||
768 | for child in self._submanifests.values(): | ||
769 | if child.repo_client: | ||
770 | yield child.repo_client | ||
771 | for tree in child.repo_client.all_children: | ||
772 | yield tree | ||
773 | |||
774 | @property | ||
775 | def path_prefix(self): | ||
776 | """The path of this submanifest, relative to the outermost manifest.""" | ||
777 | if not self._outer_client or self == self._outer_client: | ||
778 | return '' | ||
779 | return os.path.relpath(self.topdir, self._outer_client.topdir) | ||
780 | |||
781 | @property | ||
782 | def all_paths(self): | ||
783 | """All project paths for all (sub)manifests. See `paths`.""" | ||
784 | ret = {} | ||
785 | for tree in self.all_manifests: | ||
786 | prefix = tree.path_prefix | ||
787 | ret.update({os.path.join(prefix, k): v for k, v in tree.paths.items()}) | ||
788 | return ret | ||
789 | |||
790 | @property | ||
791 | def all_projects(self): | ||
792 | """All projects for all (sub)manifests. See `projects`.""" | ||
793 | return list(itertools.chain.from_iterable(x._paths.values() for x in self.all_manifests)) | ||
794 | |||
795 | @property | ||
578 | def paths(self): | 796 | def paths(self): |
797 | """Return all paths for this manifest. | ||
798 | |||
799 | Return: | ||
800 | A dictionary of {path: Project()}. `path` is relative to this manifest. | ||
801 | """ | ||
579 | self._Load() | 802 | self._Load() |
580 | return self._paths | 803 | return self._paths |
581 | 804 | ||
582 | @property | 805 | @property |
583 | def projects(self): | 806 | def projects(self): |
807 | """Return a list of all Projects in this manifest.""" | ||
584 | self._Load() | 808 | self._Load() |
585 | return list(self._paths.values()) | 809 | return list(self._paths.values()) |
586 | 810 | ||
@@ -595,6 +819,12 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
595 | return self._default | 819 | return self._default |
596 | 820 | ||
597 | @property | 821 | @property |
822 | def submanifests(self): | ||
823 | """All submanifests in this manifest.""" | ||
824 | self._Load() | ||
825 | return self._submanifests | ||
826 | |||
827 | @property | ||
598 | def repo_hooks_project(self): | 828 | def repo_hooks_project(self): |
599 | self._Load() | 829 | self._Load() |
600 | return self._repo_hooks_project | 830 | return self._repo_hooks_project |
@@ -651,8 +881,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
651 | return self._load_local_manifests and self.local_manifests | 881 | return self._load_local_manifests and self.local_manifests |
652 | 882 | ||
653 | def IsFromLocalManifest(self, project): | 883 | def IsFromLocalManifest(self, project): |
654 | """Is the project from a local manifest? | 884 | """Is the project from a local manifest?""" |
655 | """ | ||
656 | return any(x.startswith(LOCAL_MANIFEST_GROUP_PREFIX) | 885 | return any(x.startswith(LOCAL_MANIFEST_GROUP_PREFIX) |
657 | for x in project.groups) | 886 | for x in project.groups) |
658 | 887 | ||
@@ -676,6 +905,50 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
676 | def EnableGitLfs(self): | 905 | def EnableGitLfs(self): |
677 | return self.manifestProject.config.GetBoolean('repo.git-lfs') | 906 | return self.manifestProject.config.GetBoolean('repo.git-lfs') |
678 | 907 | ||
908 | def FindManifestByPath(self, path): | ||
909 | """Returns the manifest containing path.""" | ||
910 | path = os.path.abspath(path) | ||
911 | manifest = self._outer_client or self | ||
912 | old = None | ||
913 | while manifest._submanifests and manifest != old: | ||
914 | old = manifest | ||
915 | for name in manifest._submanifests: | ||
916 | tree = manifest._submanifests[name] | ||
917 | if path.startswith(tree.repo_client.manifest.topdir): | ||
918 | manifest = tree.repo_client | ||
919 | break | ||
920 | return manifest | ||
921 | |||
922 | @property | ||
923 | def subdir(self): | ||
924 | """Returns the path for per-submanifest objects for this manifest.""" | ||
925 | return self.SubmanifestInfoDir(self.path_prefix) | ||
926 | |||
927 | def SubmanifestInfoDir(self, submanifest_path, object_path=''): | ||
928 | """Return the path to submanifest-specific info for a submanifest. | ||
929 | |||
930 | Return the full path of the directory in which to put per-manifest objects. | ||
931 | |||
932 | Args: | ||
933 | submanifest_path: a string, the path of the submanifest, relative to the | ||
934 | outermost topdir. If empty, then repodir is returned. | ||
935 | object_path: a string, relative path to append to the submanifest info | ||
936 | directory path. | ||
937 | """ | ||
938 | if submanifest_path: | ||
939 | return os.path.join(self.repodir, SUBMANIFEST_DIR, submanifest_path, | ||
940 | object_path) | ||
941 | else: | ||
942 | return os.path.join(self.repodir, object_path) | ||
943 | |||
944 | def SubmanifestProject(self, submanifest_path): | ||
945 | """Return a manifestProject for a submanifest.""" | ||
946 | subdir = self.SubmanifestInfoDir(submanifest_path) | ||
947 | mp = MetaProject(self, 'manifests', | ||
948 | gitdir=os.path.join(subdir, 'manifests.git'), | ||
949 | worktree=os.path.join(subdir, 'manifests')) | ||
950 | return mp | ||
951 | |||
679 | def GetDefaultGroupsStr(self): | 952 | def GetDefaultGroupsStr(self): |
680 | """Returns the default group string for the platform.""" | 953 | """Returns the default group string for the platform.""" |
681 | return 'default,platform-' + platform.system().lower() | 954 | return 'default,platform-' + platform.system().lower() |
@@ -693,6 +966,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
693 | self._paths = {} | 966 | self._paths = {} |
694 | self._remotes = {} | 967 | self._remotes = {} |
695 | self._default = None | 968 | self._default = None |
969 | self._submanifests = {} | ||
696 | self._repo_hooks_project = None | 970 | self._repo_hooks_project = None |
697 | self._superproject = {} | 971 | self._superproject = {} |
698 | self._contactinfo = ContactInfo(Wrapper().BUG_URL) | 972 | self._contactinfo = ContactInfo(Wrapper().BUG_URL) |
@@ -700,20 +974,29 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
700 | self.branch = None | 974 | self.branch = None |
701 | self._manifest_server = None | 975 | self._manifest_server = None |
702 | 976 | ||
703 | def _Load(self): | 977 | def _Load(self, initial_client=None, submanifest_depth=0): |
978 | if submanifest_depth > MAX_SUBMANIFEST_DEPTH: | ||
979 | raise ManifestParseError('maximum submanifest depth %d exceeded.' % | ||
980 | MAX_SUBMANIFEST_DEPTH) | ||
704 | if not self._loaded: | 981 | if not self._loaded: |
982 | if self._outer_client and self._outer_client != self: | ||
983 | # This will load all clients. | ||
984 | self._outer_client._Load(initial_client=self) | ||
985 | |||
705 | m = self.manifestProject | 986 | m = self.manifestProject |
706 | b = m.GetBranch(m.CurrentBranch).merge | 987 | b = m.GetBranch(m.CurrentBranch).merge |
707 | if b is not None and b.startswith(R_HEADS): | 988 | if b is not None and b.startswith(R_HEADS): |
708 | b = b[len(R_HEADS):] | 989 | b = b[len(R_HEADS):] |
709 | self.branch = b | 990 | self.branch = b |
710 | 991 | ||
992 | parent_groups = self.parent_groups | ||
993 | |||
711 | # The manifestFile was specified by the user which is why we allow include | 994 | # The manifestFile was specified by the user which is why we allow include |
712 | # paths to point anywhere. | 995 | # paths to point anywhere. |
713 | nodes = [] | 996 | nodes = [] |
714 | nodes.append(self._ParseManifestXml( | 997 | nodes.append(self._ParseManifestXml( |
715 | self.manifestFile, self.manifestProject.worktree, | 998 | self.manifestFile, self.manifestProject.worktree, |
716 | restrict_includes=False)) | 999 | parent_groups=parent_groups, restrict_includes=False)) |
717 | 1000 | ||
718 | if self._load_local_manifests and self.local_manifests: | 1001 | if self._load_local_manifests and self.local_manifests: |
719 | try: | 1002 | try: |
@@ -722,9 +1005,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
722 | local = os.path.join(self.local_manifests, local_file) | 1005 | local = os.path.join(self.local_manifests, local_file) |
723 | # Since local manifests are entirely managed by the user, allow | 1006 | # Since local manifests are entirely managed by the user, allow |
724 | # them to point anywhere the user wants. | 1007 | # them to point anywhere the user wants. |
1008 | local_group = f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}' | ||
725 | nodes.append(self._ParseManifestXml( | 1009 | nodes.append(self._ParseManifestXml( |
726 | local, self.repodir, | 1010 | local, self.subdir, |
727 | parent_groups=f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}', | 1011 | parent_groups=f'{local_group},{parent_groups}', |
728 | restrict_includes=False)) | 1012 | restrict_includes=False)) |
729 | except OSError: | 1013 | except OSError: |
730 | pass | 1014 | pass |
@@ -743,6 +1027,23 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
743 | 1027 | ||
744 | self._loaded = True | 1028 | self._loaded = True |
745 | 1029 | ||
1030 | # Now that we have loaded this manifest, load any submanifest manifests | ||
1031 | # as well. We need to do this after self._loaded is set to avoid looping. | ||
1032 | if self._outer_client: | ||
1033 | for name in self._submanifests: | ||
1034 | tree = self._submanifests[name] | ||
1035 | spec = tree.ToSubmanifestSpec(self) | ||
1036 | present = os.path.exists(os.path.join(self.subdir, MANIFEST_FILE_NAME)) | ||
1037 | if present and tree.present and not tree.repo_client: | ||
1038 | if initial_client and initial_client.topdir == self.topdir: | ||
1039 | tree.repo_client = self | ||
1040 | tree.present = present | ||
1041 | elif not os.path.exists(self.subdir): | ||
1042 | tree.present = False | ||
1043 | if tree.present: | ||
1044 | tree.repo_client._Load(initial_client=initial_client, | ||
1045 | submanifest_depth=submanifest_depth + 1) | ||
1046 | |||
746 | def _ParseManifestXml(self, path, include_root, parent_groups='', | 1047 | def _ParseManifestXml(self, path, include_root, parent_groups='', |
747 | restrict_includes=True): | 1048 | restrict_includes=True): |
748 | """Parse a manifest XML and return the computed nodes. | 1049 | """Parse a manifest XML and return the computed nodes. |
@@ -832,6 +1133,20 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
832 | if self._default is None: | 1133 | if self._default is None: |
833 | self._default = _Default() | 1134 | self._default = _Default() |
834 | 1135 | ||
1136 | submanifest_paths = set() | ||
1137 | for node in itertools.chain(*node_list): | ||
1138 | if node.nodeName == 'submanifest': | ||
1139 | submanifest = self._ParseSubmanifest(node) | ||
1140 | if submanifest: | ||
1141 | if submanifest.name in self._submanifests: | ||
1142 | if submanifest != self._submanifests[submanifest.name]: | ||
1143 | raise ManifestParseError( | ||
1144 | 'submanifest %s already exists with different attributes' % | ||
1145 | (submanifest.name)) | ||
1146 | else: | ||
1147 | self._submanifests[submanifest.name] = submanifest | ||
1148 | submanifest_paths.add(submanifest.relpath) | ||
1149 | |||
835 | for node in itertools.chain(*node_list): | 1150 | for node in itertools.chain(*node_list): |
836 | if node.nodeName == 'notice': | 1151 | if node.nodeName == 'notice': |
837 | if self._notice is not None: | 1152 | if self._notice is not None: |
@@ -859,6 +1174,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
859 | raise ManifestParseError( | 1174 | raise ManifestParseError( |
860 | 'duplicate path %s in %s' % | 1175 | 'duplicate path %s in %s' % |
861 | (project.relpath, self.manifestFile)) | 1176 | (project.relpath, self.manifestFile)) |
1177 | for tree in submanifest_paths: | ||
1178 | if project.relpath.startswith(tree): | ||
1179 | raise ManifestParseError( | ||
1180 | 'project %s conflicts with submanifest path %s' % | ||
1181 | (project.relpath, tree)) | ||
862 | self._paths[project.relpath] = project | 1182 | self._paths[project.relpath] = project |
863 | projects.append(project) | 1183 | projects.append(project) |
864 | for subproject in project.subprojects: | 1184 | for subproject in project.subprojects: |
@@ -883,8 +1203,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
883 | if groups: | 1203 | if groups: |
884 | groups = self._ParseList(groups) | 1204 | groups = self._ParseList(groups) |
885 | revision = node.getAttribute('revision') | 1205 | revision = node.getAttribute('revision') |
886 | remote = node.getAttribute('remote') | 1206 | remote_name = node.getAttribute('remote') |
887 | if remote: | 1207 | if not remote_name: |
1208 | remote = self._default.remote | ||
1209 | else: | ||
888 | remote = self._get_remote(node) | 1210 | remote = self._get_remote(node) |
889 | 1211 | ||
890 | named_projects = self._projects[name] | 1212 | named_projects = self._projects[name] |
@@ -899,12 +1221,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
899 | if revision: | 1221 | if revision: |
900 | p.SetRevision(revision) | 1222 | p.SetRevision(revision) |
901 | 1223 | ||
902 | if remote: | 1224 | if remote_name: |
903 | p.remote = remote.ToRemoteSpec(name) | 1225 | p.remote = remote.ToRemoteSpec(name) |
904 | 1226 | ||
905 | if dest_path: | 1227 | if dest_path: |
906 | del self._paths[p.relpath] | 1228 | del self._paths[p.relpath] |
907 | relpath, worktree, gitdir, objdir, _ = self.GetProjectPaths(name, dest_path) | 1229 | relpath, worktree, gitdir, objdir, _ = self.GetProjectPaths( |
1230 | name, dest_path, remote.name) | ||
908 | p.UpdatePaths(relpath, worktree, gitdir, objdir) | 1231 | p.UpdatePaths(relpath, worktree, gitdir, objdir) |
909 | self._paths[p.relpath] = p | 1232 | self._paths[p.relpath] = p |
910 | 1233 | ||
@@ -1109,6 +1432,53 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1109 | 1432 | ||
1110 | return '\n'.join(cleanLines) | 1433 | return '\n'.join(cleanLines) |
1111 | 1434 | ||
1435 | def _ParseSubmanifest(self, node): | ||
1436 | """Reads a <submanifest> element from the manifest file.""" | ||
1437 | name = self._reqatt(node, 'name') | ||
1438 | remote = node.getAttribute('remote') | ||
1439 | if remote == '': | ||
1440 | remote = None | ||
1441 | project = node.getAttribute('project') | ||
1442 | if project == '': | ||
1443 | project = None | ||
1444 | revision = node.getAttribute('revision') | ||
1445 | if revision == '': | ||
1446 | revision = None | ||
1447 | manifestName = node.getAttribute('manifest-name') | ||
1448 | if manifestName == '': | ||
1449 | manifestName = None | ||
1450 | groups = '' | ||
1451 | if node.hasAttribute('groups'): | ||
1452 | groups = node.getAttribute('groups') | ||
1453 | groups = self._ParseList(groups) | ||
1454 | path = node.getAttribute('path') | ||
1455 | if path == '': | ||
1456 | path = None | ||
1457 | if revision: | ||
1458 | msg = self._CheckLocalPath(revision.split('/')[-1]) | ||
1459 | if msg: | ||
1460 | raise ManifestInvalidPathError( | ||
1461 | '<submanifest> invalid "revision": %s: %s' % (revision, msg)) | ||
1462 | else: | ||
1463 | msg = self._CheckLocalPath(name) | ||
1464 | if msg: | ||
1465 | raise ManifestInvalidPathError( | ||
1466 | '<submanifest> invalid "name": %s: %s' % (name, msg)) | ||
1467 | else: | ||
1468 | msg = self._CheckLocalPath(path) | ||
1469 | if msg: | ||
1470 | raise ManifestInvalidPathError( | ||
1471 | '<submanifest> invalid "path": %s: %s' % (path, msg)) | ||
1472 | |||
1473 | submanifest = _XmlSubmanifest(name, remote, project, revision, manifestName, | ||
1474 | groups, path, self) | ||
1475 | |||
1476 | for n in node.childNodes: | ||
1477 | if n.nodeName == 'annotation': | ||
1478 | self._ParseAnnotation(submanifest, n) | ||
1479 | |||
1480 | return submanifest | ||
1481 | |||
1112 | def _JoinName(self, parent_name, name): | 1482 | def _JoinName(self, parent_name, name): |
1113 | return os.path.join(parent_name, name) | 1483 | return os.path.join(parent_name, name) |
1114 | 1484 | ||
@@ -1172,7 +1542,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1172 | 1542 | ||
1173 | if parent is None: | 1543 | if parent is None: |
1174 | relpath, worktree, gitdir, objdir, use_git_worktrees = \ | 1544 | relpath, worktree, gitdir, objdir, use_git_worktrees = \ |
1175 | self.GetProjectPaths(name, path) | 1545 | self.GetProjectPaths(name, path, remote.name) |
1176 | else: | 1546 | else: |
1177 | use_git_worktrees = False | 1547 | use_git_worktrees = False |
1178 | relpath, worktree, gitdir, objdir = \ | 1548 | relpath, worktree, gitdir, objdir = \ |
@@ -1218,31 +1588,54 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1218 | 1588 | ||
1219 | return project | 1589 | return project |
1220 | 1590 | ||
1221 | def GetProjectPaths(self, name, path): | 1591 | def GetProjectPaths(self, name, path, remote): |
1592 | """Return the paths for a project. | ||
1593 | |||
1594 | Args: | ||
1595 | name: a string, the name of the project. | ||
1596 | path: a string, the path of the project. | ||
1597 | remote: a string, the remote.name of the project. | ||
1598 | """ | ||
1222 | # The manifest entries might have trailing slashes. Normalize them to avoid | 1599 | # The manifest entries might have trailing slashes. Normalize them to avoid |
1223 | # unexpected filesystem behavior since we do string concatenation below. | 1600 | # unexpected filesystem behavior since we do string concatenation below. |
1224 | path = path.rstrip('/') | 1601 | path = path.rstrip('/') |
1225 | name = name.rstrip('/') | 1602 | name = name.rstrip('/') |
1603 | remote = remote.rstrip('/') | ||
1226 | use_git_worktrees = False | 1604 | use_git_worktrees = False |
1605 | use_remote_name = bool(self._outer_client._submanifests) | ||
1227 | relpath = path | 1606 | relpath = path |
1228 | if self.IsMirror: | 1607 | if self.IsMirror: |
1229 | worktree = None | 1608 | worktree = None |
1230 | gitdir = os.path.join(self.topdir, '%s.git' % name) | 1609 | gitdir = os.path.join(self.topdir, '%s.git' % name) |
1231 | objdir = gitdir | 1610 | objdir = gitdir |
1232 | else: | 1611 | else: |
1612 | if use_remote_name: | ||
1613 | namepath = os.path.join(remote, f'{name}.git') | ||
1614 | else: | ||
1615 | namepath = f'{name}.git' | ||
1233 | worktree = os.path.join(self.topdir, path).replace('\\', '/') | 1616 | worktree = os.path.join(self.topdir, path).replace('\\', '/') |
1234 | gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path) | 1617 | gitdir = os.path.join(self.subdir, 'projects', '%s.git' % path) |
1235 | # We allow people to mix git worktrees & non-git worktrees for now. | 1618 | # We allow people to mix git worktrees & non-git worktrees for now. |
1236 | # This allows for in situ migration of repo clients. | 1619 | # This allows for in situ migration of repo clients. |
1237 | if os.path.exists(gitdir) or not self.UseGitWorktrees: | 1620 | if os.path.exists(gitdir) or not self.UseGitWorktrees: |
1238 | objdir = os.path.join(self.repodir, 'project-objects', '%s.git' % name) | 1621 | objdir = os.path.join(self.subdir, 'project-objects', namepath) |
1239 | else: | 1622 | else: |
1240 | use_git_worktrees = True | 1623 | use_git_worktrees = True |
1241 | gitdir = os.path.join(self.repodir, 'worktrees', '%s.git' % name) | 1624 | gitdir = os.path.join(self.repodir, 'worktrees', namepath) |
1242 | objdir = gitdir | 1625 | objdir = gitdir |
1243 | return relpath, worktree, gitdir, objdir, use_git_worktrees | 1626 | return relpath, worktree, gitdir, objdir, use_git_worktrees |
1244 | 1627 | ||
1245 | def GetProjectsWithName(self, name): | 1628 | def GetProjectsWithName(self, name, all_manifests=False): |
1629 | """All projects with |name|. | ||
1630 | |||
1631 | Args: | ||
1632 | name: a string, the name of the project. | ||
1633 | all_manifests: a boolean, if True, then all manifests are searched. If | ||
1634 | False, then only this manifest is searched. | ||
1635 | """ | ||
1636 | if all_manifests: | ||
1637 | return list(itertools.chain.from_iterable( | ||
1638 | x._projects.get(name, []) for x in self.all_manifests)) | ||
1246 | return self._projects.get(name, []) | 1639 | return self._projects.get(name, []) |
1247 | 1640 | ||
1248 | def GetSubprojectName(self, parent, submodule_path): | 1641 | def GetSubprojectName(self, parent, submodule_path): |
@@ -1498,19 +1891,26 @@ class GitcManifest(XmlManifest): | |||
1498 | class RepoClient(XmlManifest): | 1891 | class RepoClient(XmlManifest): |
1499 | """Manages a repo client checkout.""" | 1892 | """Manages a repo client checkout.""" |
1500 | 1893 | ||
1501 | def __init__(self, repodir, manifest_file=None): | 1894 | def __init__(self, repodir, manifest_file=None, submanifest_path='', **kwargs): |
1502 | self.isGitcClient = False | 1895 | self.isGitcClient = False |
1896 | submanifest_path = submanifest_path or '' | ||
1897 | if submanifest_path: | ||
1898 | self._CheckLocalPath(submanifest_path) | ||
1899 | prefix = os.path.join(repodir, SUBMANIFEST_DIR, submanifest_path) | ||
1900 | else: | ||
1901 | prefix = repodir | ||
1503 | 1902 | ||
1504 | if os.path.exists(os.path.join(repodir, LOCAL_MANIFEST_NAME)): | 1903 | if os.path.exists(os.path.join(prefix, LOCAL_MANIFEST_NAME)): |
1505 | print('error: %s is not supported; put local manifests in `%s` instead' % | 1904 | print('error: %s is not supported; put local manifests in `%s` instead' % |
1506 | (LOCAL_MANIFEST_NAME, os.path.join(repodir, LOCAL_MANIFESTS_DIR_NAME)), | 1905 | (LOCAL_MANIFEST_NAME, os.path.join(prefix, LOCAL_MANIFESTS_DIR_NAME)), |
1507 | file=sys.stderr) | 1906 | file=sys.stderr) |
1508 | sys.exit(1) | 1907 | sys.exit(1) |
1509 | 1908 | ||
1510 | if manifest_file is None: | 1909 | if manifest_file is None: |
1511 | manifest_file = os.path.join(repodir, MANIFEST_FILE_NAME) | 1910 | manifest_file = os.path.join(prefix, MANIFEST_FILE_NAME) |
1512 | local_manifests = os.path.abspath(os.path.join(repodir, LOCAL_MANIFESTS_DIR_NAME)) | 1911 | local_manifests = os.path.abspath(os.path.join(prefix, LOCAL_MANIFESTS_DIR_NAME)) |
1513 | super().__init__(repodir, manifest_file, local_manifests) | 1912 | super().__init__(repodir, manifest_file, local_manifests, |
1913 | submanifest_path=submanifest_path, **kwargs) | ||
1514 | 1914 | ||
1515 | # TODO: Completely separate manifest logic out of the client. | 1915 | # TODO: Completely separate manifest logic out of the client. |
1516 | self.manifest = self | 1916 | self.manifest = self |
@@ -546,6 +546,18 @@ class Project(object): | |||
546 | # project containing repo hooks. | 546 | # project containing repo hooks. |
547 | self.enabled_repo_hooks = [] | 547 | self.enabled_repo_hooks = [] |
548 | 548 | ||
549 | def RelPath(self, local=True): | ||
550 | """Return the path for the project relative to a manifest. | ||
551 | |||
552 | Args: | ||
553 | local: a boolean, if True, the path is relative to the local | ||
554 | (sub)manifest. If false, the path is relative to the | ||
555 | outermost manifest. | ||
556 | """ | ||
557 | if local: | ||
558 | return self.relpath | ||
559 | return os.path.join(self.manifest.path_prefix, self.relpath) | ||
560 | |||
549 | def SetRevision(self, revisionExpr, revisionId=None): | 561 | def SetRevision(self, revisionExpr, revisionId=None): |
550 | """Set revisionId based on revision expression and id""" | 562 | """Set revisionId based on revision expression and id""" |
551 | self.revisionExpr = revisionExpr | 563 | self.revisionExpr = revisionExpr |
@@ -2503,22 +2515,21 @@ class Project(object): | |||
2503 | mp = self.manifest.manifestProject | 2515 | mp = self.manifest.manifestProject |
2504 | ref_dir = mp.config.GetString('repo.reference') or '' | 2516 | ref_dir = mp.config.GetString('repo.reference') or '' |
2505 | 2517 | ||
2518 | def _expanded_ref_dirs(): | ||
2519 | """Iterate through the possible git reference directory paths.""" | ||
2520 | name = self.name + '.git' | ||
2521 | yield mirror_git or os.path.join(ref_dir, name) | ||
2522 | for prefix in '', self.remote.name: | ||
2523 | yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name) | ||
2524 | yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name) | ||
2525 | |||
2506 | if ref_dir or mirror_git: | 2526 | if ref_dir or mirror_git: |
2507 | if not mirror_git: | 2527 | found_ref_dir = None |
2508 | mirror_git = os.path.join(ref_dir, self.name + '.git') | 2528 | for path in _expanded_ref_dirs(): |
2509 | repo_git = os.path.join(ref_dir, '.repo', 'project-objects', | 2529 | if os.path.exists(path): |
2510 | self.name + '.git') | 2530 | found_ref_dir = path |
2511 | worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees', | 2531 | break |
2512 | self.name + '.git') | 2532 | ref_dir = found_ref_dir |
2513 | |||
2514 | if os.path.exists(mirror_git): | ||
2515 | ref_dir = mirror_git | ||
2516 | elif os.path.exists(repo_git): | ||
2517 | ref_dir = repo_git | ||
2518 | elif os.path.exists(worktrees_git): | ||
2519 | ref_dir = worktrees_git | ||
2520 | else: | ||
2521 | ref_dir = None | ||
2522 | 2533 | ||
2523 | if ref_dir: | 2534 | if ref_dir: |
2524 | if not os.path.isabs(ref_dir): | 2535 | if not os.path.isabs(ref_dir): |
diff --git a/subcmds/abandon.py b/subcmds/abandon.py index 85d85f5a..c3d2d5b7 100644 --- a/subcmds/abandon.py +++ b/subcmds/abandon.py | |||
@@ -69,7 +69,8 @@ It is equivalent to "git branch -D <branchname>". | |||
69 | nb = args[0] | 69 | nb = args[0] |
70 | err = defaultdict(list) | 70 | err = defaultdict(list) |
71 | success = defaultdict(list) | 71 | success = defaultdict(list) |
72 | all_projects = self.GetProjects(args[1:]) | 72 | all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only) |
73 | _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) | ||
73 | 74 | ||
74 | def _ProcessResults(_pool, pm, states): | 75 | def _ProcessResults(_pool, pm, states): |
75 | for (results, project) in states: | 76 | for (results, project) in states: |
@@ -94,7 +95,7 @@ It is equivalent to "git branch -D <branchname>". | |||
94 | err_msg = "error: cannot abandon %s" % br | 95 | err_msg = "error: cannot abandon %s" % br |
95 | print(err_msg, file=sys.stderr) | 96 | print(err_msg, file=sys.stderr) |
96 | for proj in err[br]: | 97 | for proj in err[br]: |
97 | print(' ' * len(err_msg) + " | %s" % proj.relpath, file=sys.stderr) | 98 | print(' ' * len(err_msg) + " | %s" % _RelPath(proj), file=sys.stderr) |
98 | sys.exit(1) | 99 | sys.exit(1) |
99 | elif not success: | 100 | elif not success: |
100 | print('error: no project has local branch(es) : %s' % nb, | 101 | print('error: no project has local branch(es) : %s' % nb, |
@@ -110,5 +111,5 @@ It is equivalent to "git branch -D <branchname>". | |||
110 | result = "all project" | 111 | result = "all project" |
111 | else: | 112 | else: |
112 | result = "%s" % ( | 113 | result = "%s" % ( |
113 | ('\n' + ' ' * width + '| ').join(p.relpath for p in success[br])) | 114 | ('\n' + ' ' * width + '| ').join(_RelPath(p) for p in success[br])) |
114 | print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result)) | 115 | print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result)) |
diff --git a/subcmds/branches.py b/subcmds/branches.py index 7b5decc6..b89cc2f8 100644 --- a/subcmds/branches.py +++ b/subcmds/branches.py | |||
@@ -98,7 +98,7 @@ is shown, then the branch appears in all projects. | |||
98 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | 98 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS |
99 | 99 | ||
100 | def Execute(self, opt, args): | 100 | def Execute(self, opt, args): |
101 | projects = self.GetProjects(args) | 101 | projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
102 | out = BranchColoring(self.manifest.manifestProject.config) | 102 | out = BranchColoring(self.manifest.manifestProject.config) |
103 | all_branches = {} | 103 | all_branches = {} |
104 | project_cnt = len(projects) | 104 | project_cnt = len(projects) |
@@ -147,6 +147,7 @@ is shown, then the branch appears in all projects. | |||
147 | hdr('%c%c %-*s' % (current, published, width, name)) | 147 | hdr('%c%c %-*s' % (current, published, width, name)) |
148 | out.write(' |') | 148 | out.write(' |') |
149 | 149 | ||
150 | _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) | ||
150 | if in_cnt < project_cnt: | 151 | if in_cnt < project_cnt: |
151 | fmt = out.write | 152 | fmt = out.write |
152 | paths = [] | 153 | paths = [] |
@@ -154,19 +155,20 @@ is shown, then the branch appears in all projects. | |||
154 | if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt): | 155 | if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt): |
155 | in_type = 'in' | 156 | in_type = 'in' |
156 | for b in i.projects: | 157 | for b in i.projects: |
158 | relpath = b.project.relpath | ||
157 | if not i.IsSplitCurrent or b.current: | 159 | if not i.IsSplitCurrent or b.current: |
158 | paths.append(b.project.relpath) | 160 | paths.append(_RelPath(b.project)) |
159 | else: | 161 | else: |
160 | non_cur_paths.append(b.project.relpath) | 162 | non_cur_paths.append(_RelPath(b.project)) |
161 | else: | 163 | else: |
162 | fmt = out.notinproject | 164 | fmt = out.notinproject |
163 | in_type = 'not in' | 165 | in_type = 'not in' |
164 | have = set() | 166 | have = set() |
165 | for b in i.projects: | 167 | for b in i.projects: |
166 | have.add(b.project.relpath) | 168 | have.add(_RelPath(b.project)) |
167 | for p in projects: | 169 | for p in projects: |
168 | if p.relpath not in have: | 170 | if _RelPath(p) not in have: |
169 | paths.append(p.relpath) | 171 | paths.append(_RelPath(p)) |
170 | 172 | ||
171 | s = ' %s %s' % (in_type, ', '.join(paths)) | 173 | s = ' %s %s' % (in_type, ', '.join(paths)) |
172 | if not i.IsSplitCurrent and (width + 7 + len(s) < 80): | 174 | if not i.IsSplitCurrent and (width + 7 + len(s) < 80): |
diff --git a/subcmds/checkout.py b/subcmds/checkout.py index 9b429489..768b6027 100644 --- a/subcmds/checkout.py +++ b/subcmds/checkout.py | |||
@@ -47,7 +47,7 @@ The command is equivalent to: | |||
47 | nb = args[0] | 47 | nb = args[0] |
48 | err = [] | 48 | err = [] |
49 | success = [] | 49 | success = [] |
50 | all_projects = self.GetProjects(args[1:]) | 50 | all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only) |
51 | 51 | ||
52 | def _ProcessResults(_pool, pm, results): | 52 | def _ProcessResults(_pool, pm, results): |
53 | for status, project in results: | 53 | for status, project in results: |
diff --git a/subcmds/diff.py b/subcmds/diff.py index 00a7ec29..a1f4ba88 100644 --- a/subcmds/diff.py +++ b/subcmds/diff.py | |||
@@ -50,7 +50,7 @@ to the Unix 'patch' command. | |||
50 | return (ret, buf.getvalue()) | 50 | return (ret, buf.getvalue()) |
51 | 51 | ||
52 | def Execute(self, opt, args): | 52 | def Execute(self, opt, args): |
53 | all_projects = self.GetProjects(args) | 53 | all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
54 | 54 | ||
55 | def _ProcessResults(_pool, _output, results): | 55 | def _ProcessResults(_pool, _output, results): |
56 | ret = 0 | 56 | ret = 0 |
diff --git a/subcmds/diffmanifests.py b/subcmds/diffmanifests.py index f6cc30a2..0e5f4108 100644 --- a/subcmds/diffmanifests.py +++ b/subcmds/diffmanifests.py | |||
@@ -179,6 +179,9 @@ synced and their revisions won't be found. | |||
179 | def ValidateOptions(self, opt, args): | 179 | def ValidateOptions(self, opt, args): |
180 | if not args or len(args) > 2: | 180 | if not args or len(args) > 2: |
181 | self.OptionParser.error('missing manifests to diff') | 181 | self.OptionParser.error('missing manifests to diff') |
182 | if opt.this_manifest_only is False: | ||
183 | raise self.OptionParser.error( | ||
184 | '`diffmanifest` only supports the current tree') | ||
182 | 185 | ||
183 | def Execute(self, opt, args): | 186 | def Execute(self, opt, args): |
184 | self.out = _Coloring(self.client.globalConfig) | 187 | self.out = _Coloring(self.client.globalConfig) |
diff --git a/subcmds/download.py b/subcmds/download.py index 523f25e0..15824843 100644 --- a/subcmds/download.py +++ b/subcmds/download.py | |||
@@ -48,7 +48,7 @@ If no project is specified try to use current directory as a project. | |||
48 | dest='ffonly', action='store_true', | 48 | dest='ffonly', action='store_true', |
49 | help="force fast-forward merge") | 49 | help="force fast-forward merge") |
50 | 50 | ||
51 | def _ParseChangeIds(self, args): | 51 | def _ParseChangeIds(self, opt, args): |
52 | if not args: | 52 | if not args: |
53 | self.Usage() | 53 | self.Usage() |
54 | 54 | ||
@@ -77,7 +77,7 @@ If no project is specified try to use current directory as a project. | |||
77 | ps_id = max(int(match.group(1)), ps_id) | 77 | ps_id = max(int(match.group(1)), ps_id) |
78 | to_get.append((project, chg_id, ps_id)) | 78 | to_get.append((project, chg_id, ps_id)) |
79 | else: | 79 | else: |
80 | projects = self.GetProjects([a]) | 80 | projects = self.GetProjects([a], all_manifests=not opt.this_manifest_only) |
81 | if len(projects) > 1: | 81 | if len(projects) > 1: |
82 | # If the cwd is one of the projects, assume they want that. | 82 | # If the cwd is one of the projects, assume they want that. |
83 | try: | 83 | try: |
@@ -88,8 +88,8 @@ If no project is specified try to use current directory as a project. | |||
88 | print('error: %s matches too many projects; please re-run inside ' | 88 | print('error: %s matches too many projects; please re-run inside ' |
89 | 'the project checkout.' % (a,), file=sys.stderr) | 89 | 'the project checkout.' % (a,), file=sys.stderr) |
90 | for project in projects: | 90 | for project in projects: |
91 | print(' %s/ @ %s' % (project.relpath, project.revisionExpr), | 91 | print(' %s/ @ %s' % (project.RelPath(local=opt.this_manifest_only), |
92 | file=sys.stderr) | 92 | project.revisionExpr), file=sys.stderr) |
93 | sys.exit(1) | 93 | sys.exit(1) |
94 | else: | 94 | else: |
95 | project = projects[0] | 95 | project = projects[0] |
@@ -105,7 +105,7 @@ If no project is specified try to use current directory as a project. | |||
105 | self.OptionParser.error('-x and --ff are mutually exclusive options') | 105 | self.OptionParser.error('-x and --ff are mutually exclusive options') |
106 | 106 | ||
107 | def Execute(self, opt, args): | 107 | def Execute(self, opt, args): |
108 | for project, change_id, ps_id in self._ParseChangeIds(args): | 108 | for project, change_id, ps_id in self._ParseChangeIds(opt, args): |
109 | dl = project.DownloadPatchSet(change_id, ps_id) | 109 | dl = project.DownloadPatchSet(change_id, ps_id) |
110 | if not dl: | 110 | if not dl: |
111 | print('[%s] change %d/%d not found' | 111 | print('[%s] change %d/%d not found' |
diff --git a/subcmds/forall.py b/subcmds/forall.py index 7c1dea9e..cc578b52 100644 --- a/subcmds/forall.py +++ b/subcmds/forall.py | |||
@@ -168,6 +168,7 @@ without iterating through the remaining projects. | |||
168 | 168 | ||
169 | def Execute(self, opt, args): | 169 | def Execute(self, opt, args): |
170 | cmd = [opt.command[0]] | 170 | cmd = [opt.command[0]] |
171 | all_trees = not opt.this_manifest_only | ||
171 | 172 | ||
172 | shell = True | 173 | shell = True |
173 | if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]): | 174 | if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]): |
@@ -213,11 +214,11 @@ without iterating through the remaining projects. | |||
213 | self.manifest.Override(smart_sync_manifest_path) | 214 | self.manifest.Override(smart_sync_manifest_path) |
214 | 215 | ||
215 | if opt.regex: | 216 | if opt.regex: |
216 | projects = self.FindProjects(args) | 217 | projects = self.FindProjects(args, all_manifests=all_trees) |
217 | elif opt.inverse_regex: | 218 | elif opt.inverse_regex: |
218 | projects = self.FindProjects(args, inverse=True) | 219 | projects = self.FindProjects(args, inverse=True, all_manifests=all_trees) |
219 | else: | 220 | else: |
220 | projects = self.GetProjects(args, groups=opt.groups) | 221 | projects = self.GetProjects(args, groups=opt.groups, all_manifests=all_trees) |
221 | 222 | ||
222 | os.environ['REPO_COUNT'] = str(len(projects)) | 223 | os.environ['REPO_COUNT'] = str(len(projects)) |
223 | 224 | ||
@@ -290,6 +291,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config): | |||
290 | 291 | ||
291 | setenv('REPO_PROJECT', project.name) | 292 | setenv('REPO_PROJECT', project.name) |
292 | setenv('REPO_PATH', project.relpath) | 293 | setenv('REPO_PATH', project.relpath) |
294 | setenv('REPO_OUTERPATH', project.RelPath(local=opt.this_manifest_only)) | ||
293 | setenv('REPO_REMOTE', project.remote.name) | 295 | setenv('REPO_REMOTE', project.remote.name) |
294 | try: | 296 | try: |
295 | # If we aren't in a fully synced state and we don't have the ref the manifest | 297 | # If we aren't in a fully synced state and we don't have the ref the manifest |
@@ -320,7 +322,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config): | |||
320 | output = '' | 322 | output = '' |
321 | if ((opt.project_header and opt.verbose) | 323 | if ((opt.project_header and opt.verbose) |
322 | or not opt.project_header): | 324 | or not opt.project_header): |
323 | output = 'skipping %s/' % project.relpath | 325 | output = 'skipping %s/' % project.RelPath(local=opt.this_manifest_only) |
324 | return (1, output) | 326 | return (1, output) |
325 | 327 | ||
326 | if opt.verbose: | 328 | if opt.verbose: |
@@ -344,7 +346,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config): | |||
344 | if mirror: | 346 | if mirror: |
345 | project_header_path = project.name | 347 | project_header_path = project.name |
346 | else: | 348 | else: |
347 | project_header_path = project.relpath | 349 | project_header_path = project.RelPath(local=opt.this_manifest_only) |
348 | out.project('project %s/' % project_header_path) | 350 | out.project('project %s/' % project_header_path) |
349 | out.nl() | 351 | out.nl() |
350 | buf.write(output) | 352 | buf.write(output) |
diff --git a/subcmds/gitc_init.py b/subcmds/gitc_init.py index e705b613..1d81baf5 100644 --- a/subcmds/gitc_init.py +++ b/subcmds/gitc_init.py | |||
@@ -24,6 +24,7 @@ import wrapper | |||
24 | 24 | ||
25 | class GitcInit(init.Init, GitcAvailableCommand): | 25 | class GitcInit(init.Init, GitcAvailableCommand): |
26 | COMMON = True | 26 | COMMON = True |
27 | MULTI_MANIFEST_SUPPORT = False | ||
27 | helpSummary = "Initialize a GITC Client." | 28 | helpSummary = "Initialize a GITC Client." |
28 | helpUsage = """ | 29 | helpUsage = """ |
29 | %prog [options] [client name] | 30 | %prog [options] [client name] |
diff --git a/subcmds/grep.py b/subcmds/grep.py index 8ac4ba14..93c9ae51 100644 --- a/subcmds/grep.py +++ b/subcmds/grep.py | |||
@@ -172,15 +172,16 @@ contain a line that matches both expressions: | |||
172 | return (project, p.Wait(), p.stdout, p.stderr) | 172 | return (project, p.Wait(), p.stdout, p.stderr) |
173 | 173 | ||
174 | @staticmethod | 174 | @staticmethod |
175 | def _ProcessResults(full_name, have_rev, _pool, out, results): | 175 | def _ProcessResults(full_name, have_rev, opt, _pool, out, results): |
176 | git_failed = False | 176 | git_failed = False |
177 | bad_rev = False | 177 | bad_rev = False |
178 | have_match = False | 178 | have_match = False |
179 | _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) | ||
179 | 180 | ||
180 | for project, rc, stdout, stderr in results: | 181 | for project, rc, stdout, stderr in results: |
181 | if rc < 0: | 182 | if rc < 0: |
182 | git_failed = True | 183 | git_failed = True |
183 | out.project('--- project %s ---' % project.relpath) | 184 | out.project('--- project %s ---' % _RelPath(project)) |
184 | out.nl() | 185 | out.nl() |
185 | out.fail('%s', stderr) | 186 | out.fail('%s', stderr) |
186 | out.nl() | 187 | out.nl() |
@@ -192,7 +193,7 @@ contain a line that matches both expressions: | |||
192 | if have_rev and 'fatal: ambiguous argument' in stderr: | 193 | if have_rev and 'fatal: ambiguous argument' in stderr: |
193 | bad_rev = True | 194 | bad_rev = True |
194 | else: | 195 | else: |
195 | out.project('--- project %s ---' % project.relpath) | 196 | out.project('--- project %s ---' % _RelPath(project)) |
196 | out.nl() | 197 | out.nl() |
197 | out.fail('%s', stderr.strip()) | 198 | out.fail('%s', stderr.strip()) |
198 | out.nl() | 199 | out.nl() |
@@ -208,13 +209,13 @@ contain a line that matches both expressions: | |||
208 | rev, line = line.split(':', 1) | 209 | rev, line = line.split(':', 1) |
209 | out.write("%s", rev) | 210 | out.write("%s", rev) |
210 | out.write(':') | 211 | out.write(':') |
211 | out.project(project.relpath) | 212 | out.project(_RelPath(project)) |
212 | out.write('/') | 213 | out.write('/') |
213 | out.write("%s", line) | 214 | out.write("%s", line) |
214 | out.nl() | 215 | out.nl() |
215 | elif full_name: | 216 | elif full_name: |
216 | for line in r: | 217 | for line in r: |
217 | out.project(project.relpath) | 218 | out.project(_RelPath(project)) |
218 | out.write('/') | 219 | out.write('/') |
219 | out.write("%s", line) | 220 | out.write("%s", line) |
220 | out.nl() | 221 | out.nl() |
@@ -239,7 +240,7 @@ contain a line that matches both expressions: | |||
239 | cmd_argv.append(args[0]) | 240 | cmd_argv.append(args[0]) |
240 | args = args[1:] | 241 | args = args[1:] |
241 | 242 | ||
242 | projects = self.GetProjects(args) | 243 | projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
243 | 244 | ||
244 | full_name = False | 245 | full_name = False |
245 | if len(projects) > 1: | 246 | if len(projects) > 1: |
@@ -259,7 +260,7 @@ contain a line that matches both expressions: | |||
259 | opt.jobs, | 260 | opt.jobs, |
260 | functools.partial(self._ExecuteOne, cmd_argv), | 261 | functools.partial(self._ExecuteOne, cmd_argv), |
261 | projects, | 262 | projects, |
262 | callback=functools.partial(self._ProcessResults, full_name, have_rev), | 263 | callback=functools.partial(self._ProcessResults, full_name, have_rev, opt), |
263 | output=out, | 264 | output=out, |
264 | ordered=True) | 265 | ordered=True) |
265 | 266 | ||
diff --git a/subcmds/info.py b/subcmds/info.py index 6c1246ef..4bedf9d5 100644 --- a/subcmds/info.py +++ b/subcmds/info.py | |||
@@ -61,6 +61,8 @@ class Info(PagedCommand): | |||
61 | 61 | ||
62 | self.opt = opt | 62 | self.opt = opt |
63 | 63 | ||
64 | if not opt.this_manifest_only: | ||
65 | self.manifest = self.manifest.outer_client | ||
64 | manifestConfig = self.manifest.manifestProject.config | 66 | manifestConfig = self.manifest.manifestProject.config |
65 | mergeBranch = manifestConfig.GetBranch("default").merge | 67 | mergeBranch = manifestConfig.GetBranch("default").merge |
66 | manifestGroups = (manifestConfig.GetString('manifest.groups') | 68 | manifestGroups = (manifestConfig.GetString('manifest.groups') |
@@ -80,17 +82,17 @@ class Info(PagedCommand): | |||
80 | self.printSeparator() | 82 | self.printSeparator() |
81 | 83 | ||
82 | if not opt.overview: | 84 | if not opt.overview: |
83 | self.printDiffInfo(args) | 85 | self._printDiffInfo(opt, args) |
84 | else: | 86 | else: |
85 | self.printCommitOverview(args) | 87 | self._printCommitOverview(opt, args) |
86 | 88 | ||
87 | def printSeparator(self): | 89 | def printSeparator(self): |
88 | self.text("----------------------------") | 90 | self.text("----------------------------") |
89 | self.out.nl() | 91 | self.out.nl() |
90 | 92 | ||
91 | def printDiffInfo(self, args): | 93 | def _printDiffInfo(self, opt, args): |
92 | # We let exceptions bubble up to main as they'll be well structured. | 94 | # We let exceptions bubble up to main as they'll be well structured. |
93 | projs = self.GetProjects(args) | 95 | projs = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
94 | 96 | ||
95 | for p in projs: | 97 | for p in projs: |
96 | self.heading("Project: ") | 98 | self.heading("Project: ") |
@@ -179,9 +181,9 @@ class Info(PagedCommand): | |||
179 | self.text(" ".join(split[1:])) | 181 | self.text(" ".join(split[1:])) |
180 | self.out.nl() | 182 | self.out.nl() |
181 | 183 | ||
182 | def printCommitOverview(self, args): | 184 | def _printCommitOverview(self, opt, args): |
183 | all_branches = [] | 185 | all_branches = [] |
184 | for project in self.GetProjects(args): | 186 | for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only): |
185 | br = [project.GetUploadableBranch(x) | 187 | br = [project.GetUploadableBranch(x) |
186 | for x in project.GetBranches()] | 188 | for x in project.GetBranches()] |
187 | br = [x for x in br if x] | 189 | br = [x for x in br if x] |
@@ -200,7 +202,7 @@ class Info(PagedCommand): | |||
200 | if project != branch.project: | 202 | if project != branch.project: |
201 | project = branch.project | 203 | project = branch.project |
202 | self.out.nl() | 204 | self.out.nl() |
203 | self.headtext(project.relpath) | 205 | self.headtext(project.RelPath(local=opt.this_manifest_only)) |
204 | self.out.nl() | 206 | self.out.nl() |
205 | 207 | ||
206 | commits = branch.commits | 208 | commits = branch.commits |
diff --git a/subcmds/init.py b/subcmds/init.py index 32c85f79..b9775a34 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
@@ -32,6 +32,7 @@ from wrapper import Wrapper | |||
32 | 32 | ||
33 | class Init(InteractiveCommand, MirrorSafeCommand): | 33 | class Init(InteractiveCommand, MirrorSafeCommand): |
34 | COMMON = True | 34 | COMMON = True |
35 | MULTI_MANIFEST_SUPPORT = False | ||
35 | helpSummary = "Initialize a repo client checkout in the current directory" | 36 | helpSummary = "Initialize a repo client checkout in the current directory" |
36 | helpUsage = """ | 37 | helpUsage = """ |
37 | %prog [options] [manifest url] | 38 | %prog [options] [manifest url] |
@@ -90,6 +91,17 @@ to update the working directory files. | |||
90 | 91 | ||
91 | def _Options(self, p, gitc_init=False): | 92 | def _Options(self, p, gitc_init=False): |
92 | Wrapper().InitParser(p, gitc_init=gitc_init) | 93 | Wrapper().InitParser(p, gitc_init=gitc_init) |
94 | m = p.add_option_group('Multi-manifest') | ||
95 | m.add_option('--outer-manifest', action='store_true', | ||
96 | help='operate starting at the outermost manifest') | ||
97 | m.add_option('--no-outer-manifest', dest='outer_manifest', | ||
98 | action='store_false', default=None, | ||
99 | help='do not operate on outer manifests') | ||
100 | m.add_option('--this-manifest-only', action='store_true', default=None, | ||
101 | help='only operate on this (sub)manifest') | ||
102 | m.add_option('--no-this-manifest-only', '--all-manifests', | ||
103 | dest='this_manifest_only', action='store_false', | ||
104 | help='operate on this manifest and its submanifests') | ||
93 | 105 | ||
94 | def _RegisteredEnvironmentOptions(self): | 106 | def _RegisteredEnvironmentOptions(self): |
95 | return {'REPO_MANIFEST_URL': 'manifest_url', | 107 | return {'REPO_MANIFEST_URL': 'manifest_url', |
diff --git a/subcmds/list.py b/subcmds/list.py index 6adf85b7..ad8036ee 100644 --- a/subcmds/list.py +++ b/subcmds/list.py | |||
@@ -77,16 +77,17 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. | |||
77 | args: Positional args. Can be a list of projects to list, or empty. | 77 | args: Positional args. Can be a list of projects to list, or empty. |
78 | """ | 78 | """ |
79 | if not opt.regex: | 79 | if not opt.regex: |
80 | projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all) | 80 | projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all, |
81 | all_manifests=not opt.this_manifest_only) | ||
81 | else: | 82 | else: |
82 | projects = self.FindProjects(args) | 83 | projects = self.FindProjects(args, all_manifests=not opt.this_manifest_only) |
83 | 84 | ||
84 | def _getpath(x): | 85 | def _getpath(x): |
85 | if opt.fullpath: | 86 | if opt.fullpath: |
86 | return x.worktree | 87 | return x.worktree |
87 | if opt.relative_to: | 88 | if opt.relative_to: |
88 | return os.path.relpath(x.worktree, opt.relative_to) | 89 | return os.path.relpath(x.worktree, opt.relative_to) |
89 | return x.relpath | 90 | return x.RelPath(local=opt.this_manifest_only) |
90 | 91 | ||
91 | lines = [] | 92 | lines = [] |
92 | for project in projects: | 93 | for project in projects: |
diff --git a/subcmds/manifest.py b/subcmds/manifest.py index 0fbdeac0..08905cb4 100644 --- a/subcmds/manifest.py +++ b/subcmds/manifest.py | |||
@@ -15,6 +15,7 @@ | |||
15 | import json | 15 | import json |
16 | import os | 16 | import os |
17 | import sys | 17 | import sys |
18 | import optparse | ||
18 | 19 | ||
19 | from command import PagedCommand | 20 | from command import PagedCommand |
20 | 21 | ||
@@ -75,7 +76,7 @@ to indicate the remote ref to push changes to via 'repo upload'. | |||
75 | p.add_option('-o', '--output-file', | 76 | p.add_option('-o', '--output-file', |
76 | dest='output_file', | 77 | dest='output_file', |
77 | default='-', | 78 | default='-', |
78 | help='file to save the manifest to', | 79 | help='file to save the manifest to. (Filename prefix for multi-tree.)', |
79 | metavar='-|NAME.xml') | 80 | metavar='-|NAME.xml') |
80 | 81 | ||
81 | def _Output(self, opt): | 82 | def _Output(self, opt): |
@@ -83,36 +84,45 @@ to indicate the remote ref to push changes to via 'repo upload'. | |||
83 | if opt.manifest_name: | 84 | if opt.manifest_name: |
84 | self.manifest.Override(opt.manifest_name, False) | 85 | self.manifest.Override(opt.manifest_name, False) |
85 | 86 | ||
86 | if opt.output_file == '-': | 87 | for manifest in self.ManifestList(opt): |
87 | fd = sys.stdout | 88 | output_file = opt.output_file |
88 | else: | 89 | if output_file == '-': |
89 | fd = open(opt.output_file, 'w') | 90 | fd = sys.stdout |
90 | 91 | else: | |
91 | self.manifest.SetUseLocalManifests(not opt.ignore_local_manifests) | 92 | if manifest.path_prefix: |
92 | 93 | output_file = f'{opt.output_file}:{manifest.path_prefix.replace("/", "%2f")}' | |
93 | if opt.json: | 94 | fd = open(output_file, 'w') |
94 | print('warning: --json is experimental!', file=sys.stderr) | 95 | |
95 | doc = self.manifest.ToDict(peg_rev=opt.peg_rev, | 96 | manifest.SetUseLocalManifests(not opt.ignore_local_manifests) |
96 | peg_rev_upstream=opt.peg_rev_upstream, | 97 | |
97 | peg_rev_dest_branch=opt.peg_rev_dest_branch) | 98 | if opt.json: |
98 | 99 | print('warning: --json is experimental!', file=sys.stderr) | |
99 | json_settings = { | 100 | doc = manifest.ToDict(peg_rev=opt.peg_rev, |
100 | # JSON style guide says Uunicode characters are fully allowed. | 101 | peg_rev_upstream=opt.peg_rev_upstream, |
101 | 'ensure_ascii': False, | 102 | peg_rev_dest_branch=opt.peg_rev_dest_branch) |
102 | # We use 2 space indent to match JSON style guide. | 103 | |
103 | 'indent': 2 if opt.pretty else None, | 104 | json_settings = { |
104 | 'separators': (',', ': ') if opt.pretty else (',', ':'), | 105 | # JSON style guide says Uunicode characters are fully allowed. |
105 | 'sort_keys': True, | 106 | 'ensure_ascii': False, |
106 | } | 107 | # We use 2 space indent to match JSON style guide. |
107 | fd.write(json.dumps(doc, **json_settings)) | 108 | 'indent': 2 if opt.pretty else None, |
108 | else: | 109 | 'separators': (',', ': ') if opt.pretty else (',', ':'), |
109 | self.manifest.Save(fd, | 110 | 'sort_keys': True, |
110 | peg_rev=opt.peg_rev, | 111 | } |
111 | peg_rev_upstream=opt.peg_rev_upstream, | 112 | fd.write(json.dumps(doc, **json_settings)) |
112 | peg_rev_dest_branch=opt.peg_rev_dest_branch) | 113 | else: |
113 | fd.close() | 114 | manifest.Save(fd, |
114 | if opt.output_file != '-': | 115 | peg_rev=opt.peg_rev, |
115 | print('Saved manifest to %s' % opt.output_file, file=sys.stderr) | 116 | peg_rev_upstream=opt.peg_rev_upstream, |
117 | peg_rev_dest_branch=opt.peg_rev_dest_branch) | ||
118 | if output_file != '-': | ||
119 | fd.close() | ||
120 | if manifest.path_prefix: | ||
121 | print(f'Saved {manifest.path_prefix} submanifest to {output_file}', | ||
122 | file=sys.stderr) | ||
123 | else: | ||
124 | print(f'Saved manifest to {output_file}', file=sys.stderr) | ||
125 | |||
116 | 126 | ||
117 | def ValidateOptions(self, opt, args): | 127 | def ValidateOptions(self, opt, args): |
118 | if args: | 128 | if args: |
diff --git a/subcmds/overview.py b/subcmds/overview.py index 63f5a79e..11dba95f 100644 --- a/subcmds/overview.py +++ b/subcmds/overview.py | |||
@@ -47,7 +47,7 @@ are displayed. | |||
47 | 47 | ||
48 | def Execute(self, opt, args): | 48 | def Execute(self, opt, args): |
49 | all_branches = [] | 49 | all_branches = [] |
50 | for project in self.GetProjects(args): | 50 | for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only): |
51 | br = [project.GetUploadableBranch(x) | 51 | br = [project.GetUploadableBranch(x) |
52 | for x in project.GetBranches()] | 52 | for x in project.GetBranches()] |
53 | br = [x for x in br if x] | 53 | br = [x for x in br if x] |
@@ -76,7 +76,7 @@ are displayed. | |||
76 | if project != branch.project: | 76 | if project != branch.project: |
77 | project = branch.project | 77 | project = branch.project |
78 | out.nl() | 78 | out.nl() |
79 | out.project('project %s/' % project.relpath) | 79 | out.project('project %s/' % project.RelPath(local=opt.this_manifest_only)) |
80 | out.nl() | 80 | out.nl() |
81 | 81 | ||
82 | commits = branch.commits | 82 | commits = branch.commits |
diff --git a/subcmds/prune.py b/subcmds/prune.py index 584ee7ed..251accaa 100644 --- a/subcmds/prune.py +++ b/subcmds/prune.py | |||
@@ -31,7 +31,7 @@ class Prune(PagedCommand): | |||
31 | return project.PruneHeads() | 31 | return project.PruneHeads() |
32 | 32 | ||
33 | def Execute(self, opt, args): | 33 | def Execute(self, opt, args): |
34 | projects = self.GetProjects(args) | 34 | projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
35 | 35 | ||
36 | # NB: Should be able to refactor this module to display summary as results | 36 | # NB: Should be able to refactor this module to display summary as results |
37 | # come back from children. | 37 | # come back from children. |
@@ -63,7 +63,7 @@ class Prune(PagedCommand): | |||
63 | if project != branch.project: | 63 | if project != branch.project: |
64 | project = branch.project | 64 | project = branch.project |
65 | out.nl() | 65 | out.nl() |
66 | out.project('project %s/' % project.relpath) | 66 | out.project('project %s/' % project.RelPath(local=opt.this_manifest_only)) |
67 | out.nl() | 67 | out.nl() |
68 | 68 | ||
69 | print('%s %-33s ' % ( | 69 | print('%s %-33s ' % ( |
diff --git a/subcmds/rebase.py b/subcmds/rebase.py index 7c53eb7a..3d1a63e4 100644 --- a/subcmds/rebase.py +++ b/subcmds/rebase.py | |||
@@ -69,7 +69,7 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
69 | 'consistent if you previously synced to a manifest)') | 69 | 'consistent if you previously synced to a manifest)') |
70 | 70 | ||
71 | def Execute(self, opt, args): | 71 | def Execute(self, opt, args): |
72 | all_projects = self.GetProjects(args) | 72 | all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
73 | one_project = len(all_projects) == 1 | 73 | one_project = len(all_projects) == 1 |
74 | 74 | ||
75 | if opt.interactive and not one_project: | 75 | if opt.interactive and not one_project: |
@@ -98,6 +98,7 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
98 | config = self.manifest.manifestProject.config | 98 | config = self.manifest.manifestProject.config |
99 | out = RebaseColoring(config) | 99 | out = RebaseColoring(config) |
100 | out.redirect(sys.stdout) | 100 | out.redirect(sys.stdout) |
101 | _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) | ||
101 | 102 | ||
102 | ret = 0 | 103 | ret = 0 |
103 | for project in all_projects: | 104 | for project in all_projects: |
@@ -107,7 +108,7 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
107 | cb = project.CurrentBranch | 108 | cb = project.CurrentBranch |
108 | if not cb: | 109 | if not cb: |
109 | if one_project: | 110 | if one_project: |
110 | print("error: project %s has a detached HEAD" % project.relpath, | 111 | print("error: project %s has a detached HEAD" % _RelPath(project), |
111 | file=sys.stderr) | 112 | file=sys.stderr) |
112 | return 1 | 113 | return 1 |
113 | # ignore branches with detatched HEADs | 114 | # ignore branches with detatched HEADs |
@@ -117,7 +118,7 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
117 | if not upbranch.LocalMerge: | 118 | if not upbranch.LocalMerge: |
118 | if one_project: | 119 | if one_project: |
119 | print("error: project %s does not track any remote branches" | 120 | print("error: project %s does not track any remote branches" |
120 | % project.relpath, file=sys.stderr) | 121 | % _RelPath(project), file=sys.stderr) |
121 | return 1 | 122 | return 1 |
122 | # ignore branches without remotes | 123 | # ignore branches without remotes |
123 | continue | 124 | continue |
@@ -130,7 +131,7 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
130 | args.append(upbranch.LocalMerge) | 131 | args.append(upbranch.LocalMerge) |
131 | 132 | ||
132 | out.project('project %s: rebasing %s -> %s', | 133 | out.project('project %s: rebasing %s -> %s', |
133 | project.relpath, cb, upbranch.LocalMerge) | 134 | _RelPath(project), cb, upbranch.LocalMerge) |
134 | out.nl() | 135 | out.nl() |
135 | out.flush() | 136 | out.flush() |
136 | 137 | ||
diff --git a/subcmds/stage.py b/subcmds/stage.py index 0389a4ff..5f17cb64 100644 --- a/subcmds/stage.py +++ b/subcmds/stage.py | |||
@@ -50,7 +50,9 @@ The '%prog' command stages files to prepare the next commit. | |||
50 | self.Usage() | 50 | self.Usage() |
51 | 51 | ||
52 | def _Interactive(self, opt, args): | 52 | def _Interactive(self, opt, args): |
53 | all_projects = [p for p in self.GetProjects(args) if p.IsDirty()] | 53 | all_projects = [ |
54 | p for p in self.GetProjects(args, all_manifests=not opt.this_manifest_only) | ||
55 | if p.IsDirty()] | ||
54 | if not all_projects: | 56 | if not all_projects: |
55 | print('no projects have uncommitted modifications', file=sys.stderr) | 57 | print('no projects have uncommitted modifications', file=sys.stderr) |
56 | return | 58 | return |
@@ -62,7 +64,8 @@ The '%prog' command stages files to prepare the next commit. | |||
62 | 64 | ||
63 | for i in range(len(all_projects)): | 65 | for i in range(len(all_projects)): |
64 | project = all_projects[i] | 66 | project = all_projects[i] |
65 | out.write('%3d: %s', i + 1, project.relpath + '/') | 67 | out.write('%3d: %s', i + 1, |
68 | project.RelPath(local=opt.this_manifest_only) + '/') | ||
66 | out.nl() | 69 | out.nl() |
67 | out.nl() | 70 | out.nl() |
68 | 71 | ||
@@ -99,7 +102,9 @@ The '%prog' command stages files to prepare the next commit. | |||
99 | _AddI(all_projects[a_index - 1]) | 102 | _AddI(all_projects[a_index - 1]) |
100 | continue | 103 | continue |
101 | 104 | ||
102 | projects = [p for p in all_projects if a in [p.name, p.relpath]] | 105 | projects = [ |
106 | p for p in all_projects | ||
107 | if a in [p.name, p.RelPath(local=opt.this_manifest_only)]] | ||
103 | if len(projects) == 1: | 108 | if len(projects) == 1: |
104 | _AddI(projects[0]) | 109 | _AddI(projects[0]) |
105 | continue | 110 | continue |
diff --git a/subcmds/start.py b/subcmds/start.py index 2addaf2e..809df963 100644 --- a/subcmds/start.py +++ b/subcmds/start.py | |||
@@ -84,7 +84,8 @@ revision specified in the manifest. | |||
84 | projects = ['.'] # start it in the local project by default | 84 | projects = ['.'] # start it in the local project by default |
85 | 85 | ||
86 | all_projects = self.GetProjects(projects, | 86 | all_projects = self.GetProjects(projects, |
87 | missing_ok=bool(self.gitc_manifest)) | 87 | missing_ok=bool(self.gitc_manifest), |
88 | all_manifests=not opt.this_manifest_only) | ||
88 | 89 | ||
89 | # This must happen after we find all_projects, since GetProjects may need | 90 | # This must happen after we find all_projects, since GetProjects may need |
90 | # the local directory, which will disappear once we save the GITC manifest. | 91 | # the local directory, which will disappear once we save the GITC manifest. |
@@ -137,6 +138,6 @@ revision specified in the manifest. | |||
137 | 138 | ||
138 | if err: | 139 | if err: |
139 | for p in err: | 140 | for p in err: |
140 | print("error: %s/: cannot start %s" % (p.relpath, nb), | 141 | print("error: %s/: cannot start %s" % (p.RelPath(local=opt.this_manifest_only), nb), |
141 | file=sys.stderr) | 142 | file=sys.stderr) |
142 | sys.exit(1) | 143 | sys.exit(1) |
diff --git a/subcmds/status.py b/subcmds/status.py index 5b669547..0aa4200f 100644 --- a/subcmds/status.py +++ b/subcmds/status.py | |||
@@ -117,7 +117,7 @@ the following meanings: | |||
117 | outstring.append(''.join([status_header, item, '/'])) | 117 | outstring.append(''.join([status_header, item, '/'])) |
118 | 118 | ||
119 | def Execute(self, opt, args): | 119 | def Execute(self, opt, args): |
120 | all_projects = self.GetProjects(args) | 120 | all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
121 | 121 | ||
122 | def _ProcessResults(_pool, _output, results): | 122 | def _ProcessResults(_pool, _output, results): |
123 | ret = 0 | 123 | ret = 0 |
@@ -141,9 +141,10 @@ the following meanings: | |||
141 | if opt.orphans: | 141 | if opt.orphans: |
142 | proj_dirs = set() | 142 | proj_dirs = set() |
143 | proj_dirs_parents = set() | 143 | proj_dirs_parents = set() |
144 | for project in self.GetProjects(None, missing_ok=True): | 144 | for project in self.GetProjects(None, missing_ok=True, all_manifests=not opt.this_manifest_only): |
145 | proj_dirs.add(project.relpath) | 145 | relpath = project.RelPath(local=opt.this_manifest_only) |
146 | (head, _tail) = os.path.split(project.relpath) | 146 | proj_dirs.add(relpath) |
147 | (head, _tail) = os.path.split(relpath) | ||
147 | while head != "": | 148 | while head != "": |
148 | proj_dirs_parents.add(head) | 149 | proj_dirs_parents.add(head) |
149 | (head, _tail) = os.path.split(head) | 150 | (head, _tail) = os.path.split(head) |
diff --git a/subcmds/sync.py b/subcmds/sync.py index 707c5bbd..f5584dc8 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -66,6 +66,7 @@ _ONE_DAY_S = 24 * 60 * 60 | |||
66 | class Sync(Command, MirrorSafeCommand): | 66 | class Sync(Command, MirrorSafeCommand): |
67 | jobs = 1 | 67 | jobs = 1 |
68 | COMMON = True | 68 | COMMON = True |
69 | MULTI_MANIFEST_SUPPORT = False | ||
69 | helpSummary = "Update working tree to the latest revision" | 70 | helpSummary = "Update working tree to the latest revision" |
70 | helpUsage = """ | 71 | helpUsage = """ |
71 | %prog [<project>...] | 72 | %prog [<project>...] |
@@ -704,7 +705,7 @@ later is required to fix a server side protocol bug. | |||
704 | if project.relpath: | 705 | if project.relpath: |
705 | new_project_paths.append(project.relpath) | 706 | new_project_paths.append(project.relpath) |
706 | file_name = 'project.list' | 707 | file_name = 'project.list' |
707 | file_path = os.path.join(self.repodir, file_name) | 708 | file_path = os.path.join(self.manifest.subdir, file_name) |
708 | old_project_paths = [] | 709 | old_project_paths = [] |
709 | 710 | ||
710 | if os.path.exists(file_path): | 711 | if os.path.exists(file_path): |
@@ -760,7 +761,7 @@ later is required to fix a server side protocol bug. | |||
760 | } | 761 | } |
761 | 762 | ||
762 | copylinkfile_name = 'copy-link-files.json' | 763 | copylinkfile_name = 'copy-link-files.json' |
763 | copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name) | 764 | copylinkfile_path = os.path.join(self.manifest.subdir, copylinkfile_name) |
764 | old_copylinkfile_paths = {} | 765 | old_copylinkfile_paths = {} |
765 | 766 | ||
766 | if os.path.exists(copylinkfile_path): | 767 | if os.path.exists(copylinkfile_path): |
@@ -932,6 +933,9 @@ later is required to fix a server side protocol bug. | |||
932 | if opt.prune is None: | 933 | if opt.prune is None: |
933 | opt.prune = True | 934 | opt.prune = True |
934 | 935 | ||
936 | if self.manifest.is_multimanifest and not opt.this_manifest_only and args: | ||
937 | self.OptionParser.error('partial syncs must use --this-manifest-only') | ||
938 | |||
935 | def Execute(self, opt, args): | 939 | def Execute(self, opt, args): |
936 | if opt.jobs: | 940 | if opt.jobs: |
937 | self.jobs = opt.jobs | 941 | self.jobs = opt.jobs |
diff --git a/subcmds/upload.py b/subcmds/upload.py index c48deab6..ef3d8e9d 100644 --- a/subcmds/upload.py +++ b/subcmds/upload.py | |||
@@ -226,7 +226,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
226 | 226 | ||
227 | destination = opt.dest_branch or project.dest_branch or project.revisionExpr | 227 | destination = opt.dest_branch or project.dest_branch or project.revisionExpr |
228 | print('Upload project %s/ to remote branch %s%s:' % | 228 | print('Upload project %s/ to remote branch %s%s:' % |
229 | (project.relpath, destination, ' (private)' if opt.private else '')) | 229 | (project.RelPath(local=opt.this_manifest_only), destination, |
230 | ' (private)' if opt.private else '')) | ||
230 | print(' branch %s (%2d commit%s, %s):' % ( | 231 | print(' branch %s (%2d commit%s, %s):' % ( |
231 | name, | 232 | name, |
232 | len(commit_list), | 233 | len(commit_list), |
@@ -262,7 +263,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
262 | script.append('# Uncomment the branches to upload:') | 263 | script.append('# Uncomment the branches to upload:') |
263 | for project, avail in pending: | 264 | for project, avail in pending: |
264 | script.append('#') | 265 | script.append('#') |
265 | script.append('# project %s/:' % project.relpath) | 266 | script.append('# project %s/:' % project.RelPath(local=opt.this_manifest_only)) |
266 | 267 | ||
267 | b = {} | 268 | b = {} |
268 | for branch in avail: | 269 | for branch in avail: |
@@ -285,7 +286,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
285 | script.append('# %s' % commit) | 286 | script.append('# %s' % commit) |
286 | b[name] = branch | 287 | b[name] = branch |
287 | 288 | ||
288 | projects[project.relpath] = project | 289 | projects[project.RelPath(local=opt.this_manifest_only)] = project |
289 | branches[project.name] = b | 290 | branches[project.name] = b |
290 | script.append('') | 291 | script.append('') |
291 | 292 | ||
@@ -313,7 +314,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
313 | _die('project for branch %s not in script', name) | 314 | _die('project for branch %s not in script', name) |
314 | branch = branches[project.name].get(name) | 315 | branch = branches[project.name].get(name) |
315 | if not branch: | 316 | if not branch: |
316 | _die('branch %s not in %s', name, project.relpath) | 317 | _die('branch %s not in %s', name, project.RelPath(local=opt.this_manifest_only)) |
317 | todo.append(branch) | 318 | todo.append(branch) |
318 | if not todo: | 319 | if not todo: |
319 | _die("nothing uncommented for upload") | 320 | _die("nothing uncommented for upload") |
@@ -481,7 +482,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
481 | else: | 482 | else: |
482 | fmt = '\n (%s)' | 483 | fmt = '\n (%s)' |
483 | print(('[FAILED] %-15s %-15s' + fmt) % ( | 484 | print(('[FAILED] %-15s %-15s' + fmt) % ( |
484 | branch.project.relpath + '/', | 485 | branch.project.RelPath(local=opt.this_manifest_only) + '/', |
485 | branch.name, | 486 | branch.name, |
486 | str(branch.error)), | 487 | str(branch.error)), |
487 | file=sys.stderr) | 488 | file=sys.stderr) |
@@ -490,7 +491,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
490 | for branch in todo: | 491 | for branch in todo: |
491 | if branch.uploaded: | 492 | if branch.uploaded: |
492 | print('[OK ] %-15s %s' % ( | 493 | print('[OK ] %-15s %s' % ( |
493 | branch.project.relpath + '/', | 494 | branch.project.RelPath(local=opt.this_manifest_only) + '/', |
494 | branch.name), | 495 | branch.name), |
495 | file=sys.stderr) | 496 | file=sys.stderr) |
496 | 497 | ||
@@ -524,7 +525,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
524 | return (project, avail) | 525 | return (project, avail) |
525 | 526 | ||
526 | def Execute(self, opt, args): | 527 | def Execute(self, opt, args): |
527 | projects = self.GetProjects(args) | 528 | projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) |
528 | 529 | ||
529 | def _ProcessResults(_pool, _out, results): | 530 | def _ProcessResults(_pool, _out, results): |
530 | pending = [] | 531 | pending = [] |
@@ -534,7 +535,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
534 | print('repo: error: %s: Unable to upload branch "%s". ' | 535 | print('repo: error: %s: Unable to upload branch "%s". ' |
535 | 'You might be able to fix the branch by running:\n' | 536 | 'You might be able to fix the branch by running:\n' |
536 | ' git branch --set-upstream-to m/%s' % | 537 | ' git branch --set-upstream-to m/%s' % |
537 | (project.relpath, project.CurrentBranch, self.manifest.branch), | 538 | (project.RelPath(local=opt.this_manifest_only), project.CurrentBranch, |
539 | project.manifest.branch), | ||
538 | file=sys.stderr) | 540 | file=sys.stderr) |
539 | elif avail: | 541 | elif avail: |
540 | pending.append(result) | 542 | pending.append(result) |
@@ -554,15 +556,23 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
554 | (opt.branch,), file=sys.stderr) | 556 | (opt.branch,), file=sys.stderr) |
555 | return 1 | 557 | return 1 |
556 | 558 | ||
557 | pending_proj_names = [project.name for (project, available) in pending] | 559 | manifests = {project.manifest.topdir: project.manifest |
558 | pending_worktrees = [project.worktree for (project, available) in pending] | 560 | for (project, available) in pending} |
559 | hook = RepoHook.FromSubcmd( | 561 | ret = 0 |
560 | hook_type='pre-upload', manifest=self.manifest, | 562 | for manifest in manifests.values(): |
561 | opt=opt, abort_if_user_denies=True) | 563 | pending_proj_names = [project.name for (project, available) in pending |
562 | if not hook.Run( | 564 | if project.manifest.topdir == manifest.topdir] |
563 | project_list=pending_proj_names, | 565 | pending_worktrees = [project.worktree for (project, available) in pending |
564 | worktree_list=pending_worktrees): | 566 | if project.manifest.topdir == manifest.topdir] |
565 | return 1 | 567 | hook = RepoHook.FromSubcmd( |
568 | hook_type='pre-upload', manifest=manifest, | ||
569 | opt=opt, abort_if_user_denies=True) | ||
570 | if not hook.Run( | ||
571 | project_list=pending_proj_names, | ||
572 | worktree_list=pending_worktrees): | ||
573 | ret = 1 | ||
574 | if ret: | ||
575 | return ret | ||
566 | 576 | ||
567 | reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else [] | 577 | reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else [] |
568 | cc = _SplitEmails(opt.cc) if opt.cc else [] | 578 | cc = _SplitEmails(opt.cc) if opt.cc else [] |