From cc879a97c3e2614d19b15b4661c3cab4d33139c9 Mon Sep 17 00:00:00 2001 From: LaMont Jones Date: Thu, 18 Nov 2021 22:40:18 +0000 Subject: Add multi-manifest support with element 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 Reviewed-by: Mike Frysinger --- command.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 14 deletions(-) (limited to 'command.py') diff --git a/command.py b/command.py index b972a0be..12fe4172 100644 --- a/command.py +++ b/command.py @@ -61,13 +61,21 @@ class Command(object): # it is the number of parallel jobs to default to. PARALLEL_JOBS = None + # Whether this command supports Multi-manifest. If False, then main.py will + # iterate over the manifests and invoke the command once per (sub)manifest. + # This is only checked after calling ValidateOptions, so that partially + # migrated subcommands can set it to False. + MULTI_MANIFEST_SUPPORT = True + def __init__(self, repodir=None, client=None, manifest=None, gitc_manifest=None, - git_event_log=None): + git_event_log=None, outer_client=None, outer_manifest=None): self.repodir = repodir self.client = client + self.outer_client = outer_client or client self.manifest = manifest self.gitc_manifest = gitc_manifest self.git_event_log = git_event_log + self.outer_manifest = outer_manifest # Cache for the OptionParser property. self._optparse = None @@ -135,6 +143,18 @@ class Command(object): type=int, default=self.PARALLEL_JOBS, help=f'number of jobs to run in parallel (default: {default})') + m = p.add_option_group('Multi-manifest options') + m.add_option('--outer-manifest', action='store_true', + help='operate starting at the outermost manifest') + m.add_option('--no-outer-manifest', dest='outer_manifest', + action='store_false', default=None, + help='do not operate on outer manifests') + m.add_option('--this-manifest-only', action='store_true', default=None, + help='only operate on this (sub)manifest') + m.add_option('--no-this-manifest-only', '--all-manifests', + dest='this_manifest_only', action='store_false', + help='operate on this manifest and its submanifests') + def _Options(self, p): """Initialize the option parser with subcommand-specific options.""" @@ -252,16 +272,19 @@ class Command(object): return project def GetProjects(self, args, manifest=None, groups='', missing_ok=False, - submodules_ok=False): + submodules_ok=False, all_manifests=False): """A list of projects that match the arguments. """ - if not manifest: - manifest = self.manifest - all_projects_list = manifest.projects + if all_manifests: + if not manifest: + manifest = self.manifest.outer_client + all_projects_list = manifest.all_projects + else: + if not manifest: + manifest = self.manifest + all_projects_list = manifest.projects result = [] - mp = manifest.manifestProject - if not groups: groups = manifest.GetGroupsStr() groups = [x for x in re.split(r'[,\s]+', groups) if x] @@ -282,12 +305,19 @@ class Command(object): for arg in args: # We have to filter by manifest groups in case the requested project is # checked out multiple times or differently based on them. - projects = [project for project in manifest.GetProjectsWithName(arg) + projects = [project for project in manifest.GetProjectsWithName( + arg, all_manifests=all_manifests) if project.MatchesGroups(groups)] if not projects: path = os.path.abspath(arg).replace('\\', '/') - project = self._GetProjectByPath(manifest, path) + tree = manifest + if all_manifests: + # Look for the deepest matching submanifest. + for tree in reversed(list(manifest.all_manifests)): + if path.startswith(tree.topdir): + break + project = self._GetProjectByPath(tree, path) # If it's not a derived project, update path->project mapping and # search again, as arg might actually point to a derived subproject. @@ -308,7 +338,8 @@ class Command(object): for project in projects: if not missing_ok and not project.Exists: - raise NoSuchProjectError('%s (%s)' % (arg, project.relpath)) + raise NoSuchProjectError('%s (%s)' % ( + arg, project.RelPath(local=not all_manifests))) if not project.MatchesGroups(groups): raise InvalidProjectGroupsError(arg) @@ -319,12 +350,22 @@ class Command(object): result.sort(key=_getpath) return result - def FindProjects(self, args, inverse=False): + def FindProjects(self, args, inverse=False, all_manifests=False): + """Find projects from command line arguments. + + Args: + args: a list of (case-insensitive) strings, projects to search for. + inverse: a boolean, if True, then projects not matching any |args| are + returned. + all_manifests: a boolean, if True then all manifests and submanifests are + used. If False, then only the local (sub)manifest is used. + """ result = [] patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args] - for project in self.GetProjects(''): + for project in self.GetProjects('', all_manifests=all_manifests): + paths = [project.name, project.RelPath(local=not all_manifests)] for pattern in patterns: - match = pattern.search(project.name) or pattern.search(project.relpath) + match = any(pattern.search(x) for x in paths) if not inverse and match: result.append(project) break @@ -333,9 +374,24 @@ class Command(object): else: if inverse: result.append(project) - result.sort(key=lambda project: project.relpath) + result.sort(key=lambda project: (project.manifest.path_prefix, + project.relpath)) return result + def ManifestList(self, opt): + """Yields all of the manifests to traverse. + + Args: + opt: The command options. + """ + top = self.outer_manifest + if opt.outer_manifest is False or opt.this_manifest_only: + top = self.manifest + yield top + if not opt.this_manifest_only: + for child in top.all_children: + yield child + class InteractiveCommand(Command): """Command which requires user interaction on the tty and -- cgit v1.2.3-54-g00ecf