diff options
| author | LaMont Jones <lamontjones@google.com> | 2021-11-18 22:40:18 +0000 |
|---|---|---|
| committer | LaMont Jones <lamontjones@google.com> | 2022-02-17 21:57:55 +0000 |
| commit | cc879a97c3e2614d19b15b4661c3cab4d33139c9 (patch) | |
| tree | 69d225e9f0e9d79fec8f423d9c40c275f0bf3b8c /command.py | |
| parent | 87cce68b28c34fa86895baa8d7f48307382e6c75 (diff) | |
| download | git-repo-cc879a97c3e2614d19b15b4661c3cab4d33139c9.tar.gz | |
Add multi-manifest support with <submanifest> elementv2.22
To be addressed in another change:
- a partial `repo sync` (with a list of projects/paths to sync)
requires `--this-tree-only`.
Change-Id: I6c7400bf001540e9d7694fa70934f8f204cb5f57
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322657
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Diffstat (limited to 'command.py')
| -rw-r--r-- | command.py | 84 |
1 files changed, 70 insertions, 14 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 |
