Merge branch 'stable'
* stable:
Automatically install Gerrit Code Review's commit-msg hook
Fail sync when encountering "N commits behind."
Check that we are not overwriting a local repository when syncing.
Honor url.insteadOf when setting up SSH control master connection
sync: Fix split call on malformed email addresses
Fixing project renaming bug.
Conflicts:
hooks/commit-msg
project.py
subcmds/sync.py
Change-Id: I5eaf8fef8cbe4a95d124368112293a9ca64325bf
diff --git a/command.py b/command.py
index a941b95..6e4e2c5 100644
--- a/command.py
+++ b/command.py
@@ -17,6 +17,8 @@
import optparse
import sys
+import manifest_loader
+
from error import NoSuchProjectError
class Command(object):
@@ -24,7 +26,6 @@
"""
common = False
- manifest = None
_optparse = None
def WantPager(self, opt):
@@ -57,10 +58,25 @@
"""
raise NotImplementedError
+ @property
+ def manifest(self):
+ return self.GetManifest()
+
+ def GetManifest(self, reparse=False, type=None):
+ return manifest_loader.GetManifest(self.repodir,
+ reparse=reparse,
+ type=type)
+
def GetProjects(self, args, missing_ok=False):
"""A list of projects that match the arguments.
"""
all = self.manifest.projects
+
+ mp = self.manifest.manifestProject
+ if mp.relpath == '.':
+ all = dict(all)
+ all[mp.name] = mp
+
result = []
if not args:
@@ -81,7 +97,9 @@
for p in all.values():
by_path[p.worktree] = p
- if os.path.exists(path):
+ try:
+ project = by_path[path]
+ except KeyError:
while path \
and path != '/' \
and path != self.manifest.topdir:
@@ -90,11 +108,6 @@
break
except KeyError:
path = os.path.dirname(path)
- else:
- try:
- project = by_path[path]
- except KeyError:
- pass
if not project:
raise NoSuchProjectError(arg)
diff --git a/docs/manifest_submodule.txt b/docs/manifest_submodule.txt
new file mode 100644
index 0000000..e7d1f64
--- /dev/null
+++ b/docs/manifest_submodule.txt
@@ -0,0 +1,130 @@
+repo Manifest Format (submodule)
+================================
+
+A repo manifest describes the structure of a repo client; that is
+the directories that are visible and where they should be obtained
+from with git.
+
+The basic structure of a manifest is a bare Git repository holding
+a 'gitmodules' file in the top level directory, and one or more
+gitlink references pointing at commits from the referenced projects.
+This is the same structure as used by 'git submodule'.
+
+Manifests are inherently version controlled, since they are kept
+within a Git repository. Updates to manifests are automatically
+obtained by clients during `repo sync`.
+
+.gitmodules
+===========
+
+The '.gitmodules' file, located in the top-level directory of the
+client's working tree (or manifest repository), is a text file with
+a syntax matching the requirements of 'git config'.
+
+This file contains one subsection per project (also called a
+submodule by git), and the subsection value is a unique name to
+describe the project. Each submodule section must contain the
+following required keys:
+
+ * path
+ * url
+
+submodule.<name>.path
+---------------------
+
+Defines the path, relative to the top-level directory of the client's
+working tree, where the project is expected to be checked out. The
+path name must not end with a '/'. All paths must be unique within
+the .gitmodules file.
+
+At the specified path within the manifest repository a gitlink
+tree entry (an entry with file mode 160000) must exist referencing
+a commit SHA-1 from the project. This tree entry specifies the
+exact version of the project that `repo sync` will synchronize the
+client's working tree to.
+
+submodule.<name>.url
+--------------------
+
+Defines a URL from where the project repository can be cloned.
+By default `repo sync` will clone from this URL whenever a user
+needs to access this project.
+
+submodule.<name>.revision
+-------------------------
+
+Name of the branch in the project repository that Gerrit Code Review
+should automatically refresh the project's gitlink entry from.
+
+If set, during submit of a change within the referenced project,
+Gerrit Code Review will automatically update the manifest
+repository's corresponding gitlink to the new commit SHA-1 of
+this branch.
+
+Valid values are a short branch name (e.g. 'master'), a full ref
+name (e.g. 'refs/heads/master'), or '.' to request using the same
+branch name as the manifest branch itself. Since '.' automatically
+uses the manifest branch, '.' is the recommended value.
+
+If this key is not set, Gerrit Code Review will NOT automatically
+update the gitlink. An unset key requires the manifest maintainer
+to manually update the gitlink when it is necessary to reference
+a different revision of the project.
+
+submodule.<name>.update
+-----------------------
+
+This key is not supported by repo. If set, it will be ignored.
+
+.review
+=======
+
+The optional '.review' file, located in the top-level directory of
+the client's working tree (or manifest repository), is a text file
+with a syntax matching the requirements of 'git config'.
+
+This file describes how `repo upload` should interact with the
+project's preferred code review system.
+
+review.url
+----------
+
+URL of the default Gerrit Code Review server. If a project does
+not have a specific URL in the '.review' file, this default URL
+will be used instead.
+
+review.<name>.url
+-----------------
+
+Project specific URL of the Gerrit Code Review server, for the
+submodule whose project name is <name>.
+
+Example
+=======
+
+ $ cat .gitmodules
+ [submodule "app/Clock"]
+ path = clock
+ url = git://vcs.example.com/ClockWidget.git
+ revision = .
+ [submodule "app/Browser"]
+ path = net/browser
+ url = git://netgroup.example.com/network/web/Browser.git
+ revision = .
+
+ $ cat .review
+ [review]
+ url = vcs-gerrit.example.com
+ [review "app/Browser"]
+ url = netgroup.example.com
+
+In the above example, the app/Clock project will send its code
+reviews to the default server, vcs-gerrit.example.com, while
+app/Browser will send its code reviews to netgroup.example.com.
+
+See Also
+========
+
+ * http://www.kernel.org/pub/software/scm/git/docs/gitmodules.html
+ * http://www.kernel.org/pub/software/scm/git/docs/git-config.html
+ * http://code.google.com/p/gerrit/
diff --git a/docs/manifest-format.txt b/docs/manifest_xml.txt
similarity index 100%
rename from docs/manifest-format.txt
rename to docs/manifest_xml.txt
diff --git a/git_config.py b/git_config.py
index 45a2d25..4a42c04 100644
--- a/git_config.py
+++ b/git_config.py
@@ -19,6 +19,8 @@
import subprocess
import sys
import time
+import urllib2
+
from signal import SIGTERM
from urllib2 import urlopen, HTTPError
from error import GitError, UploadError
@@ -71,6 +73,14 @@
else:
self._pickle = pickleFile
+ def ClearCache(self):
+ if os.path.exists(self._pickle):
+ os.remove(self._pickle)
+ self._cache_dict = None
+ self._section_dict = None
+ self._remotes = {}
+ self._branches = {}
+
def Has(self, name, include_defaults = True):
"""Return true if this configuration file has the key.
"""
@@ -254,9 +264,11 @@
finally:
fd.close()
except IOError:
- os.remove(self._pickle)
+ if os.path.exists(self._pickle):
+ os.remove(self._pickle)
except cPickle.PickleError:
- os.remove(self._pickle)
+ if os.path.exists(self._pickle):
+ os.remove(self._pickle)
def _ReadGit(self):
"""
@@ -356,10 +368,14 @@
_ssh_cache = {}
_ssh_master = True
-def _open_ssh(host, port):
+def _open_ssh(host, port=None):
global _ssh_master
+ if port is not None:
+ key = '%s:%s' % (host, port)
+ else:
+ key = host
+
- key = '%s:%s' % (host, port)
if key in _ssh_cache:
return True
@@ -372,10 +388,13 @@
command = ['ssh',
'-o','ControlPath %s' % _ssh_sock(),
- '-p',str(port),
'-M',
'-N',
host]
+
+ if port is not None:
+ command[3:3] = ['-p',str(port)]
+
try:
Trace(': %s', ' '.join(command))
p = subprocess.Popen(command)
@@ -417,7 +436,7 @@
if ':' in host:
host, port = host.split(':')
else:
- port = 22
+ port = None
if scheme in ('ssh', 'git+ssh', 'ssh+git'):
return _open_ssh(host, port)
return False
@@ -425,7 +444,7 @@
m = URI_SCP.match(url)
if m:
host = m.group(1)
- return _open_ssh(host, 22)
+ return _open_ssh(host)
return False
@@ -492,23 +511,25 @@
try:
info = urlopen(u).read()
if info == 'NOT_AVAILABLE':
- raise UploadError('Upload over ssh unavailable')
+ raise UploadError('%s: SSH disabled' % self.review)
if '<' in info:
# Assume the server gave us some sort of HTML
# response back, like maybe a login page.
#
- raise UploadError('Cannot read %s:\n%s' % (u, info))
+ raise UploadError('%s: Cannot parse response' % u)
self._review_protocol = 'ssh'
self._review_host = info.split(" ")[0]
self._review_port = info.split(" ")[1]
+ except urllib2.URLError, e:
+ raise UploadError('%s: %s' % (self.review, e.reason[1]))
except HTTPError, e:
if e.code == 404:
self._review_protocol = 'http-post'
self._review_host = None
self._review_port = None
else:
- raise UploadError('Cannot guess Gerrit version')
+ raise UploadError('Upload over ssh unavailable')
REVIEW_CACHE[u] = (
self._review_protocol,
diff --git a/git_refs.py b/git_refs.py
index ac8ed0c..b24a0b4 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -21,7 +21,6 @@
R_HEADS = 'refs/heads/'
R_TAGS = 'refs/tags/'
R_PUB = 'refs/published/'
-R_M = 'refs/remotes/m/'
class GitRefs(object):
diff --git a/main.py b/main.py
index 70ddeff..2cc7e44 100755
--- a/main.py
+++ b/main.py
@@ -32,11 +32,9 @@
from command import InteractiveCommand
from command import MirrorSafeCommand
from command import PagedCommand
-from editor import Editor
from error import ManifestInvalidRevisionError
from error import NoSuchProjectError
from error import RepoChangedException
-from manifest_xml import XmlManifest
from pager import RunPager
from subcmds import all as all_commands
@@ -61,6 +59,8 @@
def __init__(self, repodir):
self.repodir = repodir
self.commands = all_commands
+ # add 'branch' as an alias for 'branches'
+ all_commands['branch'] = all_commands['branches']
def _Run(self, argv):
name = None
@@ -97,8 +97,6 @@
sys.exit(1)
cmd.repodir = self.repodir
- cmd.manifest = XmlManifest(cmd.repodir)
- Editor.globalConfig = cmd.manifest.globalConfig
if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
print >>sys.stderr, \
diff --git a/manifest.py b/manifest.py
new file mode 100644
index 0000000..f737e86
--- /dev/null
+++ b/manifest.py
@@ -0,0 +1,51 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from error import ManifestParseError
+from editor import Editor
+from git_config import GitConfig
+from project import MetaProject
+
+class Manifest(object):
+ """any manifest format"""
+
+ def __init__(self, repodir):
+ self.repodir = os.path.abspath(repodir)
+ self.topdir = os.path.dirname(self.repodir)
+ self.globalConfig = GitConfig.ForUser()
+ Editor.globalConfig = self.globalConfig
+
+ self.repoProject = MetaProject(self, 'repo',
+ gitdir = os.path.join(repodir, 'repo/.git'),
+ worktree = os.path.join(repodir, 'repo'))
+
+ @property
+ def IsMirror(self):
+ return self.manifestProject.config.GetBoolean('repo.mirror')
+
+ @property
+ def projects(self):
+ return {}
+
+ def InitBranch(self):
+ pass
+
+ def SetMRefs(self, project):
+ pass
+
+ def Upgrade_Local(self, old):
+ raise ManifestParseError, 'unsupported upgrade path'
diff --git a/manifest_loader.py b/manifest_loader.py
new file mode 100644
index 0000000..467cb42
--- /dev/null
+++ b/manifest_loader.py
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from manifest_submodule import SubmoduleManifest
+from manifest_xml import XmlManifest
+
+def ParseManifest(repodir, type=None):
+ if type:
+ return type(repodir)
+ if SubmoduleManifest.Is(repodir):
+ return SubmoduleManifest(repodir)
+ return XmlManifest(repodir)
+
+_manifest = None
+
+def GetManifest(repodir, reparse=False, type=None):
+ global _manifest
+ if _manifest is None \
+ or reparse \
+ or (type and _manifest.__class__ != type):
+ _manifest = ParseManifest(repodir, type=type)
+ return _manifest
diff --git a/manifest_submodule.py b/manifest_submodule.py
new file mode 100644
index 0000000..92f187a
--- /dev/null
+++ b/manifest_submodule.py
@@ -0,0 +1,474 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import os
+import shutil
+
+from error import GitError
+from error import ManifestParseError
+from git_command import GitCommand
+from git_config import GitConfig
+from git_config import IsId
+from manifest import Manifest
+from progress import Progress
+from project import RemoteSpec
+from project import Project
+from project import MetaProject
+from project import R_HEADS
+from project import HEAD
+from project import _lwrite
+
+import manifest_xml
+
+GITLINK = '160000'
+
+def _rmdir(dir, top):
+ while dir != top:
+ try:
+ os.rmdir(dir)
+ except OSError:
+ break
+ dir = os.path.dirname(dir)
+
+def _rmref(gitdir, ref):
+ os.remove(os.path.join(gitdir, ref))
+ log = os.path.join(gitdir, 'logs', ref)
+ if os.path.exists(log):
+ os.remove(log)
+ _rmdir(os.path.dirname(log), gitdir)
+
+def _has_gitmodules(d):
+ return os.path.exists(os.path.join(d, '.gitmodules'))
+
+class SubmoduleManifest(Manifest):
+ """manifest from .gitmodules file"""
+
+ @classmethod
+ def Is(cls, repodir):
+ return _has_gitmodules(os.path.dirname(repodir)) \
+ or _has_gitmodules(os.path.join(repodir, 'manifest')) \
+ or _has_gitmodules(os.path.join(repodir, 'manifests'))
+
+ @classmethod
+ def IsBare(cls, p):
+ try:
+ p.bare_git.cat_file('-e', '%s:.gitmodules' % p.GetRevisionId())
+ except GitError:
+ return False
+ return True
+
+ def __init__(self, repodir):
+ Manifest.__init__(self, repodir)
+
+ gitdir = os.path.join(repodir, 'manifest.git')
+ config = GitConfig.ForRepository(gitdir = gitdir)
+
+ if config.GetBoolean('repo.mirror'):
+ worktree = os.path.join(repodir, 'manifest')
+ relpath = None
+ else:
+ worktree = self.topdir
+ relpath = '.'
+
+ self.manifestProject = MetaProject(self, '__manifest__',
+ gitdir = gitdir,
+ worktree = worktree,
+ relpath = relpath)
+ self._modules = GitConfig(os.path.join(worktree, '.gitmodules'),
+ pickleFile = os.path.join(
+ repodir, '.repopickle_gitmodules'
+ ))
+ self._review = GitConfig(os.path.join(worktree, '.review'),
+ pickleFile = os.path.join(
+ repodir, '.repopickle_review'
+ ))
+ self._Unload()
+
+ @property
+ def projects(self):
+ self._Load()
+ return self._projects
+
+ def InitBranch(self):
+ m = self.manifestProject
+ if m.CurrentBranch is None:
+ b = m.revisionExpr
+ if b.startswith(R_HEADS):
+ b = b[len(R_HEADS):]
+ return m.StartBranch(b)
+ return True
+
+ def SetMRefs(self, project):
+ if project.revisionId is None:
+ # Special project, e.g. the manifest or repo executable.
+ #
+ return
+
+ ref = 'refs/remotes/m'
+ cur = project.bare_ref.get(ref)
+ exp = project.revisionId
+ if cur != exp:
+ msg = 'manifest set to %s' % exp
+ project.bare_git.UpdateRef(ref, exp, message = msg, detach = True)
+
+ ref = 'refs/remotes/m-revision'
+ cur = project.bare_ref.symref(ref)
+ exp = project.revisionExpr
+ if exp is None:
+ if cur:
+ _rmref(project.gitdir, ref)
+ elif cur != exp:
+ remote = project.GetRemote(project.remote.name)
+ dst = remote.ToLocal(exp)
+ msg = 'manifest set to %s (%s)' % (exp, dst)
+ project.bare_git.symbolic_ref('-m', msg, ref, dst)
+
+ def Upgrade_Local(self, old):
+ if isinstance(old, manifest_xml.XmlManifest):
+ self.FromXml_Local_1(old, checkout=True)
+ self.FromXml_Local_2(old)
+ else:
+ raise ManifestParseError, 'cannot upgrade manifest'
+
+ def FromXml_Local_1(self, old, checkout):
+ os.rename(old.manifestProject.gitdir,
+ os.path.join(old.repodir, 'manifest.git'))
+
+ oldmp = old.manifestProject
+ oldBranch = oldmp.CurrentBranch
+ b = oldmp.GetBranch(oldBranch).merge
+ if not b:
+ raise ManifestParseError, 'cannot upgrade manifest'
+ if b.startswith(R_HEADS):
+ b = b[len(R_HEADS):]
+
+ newmp = self.manifestProject
+ self._CleanOldMRefs(newmp)
+ if oldBranch != b:
+ newmp.bare_git.branch('-m', oldBranch, b)
+ newmp.config.ClearCache()
+
+ old_remote = newmp.GetBranch(b).remote.name
+ act_remote = self._GuessRemoteName(old)
+ if old_remote != act_remote:
+ newmp.bare_git.remote('rename', old_remote, act_remote)
+ newmp.config.ClearCache()
+ newmp.remote.name = act_remote
+ print >>sys.stderr, "Assuming remote named '%s'" % act_remote
+
+ if checkout:
+ for p in old.projects.values():
+ for c in p.copyfiles:
+ if os.path.exists(c.abs_dest):
+ os.remove(c.abs_dest)
+ newmp._InitWorkTree()
+ else:
+ newmp._LinkWorkTree()
+
+ _lwrite(os.path.join(newmp.worktree,'.git',HEAD),
+ 'ref: refs/heads/%s\n' % b)
+
+ def _GuessRemoteName(self, old):
+ used = {}
+ for p in old.projects.values():
+ n = p.remote.name
+ used[n] = used.get(n, 0) + 1
+
+ remote_name = 'origin'
+ remote_used = 0
+ for n in used.keys():
+ if remote_used < used[n]:
+ remote_used = used[n]
+ remote_name = n
+ return remote_name
+
+ def FromXml_Local_2(self, old):
+ shutil.rmtree(old.manifestProject.worktree)
+ os.remove(old._manifestFile)
+
+ my_remote = self._Remote().name
+ new_base = os.path.join(self.repodir, 'projects')
+ old_base = os.path.join(self.repodir, 'projects.old')
+ os.rename(new_base, old_base)
+ os.makedirs(new_base)
+
+ info = []
+ pm = Progress('Converting projects', len(self.projects))
+ for p in self.projects.values():
+ pm.update()
+
+ old_p = old.projects.get(p.name)
+ old_gitdir = os.path.join(old_base, '%s.git' % p.relpath)
+ if not os.path.isdir(old_gitdir):
+ continue
+
+ parent = os.path.dirname(p.gitdir)
+ if not os.path.isdir(parent):
+ os.makedirs(parent)
+ os.rename(old_gitdir, p.gitdir)
+ _rmdir(os.path.dirname(old_gitdir), self.repodir)
+
+ if not os.path.isdir(p.worktree):
+ os.makedirs(p.worktree)
+
+ if os.path.isdir(os.path.join(p.worktree, '.git')):
+ p._LinkWorkTree(relink=True)
+
+ self._CleanOldMRefs(p)
+ if old_p and old_p.remote.name != my_remote:
+ info.append("%s/: renamed remote '%s' to '%s'" \
+ % (p.relpath, old_p.remote.name, my_remote))
+ p.bare_git.remote('rename', old_p.remote.name, my_remote)
+ p.config.ClearCache()
+
+ self.SetMRefs(p)
+ pm.end()
+ for i in info:
+ print >>sys.stderr, i
+
+ def _CleanOldMRefs(self, p):
+ all_refs = p._allrefs
+ for ref in all_refs.keys():
+ if ref.startswith(manifest_xml.R_M):
+ if p.bare_ref.symref(ref) != '':
+ _rmref(p.gitdir, ref)
+ else:
+ p.bare_git.DeleteRef(ref, all_refs[ref])
+
+ def FromXml_Definition(self, old):
+ """Convert another manifest representation to this one.
+ """
+ mp = self.manifestProject
+ gm = self._modules
+ gr = self._review
+
+ fd = open(os.path.join(mp.worktree, '.gitignore'), 'ab')
+ fd.write('/.repo\n')
+ fd.close()
+
+ sort_projects = list(old.projects.keys())
+ sort_projects.sort()
+
+ b = mp.GetBranch(mp.CurrentBranch).merge
+ if b.startswith(R_HEADS):
+ b = b[len(R_HEADS):]
+
+ info = []
+ pm = Progress('Converting manifest', len(sort_projects))
+ for p in sort_projects:
+ pm.update()
+ p = old.projects[p]
+
+ gm.SetString('submodule.%s.path' % p.name, p.relpath)
+ gm.SetString('submodule.%s.url' % p.name, p.remote.url)
+
+ if gr.GetString('review.url') is None:
+ gr.SetString('review.url', p.remote.review)
+ elif gr.GetString('review.url') != p.remote.review:
+ gr.SetString('review.%s.url' % p.name, p.remote.review)
+
+ r = p.revisionExpr
+ if r and not IsId(r):
+ if r.startswith(R_HEADS):
+ r = r[len(R_HEADS):]
+ if r == b:
+ r = '.'
+ gm.SetString('submodule.%s.revision' % p.name, r)
+
+ for c in p.copyfiles:
+ info.append('Moved %s out of %s' % (c.src, p.relpath))
+ c._Copy()
+ p.work_git.rm(c.src)
+ mp.work_git.add(c.dest)
+
+ self.SetRevisionId(p.relpath, p.GetRevisionId())
+ mp.work_git.add('.gitignore', '.gitmodules', '.review')
+ pm.end()
+ for i in info:
+ print >>sys.stderr, i
+
+ def _Unload(self):
+ self._loaded = False
+ self._projects = {}
+ self._revisionIds = None
+ self.branch = None
+
+ def _Load(self):
+ if not self._loaded:
+ f = os.path.join(self.repodir, manifest_xml.LOCAL_MANIFEST_NAME)
+ if os.path.exists(f):
+ print >>sys.stderr, 'warning: ignoring %s' % f
+
+ m = self.manifestProject
+ b = m.CurrentBranch
+ if not b:
+ raise ManifestParseError, 'manifest cannot be on detached HEAD'
+ b = m.GetBranch(b).merge
+ if b.startswith(R_HEADS):
+ b = b[len(R_HEADS):]
+ self.branch = b
+ m.remote.name = self._Remote().name
+
+ self._ParseModules()
+
+ if self.IsMirror:
+ self._AddMetaProjectMirror(self.repoProject)
+ self._AddMetaProjectMirror(self.manifestProject)
+
+ self._loaded = True
+
+ def _ParseModules(self):
+ byPath = dict()
+ for name in self._modules.GetSubSections('submodule'):
+ p = self._ParseProject(name)
+ if self._projects.get(p.name):
+ raise ManifestParseError, 'duplicate project "%s"' % p.name
+ if byPath.get(p.relpath):
+ raise ManifestParseError, 'duplicate path "%s"' % p.relpath
+ self._projects[p.name] = p
+ byPath[p.relpath] = p
+
+ for relpath in self._allRevisionIds.keys():
+ if relpath not in byPath:
+ raise ManifestParseError, \
+ 'project "%s" not in .gitmodules' \
+ % relpath
+
+ def _Remote(self):
+ m = self.manifestProject
+ b = m.GetBranch(m.CurrentBranch)
+ return b.remote
+
+ def _ResolveUrl(self, url):
+ if url.startswith('./') or url.startswith('../'):
+ base = self._Remote().url
+ try:
+ base = base[:base.rindex('/')+1]
+ except ValueError:
+ base = base[:base.rindex(':')+1]
+ if url.startswith('./'):
+ url = url[2:]
+ while '/' in base and url.startswith('../'):
+ base = base[:base.rindex('/')+1]
+ url = url[3:]
+ return base + url
+ return url
+
+ def _GetRevisionId(self, path):
+ return self._allRevisionIds.get(path)
+
+ @property
+ def _allRevisionIds(self):
+ if self._revisionIds is None:
+ a = dict()
+ p = GitCommand(self.manifestProject,
+ ['ls-files','-z','--stage'],
+ capture_stdout = True)
+ for line in p.process.stdout.read().split('\0')[:-1]:
+ l_info, l_path = line.split('\t', 2)
+ l_mode, l_id, l_stage = l_info.split(' ', 2)
+ if l_mode == GITLINK and l_stage == '0':
+ a[l_path] = l_id
+ p.Wait()
+ self._revisionIds = a
+ return self._revisionIds
+
+ def SetRevisionId(self, path, id):
+ self.manifestProject.work_git.update_index(
+ '--add','--cacheinfo', GITLINK, id, path)
+
+ def _ParseProject(self, name):
+ gm = self._modules
+ gr = self._review
+
+ path = gm.GetString('submodule.%s.path' % name)
+ if not path:
+ path = name
+
+ revId = self._GetRevisionId(path)
+ if not revId:
+ raise ManifestParseError(
+ 'submodule "%s" has no revision at "%s"' \
+ % (name, path))
+
+ url = gm.GetString('submodule.%s.url' % name)
+ if not url:
+ url = name
+ url = self._ResolveUrl(url)
+
+ review = gr.GetString('review.%s.url' % name)
+ if not review:
+ review = gr.GetString('review.url')
+ if not review:
+ review = self._Remote().review
+
+ remote = RemoteSpec(self._Remote().name, url, review)
+ revExpr = gm.GetString('submodule.%s.revision' % name)
+ if revExpr == '.':
+ revExpr = self.branch
+
+ if self.IsMirror:
+ relpath = None
+ worktree = None
+ gitdir = os.path.join(self.topdir, '%s.git' % name)
+ else:
+ worktree = os.path.join(self.topdir, path)
+ gitdir = os.path.join(self.repodir, 'projects/%s.git' % name)
+
+ return Project(manifest = self,
+ name = name,
+ remote = remote,
+ gitdir = gitdir,
+ worktree = worktree,
+ relpath = path,
+ revisionExpr = revExpr,
+ revisionId = revId)
+
+ def _AddMetaProjectMirror(self, m):
+ m_url = m.GetRemote(m.remote.name).url
+ if m_url.endswith('/.git'):
+ raise ManifestParseError, 'refusing to mirror %s' % m_url
+
+ name = self._GuessMetaName(m_url)
+ if name.endswith('.git'):
+ name = name[:-4]
+
+ if name not in self._projects:
+ m.PreSync()
+ gitdir = os.path.join(self.topdir, '%s.git' % name)
+ project = Project(manifest = self,
+ name = name,
+ remote = RemoteSpec(self._Remote().name, m_url),
+ gitdir = gitdir,
+ worktree = None,
+ relpath = None,
+ revisionExpr = m.revisionExpr,
+ revisionId = None)
+ self._projects[project.name] = project
+
+ def _GuessMetaName(self, m_url):
+ parts = m_url.split('/')
+ name = parts[-1]
+ parts = parts[0:-1]
+ s = len(parts) - 1
+ while s > 0:
+ l = '/'.join(parts[0:s]) + '/'
+ r = '/'.join(parts[s:]) + '/'
+ for p in self._projects.values():
+ if p.name.startswith(r) and p.remote.url.startswith(l):
+ return r + name
+ s -= 1
+ return m_url[m_url.rindex('/') + 1:]
diff --git a/manifest_xml.py b/manifest_xml.py
index 7d02f9d..d888653 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -17,12 +17,19 @@
import sys
import xml.dom.minidom
-from git_config import GitConfig, IsId
-from project import RemoteSpec, Project, MetaProject, R_HEADS, HEAD
+from git_config import GitConfig
+from git_config import IsId
+from manifest import Manifest
+from project import RemoteSpec
+from project import Project
+from project import MetaProject
+from project import R_HEADS
+from project import HEAD
from error import ManifestParseError
MANIFEST_FILE_NAME = 'manifest.xml'
LOCAL_MANIFEST_NAME = 'local_manifest.xml'
+R_M = 'refs/remotes/m/'
class _Default(object):
"""Project defaults within the manifest."""
@@ -46,19 +53,13 @@
url += '/%s.git' % projectName
return RemoteSpec(self.name, url, self.reviewUrl)
-class XmlManifest(object):
+class XmlManifest(Manifest):
"""manages the repo configuration file"""
def __init__(self, repodir):
- self.repodir = os.path.abspath(repodir)
- self.topdir = os.path.dirname(self.repodir)
- self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
- self.globalConfig = GitConfig.ForUser()
+ Manifest.__init__(self, repodir)
- self.repoProject = MetaProject(self, 'repo',
- gitdir = os.path.join(repodir, 'repo/.git'),
- worktree = os.path.join(repodir, 'repo'))
-
+ self._manifestFile = os.path.join(repodir, MANIFEST_FILE_NAME)
self.manifestProject = MetaProject(self, 'manifests',
gitdir = os.path.join(repodir, 'manifests.git'),
worktree = os.path.join(repodir, 'manifests'))
@@ -72,18 +73,18 @@
if not os.path.isfile(path):
raise ManifestParseError('manifest %s not found' % name)
- old = self.manifestFile
+ old = self._manifestFile
try:
- self.manifestFile = path
+ self._manifestFile = path
self._Unload()
self._Load()
finally:
- self.manifestFile = old
+ self._manifestFile = old
try:
- if os.path.exists(self.manifestFile):
- os.remove(self.manifestFile)
- os.symlink('manifests/%s' % name, self.manifestFile)
+ if os.path.exists(self._manifestFile):
+ os.remove(self._manifestFile)
+ os.symlink('manifests/%s' % name, self._manifestFile)
except OSError, e:
raise ManifestParseError('cannot link manifest %s' % name)
@@ -168,9 +169,15 @@
self._Load()
return self._default
- @property
- def IsMirror(self):
- return self.manifestProject.config.GetBoolean('repo.mirror')
+ def InitBranch(self):
+ m = self.manifestProject
+ if m.CurrentBranch is None:
+ return m.StartBranch('default')
+ return True
+
+ def SetMRefs(self, project):
+ if self.branch:
+ project._InitAnyMRef(R_M + self.branch)
def _Unload(self):
self._loaded = False
@@ -182,7 +189,10 @@
def _Load(self):
if not self._loaded:
m = self.manifestProject
- b = m.GetBranch(m.CurrentBranch).merge
+ b = m.GetBranch(m.CurrentBranch)
+ if b.remote and b.remote.name:
+ m.remote.name = b.remote.name
+ b = b.merge
if b is not None and b.startswith(R_HEADS):
b = b[len(R_HEADS):]
self.branch = b
@@ -192,11 +202,11 @@
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
if os.path.exists(local):
try:
- real = self.manifestFile
- self.manifestFile = local
+ real = self._manifestFile
+ self._manifestFile = local
self._ParseManifest(False)
finally:
- self.manifestFile = real
+ self._manifestFile = real
if self.IsMirror:
self._AddMetaProjectMirror(self.repoProject)
@@ -205,17 +215,17 @@
self._loaded = True
def _ParseManifest(self, is_root_file):
- root = xml.dom.minidom.parse(self.manifestFile)
+ root = xml.dom.minidom.parse(self._manifestFile)
if not root or not root.childNodes:
raise ManifestParseError, \
"no root node in %s" % \
- self.manifestFile
+ self._manifestFile
config = root.childNodes[0]
if config.nodeName != 'manifest':
raise ManifestParseError, \
"no <manifest> in %s" % \
- self.manifestFile
+ self._manifestFile
for node in config.childNodes:
if node.nodeName == 'remove-project':
@@ -233,7 +243,7 @@
if self._remotes.get(remote.name):
raise ManifestParseError, \
'duplicate remote %s in %s' % \
- (remote.name, self.manifestFile)
+ (remote.name, self._manifestFile)
self._remotes[remote.name] = remote
for node in config.childNodes:
@@ -241,7 +251,7 @@
if self._default is not None:
raise ManifestParseError, \
'duplicate default in %s' % \
- (self.manifestFile)
+ (self._manifestFile)
self._default = self._ParseDefault(node)
if self._default is None:
self._default = _Default()
@@ -252,7 +262,7 @@
if self._projects.get(project.name):
raise ManifestParseError, \
'duplicate project %s in %s' % \
- (project.name, self.manifestFile)
+ (project.name, self._manifestFile)
self._projects[project.name] = project
def _AddMetaProjectMirror(self, m):
@@ -324,7 +334,7 @@
if remote is None:
raise ManifestParseError, \
"no remote for project %s within %s" % \
- (name, self.manifestFile)
+ (name, self._manifestFile)
revisionExpr = node.getAttribute('revision')
if not revisionExpr:
@@ -332,7 +342,7 @@
if not revisionExpr:
raise ManifestParseError, \
"no revision for project %s within %s" % \
- (name, self.manifestFile)
+ (name, self._manifestFile)
path = node.getAttribute('path')
if not path:
@@ -340,7 +350,7 @@
if path.startswith('/'):
raise ManifestParseError, \
"project %s path cannot be absolute in %s" % \
- (name, self.manifestFile)
+ (name, self._manifestFile)
if self.IsMirror:
relpath = None
@@ -382,7 +392,7 @@
if not v:
raise ManifestParseError, \
"remote %s not defined in %s" % \
- (name, self.manifestFile)
+ (name, self._manifestFile)
return v
def _reqatt(self, node, attname):
@@ -393,5 +403,5 @@
if not v:
raise ManifestParseError, \
"no %s in <%s> within %s" % \
- (attname, node.nodeName, self.manifestFile)
+ (attname, node.nodeName, self._manifestFile)
return v
diff --git a/project.py b/project.py
index ff896d0..5a143a7 100644
--- a/project.py
+++ b/project.py
@@ -27,7 +27,7 @@
from error import GitError, ImportError, UploadError
from error import ManifestInvalidRevisionError
-from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
+from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB
def _lwrite(path, content):
lock = '%s.lock' % path
@@ -598,7 +598,7 @@
return False
if self.worktree:
- self._InitMRef()
+ self.manifest.SetMRefs(self)
else:
self._InitMirrorHead()
try:
@@ -1093,10 +1093,6 @@
remote.ResetFetch(mirror=True)
remote.Save()
- def _InitMRef(self):
- if self.manifest.branch:
- self._InitAnyMRef(R_M + self.manifest.branch)
-
def _InitMirrorHead(self):
self._InitAnyMRef(HEAD)
@@ -1115,33 +1111,40 @@
msg = 'manifest set to %s' % self.revisionExpr
self.bare_git.symbolic_ref('-m', msg, ref, dst)
- def _InitWorkTree(self):
+ def _LinkWorkTree(self, relink=False):
dotgit = os.path.join(self.worktree, '.git')
- if not os.path.exists(dotgit):
+ if not relink:
os.makedirs(dotgit)
- for name in ['config',
- 'description',
- 'hooks',
- 'info',
- 'logs',
- 'objects',
- 'packed-refs',
- 'refs',
- 'rr-cache',
- 'svn']:
- try:
- src = os.path.join(self.gitdir, name)
- dst = os.path.join(dotgit, name)
- if os.path.islink(dst) or not os.path.exists(dst):
- os.symlink(relpath(src, dst), dst)
- else:
- raise GitError('cannot overwrite a local work tree')
- except OSError, e:
- if e.errno == errno.EPERM:
- raise GitError('filesystem must support symlinks')
- else:
- raise
+ for name in ['config',
+ 'description',
+ 'hooks',
+ 'info',
+ 'logs',
+ 'objects',
+ 'packed-refs',
+ 'refs',
+ 'rr-cache',
+ 'svn']:
+ try:
+ src = os.path.join(self.gitdir, name)
+ dst = os.path.join(dotgit, name)
+ if relink:
+ os.remove(dst)
+ if os.path.islink(dst) or not os.path.exists(dst):
+ os.symlink(relpath(src, dst), dst)
+ else:
+ raise GitError('cannot overwrite a local work tree')
+ except OSError, e:
+ if e.errno == errno.EPERM:
+ raise GitError('filesystem must support symlinks')
+ else:
+ raise
+
+ def _InitWorkTree(self):
+ dotgit = os.path.join(self.worktree, '.git')
+ if not os.path.exists(dotgit):
+ self._LinkWorkTree()
_lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
@@ -1439,15 +1442,17 @@
class MetaProject(Project):
"""A special project housed under .repo.
"""
- def __init__(self, manifest, name, gitdir, worktree):
+ def __init__(self, manifest, name, gitdir, worktree, relpath=None):
repodir = manifest.repodir
+ if relpath is None:
+ relpath = '.repo/%s' % name
Project.__init__(self,
manifest = manifest,
name = name,
gitdir = gitdir,
worktree = worktree,
remote = RemoteSpec('origin'),
- relpath = '.repo/%s' % name,
+ relpath = relpath,
revisionExpr = 'refs/heads/master',
revisionId = None)
@@ -1455,10 +1460,12 @@
if self.Exists:
cb = self.CurrentBranch
if cb:
- base = self.GetBranch(cb).merge
- if base:
- self.revisionExpr = base
+ cb = self.GetBranch(cb)
+ if cb.merge:
+ self.revisionExpr = cb.merge
self.revisionId = None
+ if cb.remote and cb.remote.name:
+ self.remote.name = cb.remote.name
@property
def LastFetch(self):
diff --git a/repo b/repo
index 1374255..ff7c418 100755
--- a/repo
+++ b/repo
@@ -28,7 +28,7 @@
del magic
# increment this whenever we make important changes to this script
-VERSION = (1, 8)
+VERSION = (1, 9)
# increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (1,0)
@@ -109,12 +109,17 @@
group.add_option('-u', '--manifest-url',
dest='manifest_url',
help='manifest repository location', metavar='URL')
+group.add_option('-o', '--origin',
+ dest='manifest_origin',
+ help="use REMOTE instead of 'origin' to track upstream",
+ metavar='REMOTE')
group.add_option('-b', '--manifest-branch',
dest='manifest_branch',
help='manifest branch or revision', metavar='REVISION')
group.add_option('-m', '--manifest-name',
dest='manifest_name',
- help='initial manifest file', metavar='NAME.xml')
+ help='initial manifest file (deprecated)',
+ metavar='NAME.xml')
group.add_option('--mirror',
dest='mirror', action='store_true',
help='mirror the forrest')
diff --git a/subcmds/download.py b/subcmds/download.py
index a6f3aa4..61eadd5 100644
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -36,6 +36,9 @@
pass
def _ParseChangeIds(self, args):
+ if not args:
+ self.Usage()
+
to_get = []
project = None
diff --git a/subcmds/forall.py b/subcmds/forall.py
index b66313d..6bd867e 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -169,6 +169,12 @@
else:
cwd = project.worktree
+ if not os.path.exists(cwd):
+ if (opt.project_header and opt.verbose) \
+ or not opt.project_header:
+ print >>sys.stderr, 'skipping %s/' % project.relpath
+ continue
+
if opt.project_header:
stdin = subprocess.PIPE
stdout = subprocess.PIPE
diff --git a/subcmds/help.py b/subcmds/help.py
index c5979fd..e2f3074 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -94,6 +94,8 @@
body = getattr(cmd, bodyAttr)
except AttributeError:
return
+ if body == '' or body is None:
+ return
self.nl()
@@ -163,6 +165,7 @@
print >>sys.stderr, "repo: '%s' is not a repo command." % name
sys.exit(1)
+ cmd.repodir = self.repodir
self._PrintCommandHelp(cmd)
else:
diff --git a/subcmds/init.py b/subcmds/init.py
index 75a58f1..cdbbfdf 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -21,6 +21,9 @@
from error import ManifestParseError
from project import SyncBuffer
from git_command import git_require, MIN_GIT_VERSION
+from manifest_submodule import SubmoduleManifest
+from manifest_xml import XmlManifest
+from subcmds.sync import _ReloadManifest
class Init(InteractiveCommand, MirrorSafeCommand):
common = True
@@ -37,10 +40,6 @@
The optional -b argument can be used to select the manifest branch
to checkout and use. If no branch is specified, master is assumed.
-The optional -m argument can be used to specify an alternate manifest
-to be used. If no manifest is specified, the manifest default.xml
-will be used.
-
Switching Manifest Branches
---------------------------
@@ -65,9 +64,15 @@
g.add_option('-b', '--manifest-branch',
dest='manifest_branch',
help='manifest branch or revision', metavar='REVISION')
- g.add_option('-m', '--manifest-name',
- dest='manifest_name', default='default.xml',
- help='initial manifest file', metavar='NAME.xml')
+ g.add_option('-o', '--origin',
+ dest='manifest_origin',
+ help="use REMOTE instead of 'origin' to track upstream",
+ metavar='REMOTE')
+ if isinstance(self.manifest, XmlManifest) \
+ or not self.manifest.manifestProject.Exists:
+ g.add_option('-m', '--manifest-name',
+ dest='manifest_name', default='default.xml',
+ help='initial manifest file', metavar='NAME.xml')
g.add_option('--mirror',
dest='mirror', action='store_true',
help='mirror the forrest')
@@ -85,30 +90,42 @@
dest='no_repo_verify', action='store_true',
help='do not verify repo source code')
- def _SyncManifest(self, opt):
+ def _ApplyOptions(self, opt, is_new):
m = self.manifest.manifestProject
- is_new = not m.Exists
if is_new:
- if not opt.manifest_url:
- print >>sys.stderr, 'fatal: manifest url (-u) is required.'
- sys.exit(1)
-
- if not opt.quiet:
- print >>sys.stderr, 'Getting manifest ...'
- print >>sys.stderr, ' from %s' % opt.manifest_url
- m._InitGitDir()
+ if opt.manifest_origin:
+ m.remote.name = opt.manifest_origin
if opt.manifest_branch:
m.revisionExpr = opt.manifest_branch
else:
m.revisionExpr = 'refs/heads/master'
else:
+ if opt.manifest_origin:
+ print >>sys.stderr, 'fatal: cannot change origin name'
+ sys.exit(1)
+
if opt.manifest_branch:
m.revisionExpr = opt.manifest_branch
else:
m.PreSync()
+ def _SyncManifest(self, opt):
+ m = self.manifest.manifestProject
+ is_new = not m.Exists
+
+ if is_new:
+ if not opt.manifest_url:
+ print >>sys.stderr, 'fatal: manifest url (-u) is required.'
+ sys.exit(1)
+
+ if not opt.quiet:
+ print >>sys.stderr, 'Getting manifest ...'
+ print >>sys.stderr, ' from %s' % opt.manifest_url
+ m._InitGitDir()
+
+ self._ApplyOptions(opt, is_new)
if opt.manifest_url:
r = m.GetRemote(m.remote.name)
r.url = opt.manifest_url
@@ -118,6 +135,7 @@
if opt.mirror:
if is_new:
m.config.SetString('repo.mirror', 'true')
+ m.config.ClearCache()
else:
print >>sys.stderr, 'fatal: --mirror not supported on existing client'
sys.exit(1)
@@ -127,14 +145,29 @@
print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url
sys.exit(1)
+ if is_new and SubmoduleManifest.IsBare(m):
+ new = self.GetManifest(reparse=True, type=SubmoduleManifest)
+ if m.gitdir != new.manifestProject.gitdir:
+ os.rename(m.gitdir, new.manifestProject.gitdir)
+ new = self.GetManifest(reparse=True, type=SubmoduleManifest)
+ m = new.manifestProject
+ self._ApplyOptions(opt, is_new)
+
+ if not is_new:
+ # Force the manifest to load if it exists, the old graph
+ # may be needed inside of _ReloadManifest().
+ #
+ self.manifest.projects
+
syncbuf = SyncBuffer(m.config)
m.Sync_LocalHalf(syncbuf)
syncbuf.Finish()
+ _ReloadManifest(self)
+ self._ApplyOptions(opt, is_new)
- if is_new or m.CurrentBranch is None:
- if not m.StartBranch('default'):
- print >>sys.stderr, 'fatal: cannot create default in manifest'
- sys.exit(1)
+ if not self.manifest.InitBranch():
+ print >>sys.stderr, 'fatal: cannot create branch in manifest'
+ sys.exit(1)
def _LinkManifest(self, name):
if not name:
@@ -216,7 +249,8 @@
def Execute(self, opt, args):
git_require(MIN_GIT_VERSION, fail=True)
self._SyncManifest(opt)
- self._LinkManifest(opt.manifest_name)
+ if isinstance(self.manifest, XmlManifest):
+ self._LinkManifest(opt.manifest_name)
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
self._ConfigureUser()
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index 4374a9d..dcd3df1 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -17,14 +17,25 @@
import sys
from command import PagedCommand
+from manifest_submodule import SubmoduleManifest
+from manifest_xml import XmlManifest
+
+def _doc(name):
+ r = os.path.dirname(__file__)
+ r = os.path.dirname(r)
+ fd = open(os.path.join(r, 'docs', name))
+ try:
+ return fd.read()
+ finally:
+ fd.close()
class Manifest(PagedCommand):
common = False
helpSummary = "Manifest inspection utility"
helpUsage = """
-%prog [-o {-|NAME.xml} [-r]]
+%prog [options]
"""
- _helpDescription = """
+ _xmlHelp = """
With the -o option, exports the current manifest for inspection.
The manifest and (if present) local_manifest.xml are combined
@@ -35,23 +46,30 @@
@property
def helpDescription(self):
- help = self._helpDescription + '\n'
- r = os.path.dirname(__file__)
- r = os.path.dirname(r)
- fd = open(os.path.join(r, 'docs', 'manifest-format.txt'))
- for line in fd:
- help += line
- fd.close()
+ help = ''
+ if isinstance(self.manifest, XmlManifest):
+ help += self._xmlHelp + '\n' + _doc('manifest_xml.txt')
+ if isinstance(self.manifest, SubmoduleManifest):
+ help += _doc('manifest_submodule.txt')
return help
def _Options(self, p):
- p.add_option('-r', '--revision-as-HEAD',
- dest='peg_rev', action='store_true',
- help='Save revisions as current HEAD')
- p.add_option('-o', '--output-file',
- dest='output_file',
- help='File to save the manifest to',
- metavar='-|NAME.xml')
+ if isinstance(self.manifest, XmlManifest):
+ p.add_option('--upgrade',
+ dest='upgrade', action='store_true',
+ help='Upgrade XML manifest to submodule')
+ p.add_option('-r', '--revision-as-HEAD',
+ dest='peg_rev', action='store_true',
+ help='Save revisions as current HEAD')
+ p.add_option('-o', '--output-file',
+ dest='output_file',
+ help='File to save the manifest to',
+ metavar='-|NAME.xml')
+
+ def WantPager(self, opt):
+ if isinstance(self.manifest, XmlManifest) and opt.upgrade:
+ return False
+ return True
def _Output(self, opt):
if opt.output_file == '-':
@@ -64,13 +82,38 @@
if opt.output_file != '-':
print >>sys.stderr, 'Saved manifest to %s' % opt.output_file
+ def _Upgrade(self):
+ old = self.manifest
+
+ if isinstance(old, SubmoduleManifest):
+ print >>sys.stderr, 'error: already upgraded'
+ sys.exit(1)
+
+ old._Load()
+ for p in old.projects.values():
+ if not os.path.exists(p.gitdir) \
+ or not os.path.exists(p.worktree):
+ print >>sys.stderr, 'fatal: project "%s" missing' % p.relpath
+ sys.exit(1)
+
+ new = SubmoduleManifest(old.repodir)
+ new.FromXml_Local_1(old, checkout=False)
+ new.FromXml_Definition(old)
+ new.FromXml_Local_2(old)
+ print >>sys.stderr, 'upgraded manifest; commit result manually'
+
def Execute(self, opt, args):
if args:
self.Usage()
- if opt.output_file is not None:
- self._Output(opt)
- return
+ if isinstance(self.manifest, XmlManifest):
+ if opt.upgrade:
+ self._Upgrade()
+ return
+
+ if opt.output_file is not None:
+ self._Output(opt)
+ return
print >>sys.stderr, 'error: no operation to perform'
print >>sys.stderr, 'error: see repo help manifest'
diff --git a/subcmds/selfupdate.py b/subcmds/selfupdate.py
index 4f46a12..46aa3a1 100644
--- a/subcmds/selfupdate.py
+++ b/subcmds/selfupdate.py
@@ -55,6 +55,7 @@
print >>sys.stderr, "error: can't update repo"
sys.exit(1)
+ rp.bare_git.gc('--auto')
_PostRepoFetch(rp,
no_repo_verify = opt.no_repo_verify,
verbose = True)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index ceb81ea..d89c2b8 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -117,6 +117,8 @@
print >>sys.stderr, 'error: Cannot fetch %s' % project.name
sys.exit(1)
pm.end()
+ for project in projects:
+ project.bare_git.gc('--auto')
return fetched
def UpdateProjectList(self):
@@ -215,7 +217,14 @@
# bail out now; the rest touches the working tree
return
- self.manifest._Unload()
+ if mp.HasChanges:
+ syncbuf = SyncBuffer(mp.config)
+ mp.Sync_LocalHalf(syncbuf)
+ if not syncbuf.Finish():
+ sys.exit(1)
+ _ReloadManifest(self)
+ mp = self.manifest.manifestProject
+
all = self.GetProjects(args, missing_ok=True)
missing = []
for project in all:
@@ -242,6 +251,14 @@
if not syncbuf.Finish():
sys.exit(1)
+def _ReloadManifest(cmd):
+ old = cmd.manifest
+ new = cmd.GetManifest(reparse=True)
+
+ if old.__class__ != new.__class__:
+ print >>sys.stderr, 'NOTICE: manifest format has changed ***'
+ new.Upgrade_Local(old)
+
def _PostRepoUpgrade(manifest):
for project in manifest.projects.values():
if project.Exists:
diff --git a/subcmds/upload.py b/subcmds/upload.py
index aea399b..2ab6a48 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -55,11 +55,11 @@
new users. Users passed as --reviewers must already be registered
with the code review system, or the upload will fail.
-If the --replace option is passed the user can designate which
-existing change(s) in Gerrit match up to the commits in the branch
-being uploaded. For each matched pair of change,commit the commit
-will be added as a new patch set, completely replacing the set of
-files and description associated with the change in Gerrit.
+If the --replace option (deprecated) is passed the user can designate
+which existing change(s) in Gerrit match up to the commits in the
+branch being uploaded. For each matched pair of change,commit the
+commit will be added as a new patch set, completely replacing the
+set of files and description associated with the change in Gerrit.
Configuration
-------------
@@ -92,7 +92,7 @@
def _Options(self, p):
p.add_option('--replace',
dest='replace', action='store_true',
- help='Upload replacement patchesets from this branch')
+ help='Upload replacement patchsets from this branch (deprecated)')
p.add_option('--re', '--reviewers',
type='string', action='append', dest='reviewers',
help='Request reviews from these people.')
@@ -273,15 +273,19 @@
have_errors = True
print >>sys.stderr, ''
- print >>sys.stderr, '--------------------------------------------'
+ print >>sys.stderr, '----------------------------------------------------------------------'
if have_errors:
for branch in todo:
if not branch.uploaded:
- print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % (
+ if len(str(branch.error)) <= 30:
+ fmt = ' (%s)'
+ else:
+ fmt = '\n (%s)'
+ print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
branch.project.relpath + '/', \
branch.name, \
- branch.error)
+ str(branch.error))
print >>sys.stderr, ''
for branch in todo: