Add multi-manifest support with <submanifest> 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 <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
This commit is contained in:
LaMont Jones 2021-11-18 22:40:18 +00:00
parent 87cce68b28
commit cc879a97c3
28 changed files with 794 additions and 162 deletions

View File

@ -61,13 +61,21 @@ class Command(object):
# it is the number of parallel jobs to default to. # it is the number of parallel jobs to default to.
PARALLEL_JOBS = None 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, 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.repodir = repodir
self.client = client self.client = client
self.outer_client = outer_client or client
self.manifest = manifest self.manifest = manifest
self.gitc_manifest = gitc_manifest self.gitc_manifest = gitc_manifest
self.git_event_log = git_event_log self.git_event_log = git_event_log
self.outer_manifest = outer_manifest
# Cache for the OptionParser property. # Cache for the OptionParser property.
self._optparse = None self._optparse = None
@ -135,6 +143,18 @@ class Command(object):
type=int, default=self.PARALLEL_JOBS, type=int, default=self.PARALLEL_JOBS,
help=f'number of jobs to run in parallel (default: {default})') 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): def _Options(self, p):
"""Initialize the option parser with subcommand-specific options.""" """Initialize the option parser with subcommand-specific options."""
@ -252,16 +272,19 @@ class Command(object):
return project return project
def GetProjects(self, args, manifest=None, groups='', missing_ok=False, 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. """A list of projects that match the arguments.
""" """
if not manifest: if all_manifests:
manifest = self.manifest if not manifest:
all_projects_list = manifest.projects manifest = self.manifest.outer_client
all_projects_list = manifest.all_projects
else:
if not manifest:
manifest = self.manifest
all_projects_list = manifest.projects
result = [] result = []
mp = manifest.manifestProject
if not groups: if not groups:
groups = manifest.GetGroupsStr() groups = manifest.GetGroupsStr()
groups = [x for x in re.split(r'[,\s]+', groups) if x] groups = [x for x in re.split(r'[,\s]+', groups) if x]
@ -282,12 +305,19 @@ class Command(object):
for arg in args: for arg in args:
# We have to filter by manifest groups in case the requested project is # We have to filter by manifest groups in case the requested project is
# checked out multiple times or differently based on them. # 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 project.MatchesGroups(groups)]
if not projects: if not projects:
path = os.path.abspath(arg).replace('\\', '/') 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 # If it's not a derived project, update path->project mapping and
# search again, as arg might actually point to a derived subproject. # search again, as arg might actually point to a derived subproject.
@ -308,7 +338,8 @@ class Command(object):
for project in projects: for project in projects:
if not missing_ok and not project.Exists: 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): if not project.MatchesGroups(groups):
raise InvalidProjectGroupsError(arg) raise InvalidProjectGroupsError(arg)
@ -319,12 +350,22 @@ class Command(object):
result.sort(key=_getpath) result.sort(key=_getpath)
return result 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 = [] result = []
patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args] 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: 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: if not inverse and match:
result.append(project) result.append(project)
break break
@ -333,9 +374,24 @@ class Command(object):
else: else:
if inverse: if inverse:
result.append(project) result.append(project)
result.sort(key=lambda project: project.relpath) result.sort(key=lambda project: (project.manifest.path_prefix,
project.relpath))
return result 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): class InteractiveCommand(Command):
"""Command which requires user interaction on the tty and """Command which requires user interaction on the tty and

View File

@ -50,6 +50,10 @@ For example, if you want to change the manifest branch, you can simply run
For more documentation on the manifest format, including the local_manifests For more documentation on the manifest format, including the local_manifests
support, see the [manifest-format.md] file. support, see the [manifest-format.md] file.
* `submanifests/{submanifest.path}/`: The path prefix to the manifest state of
a submanifest included in a multi-manifest checkout. The outermost manifest
manifest state is found adjacent to `submanifests/`.
* `manifests/`: A git checkout of the manifest project. Its `.git/` state * `manifests/`: A git checkout of the manifest project. Its `.git/` state
points to the `manifest.git` bare checkout (see below). It tracks the git points to the `manifest.git` bare checkout (see below). It tracks the git
branch specified at `repo init` time via `--manifest-branch`. branch specified at `repo init` time via `--manifest-branch`.

View File

@ -26,6 +26,7 @@ following DTD:
remote*, remote*,
default?, default?,
manifest-server?, manifest-server?,
submanifest*?,
remove-project*, remove-project*,
project*, project*,
extend-project*, extend-project*,
@ -57,6 +58,15 @@ following DTD:
<!ELEMENT manifest-server EMPTY> <!ELEMENT manifest-server EMPTY>
<!ATTLIST manifest-server url CDATA #REQUIRED> <!ATTLIST manifest-server url CDATA #REQUIRED>
<!ELEMENT submanifest EMPTY>
<!ATTLIST submanifest name ID #REQUIRED>
<!ATTLIST submanifest remote IDREF #IMPLIED>
<!ATTLIST submanifest project CDATA #IMPLIED>
<!ATTLIST submanifest manifest-name CDATA #IMPLIED>
<!ATTLIST submanifest revision CDATA #IMPLIED>
<!ATTLIST submanifest path CDATA #IMPLIED>
<!ATTLIST submanifest groups CDATA #IMPLIED>
<!ELEMENT project (annotation*, <!ELEMENT project (annotation*,
project*, project*,
copyfile*, copyfile*,
@ -236,6 +246,60 @@ the specified tag. This is used by repo sync when the --smart-tag option
is given. is given.
### Element submanifest
One or more submanifest elements may be specified. Each element describes a
single manifest to be checked out as a child.
Attribute `name`: A unique name (within the current (sub)manifest) for this
submanifest. It acts as a default for `revision` below. The same name can be
used for submanifests with different parent (sub)manifests.
Attribute `remote`: Name of a previously defined remote element.
If not supplied the remote given by the default element is used.
Attribute `project`: The manifest project name. The project's name is appended
onto its remote's fetch URL to generate the actual URL to configure the Git
remote with. The URL gets formed as:
${remote_fetch}/${project_name}.git
where ${remote_fetch} is the remote's fetch attribute and
${project_name} is the project's name attribute. The suffix ".git"
is always appended as repo assumes the upstream is a forest of
bare Git repositories. If the project has a parent element, its
name will be prefixed by the parent's.
The project name must match the name Gerrit knows, if Gerrit is
being used for code reviews.
`project` must not be empty, and may not be an absolute path or use "." or ".."
path components. It is always interpreted relative to the remote's fetch
settings, so if a different base path is needed, declare a different remote
with the new settings needed.
If not supplied the remote and project for this manifest will be used: `remote`
cannot be supplied.
Attribute `manifest-name`: The manifest filename in the manifest project. If
not supplied, `default.xml` is used.
Attribute `revision`: Name of a Git branch (e.g. "main" or "refs/heads/main"),
tag (e.g. "refs/tags/stable"), or a commit hash. If not supplied, `name` is
used.
Attribute `path`: An optional path relative to the top directory
of the repo client where the submanifest repo client top directory
should be placed. If not supplied, `revision` is used.
`path` may not be an absolute path or use "." or ".." path components.
Attribute `groups`: List of additional groups to which all projects
in the included submanifest belong. This appends and recurses, meaning
all projects in submanifests carry all parent submanifest groups.
Same syntax as the corresponding element of `project`.
### Element project ### Element project
One or more project elements may be specified. Each element One or more project elements may be specified. Each element
@ -471,7 +535,7 @@ These restrictions are not enforced for [Local Manifests].
Attribute `groups`: List of additional groups to which all projects Attribute `groups`: List of additional groups to which all projects
in the included manifest belong. This appends and recurses, meaning in the included manifest belong. This appends and recurses, meaning
all projects in sub-manifests carry all parent include groups. all projects in included manifests carry all parent include groups.
Same syntax as the corresponding element of `project`. Same syntax as the corresponding element of `project`.
## Local Manifests {#local-manifests} ## Local Manifests {#local-manifests}

View File

@ -92,7 +92,8 @@ class Superproject(object):
self._branch = manifest.branch self._branch = manifest.branch
self._repodir = os.path.abspath(repodir) self._repodir = os.path.abspath(repodir)
self._superproject_dir = superproject_dir self._superproject_dir = superproject_dir
self._superproject_path = os.path.join(self._repodir, superproject_dir) self._superproject_path = manifest.SubmanifestInfoDir(manifest.path_prefix,
superproject_dir)
self._manifest_path = os.path.join(self._superproject_path, self._manifest_path = os.path.join(self._superproject_path,
_SUPERPROJECT_MANIFEST_NAME) _SUPERPROJECT_MANIFEST_NAME)
git_name = '' git_name = ''

43
main.py
View File

@ -127,6 +127,8 @@ global_options.add_option('--event-log',
help='filename of event log to append timeline to') help='filename of event log to append timeline to')
global_options.add_option('--git-trace2-event-log', action='store', global_options.add_option('--git-trace2-event-log', action='store',
help='directory to write git trace2 event log to') help='directory to write git trace2 event log to')
global_options.add_option('--submanifest-path', action='store',
metavar='REL_PATH', help='submanifest path')
class _Repo(object): class _Repo(object):
@ -217,7 +219,12 @@ class _Repo(object):
SetDefaultColoring(gopts.color) SetDefaultColoring(gopts.color)
git_trace2_event_log = EventLog() git_trace2_event_log = EventLog()
repo_client = RepoClient(self.repodir) outer_client = RepoClient(self.repodir)
repo_client = outer_client
if gopts.submanifest_path:
repo_client = RepoClient(self.repodir,
submanifest_path=gopts.submanifest_path,
outer_client=outer_client)
gitc_manifest = None gitc_manifest = None
gitc_client_name = gitc_utils.parse_clientdir(os.getcwd()) gitc_client_name = gitc_utils.parse_clientdir(os.getcwd())
if gitc_client_name: if gitc_client_name:
@ -229,6 +236,8 @@ class _Repo(object):
repodir=self.repodir, repodir=self.repodir,
client=repo_client, client=repo_client,
manifest=repo_client.manifest, manifest=repo_client.manifest,
outer_client=outer_client,
outer_manifest=outer_client.manifest,
gitc_manifest=gitc_manifest, gitc_manifest=gitc_manifest,
git_event_log=git_trace2_event_log) git_event_log=git_trace2_event_log)
except KeyError: except KeyError:
@ -283,7 +292,37 @@ class _Repo(object):
try: try:
cmd.CommonValidateOptions(copts, cargs) cmd.CommonValidateOptions(copts, cargs)
cmd.ValidateOptions(copts, cargs) cmd.ValidateOptions(copts, cargs)
result = cmd.Execute(copts, cargs)
this_manifest_only = copts.this_manifest_only
# If not specified, default to using the outer manifest.
outer_manifest = copts.outer_manifest is not False
if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only:
result = cmd.Execute(copts, cargs)
elif outer_manifest and repo_client.manifest.is_submanifest:
# The command does not support multi-manifest, we are using a
# submanifest, and the command line is for the outermost manifest.
# Re-run using the outermost manifest, which will recurse through the
# submanifests.
gopts.submanifest_path = ''
result = self._Run(name, gopts, argv)
else:
# No multi-manifest support. Run the command in the current
# (sub)manifest, and then any child submanifests.
result = cmd.Execute(copts, cargs)
for submanifest in repo_client.manifest.submanifests.values():
spec = submanifest.ToSubmanifestSpec(root=repo_client.outer_client)
gopts.submanifest_path = submanifest.repo_client.path_prefix
child_argv = argv[:]
child_argv.append('--no-outer-manifest')
# Not all subcommands support the 3 manifest options, so only add them
# if the original command includes them.
if hasattr(copts, 'manifest_url'):
child_argv.extend(['--manifest-url', spec.manifestUrl])
if hasattr(copts, 'manifest_name'):
child_argv.extend(['--manifest-name', spec.manifestName])
if hasattr(copts, 'manifest_branch'):
child_argv.extend(['--manifest-branch', spec.revision])
result = self._Run(name, gopts, child_argv) or result
except (DownloadError, ManifestInvalidRevisionError, except (DownloadError, ManifestInvalidRevisionError,
NoManifestException) as e: NoManifestException) as e:
print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)), print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),

View File

@ -33,6 +33,9 @@ from wrapper import Wrapper
MANIFEST_FILE_NAME = 'manifest.xml' MANIFEST_FILE_NAME = 'manifest.xml'
LOCAL_MANIFEST_NAME = 'local_manifest.xml' LOCAL_MANIFEST_NAME = 'local_manifest.xml'
LOCAL_MANIFESTS_DIR_NAME = 'local_manifests' LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
SUBMANIFEST_DIR = 'submanifests'
# Limit submanifests to an arbitrary depth for loop detection.
MAX_SUBMANIFEST_DEPTH = 8
# Add all projects from local manifest into a group. # Add all projects from local manifest into a group.
LOCAL_MANIFEST_GROUP_PREFIX = 'local:' LOCAL_MANIFEST_GROUP_PREFIX = 'local:'
@ -197,10 +200,122 @@ class _XmlRemote(object):
self.annotations.append(Annotation(name, value, keep)) self.annotations.append(Annotation(name, value, keep))
class _XmlSubmanifest:
"""Manage the <submanifest> element specified in the manifest.
Attributes:
name: a string, the name for this submanifest.
remote: a string, the remote.name for this submanifest.
project: a string, the name of the manifest project.
revision: a string, the commitish.
manifestName: a string, the submanifest file name.
groups: a list of strings, the groups to add to all projects in the submanifest.
path: a string, the relative path for the submanifest checkout.
annotations: (derived) a list of annotations.
present: (derived) a boolean, whether the submanifest's manifest file is present.
"""
def __init__(self,
name,
remote=None,
project=None,
revision=None,
manifestName=None,
groups=None,
path=None,
parent=None):
self.name = name
self.remote = remote
self.project = project
self.revision = revision
self.manifestName = manifestName
self.groups = groups
self.path = path
self.annotations = []
outer_client = parent._outer_client or parent
if self.remote and not self.project:
raise ManifestParseError(
f'Submanifest {name}: must specify project when remote is given.')
rc = self.repo_client = RepoClient(
parent.repodir, manifestName, parent_groups=','.join(groups) or '',
submanifest_path=self.relpath, outer_client=outer_client)
self.present = os.path.exists(os.path.join(self.repo_client.subdir,
MANIFEST_FILE_NAME))
def __eq__(self, other):
if not isinstance(other, _XmlSubmanifest):
return False
return (
self.name == other.name and
self.remote == other.remote and
self.project == other.project and
self.revision == other.revision and
self.manifestName == other.manifestName and
self.groups == other.groups and
self.path == other.path and
sorted(self.annotations) == sorted(other.annotations))
def __ne__(self, other):
return not self.__eq__(other)
def ToSubmanifestSpec(self, root):
"""Return a SubmanifestSpec object, populating attributes"""
mp = root.manifestProject
remote = root.remotes[self.remote or root.default.remote.name]
# If a project was given, generate the url from the remote and project.
# If not, use this manifestProject's url.
if self.project:
manifestUrl = remote.ToRemoteSpec(self.project).url
else:
manifestUrl = mp.GetRemote(mp.remote.name).url
manifestName = self.manifestName or 'default.xml'
revision = self.revision or self.name
path = self.path or revision.split('/')[-1]
groups = self.groups or []
return SubmanifestSpec(self.name, manifestUrl, manifestName, revision, path,
groups)
@property
def relpath(self):
"""The path of this submanifest relative to the parent manifest."""
revision = self.revision or self.name
return self.path or revision.split('/')[-1]
def GetGroupsStr(self):
"""Returns the `groups` given for this submanifest."""
if self.groups:
return ','.join(self.groups)
return ''
def AddAnnotation(self, name, value, keep):
"""Add annotations to the submanifest."""
self.annotations.append(Annotation(name, value, keep))
class SubmanifestSpec:
"""The submanifest element, with all fields expanded."""
def __init__(self,
name,
manifestUrl,
manifestName,
revision,
path,
groups):
self.name = name
self.manifestUrl = manifestUrl
self.manifestName = manifestName
self.revision = revision
self.path = path
self.groups = groups or []
class XmlManifest(object): class XmlManifest(object):
"""manages the repo configuration file""" """manages the repo configuration file"""
def __init__(self, repodir, manifest_file, local_manifests=None): def __init__(self, repodir, manifest_file, local_manifests=None,
outer_client=None, parent_groups='', submanifest_path=''):
"""Initialize. """Initialize.
Args: Args:
@ -210,23 +325,37 @@ class XmlManifest(object):
be |repodir|/|MANIFEST_FILE_NAME|. be |repodir|/|MANIFEST_FILE_NAME|.
local_manifests: Full path to the directory of local override manifests. local_manifests: Full path to the directory of local override manifests.
This will usually be |repodir|/|LOCAL_MANIFESTS_DIR_NAME|. This will usually be |repodir|/|LOCAL_MANIFESTS_DIR_NAME|.
outer_client: RepoClient of the outertree.
parent_groups: a string, the groups to apply to this projects.
submanifest_path: The submanifest root relative to the repo root.
""" """
# TODO(vapier): Move this out of this class. # TODO(vapier): Move this out of this class.
self.globalConfig = GitConfig.ForUser() self.globalConfig = GitConfig.ForUser()
self.repodir = os.path.abspath(repodir) self.repodir = os.path.abspath(repodir)
self.topdir = os.path.dirname(self.repodir) self._CheckLocalPath(submanifest_path)
self.topdir = os.path.join(os.path.dirname(self.repodir), submanifest_path)
self.manifestFile = manifest_file self.manifestFile = manifest_file
self.local_manifests = local_manifests self.local_manifests = local_manifests
self._load_local_manifests = True self._load_local_manifests = True
self.parent_groups = parent_groups
if outer_client and self.isGitcClient:
raise ManifestParseError('Multi-manifest is incompatible with `gitc-init`')
if submanifest_path and not outer_client:
# If passing a submanifest_path, there must be an outer_client.
raise ManifestParseError(f'Bad call to {self.__class__.__name__}')
# If self._outer_client is None, this is not a checkout that supports
# multi-tree.
self._outer_client = outer_client or self
self.repoProject = MetaProject(self, 'repo', self.repoProject = MetaProject(self, 'repo',
gitdir=os.path.join(repodir, 'repo/.git'), gitdir=os.path.join(repodir, 'repo/.git'),
worktree=os.path.join(repodir, 'repo')) worktree=os.path.join(repodir, 'repo'))
mp = MetaProject(self, 'manifests', mp = self.SubmanifestProject(self.path_prefix)
gitdir=os.path.join(repodir, 'manifests.git'),
worktree=os.path.join(repodir, 'manifests'))
self.manifestProject = mp self.manifestProject = mp
# This is a bit hacky, but we're in a chicken & egg situation: all the # 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
ae.setAttribute('value', a.value) ae.setAttribute('value', a.value)
e.appendChild(ae) e.appendChild(ae)
def _SubmanifestToXml(self, r, doc, root):
"""Generate XML <submanifest/> node."""
e = doc.createElement('submanifest')
root.appendChild(e)
e.setAttribute('name', r.name)
if r.remote is not None:
e.setAttribute('remote', r.remote)
if r.project is not None:
e.setAttribute('project', r.project)
if r.manifestName is not None:
e.setAttribute('manifest-name', r.manifestName)
if r.revision is not None:
e.setAttribute('revision', r.revision)
if r.path is not None:
e.setAttribute('path', r.path)
if r.groups:
e.setAttribute('groups', r.GetGroupsStr())
for a in r.annotations:
if a.keep == 'true':
ae = doc.createElement('annotation')
ae.setAttribute('name', a.name)
ae.setAttribute('value', a.value)
e.appendChild(ae)
def _ParseList(self, field): def _ParseList(self, field):
"""Parse fields that contain flattened lists. """Parse fields that contain flattened lists.
@ -329,6 +483,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
doc = xml.dom.minidom.Document() doc = xml.dom.minidom.Document()
root = doc.createElement('manifest') root = doc.createElement('manifest')
if self.is_submanifest:
root.setAttribute('path', self.path_prefix)
doc.appendChild(root) doc.appendChild(root)
# Save out the notice. There's a little bit of work here to give it the # 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
root.appendChild(e) root.appendChild(e)
root.appendChild(doc.createTextNode('')) root.appendChild(doc.createTextNode(''))
for r in sorted(self.submanifests):
self._SubmanifestToXml(self.submanifests[r], doc, root)
if self.submanifests:
root.appendChild(doc.createTextNode(''))
def output_projects(parent, parent_node, projects): def output_projects(parent, parent_node, projects):
for project_name in projects: for project_name in projects:
for project in self._projects[project_name]: for project in self._projects[project_name]:
@ -537,6 +698,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
'project', 'project',
'extend-project', 'extend-project',
'include', 'include',
'submanifest',
# These are children of 'project' nodes. # These are children of 'project' nodes.
'annotation', 'annotation',
'project', 'project',
@ -574,13 +736,75 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
def _output_manifest_project_extras(self, p, e): def _output_manifest_project_extras(self, p, e):
"""Manifests can modify e if they support extra project attributes.""" """Manifests can modify e if they support extra project attributes."""
@property
def is_multimanifest(self):
"""Whether this is a multimanifest checkout"""
return bool(self.outer_client.submanifests)
@property
def is_submanifest(self):
"""Whether this manifest is a submanifest"""
return self._outer_client and self._outer_client != self
@property
def outer_client(self):
"""The instance of the outermost manifest client"""
self._Load()
return self._outer_client
@property
def all_manifests(self):
"""Generator yielding all (sub)manifests."""
self._Load()
outer = self._outer_client
yield outer
for tree in outer.all_children:
yield tree
@property
def all_children(self):
"""Generator yielding all child submanifests."""
self._Load()
for child in self._submanifests.values():
if child.repo_client:
yield child.repo_client
for tree in child.repo_client.all_children:
yield tree
@property
def path_prefix(self):
"""The path of this submanifest, relative to the outermost manifest."""
if not self._outer_client or self == self._outer_client:
return ''
return os.path.relpath(self.topdir, self._outer_client.topdir)
@property
def all_paths(self):
"""All project paths for all (sub)manifests. See `paths`."""
ret = {}
for tree in self.all_manifests:
prefix = tree.path_prefix
ret.update({os.path.join(prefix, k): v for k, v in tree.paths.items()})
return ret
@property
def all_projects(self):
"""All projects for all (sub)manifests. See `projects`."""
return list(itertools.chain.from_iterable(x._paths.values() for x in self.all_manifests))
@property @property
def paths(self): def paths(self):
"""Return all paths for this manifest.
Return:
A dictionary of {path: Project()}. `path` is relative to this manifest.
"""
self._Load() self._Load()
return self._paths return self._paths
@property @property
def projects(self): def projects(self):
"""Return a list of all Projects in this manifest."""
self._Load() self._Load()
return list(self._paths.values()) return list(self._paths.values())
@ -594,6 +818,12 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
self._Load() self._Load()
return self._default return self._default
@property
def submanifests(self):
"""All submanifests in this manifest."""
self._Load()
return self._submanifests
@property @property
def repo_hooks_project(self): def repo_hooks_project(self):
self._Load() self._Load()
@ -651,8 +881,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
return self._load_local_manifests and self.local_manifests return self._load_local_manifests and self.local_manifests
def IsFromLocalManifest(self, project): def IsFromLocalManifest(self, project):
"""Is the project from a local manifest? """Is the project from a local manifest?"""
"""
return any(x.startswith(LOCAL_MANIFEST_GROUP_PREFIX) return any(x.startswith(LOCAL_MANIFEST_GROUP_PREFIX)
for x in project.groups) for x in project.groups)
@ -676,6 +905,50 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
def EnableGitLfs(self): def EnableGitLfs(self):
return self.manifestProject.config.GetBoolean('repo.git-lfs') return self.manifestProject.config.GetBoolean('repo.git-lfs')
def FindManifestByPath(self, path):
"""Returns the manifest containing path."""
path = os.path.abspath(path)
manifest = self._outer_client or self
old = None
while manifest._submanifests and manifest != old:
old = manifest
for name in manifest._submanifests:
tree = manifest._submanifests[name]
if path.startswith(tree.repo_client.manifest.topdir):
manifest = tree.repo_client
break
return manifest
@property
def subdir(self):
"""Returns the path for per-submanifest objects for this manifest."""
return self.SubmanifestInfoDir(self.path_prefix)
def SubmanifestInfoDir(self, submanifest_path, object_path=''):
"""Return the path to submanifest-specific info for a submanifest.
Return the full path of the directory in which to put per-manifest objects.
Args:
submanifest_path: a string, the path of the submanifest, relative to the
outermost topdir. If empty, then repodir is returned.
object_path: a string, relative path to append to the submanifest info
directory path.
"""
if submanifest_path:
return os.path.join(self.repodir, SUBMANIFEST_DIR, submanifest_path,
object_path)
else:
return os.path.join(self.repodir, object_path)
def SubmanifestProject(self, submanifest_path):
"""Return a manifestProject for a submanifest."""
subdir = self.SubmanifestInfoDir(submanifest_path)
mp = MetaProject(self, 'manifests',
gitdir=os.path.join(subdir, 'manifests.git'),
worktree=os.path.join(subdir, 'manifests'))
return mp
def GetDefaultGroupsStr(self): def GetDefaultGroupsStr(self):
"""Returns the default group string for the platform.""" """Returns the default group string for the platform."""
return 'default,platform-' + platform.system().lower() return 'default,platform-' + platform.system().lower()
@ -693,6 +966,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
self._paths = {} self._paths = {}
self._remotes = {} self._remotes = {}
self._default = None self._default = None
self._submanifests = {}
self._repo_hooks_project = None self._repo_hooks_project = None
self._superproject = {} self._superproject = {}
self._contactinfo = ContactInfo(Wrapper().BUG_URL) self._contactinfo = ContactInfo(Wrapper().BUG_URL)
@ -700,20 +974,29 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
self.branch = None self.branch = None
self._manifest_server = None self._manifest_server = None
def _Load(self): def _Load(self, initial_client=None, submanifest_depth=0):
if submanifest_depth > MAX_SUBMANIFEST_DEPTH:
raise ManifestParseError('maximum submanifest depth %d exceeded.' %
MAX_SUBMANIFEST_DEPTH)
if not self._loaded: if not self._loaded:
if self._outer_client and self._outer_client != self:
# This will load all clients.
self._outer_client._Load(initial_client=self)
m = self.manifestProject m = self.manifestProject
b = m.GetBranch(m.CurrentBranch).merge b = m.GetBranch(m.CurrentBranch).merge
if b is not None and b.startswith(R_HEADS): if b is not None and b.startswith(R_HEADS):
b = b[len(R_HEADS):] b = b[len(R_HEADS):]
self.branch = b self.branch = b
parent_groups = self.parent_groups
# The manifestFile was specified by the user which is why we allow include # The manifestFile was specified by the user which is why we allow include
# paths to point anywhere. # paths to point anywhere.
nodes = [] nodes = []
nodes.append(self._ParseManifestXml( nodes.append(self._ParseManifestXml(
self.manifestFile, self.manifestProject.worktree, self.manifestFile, self.manifestProject.worktree,
restrict_includes=False)) parent_groups=parent_groups, restrict_includes=False))
if self._load_local_manifests and self.local_manifests: if self._load_local_manifests and self.local_manifests:
try: try:
@ -722,9 +1005,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
local = os.path.join(self.local_manifests, local_file) local = os.path.join(self.local_manifests, local_file)
# Since local manifests are entirely managed by the user, allow # Since local manifests are entirely managed by the user, allow
# them to point anywhere the user wants. # them to point anywhere the user wants.
local_group = f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}'
nodes.append(self._ParseManifestXml( nodes.append(self._ParseManifestXml(
local, self.repodir, local, self.subdir,
parent_groups=f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}', parent_groups=f'{local_group},{parent_groups}',
restrict_includes=False)) restrict_includes=False))
except OSError: except OSError:
pass pass
@ -743,6 +1027,23 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
self._loaded = True self._loaded = True
# Now that we have loaded this manifest, load any submanifest manifests
# as well. We need to do this after self._loaded is set to avoid looping.
if self._outer_client:
for name in self._submanifests:
tree = self._submanifests[name]
spec = tree.ToSubmanifestSpec(self)
present = os.path.exists(os.path.join(self.subdir, MANIFEST_FILE_NAME))
if present and tree.present and not tree.repo_client:
if initial_client and initial_client.topdir == self.topdir:
tree.repo_client = self
tree.present = present
elif not os.path.exists(self.subdir):
tree.present = False
if tree.present:
tree.repo_client._Load(initial_client=initial_client,
submanifest_depth=submanifest_depth + 1)
def _ParseManifestXml(self, path, include_root, parent_groups='', def _ParseManifestXml(self, path, include_root, parent_groups='',
restrict_includes=True): restrict_includes=True):
"""Parse a manifest XML and return the computed nodes. """Parse a manifest XML and return the computed nodes.
@ -832,6 +1133,20 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if self._default is None: if self._default is None:
self._default = _Default() self._default = _Default()
submanifest_paths = set()
for node in itertools.chain(*node_list):
if node.nodeName == 'submanifest':
submanifest = self._ParseSubmanifest(node)
if submanifest:
if submanifest.name in self._submanifests:
if submanifest != self._submanifests[submanifest.name]:
raise ManifestParseError(
'submanifest %s already exists with different attributes' %
(submanifest.name))
else:
self._submanifests[submanifest.name] = submanifest
submanifest_paths.add(submanifest.relpath)
for node in itertools.chain(*node_list): for node in itertools.chain(*node_list):
if node.nodeName == 'notice': if node.nodeName == 'notice':
if self._notice is not None: if self._notice is not None:
@ -859,6 +1174,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
raise ManifestParseError( raise ManifestParseError(
'duplicate path %s in %s' % 'duplicate path %s in %s' %
(project.relpath, self.manifestFile)) (project.relpath, self.manifestFile))
for tree in submanifest_paths:
if project.relpath.startswith(tree):
raise ManifestParseError(
'project %s conflicts with submanifest path %s' %
(project.relpath, tree))
self._paths[project.relpath] = project self._paths[project.relpath] = project
projects.append(project) projects.append(project)
for subproject in project.subprojects: for subproject in project.subprojects:
@ -883,8 +1203,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if groups: if groups:
groups = self._ParseList(groups) groups = self._ParseList(groups)
revision = node.getAttribute('revision') revision = node.getAttribute('revision')
remote = node.getAttribute('remote') remote_name = node.getAttribute('remote')
if remote: if not remote_name:
remote = self._default.remote
else:
remote = self._get_remote(node) remote = self._get_remote(node)
named_projects = self._projects[name] named_projects = self._projects[name]
@ -899,12 +1221,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if revision: if revision:
p.SetRevision(revision) p.SetRevision(revision)
if remote: if remote_name:
p.remote = remote.ToRemoteSpec(name) p.remote = remote.ToRemoteSpec(name)
if dest_path: if dest_path:
del self._paths[p.relpath] del self._paths[p.relpath]
relpath, worktree, gitdir, objdir, _ = self.GetProjectPaths(name, dest_path) relpath, worktree, gitdir, objdir, _ = self.GetProjectPaths(
name, dest_path, remote.name)
p.UpdatePaths(relpath, worktree, gitdir, objdir) p.UpdatePaths(relpath, worktree, gitdir, objdir)
self._paths[p.relpath] = p self._paths[p.relpath] = p
@ -1109,6 +1432,53 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
return '\n'.join(cleanLines) return '\n'.join(cleanLines)
def _ParseSubmanifest(self, node):
"""Reads a <submanifest> element from the manifest file."""
name = self._reqatt(node, 'name')
remote = node.getAttribute('remote')
if remote == '':
remote = None
project = node.getAttribute('project')
if project == '':
project = None
revision = node.getAttribute('revision')
if revision == '':
revision = None
manifestName = node.getAttribute('manifest-name')
if manifestName == '':
manifestName = None
groups = ''
if node.hasAttribute('groups'):
groups = node.getAttribute('groups')
groups = self._ParseList(groups)
path = node.getAttribute('path')
if path == '':
path = None
if revision:
msg = self._CheckLocalPath(revision.split('/')[-1])
if msg:
raise ManifestInvalidPathError(
'<submanifest> invalid "revision": %s: %s' % (revision, msg))
else:
msg = self._CheckLocalPath(name)
if msg:
raise ManifestInvalidPathError(
'<submanifest> invalid "name": %s: %s' % (name, msg))
else:
msg = self._CheckLocalPath(path)
if msg:
raise ManifestInvalidPathError(
'<submanifest> invalid "path": %s: %s' % (path, msg))
submanifest = _XmlSubmanifest(name, remote, project, revision, manifestName,
groups, path, self)
for n in node.childNodes:
if n.nodeName == 'annotation':
self._ParseAnnotation(submanifest, n)
return submanifest
def _JoinName(self, parent_name, name): def _JoinName(self, parent_name, name):
return os.path.join(parent_name, name) return os.path.join(parent_name, name)
@ -1172,7 +1542,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if parent is None: if parent is None:
relpath, worktree, gitdir, objdir, use_git_worktrees = \ relpath, worktree, gitdir, objdir, use_git_worktrees = \
self.GetProjectPaths(name, path) self.GetProjectPaths(name, path, remote.name)
else: else:
use_git_worktrees = False use_git_worktrees = False
relpath, worktree, gitdir, objdir = \ relpath, worktree, gitdir, objdir = \
@ -1218,31 +1588,54 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
return project return project
def GetProjectPaths(self, name, path): def GetProjectPaths(self, name, path, remote):
"""Return the paths for a project.
Args:
name: a string, the name of the project.
path: a string, the path of the project.
remote: a string, the remote.name of the project.
"""
# The manifest entries might have trailing slashes. Normalize them to avoid # The manifest entries might have trailing slashes. Normalize them to avoid
# unexpected filesystem behavior since we do string concatenation below. # unexpected filesystem behavior since we do string concatenation below.
path = path.rstrip('/') path = path.rstrip('/')
name = name.rstrip('/') name = name.rstrip('/')
remote = remote.rstrip('/')
use_git_worktrees = False use_git_worktrees = False
use_remote_name = bool(self._outer_client._submanifests)
relpath = path relpath = path
if self.IsMirror: if self.IsMirror:
worktree = None worktree = None
gitdir = os.path.join(self.topdir, '%s.git' % name) gitdir = os.path.join(self.topdir, '%s.git' % name)
objdir = gitdir objdir = gitdir
else: else:
if use_remote_name:
namepath = os.path.join(remote, f'{name}.git')
else:
namepath = f'{name}.git'
worktree = os.path.join(self.topdir, path).replace('\\', '/') worktree = os.path.join(self.topdir, path).replace('\\', '/')
gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path) gitdir = os.path.join(self.subdir, 'projects', '%s.git' % path)
# We allow people to mix git worktrees & non-git worktrees for now. # We allow people to mix git worktrees & non-git worktrees for now.
# This allows for in situ migration of repo clients. # This allows for in situ migration of repo clients.
if os.path.exists(gitdir) or not self.UseGitWorktrees: if os.path.exists(gitdir) or not self.UseGitWorktrees:
objdir = os.path.join(self.repodir, 'project-objects', '%s.git' % name) objdir = os.path.join(self.subdir, 'project-objects', namepath)
else: else:
use_git_worktrees = True use_git_worktrees = True
gitdir = os.path.join(self.repodir, 'worktrees', '%s.git' % name) gitdir = os.path.join(self.repodir, 'worktrees', namepath)
objdir = gitdir objdir = gitdir
return relpath, worktree, gitdir, objdir, use_git_worktrees return relpath, worktree, gitdir, objdir, use_git_worktrees
def GetProjectsWithName(self, name): def GetProjectsWithName(self, name, all_manifests=False):
"""All projects with |name|.
Args:
name: a string, the name of the project.
all_manifests: a boolean, if True, then all manifests are searched. If
False, then only this manifest is searched.
"""
if all_manifests:
return list(itertools.chain.from_iterable(
x._projects.get(name, []) for x in self.all_manifests))
return self._projects.get(name, []) return self._projects.get(name, [])
def GetSubprojectName(self, parent, submodule_path): def GetSubprojectName(self, parent, submodule_path):
@ -1498,19 +1891,26 @@ class GitcManifest(XmlManifest):
class RepoClient(XmlManifest): class RepoClient(XmlManifest):
"""Manages a repo client checkout.""" """Manages a repo client checkout."""
def __init__(self, repodir, manifest_file=None): def __init__(self, repodir, manifest_file=None, submanifest_path='', **kwargs):
self.isGitcClient = False self.isGitcClient = False
submanifest_path = submanifest_path or ''
if submanifest_path:
self._CheckLocalPath(submanifest_path)
prefix = os.path.join(repodir, SUBMANIFEST_DIR, submanifest_path)
else:
prefix = repodir
if os.path.exists(os.path.join(repodir, LOCAL_MANIFEST_NAME)): if os.path.exists(os.path.join(prefix, LOCAL_MANIFEST_NAME)):
print('error: %s is not supported; put local manifests in `%s` instead' % print('error: %s is not supported; put local manifests in `%s` instead' %
(LOCAL_MANIFEST_NAME, os.path.join(repodir, LOCAL_MANIFESTS_DIR_NAME)), (LOCAL_MANIFEST_NAME, os.path.join(prefix, LOCAL_MANIFESTS_DIR_NAME)),
file=sys.stderr) file=sys.stderr)
sys.exit(1) sys.exit(1)
if manifest_file is None: if manifest_file is None:
manifest_file = os.path.join(repodir, MANIFEST_FILE_NAME) manifest_file = os.path.join(prefix, MANIFEST_FILE_NAME)
local_manifests = os.path.abspath(os.path.join(repodir, LOCAL_MANIFESTS_DIR_NAME)) local_manifests = os.path.abspath(os.path.join(prefix, LOCAL_MANIFESTS_DIR_NAME))
super().__init__(repodir, manifest_file, local_manifests) super().__init__(repodir, manifest_file, local_manifests,
submanifest_path=submanifest_path, **kwargs)
# TODO: Completely separate manifest logic out of the client. # TODO: Completely separate manifest logic out of the client.
self.manifest = self self.manifest = self

View File

@ -546,6 +546,18 @@ class Project(object):
# project containing repo hooks. # project containing repo hooks.
self.enabled_repo_hooks = [] self.enabled_repo_hooks = []
def RelPath(self, local=True):
"""Return the path for the project relative to a manifest.
Args:
local: a boolean, if True, the path is relative to the local
(sub)manifest. If false, the path is relative to the
outermost manifest.
"""
if local:
return self.relpath
return os.path.join(self.manifest.path_prefix, self.relpath)
def SetRevision(self, revisionExpr, revisionId=None): def SetRevision(self, revisionExpr, revisionId=None):
"""Set revisionId based on revision expression and id""" """Set revisionId based on revision expression and id"""
self.revisionExpr = revisionExpr self.revisionExpr = revisionExpr
@ -2503,22 +2515,21 @@ class Project(object):
mp = self.manifest.manifestProject mp = self.manifest.manifestProject
ref_dir = mp.config.GetString('repo.reference') or '' ref_dir = mp.config.GetString('repo.reference') or ''
if ref_dir or mirror_git: def _expanded_ref_dirs():
if not mirror_git: """Iterate through the possible git reference directory paths."""
mirror_git = os.path.join(ref_dir, self.name + '.git') name = self.name + '.git'
repo_git = os.path.join(ref_dir, '.repo', 'project-objects', yield mirror_git or os.path.join(ref_dir, name)
self.name + '.git') for prefix in '', self.remote.name:
worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees', yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
self.name + '.git') yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
if os.path.exists(mirror_git): if ref_dir or mirror_git:
ref_dir = mirror_git found_ref_dir = None
elif os.path.exists(repo_git): for path in _expanded_ref_dirs():
ref_dir = repo_git if os.path.exists(path):
elif os.path.exists(worktrees_git): found_ref_dir = path
ref_dir = worktrees_git break
else: ref_dir = found_ref_dir
ref_dir = None
if ref_dir: if ref_dir:
if not os.path.isabs(ref_dir): if not os.path.isabs(ref_dir):

View File

@ -69,7 +69,8 @@ It is equivalent to "git branch -D <branchname>".
nb = args[0] nb = args[0]
err = defaultdict(list) err = defaultdict(list)
success = defaultdict(list) success = defaultdict(list)
all_projects = self.GetProjects(args[1:]) all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only)
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
def _ProcessResults(_pool, pm, states): def _ProcessResults(_pool, pm, states):
for (results, project) in states: for (results, project) in states:
@ -94,7 +95,7 @@ It is equivalent to "git branch -D <branchname>".
err_msg = "error: cannot abandon %s" % br err_msg = "error: cannot abandon %s" % br
print(err_msg, file=sys.stderr) print(err_msg, file=sys.stderr)
for proj in err[br]: for proj in err[br]:
print(' ' * len(err_msg) + " | %s" % proj.relpath, file=sys.stderr) print(' ' * len(err_msg) + " | %s" % _RelPath(proj), file=sys.stderr)
sys.exit(1) sys.exit(1)
elif not success: elif not success:
print('error: no project has local branch(es) : %s' % nb, print('error: no project has local branch(es) : %s' % nb,
@ -110,5 +111,5 @@ It is equivalent to "git branch -D <branchname>".
result = "all project" result = "all project"
else: else:
result = "%s" % ( result = "%s" % (
('\n' + ' ' * width + '| ').join(p.relpath for p in success[br])) ('\n' + ' ' * width + '| ').join(_RelPath(p) for p in success[br]))
print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result)) print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result))

View File

@ -98,7 +98,7 @@ is shown, then the branch appears in all projects.
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def Execute(self, opt, args): def Execute(self, opt, args):
projects = self.GetProjects(args) projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
out = BranchColoring(self.manifest.manifestProject.config) out = BranchColoring(self.manifest.manifestProject.config)
all_branches = {} all_branches = {}
project_cnt = len(projects) project_cnt = len(projects)
@ -147,6 +147,7 @@ is shown, then the branch appears in all projects.
hdr('%c%c %-*s' % (current, published, width, name)) hdr('%c%c %-*s' % (current, published, width, name))
out.write(' |') out.write(' |')
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
if in_cnt < project_cnt: if in_cnt < project_cnt:
fmt = out.write fmt = out.write
paths = [] paths = []
@ -154,19 +155,20 @@ is shown, then the branch appears in all projects.
if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt): if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt):
in_type = 'in' in_type = 'in'
for b in i.projects: for b in i.projects:
relpath = b.project.relpath
if not i.IsSplitCurrent or b.current: if not i.IsSplitCurrent or b.current:
paths.append(b.project.relpath) paths.append(_RelPath(b.project))
else: else:
non_cur_paths.append(b.project.relpath) non_cur_paths.append(_RelPath(b.project))
else: else:
fmt = out.notinproject fmt = out.notinproject
in_type = 'not in' in_type = 'not in'
have = set() have = set()
for b in i.projects: for b in i.projects:
have.add(b.project.relpath) have.add(_RelPath(b.project))
for p in projects: for p in projects:
if p.relpath not in have: if _RelPath(p) not in have:
paths.append(p.relpath) paths.append(_RelPath(p))
s = ' %s %s' % (in_type, ', '.join(paths)) s = ' %s %s' % (in_type, ', '.join(paths))
if not i.IsSplitCurrent and (width + 7 + len(s) < 80): if not i.IsSplitCurrent and (width + 7 + len(s) < 80):

View File

@ -47,7 +47,7 @@ The command is equivalent to:
nb = args[0] nb = args[0]
err = [] err = []
success = [] success = []
all_projects = self.GetProjects(args[1:]) all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only)
def _ProcessResults(_pool, pm, results): def _ProcessResults(_pool, pm, results):
for status, project in results: for status, project in results:

View File

@ -50,7 +50,7 @@ to the Unix 'patch' command.
return (ret, buf.getvalue()) return (ret, buf.getvalue())
def Execute(self, opt, args): def Execute(self, opt, args):
all_projects = self.GetProjects(args) all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
def _ProcessResults(_pool, _output, results): def _ProcessResults(_pool, _output, results):
ret = 0 ret = 0

View File

@ -179,6 +179,9 @@ synced and their revisions won't be found.
def ValidateOptions(self, opt, args): def ValidateOptions(self, opt, args):
if not args or len(args) > 2: if not args or len(args) > 2:
self.OptionParser.error('missing manifests to diff') self.OptionParser.error('missing manifests to diff')
if opt.this_manifest_only is False:
raise self.OptionParser.error(
'`diffmanifest` only supports the current tree')
def Execute(self, opt, args): def Execute(self, opt, args):
self.out = _Coloring(self.client.globalConfig) self.out = _Coloring(self.client.globalConfig)

View File

@ -48,7 +48,7 @@ If no project is specified try to use current directory as a project.
dest='ffonly', action='store_true', dest='ffonly', action='store_true',
help="force fast-forward merge") help="force fast-forward merge")
def _ParseChangeIds(self, args): def _ParseChangeIds(self, opt, args):
if not args: if not args:
self.Usage() self.Usage()
@ -77,7 +77,7 @@ If no project is specified try to use current directory as a project.
ps_id = max(int(match.group(1)), ps_id) ps_id = max(int(match.group(1)), ps_id)
to_get.append((project, chg_id, ps_id)) to_get.append((project, chg_id, ps_id))
else: else:
projects = self.GetProjects([a]) projects = self.GetProjects([a], all_manifests=not opt.this_manifest_only)
if len(projects) > 1: if len(projects) > 1:
# If the cwd is one of the projects, assume they want that. # If the cwd is one of the projects, assume they want that.
try: try:
@ -88,8 +88,8 @@ If no project is specified try to use current directory as a project.
print('error: %s matches too many projects; please re-run inside ' print('error: %s matches too many projects; please re-run inside '
'the project checkout.' % (a,), file=sys.stderr) 'the project checkout.' % (a,), file=sys.stderr)
for project in projects: for project in projects:
print(' %s/ @ %s' % (project.relpath, project.revisionExpr), print(' %s/ @ %s' % (project.RelPath(local=opt.this_manifest_only),
file=sys.stderr) project.revisionExpr), file=sys.stderr)
sys.exit(1) sys.exit(1)
else: else:
project = projects[0] project = projects[0]
@ -105,7 +105,7 @@ If no project is specified try to use current directory as a project.
self.OptionParser.error('-x and --ff are mutually exclusive options') self.OptionParser.error('-x and --ff are mutually exclusive options')
def Execute(self, opt, args): def Execute(self, opt, args):
for project, change_id, ps_id in self._ParseChangeIds(args): for project, change_id, ps_id in self._ParseChangeIds(opt, args):
dl = project.DownloadPatchSet(change_id, ps_id) dl = project.DownloadPatchSet(change_id, ps_id)
if not dl: if not dl:
print('[%s] change %d/%d not found' print('[%s] change %d/%d not found'

View File

@ -168,6 +168,7 @@ without iterating through the remaining projects.
def Execute(self, opt, args): def Execute(self, opt, args):
cmd = [opt.command[0]] cmd = [opt.command[0]]
all_trees = not opt.this_manifest_only
shell = True shell = True
if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]): if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]):
@ -213,11 +214,11 @@ without iterating through the remaining projects.
self.manifest.Override(smart_sync_manifest_path) self.manifest.Override(smart_sync_manifest_path)
if opt.regex: if opt.regex:
projects = self.FindProjects(args) projects = self.FindProjects(args, all_manifests=all_trees)
elif opt.inverse_regex: elif opt.inverse_regex:
projects = self.FindProjects(args, inverse=True) projects = self.FindProjects(args, inverse=True, all_manifests=all_trees)
else: else:
projects = self.GetProjects(args, groups=opt.groups) projects = self.GetProjects(args, groups=opt.groups, all_manifests=all_trees)
os.environ['REPO_COUNT'] = str(len(projects)) os.environ['REPO_COUNT'] = str(len(projects))
@ -290,6 +291,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
setenv('REPO_PROJECT', project.name) setenv('REPO_PROJECT', project.name)
setenv('REPO_PATH', project.relpath) setenv('REPO_PATH', project.relpath)
setenv('REPO_OUTERPATH', project.RelPath(local=opt.this_manifest_only))
setenv('REPO_REMOTE', project.remote.name) setenv('REPO_REMOTE', project.remote.name)
try: try:
# If we aren't in a fully synced state and we don't have the ref the manifest # 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):
output = '' output = ''
if ((opt.project_header and opt.verbose) if ((opt.project_header and opt.verbose)
or not opt.project_header): or not opt.project_header):
output = 'skipping %s/' % project.relpath output = 'skipping %s/' % project.RelPath(local=opt.this_manifest_only)
return (1, output) return (1, output)
if opt.verbose: if opt.verbose:
@ -344,7 +346,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
if mirror: if mirror:
project_header_path = project.name project_header_path = project.name
else: else:
project_header_path = project.relpath project_header_path = project.RelPath(local=opt.this_manifest_only)
out.project('project %s/' % project_header_path) out.project('project %s/' % project_header_path)
out.nl() out.nl()
buf.write(output) buf.write(output)

View File

@ -24,6 +24,7 @@ import wrapper
class GitcInit(init.Init, GitcAvailableCommand): class GitcInit(init.Init, GitcAvailableCommand):
COMMON = True COMMON = True
MULTI_MANIFEST_SUPPORT = False
helpSummary = "Initialize a GITC Client." helpSummary = "Initialize a GITC Client."
helpUsage = """ helpUsage = """
%prog [options] [client name] %prog [options] [client name]

View File

@ -172,15 +172,16 @@ contain a line that matches both expressions:
return (project, p.Wait(), p.stdout, p.stderr) return (project, p.Wait(), p.stdout, p.stderr)
@staticmethod @staticmethod
def _ProcessResults(full_name, have_rev, _pool, out, results): def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
git_failed = False git_failed = False
bad_rev = False bad_rev = False
have_match = False have_match = False
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
for project, rc, stdout, stderr in results: for project, rc, stdout, stderr in results:
if rc < 0: if rc < 0:
git_failed = True git_failed = True
out.project('--- project %s ---' % project.relpath) out.project('--- project %s ---' % _RelPath(project))
out.nl() out.nl()
out.fail('%s', stderr) out.fail('%s', stderr)
out.nl() out.nl()
@ -192,7 +193,7 @@ contain a line that matches both expressions:
if have_rev and 'fatal: ambiguous argument' in stderr: if have_rev and 'fatal: ambiguous argument' in stderr:
bad_rev = True bad_rev = True
else: else:
out.project('--- project %s ---' % project.relpath) out.project('--- project %s ---' % _RelPath(project))
out.nl() out.nl()
out.fail('%s', stderr.strip()) out.fail('%s', stderr.strip())
out.nl() out.nl()
@ -208,13 +209,13 @@ contain a line that matches both expressions:
rev, line = line.split(':', 1) rev, line = line.split(':', 1)
out.write("%s", rev) out.write("%s", rev)
out.write(':') out.write(':')
out.project(project.relpath) out.project(_RelPath(project))
out.write('/') out.write('/')
out.write("%s", line) out.write("%s", line)
out.nl() out.nl()
elif full_name: elif full_name:
for line in r: for line in r:
out.project(project.relpath) out.project(_RelPath(project))
out.write('/') out.write('/')
out.write("%s", line) out.write("%s", line)
out.nl() out.nl()
@ -239,7 +240,7 @@ contain a line that matches both expressions:
cmd_argv.append(args[0]) cmd_argv.append(args[0])
args = args[1:] args = args[1:]
projects = self.GetProjects(args) projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
full_name = False full_name = False
if len(projects) > 1: if len(projects) > 1:
@ -259,7 +260,7 @@ contain a line that matches both expressions:
opt.jobs, opt.jobs,
functools.partial(self._ExecuteOne, cmd_argv), functools.partial(self._ExecuteOne, cmd_argv),
projects, projects,
callback=functools.partial(self._ProcessResults, full_name, have_rev), callback=functools.partial(self._ProcessResults, full_name, have_rev, opt),
output=out, output=out,
ordered=True) ordered=True)

View File

@ -61,6 +61,8 @@ class Info(PagedCommand):
self.opt = opt self.opt = opt
if not opt.this_manifest_only:
self.manifest = self.manifest.outer_client
manifestConfig = self.manifest.manifestProject.config manifestConfig = self.manifest.manifestProject.config
mergeBranch = manifestConfig.GetBranch("default").merge mergeBranch = manifestConfig.GetBranch("default").merge
manifestGroups = (manifestConfig.GetString('manifest.groups') manifestGroups = (manifestConfig.GetString('manifest.groups')
@ -80,17 +82,17 @@ class Info(PagedCommand):
self.printSeparator() self.printSeparator()
if not opt.overview: if not opt.overview:
self.printDiffInfo(args) self._printDiffInfo(opt, args)
else: else:
self.printCommitOverview(args) self._printCommitOverview(opt, args)
def printSeparator(self): def printSeparator(self):
self.text("----------------------------") self.text("----------------------------")
self.out.nl() self.out.nl()
def printDiffInfo(self, args): def _printDiffInfo(self, opt, args):
# We let exceptions bubble up to main as they'll be well structured. # We let exceptions bubble up to main as they'll be well structured.
projs = self.GetProjects(args) projs = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
for p in projs: for p in projs:
self.heading("Project: ") self.heading("Project: ")
@ -179,9 +181,9 @@ class Info(PagedCommand):
self.text(" ".join(split[1:])) self.text(" ".join(split[1:]))
self.out.nl() self.out.nl()
def printCommitOverview(self, args): def _printCommitOverview(self, opt, args):
all_branches = [] all_branches = []
for project in self.GetProjects(args): for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only):
br = [project.GetUploadableBranch(x) br = [project.GetUploadableBranch(x)
for x in project.GetBranches()] for x in project.GetBranches()]
br = [x for x in br if x] br = [x for x in br if x]
@ -200,7 +202,7 @@ class Info(PagedCommand):
if project != branch.project: if project != branch.project:
project = branch.project project = branch.project
self.out.nl() self.out.nl()
self.headtext(project.relpath) self.headtext(project.RelPath(local=opt.this_manifest_only))
self.out.nl() self.out.nl()
commits = branch.commits commits = branch.commits

View File

@ -32,6 +32,7 @@ from wrapper import Wrapper
class Init(InteractiveCommand, MirrorSafeCommand): class Init(InteractiveCommand, MirrorSafeCommand):
COMMON = True COMMON = True
MULTI_MANIFEST_SUPPORT = False
helpSummary = "Initialize a repo client checkout in the current directory" helpSummary = "Initialize a repo client checkout in the current directory"
helpUsage = """ helpUsage = """
%prog [options] [manifest url] %prog [options] [manifest url]
@ -90,6 +91,17 @@ to update the working directory files.
def _Options(self, p, gitc_init=False): def _Options(self, p, gitc_init=False):
Wrapper().InitParser(p, gitc_init=gitc_init) Wrapper().InitParser(p, gitc_init=gitc_init)
m = p.add_option_group('Multi-manifest')
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 _RegisteredEnvironmentOptions(self): def _RegisteredEnvironmentOptions(self):
return {'REPO_MANIFEST_URL': 'manifest_url', return {'REPO_MANIFEST_URL': 'manifest_url',

View File

@ -77,16 +77,17 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
args: Positional args. Can be a list of projects to list, or empty. args: Positional args. Can be a list of projects to list, or empty.
""" """
if not opt.regex: if not opt.regex:
projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all) projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all,
all_manifests=not opt.this_manifest_only)
else: else:
projects = self.FindProjects(args) projects = self.FindProjects(args, all_manifests=not opt.this_manifest_only)
def _getpath(x): def _getpath(x):
if opt.fullpath: if opt.fullpath:
return x.worktree return x.worktree
if opt.relative_to: if opt.relative_to:
return os.path.relpath(x.worktree, opt.relative_to) return os.path.relpath(x.worktree, opt.relative_to)
return x.relpath return x.RelPath(local=opt.this_manifest_only)
lines = [] lines = []
for project in projects: for project in projects:

View File

@ -15,6 +15,7 @@
import json import json
import os import os
import sys import sys
import optparse
from command import PagedCommand from command import PagedCommand
@ -75,7 +76,7 @@ to indicate the remote ref to push changes to via 'repo upload'.
p.add_option('-o', '--output-file', p.add_option('-o', '--output-file',
dest='output_file', dest='output_file',
default='-', default='-',
help='file to save the manifest to', help='file to save the manifest to. (Filename prefix for multi-tree.)',
metavar='-|NAME.xml') metavar='-|NAME.xml')
def _Output(self, opt): def _Output(self, opt):
@ -83,36 +84,45 @@ to indicate the remote ref to push changes to via 'repo upload'.
if opt.manifest_name: if opt.manifest_name:
self.manifest.Override(opt.manifest_name, False) self.manifest.Override(opt.manifest_name, False)
if opt.output_file == '-': for manifest in self.ManifestList(opt):
fd = sys.stdout output_file = opt.output_file
else: if output_file == '-':
fd = open(opt.output_file, 'w') fd = sys.stdout
else:
if manifest.path_prefix:
output_file = f'{opt.output_file}:{manifest.path_prefix.replace("/", "%2f")}'
fd = open(output_file, 'w')
self.manifest.SetUseLocalManifests(not opt.ignore_local_manifests) manifest.SetUseLocalManifests(not opt.ignore_local_manifests)
if opt.json: if opt.json:
print('warning: --json is experimental!', file=sys.stderr) print('warning: --json is experimental!', file=sys.stderr)
doc = self.manifest.ToDict(peg_rev=opt.peg_rev, doc = manifest.ToDict(peg_rev=opt.peg_rev,
peg_rev_upstream=opt.peg_rev_upstream, peg_rev_upstream=opt.peg_rev_upstream,
peg_rev_dest_branch=opt.peg_rev_dest_branch) peg_rev_dest_branch=opt.peg_rev_dest_branch)
json_settings = {
# JSON style guide says Uunicode characters are fully allowed.
'ensure_ascii': False,
# We use 2 space indent to match JSON style guide.
'indent': 2 if opt.pretty else None,
'separators': (',', ': ') if opt.pretty else (',', ':'),
'sort_keys': True,
}
fd.write(json.dumps(doc, **json_settings))
else:
manifest.Save(fd,
peg_rev=opt.peg_rev,
peg_rev_upstream=opt.peg_rev_upstream,
peg_rev_dest_branch=opt.peg_rev_dest_branch)
if output_file != '-':
fd.close()
if manifest.path_prefix:
print(f'Saved {manifest.path_prefix} submanifest to {output_file}',
file=sys.stderr)
else:
print(f'Saved manifest to {output_file}', file=sys.stderr)
json_settings = {
# JSON style guide says Uunicode characters are fully allowed.
'ensure_ascii': False,
# We use 2 space indent to match JSON style guide.
'indent': 2 if opt.pretty else None,
'separators': (',', ': ') if opt.pretty else (',', ':'),
'sort_keys': True,
}
fd.write(json.dumps(doc, **json_settings))
else:
self.manifest.Save(fd,
peg_rev=opt.peg_rev,
peg_rev_upstream=opt.peg_rev_upstream,
peg_rev_dest_branch=opt.peg_rev_dest_branch)
fd.close()
if opt.output_file != '-':
print('Saved manifest to %s' % opt.output_file, file=sys.stderr)
def ValidateOptions(self, opt, args): def ValidateOptions(self, opt, args):
if args: if args:

View File

@ -47,7 +47,7 @@ are displayed.
def Execute(self, opt, args): def Execute(self, opt, args):
all_branches = [] all_branches = []
for project in self.GetProjects(args): for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only):
br = [project.GetUploadableBranch(x) br = [project.GetUploadableBranch(x)
for x in project.GetBranches()] for x in project.GetBranches()]
br = [x for x in br if x] br = [x for x in br if x]
@ -76,7 +76,7 @@ are displayed.
if project != branch.project: if project != branch.project:
project = branch.project project = branch.project
out.nl() out.nl()
out.project('project %s/' % project.relpath) out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
out.nl() out.nl()
commits = branch.commits commits = branch.commits

View File

@ -31,7 +31,7 @@ class Prune(PagedCommand):
return project.PruneHeads() return project.PruneHeads()
def Execute(self, opt, args): def Execute(self, opt, args):
projects = self.GetProjects(args) projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
# NB: Should be able to refactor this module to display summary as results # NB: Should be able to refactor this module to display summary as results
# come back from children. # come back from children.
@ -63,7 +63,7 @@ class Prune(PagedCommand):
if project != branch.project: if project != branch.project:
project = branch.project project = branch.project
out.nl() out.nl()
out.project('project %s/' % project.relpath) out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
out.nl() out.nl()
print('%s %-33s ' % ( print('%s %-33s ' % (

View File

@ -69,7 +69,7 @@ branch but need to incorporate new upstream changes "underneath" them.
'consistent if you previously synced to a manifest)') 'consistent if you previously synced to a manifest)')
def Execute(self, opt, args): def Execute(self, opt, args):
all_projects = self.GetProjects(args) all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
one_project = len(all_projects) == 1 one_project = len(all_projects) == 1
if opt.interactive and not one_project: if opt.interactive and not one_project:
@ -98,6 +98,7 @@ branch but need to incorporate new upstream changes "underneath" them.
config = self.manifest.manifestProject.config config = self.manifest.manifestProject.config
out = RebaseColoring(config) out = RebaseColoring(config)
out.redirect(sys.stdout) out.redirect(sys.stdout)
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
ret = 0 ret = 0
for project in all_projects: for project in all_projects:
@ -107,7 +108,7 @@ branch but need to incorporate new upstream changes "underneath" them.
cb = project.CurrentBranch cb = project.CurrentBranch
if not cb: if not cb:
if one_project: if one_project:
print("error: project %s has a detached HEAD" % project.relpath, print("error: project %s has a detached HEAD" % _RelPath(project),
file=sys.stderr) file=sys.stderr)
return 1 return 1
# ignore branches with detatched HEADs # ignore branches with detatched HEADs
@ -117,7 +118,7 @@ branch but need to incorporate new upstream changes "underneath" them.
if not upbranch.LocalMerge: if not upbranch.LocalMerge:
if one_project: if one_project:
print("error: project %s does not track any remote branches" print("error: project %s does not track any remote branches"
% project.relpath, file=sys.stderr) % _RelPath(project), file=sys.stderr)
return 1 return 1
# ignore branches without remotes # ignore branches without remotes
continue continue
@ -130,7 +131,7 @@ branch but need to incorporate new upstream changes "underneath" them.
args.append(upbranch.LocalMerge) args.append(upbranch.LocalMerge)
out.project('project %s: rebasing %s -> %s', out.project('project %s: rebasing %s -> %s',
project.relpath, cb, upbranch.LocalMerge) _RelPath(project), cb, upbranch.LocalMerge)
out.nl() out.nl()
out.flush() out.flush()

View File

@ -50,7 +50,9 @@ The '%prog' command stages files to prepare the next commit.
self.Usage() self.Usage()
def _Interactive(self, opt, args): def _Interactive(self, opt, args):
all_projects = [p for p in self.GetProjects(args) if p.IsDirty()] all_projects = [
p for p in self.GetProjects(args, all_manifests=not opt.this_manifest_only)
if p.IsDirty()]
if not all_projects: if not all_projects:
print('no projects have uncommitted modifications', file=sys.stderr) print('no projects have uncommitted modifications', file=sys.stderr)
return return
@ -62,7 +64,8 @@ The '%prog' command stages files to prepare the next commit.
for i in range(len(all_projects)): for i in range(len(all_projects)):
project = all_projects[i] project = all_projects[i]
out.write('%3d: %s', i + 1, project.relpath + '/') out.write('%3d: %s', i + 1,
project.RelPath(local=opt.this_manifest_only) + '/')
out.nl() out.nl()
out.nl() out.nl()
@ -99,7 +102,9 @@ The '%prog' command stages files to prepare the next commit.
_AddI(all_projects[a_index - 1]) _AddI(all_projects[a_index - 1])
continue continue
projects = [p for p in all_projects if a in [p.name, p.relpath]] projects = [
p for p in all_projects
if a in [p.name, p.RelPath(local=opt.this_manifest_only)]]
if len(projects) == 1: if len(projects) == 1:
_AddI(projects[0]) _AddI(projects[0])
continue continue

View File

@ -84,7 +84,8 @@ revision specified in the manifest.
projects = ['.'] # start it in the local project by default projects = ['.'] # start it in the local project by default
all_projects = self.GetProjects(projects, all_projects = self.GetProjects(projects,
missing_ok=bool(self.gitc_manifest)) missing_ok=bool(self.gitc_manifest),
all_manifests=not opt.this_manifest_only)
# This must happen after we find all_projects, since GetProjects may need # This must happen after we find all_projects, since GetProjects may need
# the local directory, which will disappear once we save the GITC manifest. # the local directory, which will disappear once we save the GITC manifest.
@ -137,6 +138,6 @@ revision specified in the manifest.
if err: if err:
for p in err: for p in err:
print("error: %s/: cannot start %s" % (p.relpath, nb), print("error: %s/: cannot start %s" % (p.RelPath(local=opt.this_manifest_only), nb),
file=sys.stderr) file=sys.stderr)
sys.exit(1) sys.exit(1)

View File

@ -117,7 +117,7 @@ the following meanings:
outstring.append(''.join([status_header, item, '/'])) outstring.append(''.join([status_header, item, '/']))
def Execute(self, opt, args): def Execute(self, opt, args):
all_projects = self.GetProjects(args) all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
def _ProcessResults(_pool, _output, results): def _ProcessResults(_pool, _output, results):
ret = 0 ret = 0
@ -141,9 +141,10 @@ the following meanings:
if opt.orphans: if opt.orphans:
proj_dirs = set() proj_dirs = set()
proj_dirs_parents = set() proj_dirs_parents = set()
for project in self.GetProjects(None, missing_ok=True): for project in self.GetProjects(None, missing_ok=True, all_manifests=not opt.this_manifest_only):
proj_dirs.add(project.relpath) relpath = project.RelPath(local=opt.this_manifest_only)
(head, _tail) = os.path.split(project.relpath) proj_dirs.add(relpath)
(head, _tail) = os.path.split(relpath)
while head != "": while head != "":
proj_dirs_parents.add(head) proj_dirs_parents.add(head)
(head, _tail) = os.path.split(head) (head, _tail) = os.path.split(head)

View File

@ -66,6 +66,7 @@ _ONE_DAY_S = 24 * 60 * 60
class Sync(Command, MirrorSafeCommand): class Sync(Command, MirrorSafeCommand):
jobs = 1 jobs = 1
COMMON = True COMMON = True
MULTI_MANIFEST_SUPPORT = False
helpSummary = "Update working tree to the latest revision" helpSummary = "Update working tree to the latest revision"
helpUsage = """ helpUsage = """
%prog [<project>...] %prog [<project>...]
@ -704,7 +705,7 @@ later is required to fix a server side protocol bug.
if project.relpath: if project.relpath:
new_project_paths.append(project.relpath) new_project_paths.append(project.relpath)
file_name = 'project.list' file_name = 'project.list'
file_path = os.path.join(self.repodir, file_name) file_path = os.path.join(self.manifest.subdir, file_name)
old_project_paths = [] old_project_paths = []
if os.path.exists(file_path): if os.path.exists(file_path):
@ -760,7 +761,7 @@ later is required to fix a server side protocol bug.
} }
copylinkfile_name = 'copy-link-files.json' copylinkfile_name = 'copy-link-files.json'
copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name) copylinkfile_path = os.path.join(self.manifest.subdir, copylinkfile_name)
old_copylinkfile_paths = {} old_copylinkfile_paths = {}
if os.path.exists(copylinkfile_path): if os.path.exists(copylinkfile_path):
@ -932,6 +933,9 @@ later is required to fix a server side protocol bug.
if opt.prune is None: if opt.prune is None:
opt.prune = True opt.prune = True
if self.manifest.is_multimanifest and not opt.this_manifest_only and args:
self.OptionParser.error('partial syncs must use --this-manifest-only')
def Execute(self, opt, args): def Execute(self, opt, args):
if opt.jobs: if opt.jobs:
self.jobs = opt.jobs self.jobs = opt.jobs

View File

@ -226,7 +226,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/
destination = opt.dest_branch or project.dest_branch or project.revisionExpr destination = opt.dest_branch or project.dest_branch or project.revisionExpr
print('Upload project %s/ to remote branch %s%s:' % print('Upload project %s/ to remote branch %s%s:' %
(project.relpath, destination, ' (private)' if opt.private else '')) (project.RelPath(local=opt.this_manifest_only), destination,
' (private)' if opt.private else ''))
print(' branch %s (%2d commit%s, %s):' % ( print(' branch %s (%2d commit%s, %s):' % (
name, name,
len(commit_list), len(commit_list),
@ -262,7 +263,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
script.append('# Uncomment the branches to upload:') script.append('# Uncomment the branches to upload:')
for project, avail in pending: for project, avail in pending:
script.append('#') script.append('#')
script.append('# project %s/:' % project.relpath) script.append('# project %s/:' % project.RelPath(local=opt.this_manifest_only))
b = {} b = {}
for branch in avail: for branch in avail:
@ -285,7 +286,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
script.append('# %s' % commit) script.append('# %s' % commit)
b[name] = branch b[name] = branch
projects[project.relpath] = project projects[project.RelPath(local=opt.this_manifest_only)] = project
branches[project.name] = b branches[project.name] = b
script.append('') script.append('')
@ -313,7 +314,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
_die('project for branch %s not in script', name) _die('project for branch %s not in script', name)
branch = branches[project.name].get(name) branch = branches[project.name].get(name)
if not branch: if not branch:
_die('branch %s not in %s', name, project.relpath) _die('branch %s not in %s', name, project.RelPath(local=opt.this_manifest_only))
todo.append(branch) todo.append(branch)
if not todo: if not todo:
_die("nothing uncommented for upload") _die("nothing uncommented for upload")
@ -481,7 +482,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
else: else:
fmt = '\n (%s)' fmt = '\n (%s)'
print(('[FAILED] %-15s %-15s' + fmt) % ( print(('[FAILED] %-15s %-15s' + fmt) % (
branch.project.relpath + '/', branch.project.RelPath(local=opt.this_manifest_only) + '/',
branch.name, branch.name,
str(branch.error)), str(branch.error)),
file=sys.stderr) file=sys.stderr)
@ -490,7 +491,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
for branch in todo: for branch in todo:
if branch.uploaded: if branch.uploaded:
print('[OK ] %-15s %s' % ( print('[OK ] %-15s %s' % (
branch.project.relpath + '/', branch.project.RelPath(local=opt.this_manifest_only) + '/',
branch.name), branch.name),
file=sys.stderr) file=sys.stderr)
@ -524,7 +525,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
return (project, avail) return (project, avail)
def Execute(self, opt, args): def Execute(self, opt, args):
projects = self.GetProjects(args) projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
def _ProcessResults(_pool, _out, results): def _ProcessResults(_pool, _out, results):
pending = [] pending = []
@ -534,7 +535,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/
print('repo: error: %s: Unable to upload branch "%s". ' print('repo: error: %s: Unable to upload branch "%s". '
'You might be able to fix the branch by running:\n' 'You might be able to fix the branch by running:\n'
' git branch --set-upstream-to m/%s' % ' git branch --set-upstream-to m/%s' %
(project.relpath, project.CurrentBranch, self.manifest.branch), (project.RelPath(local=opt.this_manifest_only), project.CurrentBranch,
project.manifest.branch),
file=sys.stderr) file=sys.stderr)
elif avail: elif avail:
pending.append(result) pending.append(result)
@ -554,15 +556,23 @@ Gerrit Code Review: https://www.gerritcodereview.com/
(opt.branch,), file=sys.stderr) (opt.branch,), file=sys.stderr)
return 1 return 1
pending_proj_names = [project.name for (project, available) in pending] manifests = {project.manifest.topdir: project.manifest
pending_worktrees = [project.worktree for (project, available) in pending] for (project, available) in pending}
hook = RepoHook.FromSubcmd( ret = 0
hook_type='pre-upload', manifest=self.manifest, for manifest in manifests.values():
opt=opt, abort_if_user_denies=True) pending_proj_names = [project.name for (project, available) in pending
if not hook.Run( if project.manifest.topdir == manifest.topdir]
project_list=pending_proj_names, pending_worktrees = [project.worktree for (project, available) in pending
worktree_list=pending_worktrees): if project.manifest.topdir == manifest.topdir]
return 1 hook = RepoHook.FromSubcmd(
hook_type='pre-upload', manifest=manifest,
opt=opt, abort_if_user_denies=True)
if not hook.Run(
project_list=pending_proj_names,
worktree_list=pending_worktrees):
ret = 1
if ret:
return ret
reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else [] reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else []
cc = _SplitEmails(opt.cc) if opt.cc else [] cc = _SplitEmails(opt.cc) if opt.cc else []