diff options
| -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 [] |
