Merge branch 'maint'
* maint:
help: Don't show empty Summary or Description sections
sync: Run `git gc --auto` after fetch
Add "repo branch" as an alias for "repo branches"
upload: Catch and cleanly report connectivity errors
forall: Silently skip missing projects
Fix to display the usage message of the command download when the user don't provide any arguments to 'repo download'.
Use os.environ.copy() instead of dict()
Make path references OS independent
diff --git a/command.py b/command.py
index 8e93787..4e0253f 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:
oldpath = None
while path \
and path != oldpath \
@@ -92,11 +110,6 @@
except KeyError:
oldpath = path
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..1718284
--- /dev/null
+++ b/docs/manifest_submodule.txt
@@ -0,0 +1,136 @@
+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.
+
+repo.notice
+-----------
+
+A message displayed when repo sync uses this manifest.
+
+
+.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 99%
rename from docs/manifest-format.txt
rename to docs/manifest_xml.txt
index 2e1c8c3..37fbd5c 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest_xml.txt
@@ -37,7 +37,7 @@
<!ELEMENT default (EMPTY)>
<!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED>
-
+
<!ELEMENT manifest-server (EMPTY)>
<!ATTLIST url CDATA #REQUIRED>
diff --git a/git_config.py b/git_config.py
index 19c19f1..ff815e3 100644
--- a/git_config.py
+++ b/git_config.py
@@ -80,6 +80,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.
"""
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 f068fd4..07b26ef 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
@@ -99,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..c03cb4a
--- /dev/null
+++ b/manifest.py
@@ -0,0 +1,59 @@
+#
+# 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 {}
+
+ @property
+ def notice(self):
+ return None
+
+ @property
+ def manifest_server(self):
+ return None
+
+ 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..cac271c
--- /dev/null
+++ b/manifest_submodule.py
@@ -0,0 +1,481 @@
+#
+# 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
+
+ @property
+ def notice(self):
+ return self._modules.GetString('repo.notice')
+
+ 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):]
+
+ if old.notice:
+ gm.SetString('repo.notice', old.notice)
+
+ 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 0103cf5..1d02f9d 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,13 +73,13 @@
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
def Link(self, name):
"""Update the repo metadata to use a different manifest.
@@ -86,9 +87,9 @@
self.Override(name)
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)
@@ -198,9 +199,15 @@
self._Load()
return self._manifest_server
- @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
@@ -214,7 +221,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
@@ -224,11 +234,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)
@@ -237,17 +247,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':
@@ -265,7 +275,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:
@@ -273,7 +283,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()
@@ -301,7 +311,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):
@@ -412,7 +422,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:
@@ -420,7 +430,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:
@@ -428,7 +438,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
@@ -470,7 +480,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):
@@ -481,5 +491,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 25347da..fde98ad 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
@@ -640,7 +640,7 @@
self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
if self.worktree:
- self._InitMRef()
+ self.manifest.SetMRefs(self)
else:
self._InitMirrorHead()
try:
@@ -1228,10 +1228,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)
@@ -1250,33 +1246,40 @@
msg = 'manifest set to %s' % self.revisionExpr
self.bare_git.symbolic_ref('-m', msg, ref, dst)
+ def _LinkWorkTree(self, relink=False):
+ dotgit = os.path.join(self.worktree, '.git')
+ 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 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):
- 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
+ self._LinkWorkTree()
_lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
@@ -1574,15 +1577,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)
@@ -1590,10 +1595,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 96daa9b..e570a0b 100755
--- a/repo
+++ b/repo
@@ -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/help.py b/subcmds/help.py
index 90b817d..e2f3074 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -165,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 17edfa0..2ca4e16 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
@@ -72,9 +75,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')
@@ -94,6 +103,27 @@
dest='no_repo_verify', action='store_true',
help='do not verify repo source code')
+ def _ApplyOptions(self, opt, is_new):
+ m = self.manifest.manifestProject
+
+ if is_new:
+ 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
@@ -108,16 +138,7 @@
print >>sys.stderr, ' from %s' % opt.manifest_url
m._InitGitDir()
- if opt.manifest_branch:
- m.revisionExpr = opt.manifest_branch
- else:
- m.revisionExpr = 'refs/heads/master'
- else:
- if opt.manifest_branch:
- m.revisionExpr = opt.manifest_branch
- else:
- m.PreSync()
-
+ self._ApplyOptions(opt, is_new)
if opt.manifest_url:
r = m.GetRemote(m.remote.name)
r.url = opt.manifest_url
@@ -130,6 +151,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)
@@ -139,14 +161,33 @@
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()
- 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 isinstance(self.manifest, XmlManifest):
+ self._LinkManifest(opt.manifest_name)
+ _ReloadManifest(self)
+
+ self._ApplyOptions(opt, is_new)
+
+ if not self.manifest.InitBranch():
+ print >>sys.stderr, 'fatal: cannot create branch in manifest'
+ sys.exit(1)
def _LinkManifest(self, name):
if not name:
@@ -229,7 +270,6 @@
def Execute(self, opt, args):
git_require(MIN_GIT_VERSION, fail=True)
self._SyncManifest(opt)
- 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/rebase.py b/subcmds/rebase.py
index 7c8e938..e341296 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -17,7 +17,7 @@
from command import Command
from git_command import GitCommand
-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
from error import GitError
class Rebase(Command):
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 36ef16d..16f1d18 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -336,7 +336,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:
@@ -363,10 +370,16 @@
if not syncbuf.Finish():
sys.exit(1)
- # If there's a notice that's supposed to print at the end of the sync, print
- # it now...
- if self.manifest.notice:
- print self.manifest.notice
+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)
+ else:
+ if new.notice:
+ print new.notice
def _PostRepoUpgrade(manifest):
for project in manifest.projects.values():