mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-28 20:17:26 +00:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc879a97c3 | |||
87cce68b28 | |||
adaa1d8734 | |||
8e91248655 | |||
630876f9e4 | |||
4aa8584ec6 | |||
b550501254 | |||
a535ae4418 | |||
67d6cdf2bc | |||
152032cca2 | |||
a3ac816278 | |||
98bb76577d | |||
d33dce0b77 | |||
89ed8acdbe | |||
71e48b7672 | |||
13576a8caf | |||
2345906d04 | |||
41289c62b4 | |||
c72bd8486a | |||
d53cb9549a | |||
cf0ba48649 | |||
2a089cfee4 |
84
command.py
84
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
|
||||
|
@ -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
|
||||
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
|
||||
points to the `manifest.git` bare checkout (see below). It tracks the git
|
||||
branch specified at `repo init` time via `--manifest-branch`.
|
||||
@ -163,6 +167,7 @@ User controlled settings are initialized when running `repo init`.
|
||||
| repo.clonefilter | `--clone-filter` | Filter setting when using [partial git clones] |
|
||||
| repo.depth | `--depth` | Create shallow checkouts when cloning |
|
||||
| repo.dissociate | `--dissociate` | Dissociate from any reference/mirrors after initial clone |
|
||||
| repo.git-lfs | `--git-lfs` | Enable [Git LFS] support |
|
||||
| repo.mirror | `--mirror` | Checkout is a repo mirror |
|
||||
| repo.partialclone | `--partial-clone` | Create [partial git clones] |
|
||||
| repo.partialcloneexclude | `--partial-clone-exclude` | Comma-delimited list of project names (not paths) to exclude while using [partial git clones] |
|
||||
@ -254,6 +259,7 @@ Repo will create & maintain a few files in the user's home directory.
|
||||
|
||||
|
||||
[git-config]: https://git-scm.com/docs/git-config
|
||||
[Git LFS]: https://git-lfs.github.com/
|
||||
[git worktree]: https://git-scm.com/docs/git-worktree
|
||||
[gitsubmodules]: https://git-scm.com/docs/gitsubmodules
|
||||
[manifest-format.md]: ./manifest-format.md
|
||||
|
@ -26,6 +26,7 @@ following DTD:
|
||||
remote*,
|
||||
default?,
|
||||
manifest-server?,
|
||||
submanifest*?,
|
||||
remove-project*,
|
||||
project*,
|
||||
extend-project*,
|
||||
@ -57,6 +58,15 @@ following DTD:
|
||||
<!ELEMENT manifest-server EMPTY>
|
||||
<!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*,
|
||||
project*,
|
||||
copyfile*,
|
||||
@ -236,6 +246,60 @@ the specified tag. This is used by repo sync when the --smart-tag option
|
||||
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
|
||||
|
||||
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
|
||||
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`.
|
||||
|
||||
## Local Manifests {#local-manifests}
|
||||
|
@ -169,7 +169,8 @@ class GitCommand(object):
|
||||
disable_editor=False,
|
||||
ssh_proxy=None,
|
||||
cwd=None,
|
||||
gitdir=None):
|
||||
gitdir=None,
|
||||
objdir=None):
|
||||
env = self._GetBasicEnv()
|
||||
|
||||
if disable_editor:
|
||||
@ -194,13 +195,24 @@ class GitCommand(object):
|
||||
cwd = project.worktree
|
||||
if not gitdir:
|
||||
gitdir = project.gitdir
|
||||
# Git on Windows wants its paths only using / for reliability.
|
||||
if platform_utils.isWindows():
|
||||
if objdir:
|
||||
objdir = objdir.replace('\\', '/')
|
||||
if gitdir:
|
||||
gitdir = gitdir.replace('\\', '/')
|
||||
|
||||
if objdir:
|
||||
# Set to the place we want to save the objects.
|
||||
env['GIT_OBJECT_DIRECTORY'] = objdir
|
||||
if gitdir:
|
||||
# Allow git to search the original place in case of local or unique refs
|
||||
# that git will attempt to resolve even if we aren't fetching them.
|
||||
env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = gitdir + '/objects'
|
||||
|
||||
command = [GIT]
|
||||
if bare:
|
||||
if gitdir:
|
||||
# Git on Windows wants its paths only using / for reliability.
|
||||
if platform_utils.isWindows():
|
||||
gitdir = gitdir.replace('\\', '/')
|
||||
env[GIT_DIR] = gitdir
|
||||
cwd = None
|
||||
command.append(cmdv[0])
|
||||
@ -234,6 +246,11 @@ class GitCommand(object):
|
||||
dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR]
|
||||
LAST_GITDIR = env[GIT_DIR]
|
||||
|
||||
if 'GIT_OBJECT_DIRECTORY' in env:
|
||||
dbg += ': export GIT_OBJECT_DIRECTORY=%s\n' % env['GIT_OBJECT_DIRECTORY']
|
||||
if 'GIT_ALTERNATE_OBJECT_DIRECTORIES' in env:
|
||||
dbg += ': export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n' % env['GIT_ALTERNATE_OBJECT_DIRECTORIES']
|
||||
|
||||
dbg += ': '
|
||||
dbg += ' '.join(command)
|
||||
if stdin == subprocess.PIPE:
|
||||
|
@ -32,7 +32,6 @@ from typing import NamedTuple
|
||||
from git_command import git_require, GitCommand
|
||||
from git_config import RepoConfig
|
||||
from git_refs import R_HEADS
|
||||
from manifest_xml import LOCAL_MANIFEST_GROUP_PREFIX
|
||||
|
||||
_SUPERPROJECT_GIT_NAME = 'superproject.git'
|
||||
_SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml'
|
||||
@ -93,7 +92,8 @@ class Superproject(object):
|
||||
self._branch = manifest.branch
|
||||
self._repodir = os.path.abspath(repodir)
|
||||
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,
|
||||
_SUPERPROJECT_MANIFEST_NAME)
|
||||
git_name = ''
|
||||
@ -311,7 +311,7 @@ class Superproject(object):
|
||||
if project.revisionId:
|
||||
return True
|
||||
# Skip the project if it comes from the local manifest.
|
||||
return any(s.startswith(LOCAL_MANIFEST_GROUP_PREFIX) for s in project.groups)
|
||||
return project.manifest.IsFromLocalManifest(project)
|
||||
|
||||
def UpdateProjectsRevisionId(self, projects):
|
||||
"""Update revisionId of every project in projects with the commit id.
|
||||
|
43
main.py
43
main.py
@ -127,6 +127,8 @@ global_options.add_option('--event-log',
|
||||
help='filename of event log to append timeline to')
|
||||
global_options.add_option('--git-trace2-event-log', action='store',
|
||||
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):
|
||||
@ -217,7 +219,12 @@ class _Repo(object):
|
||||
SetDefaultColoring(gopts.color)
|
||||
|
||||
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_client_name = gitc_utils.parse_clientdir(os.getcwd())
|
||||
if gitc_client_name:
|
||||
@ -229,6 +236,8 @@ class _Repo(object):
|
||||
repodir=self.repodir,
|
||||
client=repo_client,
|
||||
manifest=repo_client.manifest,
|
||||
outer_client=outer_client,
|
||||
outer_manifest=outer_client.manifest,
|
||||
gitc_manifest=gitc_manifest,
|
||||
git_event_log=git_trace2_event_log)
|
||||
except KeyError:
|
||||
@ -283,7 +292,37 @@ class _Repo(object):
|
||||
try:
|
||||
cmd.CommonValidateOptions(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,
|
||||
NoManifestException) as e:
|
||||
print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
|
||||
|
460
manifest_xml.py
460
manifest_xml.py
@ -33,6 +33,9 @@ from wrapper import Wrapper
|
||||
MANIFEST_FILE_NAME = 'manifest.xml'
|
||||
LOCAL_MANIFEST_NAME = 'local_manifest.xml'
|
||||
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.
|
||||
LOCAL_MANIFEST_GROUP_PREFIX = 'local:'
|
||||
@ -197,10 +200,122 @@ class _XmlRemote(object):
|
||||
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):
|
||||
"""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.
|
||||
|
||||
Args:
|
||||
@ -210,23 +325,37 @@ class XmlManifest(object):
|
||||
be |repodir|/|MANIFEST_FILE_NAME|.
|
||||
local_manifests: Full path to the directory of local override manifests.
|
||||
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.
|
||||
self.globalConfig = GitConfig.ForUser()
|
||||
|
||||
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.local_manifests = local_manifests
|
||||
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',
|
||||
gitdir=os.path.join(repodir, 'repo/.git'),
|
||||
worktree=os.path.join(repodir, 'repo'))
|
||||
|
||||
mp = MetaProject(self, 'manifests',
|
||||
gitdir=os.path.join(repodir, 'manifests.git'),
|
||||
worktree=os.path.join(repodir, 'manifests'))
|
||||
mp = self.SubmanifestProject(self.path_prefix)
|
||||
self.manifestProject = mp
|
||||
|
||||
# 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)
|
||||
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):
|
||||
"""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()
|
||||
root = doc.createElement('manifest')
|
||||
if self.is_submanifest:
|
||||
root.setAttribute('path', self.path_prefix)
|
||||
doc.appendChild(root)
|
||||
|
||||
# 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(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):
|
||||
for project_name in projects:
|
||||
for project in self._projects[project_name]:
|
||||
@ -537,6 +698,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
'project',
|
||||
'extend-project',
|
||||
'include',
|
||||
'submanifest',
|
||||
# These are children of 'project' nodes.
|
||||
'annotation',
|
||||
'project',
|
||||
@ -574,13 +736,75 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
def _output_manifest_project_extras(self, p, e):
|
||||
"""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
|
||||
def paths(self):
|
||||
"""Return all paths for this manifest.
|
||||
|
||||
Return:
|
||||
A dictionary of {path: Project()}. `path` is relative to this manifest.
|
||||
"""
|
||||
self._Load()
|
||||
return self._paths
|
||||
|
||||
@property
|
||||
def projects(self):
|
||||
"""Return a list of all Projects in this manifest."""
|
||||
self._Load()
|
||||
return list(self._paths.values())
|
||||
|
||||
@ -594,6 +818,12 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
self._Load()
|
||||
return self._default
|
||||
|
||||
@property
|
||||
def submanifests(self):
|
||||
"""All submanifests in this manifest."""
|
||||
self._Load()
|
||||
return self._submanifests
|
||||
|
||||
@property
|
||||
def repo_hooks_project(self):
|
||||
self._Load()
|
||||
@ -650,6 +880,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
def HasLocalManifests(self):
|
||||
return self._load_local_manifests and self.local_manifests
|
||||
|
||||
def IsFromLocalManifest(self, project):
|
||||
"""Is the project from a local manifest?"""
|
||||
return any(x.startswith(LOCAL_MANIFEST_GROUP_PREFIX)
|
||||
for x in project.groups)
|
||||
|
||||
@property
|
||||
def IsMirror(self):
|
||||
return self.manifestProject.config.GetBoolean('repo.mirror')
|
||||
@ -666,6 +901,54 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
def HasSubmodules(self):
|
||||
return self.manifestProject.config.GetBoolean('repo.submodules')
|
||||
|
||||
@property
|
||||
def EnableGitLfs(self):
|
||||
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):
|
||||
"""Returns the default group string for the platform."""
|
||||
return 'default,platform-' + platform.system().lower()
|
||||
@ -683,6 +966,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
self._paths = {}
|
||||
self._remotes = {}
|
||||
self._default = None
|
||||
self._submanifests = {}
|
||||
self._repo_hooks_project = None
|
||||
self._superproject = {}
|
||||
self._contactinfo = ContactInfo(Wrapper().BUG_URL)
|
||||
@ -690,20 +974,29 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
self.branch = 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 self._outer_client and self._outer_client != self:
|
||||
# This will load all clients.
|
||||
self._outer_client._Load(initial_client=self)
|
||||
|
||||
m = self.manifestProject
|
||||
b = m.GetBranch(m.CurrentBranch).merge
|
||||
if b is not None and b.startswith(R_HEADS):
|
||||
b = b[len(R_HEADS):]
|
||||
self.branch = b
|
||||
|
||||
parent_groups = self.parent_groups
|
||||
|
||||
# The manifestFile was specified by the user which is why we allow include
|
||||
# paths to point anywhere.
|
||||
nodes = []
|
||||
nodes.append(self._ParseManifestXml(
|
||||
self.manifestFile, self.manifestProject.worktree,
|
||||
restrict_includes=False))
|
||||
parent_groups=parent_groups, restrict_includes=False))
|
||||
|
||||
if self._load_local_manifests and self.local_manifests:
|
||||
try:
|
||||
@ -712,9 +1005,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
local = os.path.join(self.local_manifests, local_file)
|
||||
# Since local manifests are entirely managed by the user, allow
|
||||
# them to point anywhere the user wants.
|
||||
local_group = f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}'
|
||||
nodes.append(self._ParseManifestXml(
|
||||
local, self.repodir,
|
||||
parent_groups=f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}',
|
||||
local, self.subdir,
|
||||
parent_groups=f'{local_group},{parent_groups}',
|
||||
restrict_includes=False))
|
||||
except OSError:
|
||||
pass
|
||||
@ -733,6 +1027,23 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
|
||||
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='',
|
||||
restrict_includes=True):
|
||||
"""Parse a manifest XML and return the computed nodes.
|
||||
@ -822,6 +1133,20 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
if self._default is None:
|
||||
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):
|
||||
if node.nodeName == 'notice':
|
||||
if self._notice is not None:
|
||||
@ -849,6 +1174,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
raise ManifestParseError(
|
||||
'duplicate path %s in %s' %
|
||||
(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
|
||||
projects.append(project)
|
||||
for subproject in project.subprojects:
|
||||
@ -873,8 +1203,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
if groups:
|
||||
groups = self._ParseList(groups)
|
||||
revision = node.getAttribute('revision')
|
||||
remote = node.getAttribute('remote')
|
||||
if remote:
|
||||
remote_name = node.getAttribute('remote')
|
||||
if not remote_name:
|
||||
remote = self._default.remote
|
||||
else:
|
||||
remote = self._get_remote(node)
|
||||
|
||||
named_projects = self._projects[name]
|
||||
@ -889,12 +1221,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
if revision:
|
||||
p.SetRevision(revision)
|
||||
|
||||
if remote:
|
||||
if remote_name:
|
||||
p.remote = remote.ToRemoteSpec(name)
|
||||
|
||||
if dest_path:
|
||||
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)
|
||||
self._paths[p.relpath] = p
|
||||
|
||||
@ -1099,6 +1432,53 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
|
||||
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):
|
||||
return os.path.join(parent_name, name)
|
||||
|
||||
@ -1162,7 +1542,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
|
||||
if parent is None:
|
||||
relpath, worktree, gitdir, objdir, use_git_worktrees = \
|
||||
self.GetProjectPaths(name, path)
|
||||
self.GetProjectPaths(name, path, remote.name)
|
||||
else:
|
||||
use_git_worktrees = False
|
||||
relpath, worktree, gitdir, objdir = \
|
||||
@ -1208,31 +1588,54 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
|
||||
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
|
||||
# unexpected filesystem behavior since we do string concatenation below.
|
||||
path = path.rstrip('/')
|
||||
name = name.rstrip('/')
|
||||
remote = remote.rstrip('/')
|
||||
use_git_worktrees = False
|
||||
use_remote_name = bool(self._outer_client._submanifests)
|
||||
relpath = path
|
||||
if self.IsMirror:
|
||||
worktree = None
|
||||
gitdir = os.path.join(self.topdir, '%s.git' % name)
|
||||
objdir = gitdir
|
||||
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('\\', '/')
|
||||
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.
|
||||
# This allows for in situ migration of repo clients.
|
||||
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:
|
||||
use_git_worktrees = True
|
||||
gitdir = os.path.join(self.repodir, 'worktrees', '%s.git' % name)
|
||||
gitdir = os.path.join(self.repodir, 'worktrees', namepath)
|
||||
objdir = gitdir
|
||||
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, [])
|
||||
|
||||
def GetSubprojectName(self, parent, submodule_path):
|
||||
@ -1488,19 +1891,26 @@ class GitcManifest(XmlManifest):
|
||||
class RepoClient(XmlManifest):
|
||||
"""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
|
||||
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' %
|
||||
(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)
|
||||
sys.exit(1)
|
||||
|
||||
if manifest_file is None:
|
||||
manifest_file = os.path.join(repodir, MANIFEST_FILE_NAME)
|
||||
local_manifests = os.path.abspath(os.path.join(repodir, LOCAL_MANIFESTS_DIR_NAME))
|
||||
super().__init__(repodir, manifest_file, local_manifests)
|
||||
manifest_file = os.path.join(prefix, MANIFEST_FILE_NAME)
|
||||
local_manifests = os.path.abspath(os.path.join(prefix, LOCAL_MANIFESTS_DIR_NAME))
|
||||
super().__init__(repodir, manifest_file, local_manifests,
|
||||
submanifest_path=submanifest_path, **kwargs)
|
||||
|
||||
# TODO: Completely separate manifest logic out of the client.
|
||||
self.manifest = self
|
||||
|
260
project.py
260
project.py
@ -457,11 +457,7 @@ class RemoteSpec(object):
|
||||
|
||||
class Project(object):
|
||||
# These objects can be shared between several working trees.
|
||||
shareable_files = ['description', 'info']
|
||||
shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
|
||||
# These objects can only be used by a single working tree.
|
||||
working_tree_files = ['config', 'packed-refs', 'shallow']
|
||||
working_tree_dirs = ['logs', 'refs']
|
||||
shareable_dirs = ['hooks', 'objects', 'rr-cache']
|
||||
|
||||
def __init__(self,
|
||||
manifest,
|
||||
@ -550,6 +546,18 @@ class Project(object):
|
||||
# project containing 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):
|
||||
"""Set revisionId based on revision expression and id"""
|
||||
self.revisionExpr = revisionExpr
|
||||
@ -1124,7 +1132,7 @@ class Project(object):
|
||||
self._InitRemote()
|
||||
|
||||
if is_new:
|
||||
alt = os.path.join(self.gitdir, 'objects/info/alternates')
|
||||
alt = os.path.join(self.objdir, 'objects/info/alternates')
|
||||
try:
|
||||
with open(alt) as fd:
|
||||
# This works for both absolute and relative alternate directories.
|
||||
@ -1173,7 +1181,7 @@ class Project(object):
|
||||
mp = self.manifest.manifestProject
|
||||
dissociate = mp.config.GetBoolean('repo.dissociate')
|
||||
if dissociate:
|
||||
alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
|
||||
alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
|
||||
if os.path.exists(alternates_file):
|
||||
cmd = ['repack', '-a', '-d']
|
||||
p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
|
||||
@ -2146,8 +2154,7 @@ class Project(object):
|
||||
if prune:
|
||||
cmd.append('--prune')
|
||||
|
||||
if submodules:
|
||||
cmd.append('--recurse-submodules=on-demand')
|
||||
cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
|
||||
|
||||
spec = []
|
||||
if not current_branch_only:
|
||||
@ -2196,8 +2203,10 @@ class Project(object):
|
||||
retry_cur_sleep = retry_sleep_initial_sec
|
||||
ok = prune_tried = False
|
||||
for try_n in range(retry_fetches):
|
||||
gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
|
||||
merge_output=True, capture_stdout=quiet or bool(output_redir))
|
||||
gitcmd = GitCommand(
|
||||
self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
|
||||
ssh_proxy=ssh_proxy,
|
||||
merge_output=True, capture_stdout=quiet or bool(output_redir))
|
||||
if gitcmd.stdout and not quiet and output_redir:
|
||||
output_redir.write(gitcmd.stdout)
|
||||
ret = gitcmd.Wait()
|
||||
@ -2313,7 +2322,8 @@ class Project(object):
|
||||
cmd.append(str(f))
|
||||
cmd.append('+refs/tags/*:refs/tags/*')
|
||||
|
||||
ok = GitCommand(self, cmd, bare=True).Wait() == 0
|
||||
ok = GitCommand(
|
||||
self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
|
||||
platform_utils.remove(bundle_dst, missing_ok=True)
|
||||
platform_utils.remove(bundle_tmp, missing_ok=True)
|
||||
return ok
|
||||
@ -2483,10 +2493,9 @@ class Project(object):
|
||||
os.makedirs(self.gitdir)
|
||||
|
||||
if init_obj_dir or init_git_dir:
|
||||
self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
|
||||
copy_all=True)
|
||||
self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
|
||||
try:
|
||||
self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
|
||||
self._CheckDirReference(self.objdir, self.gitdir)
|
||||
except GitError as e:
|
||||
if force_sync:
|
||||
print("Retrying clone after deleting %s" %
|
||||
@ -2506,37 +2515,37 @@ class Project(object):
|
||||
mp = self.manifest.manifestProject
|
||||
ref_dir = mp.config.GetString('repo.reference') or ''
|
||||
|
||||
if ref_dir or mirror_git:
|
||||
if not mirror_git:
|
||||
mirror_git = os.path.join(ref_dir, self.name + '.git')
|
||||
repo_git = os.path.join(ref_dir, '.repo', 'projects',
|
||||
self.relpath + '.git')
|
||||
worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
|
||||
self.name + '.git')
|
||||
def _expanded_ref_dirs():
|
||||
"""Iterate through the possible git reference directory paths."""
|
||||
name = self.name + '.git'
|
||||
yield mirror_git or os.path.join(ref_dir, name)
|
||||
for prefix in '', self.remote.name:
|
||||
yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
|
||||
yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
|
||||
|
||||
if os.path.exists(mirror_git):
|
||||
ref_dir = mirror_git
|
||||
elif os.path.exists(repo_git):
|
||||
ref_dir = repo_git
|
||||
elif os.path.exists(worktrees_git):
|
||||
ref_dir = worktrees_git
|
||||
else:
|
||||
ref_dir = None
|
||||
if ref_dir or mirror_git:
|
||||
found_ref_dir = None
|
||||
for path in _expanded_ref_dirs():
|
||||
if os.path.exists(path):
|
||||
found_ref_dir = path
|
||||
break
|
||||
ref_dir = found_ref_dir
|
||||
|
||||
if ref_dir:
|
||||
if not os.path.isabs(ref_dir):
|
||||
# The alternate directory is relative to the object database.
|
||||
ref_dir = os.path.relpath(ref_dir,
|
||||
os.path.join(self.objdir, 'objects'))
|
||||
_lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
|
||||
_lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
|
||||
os.path.join(ref_dir, 'objects') + '\n')
|
||||
|
||||
m = self.manifest.manifestProject.config
|
||||
for key in ['user.name', 'user.email']:
|
||||
if m.Has(key, include_defaults=False):
|
||||
self.config.SetString(key, m.GetString(key))
|
||||
self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
|
||||
self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
|
||||
if not self.manifest.EnableGitLfs:
|
||||
self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
|
||||
self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
|
||||
self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
|
||||
except Exception:
|
||||
if init_obj_dir and os.path.exists(self.objdir):
|
||||
@ -2553,6 +2562,14 @@ class Project(object):
|
||||
hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
|
||||
if not os.path.exists(hooks):
|
||||
os.makedirs(hooks)
|
||||
|
||||
# Delete sample hooks. They're noise.
|
||||
for hook in glob.glob(os.path.join(hooks, '*.sample')):
|
||||
try:
|
||||
platform_utils.remove(hook, missing_ok=True)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
for stock_hook in _ProjectHooks():
|
||||
name = os.path.basename(stock_hook)
|
||||
|
||||
@ -2650,40 +2667,16 @@ class Project(object):
|
||||
else:
|
||||
active_git.symbolic_ref('-m', msg, ref, dst)
|
||||
|
||||
def _CheckDirReference(self, srcdir, destdir, share_refs):
|
||||
def _CheckDirReference(self, srcdir, destdir):
|
||||
# Git worktrees don't use symlinks to share at all.
|
||||
if self.use_git_worktrees:
|
||||
return
|
||||
|
||||
symlink_files = self.shareable_files[:]
|
||||
symlink_dirs = self.shareable_dirs[:]
|
||||
if share_refs:
|
||||
symlink_files += self.working_tree_files
|
||||
symlink_dirs += self.working_tree_dirs
|
||||
to_symlink = symlink_files + symlink_dirs
|
||||
for name in set(to_symlink):
|
||||
for name in self.shareable_dirs:
|
||||
# Try to self-heal a bit in simple cases.
|
||||
dst_path = os.path.join(destdir, name)
|
||||
src_path = os.path.join(srcdir, name)
|
||||
|
||||
if name in self.working_tree_dirs:
|
||||
# If the dir is missing under .repo/projects/, create it.
|
||||
if not os.path.exists(src_path):
|
||||
os.makedirs(src_path)
|
||||
|
||||
elif name in self.working_tree_files:
|
||||
# If it's a file under the checkout .git/ and the .repo/projects/ has
|
||||
# nothing, move the file under the .repo/projects/ tree.
|
||||
if not os.path.exists(src_path) and os.path.isfile(dst_path):
|
||||
platform_utils.rename(dst_path, src_path)
|
||||
|
||||
# If the path exists under the .repo/projects/ and there's no symlink
|
||||
# under the checkout .git/, recreate the symlink.
|
||||
if name in self.working_tree_dirs or name in self.working_tree_files:
|
||||
if os.path.exists(src_path) and not os.path.exists(dst_path):
|
||||
platform_utils.symlink(
|
||||
os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
|
||||
|
||||
dst = platform_utils.realpath(dst_path)
|
||||
if os.path.lexists(dst):
|
||||
src = platform_utils.realpath(src_path)
|
||||
@ -2696,23 +2689,17 @@ class Project(object):
|
||||
' use `repo sync --force-sync {0}` to '
|
||||
'proceed.'.format(self.relpath))
|
||||
|
||||
def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
|
||||
def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
|
||||
"""Update |dotgit| to reference |gitdir|, using symlinks where possible.
|
||||
|
||||
Args:
|
||||
gitdir: The bare git repository. Must already be initialized.
|
||||
dotgit: The repository you would like to initialize.
|
||||
share_refs: If true, |dotgit| will store its refs under |gitdir|.
|
||||
Only one work tree can store refs under a given |gitdir|.
|
||||
copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
|
||||
This saves you the effort of initializing |dotgit| yourself.
|
||||
"""
|
||||
symlink_files = self.shareable_files[:]
|
||||
symlink_dirs = self.shareable_dirs[:]
|
||||
if share_refs:
|
||||
symlink_files += self.working_tree_files
|
||||
symlink_dirs += self.working_tree_dirs
|
||||
to_symlink = symlink_files + symlink_dirs
|
||||
to_symlink = symlink_dirs
|
||||
|
||||
to_copy = []
|
||||
if copy_all:
|
||||
@ -2740,11 +2727,6 @@ class Project(object):
|
||||
elif os.path.isfile(src):
|
||||
shutil.copy(src, dst)
|
||||
|
||||
# If the source file doesn't exist, ensure the destination
|
||||
# file doesn't either.
|
||||
if name in symlink_files and not os.path.lexists(src):
|
||||
platform_utils.remove(dst, missing_ok=True)
|
||||
|
||||
except OSError as e:
|
||||
if e.errno == errno.EPERM:
|
||||
raise DownloadError(self._get_symlink_error_message())
|
||||
@ -2781,50 +2763,112 @@ class Project(object):
|
||||
self._InitMRef()
|
||||
|
||||
def _InitWorkTree(self, force_sync=False, submodules=False):
|
||||
realdotgit = os.path.join(self.worktree, '.git')
|
||||
tmpdotgit = realdotgit + '.tmp'
|
||||
init_dotgit = not os.path.exists(realdotgit)
|
||||
if init_dotgit:
|
||||
if self.use_git_worktrees:
|
||||
"""Setup the worktree .git path.
|
||||
|
||||
This is the user-visible path like src/foo/.git/.
|
||||
|
||||
With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
|
||||
With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
|
||||
|
||||
Older checkouts had .git/ directories. If we see that, migrate it.
|
||||
|
||||
This also handles changes in the manifest. Maybe this project was backed
|
||||
by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
|
||||
the path we point to under .repo/projects/ to match.
|
||||
"""
|
||||
dotgit = os.path.join(self.worktree, '.git')
|
||||
|
||||
# If using an old layout style (a directory), migrate it.
|
||||
if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
|
||||
self._MigrateOldWorkTreeGitDir(dotgit)
|
||||
|
||||
init_dotgit = not os.path.exists(dotgit)
|
||||
if self.use_git_worktrees:
|
||||
if init_dotgit:
|
||||
self._InitGitWorktree()
|
||||
self._CopyAndLinkFiles()
|
||||
return
|
||||
|
||||
dotgit = tmpdotgit
|
||||
platform_utils.rmtree(tmpdotgit, ignore_errors=True)
|
||||
os.makedirs(tmpdotgit)
|
||||
self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
|
||||
copy_all=False)
|
||||
else:
|
||||
dotgit = realdotgit
|
||||
if not init_dotgit:
|
||||
# See if the project has changed.
|
||||
if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
|
||||
platform_utils.remove(dotgit)
|
||||
|
||||
try:
|
||||
self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
|
||||
except GitError as e:
|
||||
if force_sync and not init_dotgit:
|
||||
try:
|
||||
platform_utils.rmtree(dotgit)
|
||||
return self._InitWorkTree(force_sync=False, submodules=submodules)
|
||||
except Exception:
|
||||
raise e
|
||||
raise e
|
||||
if init_dotgit or not os.path.exists(dotgit):
|
||||
os.makedirs(self.worktree, exist_ok=True)
|
||||
platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
|
||||
|
||||
if init_dotgit:
|
||||
_lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
|
||||
if init_dotgit:
|
||||
_lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
|
||||
|
||||
# Now that the .git dir is fully set up, move it to its final home.
|
||||
platform_utils.rename(tmpdotgit, realdotgit)
|
||||
# Finish checking out the worktree.
|
||||
cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
|
||||
if GitCommand(self, cmd).Wait() != 0:
|
||||
raise GitError('Cannot initialize work tree for ' + self.name)
|
||||
|
||||
# Finish checking out the worktree.
|
||||
cmd = ['read-tree', '--reset', '-u']
|
||||
cmd.append('-v')
|
||||
cmd.append(HEAD)
|
||||
if GitCommand(self, cmd).Wait() != 0:
|
||||
raise GitError('Cannot initialize work tree for ' + self.name)
|
||||
if submodules:
|
||||
self._SyncSubmodules(quiet=True)
|
||||
self._CopyAndLinkFiles()
|
||||
|
||||
if submodules:
|
||||
self._SyncSubmodules(quiet=True)
|
||||
self._CopyAndLinkFiles()
|
||||
@classmethod
|
||||
def _MigrateOldWorkTreeGitDir(cls, dotgit):
|
||||
"""Migrate the old worktree .git/ dir style to a symlink.
|
||||
|
||||
This logic specifically only uses state from |dotgit| to figure out where to
|
||||
move content and not |self|. This way if the backing project also changed
|
||||
places, we only do the .git/ dir to .git symlink migration here. The path
|
||||
updates will happen independently.
|
||||
"""
|
||||
# Figure out where in .repo/projects/ it's pointing to.
|
||||
if not os.path.islink(os.path.join(dotgit, 'refs')):
|
||||
raise GitError(f'{dotgit}: unsupported checkout state')
|
||||
gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
|
||||
|
||||
# Remove known symlink paths that exist in .repo/projects/.
|
||||
KNOWN_LINKS = {
|
||||
'config', 'description', 'hooks', 'info', 'logs', 'objects',
|
||||
'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
|
||||
}
|
||||
# Paths that we know will be in both, but are safe to clobber in .repo/projects/.
|
||||
SAFE_TO_CLOBBER = {
|
||||
'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
|
||||
'ORIG_HEAD',
|
||||
}
|
||||
|
||||
# First see if we'd succeed before starting the migration.
|
||||
unknown_paths = []
|
||||
for name in platform_utils.listdir(dotgit):
|
||||
# Ignore all temporary/backup names. These are common with vim & emacs.
|
||||
if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
|
||||
continue
|
||||
|
||||
dotgit_path = os.path.join(dotgit, name)
|
||||
if name in KNOWN_LINKS:
|
||||
if not platform_utils.islink(dotgit_path):
|
||||
unknown_paths.append(f'{dotgit_path}: should be a symlink')
|
||||
else:
|
||||
gitdir_path = os.path.join(gitdir, name)
|
||||
if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
|
||||
unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
|
||||
if unknown_paths:
|
||||
raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
|
||||
|
||||
# Now walk the paths and sync the .git/ to .repo/projects/.
|
||||
for name in platform_utils.listdir(dotgit):
|
||||
dotgit_path = os.path.join(dotgit, name)
|
||||
|
||||
# Ignore all temporary/backup names. These are common with vim & emacs.
|
||||
if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
|
||||
platform_utils.remove(dotgit_path)
|
||||
elif name in KNOWN_LINKS:
|
||||
platform_utils.remove(dotgit_path)
|
||||
else:
|
||||
gitdir_path = os.path.join(gitdir, name)
|
||||
platform_utils.remove(gitdir_path, missing_ok=True)
|
||||
platform_utils.rename(dotgit_path, gitdir_path)
|
||||
|
||||
# Now that the dir should be empty, clear it out, and symlink it over.
|
||||
platform_utils.rmdir(dotgit)
|
||||
platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
|
||||
|
||||
def _get_symlink_error_message(self):
|
||||
if platform_utils.isWindows():
|
||||
|
8
repo
8
repo
@ -149,7 +149,7 @@ if not REPO_REV:
|
||||
BUG_URL = 'https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue'
|
||||
|
||||
# increment this whenever we make important changes to this script
|
||||
VERSION = (2, 17)
|
||||
VERSION = (2, 21)
|
||||
|
||||
# increment this if the MAINTAINER_KEYS block is modified
|
||||
KEYRING_VERSION = (2, 3)
|
||||
@ -382,6 +382,11 @@ def InitParser(parser, gitc_init=False):
|
||||
group.add_option('--no-clone-bundle',
|
||||
dest='clone_bundle', action='store_false',
|
||||
help='disable use of /clone.bundle on HTTP/HTTPS (default if --partial-clone)')
|
||||
group.add_option('--git-lfs', action='store_true',
|
||||
help='enable Git LFS support')
|
||||
group.add_option('--no-git-lfs',
|
||||
dest='git_lfs', action='store_false',
|
||||
help='disable Git LFS support')
|
||||
|
||||
# Tool.
|
||||
group = parser.add_option_group('repo Version options')
|
||||
@ -618,6 +623,7 @@ def _Init(args, gitc_init=False):
|
||||
"REPO_URL set correctly?" % url, file=sys.stderr)
|
||||
|
||||
except CloneFailure:
|
||||
print('fatal: double check your --repo-rev setting.', file=sys.stderr)
|
||||
if opt.quiet:
|
||||
print('fatal: repo init failed; run without --quiet to see why',
|
||||
file=sys.stderr)
|
||||
|
@ -69,7 +69,8 @@ It is equivalent to "git branch -D <branchname>".
|
||||
nb = args[0]
|
||||
err = 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):
|
||||
for (results, project) in states:
|
||||
@ -94,7 +95,7 @@ It is equivalent to "git branch -D <branchname>".
|
||||
err_msg = "error: cannot abandon %s" % br
|
||||
print(err_msg, file=sys.stderr)
|
||||
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)
|
||||
elif not success:
|
||||
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"
|
||||
else:
|
||||
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))
|
||||
|
@ -98,7 +98,7 @@ is shown, then the branch appears in all projects.
|
||||
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
|
||||
|
||||
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)
|
||||
all_branches = {}
|
||||
project_cnt = len(projects)
|
||||
@ -147,26 +147,28 @@ is shown, then the branch appears in all projects.
|
||||
hdr('%c%c %-*s' % (current, published, width, name))
|
||||
out.write(' |')
|
||||
|
||||
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
||||
if in_cnt < project_cnt:
|
||||
fmt = out.write
|
||||
paths = []
|
||||
non_cur_paths = []
|
||||
if i.IsSplitCurrent or (in_cnt < project_cnt - in_cnt):
|
||||
if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt):
|
||||
in_type = 'in'
|
||||
for b in i.projects:
|
||||
relpath = b.project.relpath
|
||||
if not i.IsSplitCurrent or b.current:
|
||||
paths.append(b.project.relpath)
|
||||
paths.append(_RelPath(b.project))
|
||||
else:
|
||||
non_cur_paths.append(b.project.relpath)
|
||||
non_cur_paths.append(_RelPath(b.project))
|
||||
else:
|
||||
fmt = out.notinproject
|
||||
in_type = 'not in'
|
||||
have = set()
|
||||
for b in i.projects:
|
||||
have.add(b.project)
|
||||
have.add(_RelPath(b.project))
|
||||
for p in projects:
|
||||
if p not in have:
|
||||
paths.append(p.relpath)
|
||||
if _RelPath(p) not in have:
|
||||
paths.append(_RelPath(p))
|
||||
|
||||
s = ' %s %s' % (in_type, ', '.join(paths))
|
||||
if not i.IsSplitCurrent and (width + 7 + len(s) < 80):
|
||||
|
@ -47,7 +47,7 @@ The command is equivalent to:
|
||||
nb = args[0]
|
||||
err = []
|
||||
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):
|
||||
for status, project in results:
|
||||
|
@ -50,7 +50,7 @@ to the Unix 'patch' command.
|
||||
return (ret, buf.getvalue())
|
||||
|
||||
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):
|
||||
ret = 0
|
||||
|
@ -179,6 +179,9 @@ synced and their revisions won't be found.
|
||||
def ValidateOptions(self, opt, args):
|
||||
if not args or len(args) > 2:
|
||||
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):
|
||||
self.out = _Coloring(self.client.globalConfig)
|
||||
|
@ -48,7 +48,7 @@ If no project is specified try to use current directory as a project.
|
||||
dest='ffonly', action='store_true',
|
||||
help="force fast-forward merge")
|
||||
|
||||
def _ParseChangeIds(self, args):
|
||||
def _ParseChangeIds(self, opt, args):
|
||||
if not args:
|
||||
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)
|
||||
to_get.append((project, chg_id, ps_id))
|
||||
else:
|
||||
projects = self.GetProjects([a])
|
||||
projects = self.GetProjects([a], all_manifests=not opt.this_manifest_only)
|
||||
if len(projects) > 1:
|
||||
# If the cwd is one of the projects, assume they want that.
|
||||
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 '
|
||||
'the project checkout.' % (a,), file=sys.stderr)
|
||||
for project in projects:
|
||||
print(' %s/ @ %s' % (project.relpath, project.revisionExpr),
|
||||
file=sys.stderr)
|
||||
print(' %s/ @ %s' % (project.RelPath(local=opt.this_manifest_only),
|
||||
project.revisionExpr), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
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')
|
||||
|
||||
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)
|
||||
if not dl:
|
||||
print('[%s] change %d/%d not found'
|
||||
|
@ -168,6 +168,7 @@ without iterating through the remaining projects.
|
||||
|
||||
def Execute(self, opt, args):
|
||||
cmd = [opt.command[0]]
|
||||
all_trees = not opt.this_manifest_only
|
||||
|
||||
shell = True
|
||||
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)
|
||||
|
||||
if opt.regex:
|
||||
projects = self.FindProjects(args)
|
||||
projects = self.FindProjects(args, all_manifests=all_trees)
|
||||
elif opt.inverse_regex:
|
||||
projects = self.FindProjects(args, inverse=True)
|
||||
projects = self.FindProjects(args, inverse=True, all_manifests=all_trees)
|
||||
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))
|
||||
|
||||
@ -290,6 +291,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
|
||||
|
||||
setenv('REPO_PROJECT', project.name)
|
||||
setenv('REPO_PATH', project.relpath)
|
||||
setenv('REPO_OUTERPATH', project.RelPath(local=opt.this_manifest_only))
|
||||
setenv('REPO_REMOTE', project.remote.name)
|
||||
try:
|
||||
# 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 = ''
|
||||
if ((opt.project_header and opt.verbose)
|
||||
or not opt.project_header):
|
||||
output = 'skipping %s/' % project.relpath
|
||||
output = 'skipping %s/' % project.RelPath(local=opt.this_manifest_only)
|
||||
return (1, output)
|
||||
|
||||
if opt.verbose:
|
||||
@ -344,7 +346,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
|
||||
if mirror:
|
||||
project_header_path = project.name
|
||||
else:
|
||||
project_header_path = project.relpath
|
||||
project_header_path = project.RelPath(local=opt.this_manifest_only)
|
||||
out.project('project %s/' % project_header_path)
|
||||
out.nl()
|
||||
buf.write(output)
|
||||
|
@ -24,6 +24,7 @@ import wrapper
|
||||
|
||||
class GitcInit(init.Init, GitcAvailableCommand):
|
||||
COMMON = True
|
||||
MULTI_MANIFEST_SUPPORT = False
|
||||
helpSummary = "Initialize a GITC Client."
|
||||
helpUsage = """
|
||||
%prog [options] [client name]
|
||||
|
@ -172,15 +172,16 @@ contain a line that matches both expressions:
|
||||
return (project, p.Wait(), p.stdout, p.stderr)
|
||||
|
||||
@staticmethod
|
||||
def _ProcessResults(full_name, have_rev, _pool, out, results):
|
||||
def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
|
||||
git_failed = False
|
||||
bad_rev = False
|
||||
have_match = False
|
||||
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
||||
|
||||
for project, rc, stdout, stderr in results:
|
||||
if rc < 0:
|
||||
git_failed = True
|
||||
out.project('--- project %s ---' % project.relpath)
|
||||
out.project('--- project %s ---' % _RelPath(project))
|
||||
out.nl()
|
||||
out.fail('%s', stderr)
|
||||
out.nl()
|
||||
@ -192,7 +193,7 @@ contain a line that matches both expressions:
|
||||
if have_rev and 'fatal: ambiguous argument' in stderr:
|
||||
bad_rev = True
|
||||
else:
|
||||
out.project('--- project %s ---' % project.relpath)
|
||||
out.project('--- project %s ---' % _RelPath(project))
|
||||
out.nl()
|
||||
out.fail('%s', stderr.strip())
|
||||
out.nl()
|
||||
@ -208,13 +209,13 @@ contain a line that matches both expressions:
|
||||
rev, line = line.split(':', 1)
|
||||
out.write("%s", rev)
|
||||
out.write(':')
|
||||
out.project(project.relpath)
|
||||
out.project(_RelPath(project))
|
||||
out.write('/')
|
||||
out.write("%s", line)
|
||||
out.nl()
|
||||
elif full_name:
|
||||
for line in r:
|
||||
out.project(project.relpath)
|
||||
out.project(_RelPath(project))
|
||||
out.write('/')
|
||||
out.write("%s", line)
|
||||
out.nl()
|
||||
@ -239,7 +240,7 @@ contain a line that matches both expressions:
|
||||
cmd_argv.append(args[0])
|
||||
args = args[1:]
|
||||
|
||||
projects = self.GetProjects(args)
|
||||
projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
|
||||
|
||||
full_name = False
|
||||
if len(projects) > 1:
|
||||
@ -259,7 +260,7 @@ contain a line that matches both expressions:
|
||||
opt.jobs,
|
||||
functools.partial(self._ExecuteOne, cmd_argv),
|
||||
projects,
|
||||
callback=functools.partial(self._ProcessResults, full_name, have_rev),
|
||||
callback=functools.partial(self._ProcessResults, full_name, have_rev, opt),
|
||||
output=out,
|
||||
ordered=True)
|
||||
|
||||
|
@ -61,6 +61,8 @@ class Info(PagedCommand):
|
||||
|
||||
self.opt = opt
|
||||
|
||||
if not opt.this_manifest_only:
|
||||
self.manifest = self.manifest.outer_client
|
||||
manifestConfig = self.manifest.manifestProject.config
|
||||
mergeBranch = manifestConfig.GetBranch("default").merge
|
||||
manifestGroups = (manifestConfig.GetString('manifest.groups')
|
||||
@ -80,17 +82,17 @@ class Info(PagedCommand):
|
||||
self.printSeparator()
|
||||
|
||||
if not opt.overview:
|
||||
self.printDiffInfo(args)
|
||||
self._printDiffInfo(opt, args)
|
||||
else:
|
||||
self.printCommitOverview(args)
|
||||
self._printCommitOverview(opt, args)
|
||||
|
||||
def printSeparator(self):
|
||||
self.text("----------------------------")
|
||||
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.
|
||||
projs = self.GetProjects(args)
|
||||
projs = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
|
||||
|
||||
for p in projs:
|
||||
self.heading("Project: ")
|
||||
@ -179,9 +181,9 @@ class Info(PagedCommand):
|
||||
self.text(" ".join(split[1:]))
|
||||
self.out.nl()
|
||||
|
||||
def printCommitOverview(self, args):
|
||||
def _printCommitOverview(self, opt, args):
|
||||
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)
|
||||
for x in project.GetBranches()]
|
||||
br = [x for x in br if x]
|
||||
@ -200,7 +202,7 @@ class Info(PagedCommand):
|
||||
if project != branch.project:
|
||||
project = branch.project
|
||||
self.out.nl()
|
||||
self.headtext(project.relpath)
|
||||
self.headtext(project.RelPath(local=opt.this_manifest_only))
|
||||
self.out.nl()
|
||||
|
||||
commits = branch.commits
|
||||
|
@ -32,6 +32,7 @@ from wrapper import Wrapper
|
||||
|
||||
class Init(InteractiveCommand, MirrorSafeCommand):
|
||||
COMMON = True
|
||||
MULTI_MANIFEST_SUPPORT = False
|
||||
helpSummary = "Initialize a repo client checkout in the current directory"
|
||||
helpUsage = """
|
||||
%prog [options] [manifest url]
|
||||
@ -90,6 +91,17 @@ to update the working directory files.
|
||||
|
||||
def _Options(self, p, gitc_init=False):
|
||||
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):
|
||||
return {'REPO_MANIFEST_URL': 'manifest_url',
|
||||
@ -291,6 +303,15 @@ to update the working directory files.
|
||||
if opt.submodules:
|
||||
m.config.SetBoolean('repo.submodules', opt.submodules)
|
||||
|
||||
if opt.git_lfs is not None:
|
||||
if opt.git_lfs:
|
||||
git_require((2, 17, 0), fail=True, msg='Git LFS support')
|
||||
|
||||
m.config.SetBoolean('repo.git-lfs', opt.git_lfs)
|
||||
if not is_new:
|
||||
print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
|
||||
' Existing projects will require manual updates.\n', file=sys.stderr)
|
||||
|
||||
if opt.use_superproject is not None:
|
||||
m.config.SetBoolean('repo.superproject', opt.use_superproject)
|
||||
|
||||
@ -520,8 +541,12 @@ to update the working directory files.
|
||||
# Handle new --repo-rev requests.
|
||||
if opt.repo_rev:
|
||||
wrapper = Wrapper()
|
||||
remote_ref, rev = wrapper.check_repo_rev(
|
||||
rp.gitdir, opt.repo_rev, repo_verify=opt.repo_verify, quiet=opt.quiet)
|
||||
try:
|
||||
remote_ref, rev = wrapper.check_repo_rev(
|
||||
rp.gitdir, opt.repo_rev, repo_verify=opt.repo_verify, quiet=opt.quiet)
|
||||
except wrapper.CloneFailure:
|
||||
print('fatal: double check your --repo-rev setting.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
branch = rp.GetBranch('default')
|
||||
branch.merge = remote_ref
|
||||
rp.work_git.reset('--hard', rev)
|
||||
|
@ -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.
|
||||
"""
|
||||
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:
|
||||
projects = self.FindProjects(args)
|
||||
projects = self.FindProjects(args, all_manifests=not opt.this_manifest_only)
|
||||
|
||||
def _getpath(x):
|
||||
if opt.fullpath:
|
||||
return x.worktree
|
||||
if opt.relative_to:
|
||||
return os.path.relpath(x.worktree, opt.relative_to)
|
||||
return x.relpath
|
||||
return x.RelPath(local=opt.this_manifest_only)
|
||||
|
||||
lines = []
|
||||
for project in projects:
|
||||
|
@ -15,6 +15,7 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import optparse
|
||||
|
||||
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',
|
||||
dest='output_file',
|
||||
default='-',
|
||||
help='file to save the manifest to',
|
||||
help='file to save the manifest to. (Filename prefix for multi-tree.)',
|
||||
metavar='-|NAME.xml')
|
||||
|
||||
def _Output(self, opt):
|
||||
@ -83,36 +84,45 @@ to indicate the remote ref to push changes to via 'repo upload'.
|
||||
if opt.manifest_name:
|
||||
self.manifest.Override(opt.manifest_name, False)
|
||||
|
||||
if opt.output_file == '-':
|
||||
fd = sys.stdout
|
||||
else:
|
||||
fd = open(opt.output_file, 'w')
|
||||
for manifest in self.ManifestList(opt):
|
||||
output_file = opt.output_file
|
||||
if output_file == '-':
|
||||
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:
|
||||
print('warning: --json is experimental!', file=sys.stderr)
|
||||
doc = self.manifest.ToDict(peg_rev=opt.peg_rev,
|
||||
peg_rev_upstream=opt.peg_rev_upstream,
|
||||
peg_rev_dest_branch=opt.peg_rev_dest_branch)
|
||||
if opt.json:
|
||||
print('warning: --json is experimental!', file=sys.stderr)
|
||||
doc = manifest.ToDict(peg_rev=opt.peg_rev,
|
||||
peg_rev_upstream=opt.peg_rev_upstream,
|
||||
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):
|
||||
if args:
|
||||
|
@ -47,7 +47,7 @@ are displayed.
|
||||
|
||||
def Execute(self, opt, args):
|
||||
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)
|
||||
for x in project.GetBranches()]
|
||||
br = [x for x in br if x]
|
||||
@ -76,7 +76,7 @@ are displayed.
|
||||
if project != branch.project:
|
||||
project = branch.project
|
||||
out.nl()
|
||||
out.project('project %s/' % project.relpath)
|
||||
out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
|
||||
out.nl()
|
||||
|
||||
commits = branch.commits
|
||||
|
@ -31,7 +31,7 @@ class Prune(PagedCommand):
|
||||
return project.PruneHeads()
|
||||
|
||||
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
|
||||
# come back from children.
|
||||
@ -63,7 +63,7 @@ class Prune(PagedCommand):
|
||||
if project != branch.project:
|
||||
project = branch.project
|
||||
out.nl()
|
||||
out.project('project %s/' % project.relpath)
|
||||
out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
|
||||
out.nl()
|
||||
|
||||
print('%s %-33s ' % (
|
||||
|
@ -69,7 +69,7 @@ branch but need to incorporate new upstream changes "underneath" them.
|
||||
'consistent if you previously synced to a manifest)')
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
out = RebaseColoring(config)
|
||||
out.redirect(sys.stdout)
|
||||
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
|
||||
|
||||
ret = 0
|
||||
for project in all_projects:
|
||||
@ -107,7 +108,7 @@ branch but need to incorporate new upstream changes "underneath" them.
|
||||
cb = project.CurrentBranch
|
||||
if not cb:
|
||||
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)
|
||||
return 1
|
||||
# ignore branches with detatched HEADs
|
||||
@ -117,7 +118,7 @@ branch but need to incorporate new upstream changes "underneath" them.
|
||||
if not upbranch.LocalMerge:
|
||||
if one_project:
|
||||
print("error: project %s does not track any remote branches"
|
||||
% project.relpath, file=sys.stderr)
|
||||
% _RelPath(project), file=sys.stderr)
|
||||
return 1
|
||||
# ignore branches without remotes
|
||||
continue
|
||||
@ -130,7 +131,7 @@ branch but need to incorporate new upstream changes "underneath" them.
|
||||
args.append(upbranch.LocalMerge)
|
||||
|
||||
out.project('project %s: rebasing %s -> %s',
|
||||
project.relpath, cb, upbranch.LocalMerge)
|
||||
_RelPath(project), cb, upbranch.LocalMerge)
|
||||
out.nl()
|
||||
out.flush()
|
||||
|
||||
|
@ -50,7 +50,9 @@ The '%prog' command stages files to prepare the next commit.
|
||||
self.Usage()
|
||||
|
||||
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:
|
||||
print('no projects have uncommitted modifications', file=sys.stderr)
|
||||
return
|
||||
@ -62,7 +64,8 @@ The '%prog' command stages files to prepare the next commit.
|
||||
|
||||
for i in range(len(all_projects)):
|
||||
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()
|
||||
|
||||
@ -99,7 +102,9 @@ The '%prog' command stages files to prepare the next commit.
|
||||
_AddI(all_projects[a_index - 1])
|
||||
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:
|
||||
_AddI(projects[0])
|
||||
continue
|
||||
|
@ -84,7 +84,8 @@ revision specified in the manifest.
|
||||
projects = ['.'] # start it in the local project by default
|
||||
|
||||
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
|
||||
# the local directory, which will disappear once we save the GITC manifest.
|
||||
@ -137,6 +138,6 @@ revision specified in the manifest.
|
||||
|
||||
if 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)
|
||||
sys.exit(1)
|
||||
|
@ -117,7 +117,7 @@ the following meanings:
|
||||
outstring.append(''.join([status_header, item, '/']))
|
||||
|
||||
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):
|
||||
ret = 0
|
||||
@ -141,9 +141,10 @@ the following meanings:
|
||||
if opt.orphans:
|
||||
proj_dirs = set()
|
||||
proj_dirs_parents = set()
|
||||
for project in self.GetProjects(None, missing_ok=True):
|
||||
proj_dirs.add(project.relpath)
|
||||
(head, _tail) = os.path.split(project.relpath)
|
||||
for project in self.GetProjects(None, missing_ok=True, all_manifests=not opt.this_manifest_only):
|
||||
relpath = project.RelPath(local=opt.this_manifest_only)
|
||||
proj_dirs.add(relpath)
|
||||
(head, _tail) = os.path.split(relpath)
|
||||
while head != "":
|
||||
proj_dirs_parents.add(head)
|
||||
(head, _tail) = os.path.split(head)
|
||||
|
@ -66,6 +66,7 @@ _ONE_DAY_S = 24 * 60 * 60
|
||||
class Sync(Command, MirrorSafeCommand):
|
||||
jobs = 1
|
||||
COMMON = True
|
||||
MULTI_MANIFEST_SUPPORT = False
|
||||
helpSummary = "Update working tree to the latest revision"
|
||||
helpUsage = """
|
||||
%prog [<project>...]
|
||||
@ -704,7 +705,7 @@ later is required to fix a server side protocol bug.
|
||||
if project.relpath:
|
||||
new_project_paths.append(project.relpath)
|
||||
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 = []
|
||||
|
||||
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_path = os.path.join(self.manifest.repodir, copylinkfile_name)
|
||||
copylinkfile_path = os.path.join(self.manifest.subdir, copylinkfile_name)
|
||||
old_copylinkfile_paths = {}
|
||||
|
||||
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:
|
||||
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):
|
||||
if opt.jobs:
|
||||
self.jobs = opt.jobs
|
||||
@ -986,10 +990,11 @@ later is required to fix a server side protocol bug.
|
||||
|
||||
load_local_manifests = not self.manifest.HasLocalManifests
|
||||
use_superproject = git_superproject.UseSuperproject(opt, self.manifest)
|
||||
if self.manifest.IsMirror or self.manifest.IsArchive:
|
||||
if use_superproject and (self.manifest.IsMirror or self.manifest.IsArchive):
|
||||
# Don't use superproject, because we have no working tree.
|
||||
use_superproject = False
|
||||
print('Defaulting to no-use-superproject because there is no working tree.')
|
||||
if opt.use_superproject is not None:
|
||||
print('Defaulting to no-use-superproject because there is no working tree.')
|
||||
superproject_logging_data = {
|
||||
'superproject': use_superproject,
|
||||
'haslocalmanifests': bool(self.manifest.HasLocalManifests),
|
||||
|
@ -226,7 +226,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
|
||||
destination = opt.dest_branch or project.dest_branch or project.revisionExpr
|
||||
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):' % (
|
||||
name,
|
||||
len(commit_list),
|
||||
@ -262,7 +263,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
script.append('# Uncomment the branches to upload:')
|
||||
for project, avail in pending:
|
||||
script.append('#')
|
||||
script.append('# project %s/:' % project.relpath)
|
||||
script.append('# project %s/:' % project.RelPath(local=opt.this_manifest_only))
|
||||
|
||||
b = {}
|
||||
for branch in avail:
|
||||
@ -285,7 +286,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
script.append('# %s' % commit)
|
||||
b[name] = branch
|
||||
|
||||
projects[project.relpath] = project
|
||||
projects[project.RelPath(local=opt.this_manifest_only)] = project
|
||||
branches[project.name] = b
|
||||
script.append('')
|
||||
|
||||
@ -313,7 +314,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
_die('project for branch %s not in script', name)
|
||||
branch = branches[project.name].get(name)
|
||||
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)
|
||||
if not todo:
|
||||
_die("nothing uncommented for upload")
|
||||
@ -481,7 +482,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
else:
|
||||
fmt = '\n (%s)'
|
||||
print(('[FAILED] %-15s %-15s' + fmt) % (
|
||||
branch.project.relpath + '/',
|
||||
branch.project.RelPath(local=opt.this_manifest_only) + '/',
|
||||
branch.name,
|
||||
str(branch.error)),
|
||||
file=sys.stderr)
|
||||
@ -490,7 +491,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
for branch in todo:
|
||||
if branch.uploaded:
|
||||
print('[OK ] %-15s %s' % (
|
||||
branch.project.relpath + '/',
|
||||
branch.project.RelPath(local=opt.this_manifest_only) + '/',
|
||||
branch.name),
|
||||
file=sys.stderr)
|
||||
|
||||
@ -524,7 +525,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
return (project, avail)
|
||||
|
||||
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):
|
||||
pending = []
|
||||
@ -534,7 +535,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
print('repo: error: %s: Unable to upload branch "%s". '
|
||||
'You might be able to fix the branch by running:\n'
|
||||
' 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)
|
||||
elif avail:
|
||||
pending.append(result)
|
||||
@ -554,15 +556,23 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
||||
(opt.branch,), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
pending_proj_names = [project.name for (project, available) in pending]
|
||||
pending_worktrees = [project.worktree for (project, available) in pending]
|
||||
hook = RepoHook.FromSubcmd(
|
||||
hook_type='pre-upload', manifest=self.manifest,
|
||||
opt=opt, abort_if_user_denies=True)
|
||||
if not hook.Run(
|
||||
project_list=pending_proj_names,
|
||||
worktree_list=pending_worktrees):
|
||||
return 1
|
||||
manifests = {project.manifest.topdir: project.manifest
|
||||
for (project, available) in pending}
|
||||
ret = 0
|
||||
for manifest in manifests.values():
|
||||
pending_proj_names = [project.name for (project, available) in pending
|
||||
if project.manifest.topdir == manifest.topdir]
|
||||
pending_worktrees = [project.worktree for (project, available) in pending
|
||||
if project.manifest.topdir == manifest.topdir]
|
||||
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 []
|
||||
cc = _SplitEmails(opt.cc) if opt.cc else []
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
@ -335,3 +336,76 @@ class LinkFile(CopyLinkTestCase):
|
||||
platform_utils.symlink(self.tempdir, dest)
|
||||
lf._Link()
|
||||
self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest))
|
||||
|
||||
|
||||
class MigrateWorkTreeTests(unittest.TestCase):
|
||||
"""Check _MigrateOldWorkTreeGitDir handling."""
|
||||
|
||||
_SYMLINKS = {
|
||||
'config', 'description', 'hooks', 'info', 'logs', 'objects',
|
||||
'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
|
||||
}
|
||||
_FILES = {
|
||||
'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD',
|
||||
'unknown-file-should-be-migrated',
|
||||
}
|
||||
_CLEAN_FILES = {
|
||||
'a-vim-temp-file~', '#an-emacs-temp-file#',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@contextlib.contextmanager
|
||||
def _simple_layout(cls):
|
||||
"""Create a simple repo client checkout to test against."""
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
tempdir = Path(tempdir)
|
||||
|
||||
gitdir = tempdir / '.repo/projects/src/test.git'
|
||||
gitdir.mkdir(parents=True)
|
||||
cmd = ['git', 'init', '--bare', str(gitdir)]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
dotgit = tempdir / 'src/test/.git'
|
||||
dotgit.mkdir(parents=True)
|
||||
for name in cls._SYMLINKS:
|
||||
(dotgit / name).symlink_to(f'../../../.repo/projects/src/test.git/{name}')
|
||||
for name in cls._FILES | cls._CLEAN_FILES:
|
||||
(dotgit / name).write_text(name)
|
||||
|
||||
yield tempdir
|
||||
|
||||
def test_standard(self):
|
||||
"""Migrate a standard checkout that we expect."""
|
||||
with self._simple_layout() as tempdir:
|
||||
dotgit = tempdir / 'src/test/.git'
|
||||
project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
|
||||
|
||||
# Make sure the dir was transformed into a symlink.
|
||||
self.assertTrue(dotgit.is_symlink())
|
||||
self.assertEqual(os.readlink(dotgit), '../../.repo/projects/src/test.git')
|
||||
|
||||
# Make sure files were moved over.
|
||||
gitdir = tempdir / '.repo/projects/src/test.git'
|
||||
for name in self._FILES:
|
||||
self.assertEqual(name, (gitdir / name).read_text())
|
||||
# Make sure files were removed.
|
||||
for name in self._CLEAN_FILES:
|
||||
self.assertFalse((gitdir / name).exists())
|
||||
|
||||
def test_unknown(self):
|
||||
"""A checkout with unknown files should abort."""
|
||||
with self._simple_layout() as tempdir:
|
||||
dotgit = tempdir / 'src/test/.git'
|
||||
(tempdir / '.repo/projects/src/test.git/random-file').write_text('one')
|
||||
(dotgit / 'random-file').write_text('two')
|
||||
with self.assertRaises(error.GitError):
|
||||
project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
|
||||
|
||||
# Make sure no content was actually changed.
|
||||
self.assertTrue(dotgit.is_dir())
|
||||
for name in self._FILES:
|
||||
self.assertTrue((dotgit / name).is_file())
|
||||
for name in self._CLEAN_FILES:
|
||||
self.assertTrue((dotgit / name).is_file())
|
||||
for name in self._SYMLINKS:
|
||||
self.assertTrue((dotgit / name).is_symlink())
|
||||
|
Reference in New Issue
Block a user