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
 
-  key = '%s:%s' % (host, port)
+  if port is not None:
+    key = '%s:%s' % (host, port)
+  else:
+    key = host
+
   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 _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())
 
@@ -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,6 +90,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
@@ -99,16 +125,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
@@ -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: