merge v1.12.33
diff --git a/SUBMITTING_PATCHES b/SUBMITTING_PATCHES
index 50e2cf7..8656ee7 100644
--- a/SUBMITTING_PATCHES
+++ b/SUBMITTING_PATCHES
@@ -4,7 +4,9 @@
  - Provide a meaningful commit message.
  - Check for coding errors with pylint
  - Make sure all code is under the Apache License, 2.0.
- - Publish your changes for review:
+ - Publish your changes for review.
+ - Make corrections if requested.
+ - Verify your changes on gerrit so they can be submitted.
 
    git push https://gerrit-review.googlesource.com/git-repo HEAD:refs/for/master
 
@@ -75,6 +77,17 @@
 
   https://gerrit-review.googlesource.com/new-password
 
+Ensure that you have the local commit hook installed to automatically
+add a ChangeId to your commits:
+
+    curl -Lo `git rev-parse --git-dir`/hooks/commit-msg https://gerrit-review.googlesource.com/tools/hooks/commit-msg
+    chmod +x `git rev-parse --git-dir`/hooks/commit-msg
+
+If you have already committed your changes you will need to amend the commit
+to get the ChangeId added.
+
+    git commit --amend
+
 Push your patches over HTTPS to the review server, possibly through
 a remembered remote to make this easier in the future:
 
@@ -85,3 +98,18 @@
 
 You will be automatically emailed a copy of your commits, and any
 comments made by the project maintainers.
+
+
+(5) Make changes if requested
+
+The project maintainer who reviews your changes might request changes to your
+commit. If you make the requested changes you will need to amend your commit
+and push it to the review server again.
+
+
+(6) Verify your changes on gerrit
+
+After you receive a Code-Review+2 from the maintainer, select the Verified
+button on the gerrit page for the change. This verifies that you have tested
+your changes and notifies the maintainer that they are ready to be submitted.
+The maintainer will then submit your changes to the repository.
diff --git a/command.py b/command.py
index 997acec..cd5e3c3 100644
--- a/command.py
+++ b/command.py
@@ -231,7 +231,12 @@
      and does not require a working directory.
   """
 
-class RequiresGitcCommand(object):
+class GitcAvailableCommand(object):
   """Command that requires GITC to be available, but does
      not require the local client to be a GITC client.
   """
+
+class GitcClientCommand(object):
+  """Command that requires the local client to be a GITC
+     client.
+  """
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 1aa9396..140a782 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -47,10 +47,12 @@
     <!ATTLIST default sync-s      CDATA #IMPLIED>
 
     <!ELEMENT manifest-server (EMPTY)>
-    <!ATTLIST url              CDATA #REQUIRED>
+    <!ATTLIST manifest-server url CDATA #REQUIRED>
 
     <!ELEMENT project (annotation*,
-                       project*)>
+                       project*,
+                       copyfile*,
+                       linkfile*)>
     <!ATTLIST project name        CDATA #REQUIRED>
     <!ATTLIST project path        CDATA #IMPLIED>
     <!ATTLIST project remote      IDREF #IMPLIED>
@@ -68,7 +70,15 @@
     <!ATTLIST annotation value CDATA #REQUIRED>
     <!ATTLIST annotation keep  CDATA "true">
 
-    <!ELEMENT extend-project>
+    <!ELEMENT copyfile (EMPTY)>
+    <!ATTLIST copyfile src  CDATA #REQUIRED>
+    <!ATTLIST copyfile dest CDATA #REQUIRED>
+
+    <!ELEMENT linkfile (EMPTY)>
+    <!ATTLIST linkfile src CDATA #REQUIRED>
+    <!ATTLIST linkfile dest CDATA #REQUIRED>
+
+    <!ELEMENT extend-project (EMPTY)>
     <!ATTLIST extend-project name CDATA #REQUIRED>
     <!ATTLIST extend-project path CDATA #IMPLIED>
     <!ATTLIST extend-project groups CDATA #IMPLIED>
@@ -285,6 +295,21 @@
 "false".  This attribute determines whether or not the annotation will
 be kept when exported with the manifest subcommand.
 
+Element copyfile
+----------------
+
+Zero or more copyfile elements may be specified as children of a
+project element. Each element describes a src-dest pair of files;
+the "src" file will be copied to the "dest" place during 'repo sync'
+command.
+"src" is project relative, "dest" is relative to the top of the tree.
+
+Element linkfile
+----------------
+
+It's just like copyfile and runs at the same time as copyfile but
+instead of copying it creates a symlink.
+
 Element remove-project
 ----------------------
 
diff --git a/git_command.py b/git_command.py
index 0893bff..9f7d293 100644
--- a/git_command.py
+++ b/git_command.py
@@ -168,6 +168,9 @@
       if p is not None:
         s = p + ' ' + s
       _setenv(env, 'GIT_CONFIG_PARAMETERS', s)
+    if 'GIT_ALLOW_PROTOCOL' not in env:
+      _setenv(env, 'GIT_ALLOW_PROTOCOL',
+              'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc')
 
     if project:
       if not cwd:
diff --git a/gitc_utils.py b/gitc_utils.py
index d082c8d..0f3e181 100644
--- a/gitc_utils.py
+++ b/gitc_utils.py
@@ -15,6 +15,8 @@
 
 from __future__ import print_function
 import os
+import platform
+import re
 import sys
 import time
 
@@ -22,24 +24,13 @@
 import git_config
 import wrapper
 
-
-GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
 NUM_BATCH_RETRIEVE_REVISIONID = 300
 
 def get_gitc_manifest_dir():
   return wrapper.Wrapper().get_gitc_manifest_dir()
 
 def parse_clientdir(gitc_fs_path):
-  """Parse a path in the GITC FS and return its client name.
-
-  @param gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR.
-
-  @returns: The GITC client name
-  """
-  if (gitc_fs_path == GITC_FS_ROOT_DIR or
-      not gitc_fs_path.startswith(GITC_FS_ROOT_DIR)):
-    return None
-  return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split('/')[0]
+  return wrapper.Wrapper().gitc_parse_clientdir(gitc_fs_path)
 
 def _set_project_revisions(projects):
   """Sets the revisionExpr for a list of projects.
@@ -65,26 +56,82 @@
       sys.exit(1)
     proj.revisionExpr = gitcmd.stdout.split('\t')[0]
 
+def _manifest_groups(manifest):
+  """Returns the manifest group string that should be synced
+
-def generate_gitc_manifest(client_dir, manifest, projects=None):
+  This is the same logic used by Command.GetProjects(), which is used during
+  repo sync
+
+  @param manifest: The XmlManifest object
+  """
+  mp = manifest.manifestProject
+  groups = mp.config.GetString('manifest.groups')
+  if not groups:
+    groups = 'default,platform-' + platform.system().lower()
+  return groups
+
+def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
   """Generate a manifest for shafsd to use for this GITC client.
 
-  @param client_dir: GITC client directory to install the .manifest file in.
-  @param manifest: XmlManifest object representing the repo manifest.
-  @param projects: List of projects we want to update, this must be a sublist
-                   of manifest.projects to work properly. If not provided,
-                   manifest.projects is used.
+  @param gitc_manifest: Current gitc manifest, or None if there isn't one yet.
+  @param manifest: A GitcManifest object loaded with the current repo manifest.
+  @param paths: List of project paths we want to update.
   """
+
   print('Generating GITC Manifest by fetching revision SHAs for each '
         'project.')
-  if projects is None:
-    projects = manifest.projects
+  if paths is None:
+    paths = manifest.paths.keys()
+
+  groups = [x for x in re.split(r'[,\s]+', _manifest_groups(manifest)) if x]
+
+  # Convert the paths to projects, and filter them to the matched groups.
+  projects = [manifest.paths[p] for p in paths]
+  projects = [p for p in projects if p.MatchesGroups(groups)]
+
+  if gitc_manifest is not None:
+    for path, proj in manifest.paths.iteritems():
+      if not proj.MatchesGroups(groups):
+        continue
+
+      if not proj.upstream and not git_config.IsId(proj.revisionExpr):
+        proj.upstream = proj.revisionExpr
+
+      if not path in gitc_manifest.paths:
+        # Any new projects need their first revision, even if we weren't asked
+        # for them.
+        projects.append(proj)
+      elif not path in paths:
+        # And copy revisions from the previous manifest if we're not updating
+        # them now.
+        gitc_proj = gitc_manifest.paths[path]
+        if gitc_proj.old_revision:
+          proj.revisionExpr = None
+          proj.old_revision = gitc_proj.old_revision
+        else:
+          proj.revisionExpr = gitc_proj.revisionExpr
+
   index = 0
   while index < len(projects):
     _set_project_revisions(
         projects[index:(index+NUM_BATCH_RETRIEVE_REVISIONID)])
     index += NUM_BATCH_RETRIEVE_REVISIONID
+
+  if gitc_manifest is not None:
+    for path, proj in gitc_manifest.paths.iteritems():
+      if proj.old_revision and path in paths:
+        # If we updated a project that has been started, keep the old-revision
+        # updated.
+        repo_proj = manifest.paths[path]
+        repo_proj.old_revision = repo_proj.revisionExpr
+        repo_proj.revisionExpr = None
+
+  # Convert URLs from relative to absolute.
+  for name, remote in manifest.remotes.iteritems():
+    remote.fetchUrl = remote.resolvedFetchUrl
+
   # Save the manifest.
-  save_manifest(manifest, client_dir=client_dir)
+  save_manifest(manifest)
 
 def save_manifest(manifest, client_dir=None):
   """Save the manifest file in the client_dir.
@@ -95,7 +142,7 @@
   if not client_dir:
     client_dir = manifest.gitc_client_dir
   with open(os.path.join(client_dir, '.manifest'), 'w') as f:
-    manifest.Save(f)
+    manifest.Save(f, groups=_manifest_groups(manifest))
   # TODO(sbasi/jorg): Come up with a solution to remove the sleep below.
   # Give the GITC filesystem time to register the manifest changes.
-  time.sleep(3)
\ No newline at end of file
+  time.sleep(3)
diff --git a/main.py b/main.py
index a5979a8..4f4eb9f 100755
--- a/main.py
+++ b/main.py
@@ -42,7 +42,7 @@
 from git_config import init_ssh, close_ssh
 from command import InteractiveCommand
 from command import MirrorSafeCommand
-from command import RequiresGitcCommand
+from command import GitcAvailableCommand, GitcClientCommand
 from subcmds.version import Version
 from editor import Editor
 from error import DownloadError
@@ -144,11 +144,16 @@
             file=sys.stderr)
       return 1
 
-    if isinstance(cmd, RequiresGitcCommand) and not gitc_utils.get_gitc_manifest_dir():
+    if isinstance(cmd, GitcAvailableCommand) and not gitc_utils.get_gitc_manifest_dir():
       print("fatal: '%s' requires GITC to be available" % name,
             file=sys.stderr)
       return 1
 
+    if isinstance(cmd, GitcClientCommand) and not gitc_client_name:
+      print("fatal: '%s' requires a GITC client" % name,
+            file=sys.stderr)
+      return 1
+
     try:
       copts, cargs = cmd.OptionParser.parse_args(argv)
       copts = cmd.ReadEnvironmentOptions(copts)
diff --git a/manifest_xml.py b/manifest_xml.py
index abab35e..afd1f39 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -167,12 +167,13 @@
   def _ParseGroups(self, groups):
     return [x for x in re.split(r'[,\s]+', groups) if x]
 
-  def Save(self, fd, peg_rev=False, peg_rev_upstream=True):
+  def Save(self, fd, peg_rev=False, peg_rev_upstream=True, groups=None):
     """Write the current manifest out to the given file descriptor.
     """
     mp = self.manifestProject
 
-    groups = mp.config.GetString('manifest.groups')
+    if groups is None:
+      groups = mp.config.GetString('manifest.groups')
     if groups:
       groups = self._ParseGroups(groups)
 
diff --git a/project.py b/project.py
index e9d0bd1..b1ae8b6 100644
--- a/project.py
+++ b/project.py
@@ -249,7 +249,7 @@
     if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
       try:
         # remove existing file first, since it might be read-only
-        if os.path.exists(absDest):
+        if os.path.lexists(absDest):
           os.remove(absDest)
         else:
           dest_dir = os.path.dirname(absDest)
@@ -1124,7 +1124,8 @@
       clone_bundle=True,
       no_tags=False,
       archive=False,
-      optimized_fetch=False):
+      optimized_fetch=False,
+      prune=False):
     """Perform only the network IO portion of the sync process.
        Local working directory/branch state is not affected.
     """
@@ -1195,7 +1196,7 @@
     if (need_to_fetch
         and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
                                   current_branch_only=current_branch_only,
-                                  no_tags=no_tags)):
+                                  no_tags=no_tags, prune=prune)):
       return False
 
     if self.worktree:
@@ -1596,8 +1597,6 @@
 
     if kill:
       old = self.bare_git.GetHead()
-      if old is None:
-        old = 'refs/heads/please_never_use_this_as_a_branch_name'
 
       try:
         self.bare_git.DetachHead(rev)
@@ -1609,7 +1608,10 @@
                        capture_stderr=True)
         b.Wait()
       finally:
-        self.bare_git.SetHead(old)
+        if ID_RE.match(old):
+          self.bare_git.DetachHead(old)
+        else:
+          self.bare_git.SetHead(old)
         left = self._allrefs
 
       for branch in kill:
@@ -1809,7 +1811,8 @@
                    initial=False,
                    quiet=False,
                    alt_dir=None,
-                   no_tags=False):
+                   no_tags=False,
+                   prune=False):
 
     is_sha1 = False
     tag_name = None
@@ -1923,6 +1926,9 @@
       cmd.append('--tags')
 
     refs = []
+    if prune:
+      cmd.append('--prune')
+
     spec = []
     if not current_branch_only:
       # Fetch whole repo
diff --git a/repo b/repo
index b7d8024..4721174 100755
--- a/repo
+++ b/repo
@@ -1,8 +1,11 @@
 #!/usr/bin/env python
 
-## repo default configuration
-##
-REPO_URL = 'https://gerrit.googlesource.com/git-repo'
+# repo default configuration
+#
+import os
+REPO_URL = os.environ.get('REPO_URL', None)
+if not REPO_URL:
+  REPO_URL = 'https://gerrit.googlesource.com/git-repo'
 REPO_REV = 'stable'
 
 # Copyright (C) 2008 Google Inc.
@@ -20,7 +23,7 @@
 # limitations under the License.
 
 # increment this whenever we make important changes to this script
-VERSION = (1, 21)
+VERSION = (1, 22)
 
 # increment this if the MAINTAINER_KEYS block is modified
 KEYRING_VERSION = (1, 2)
@@ -101,19 +104,19 @@
 -----END PGP PUBLIC KEY BLOCK-----
 """
 
-GIT = 'git'                     # our git command
-MIN_GIT_VERSION = (1, 7, 2)     # minimum supported git version
-repodir = '.repo'               # name of repo's private directory
-S_repo = 'repo'                 # special repo repository
-S_manifests = 'manifests'       # special manifest repository
-REPO_MAIN = S_repo + '/main.py' # main script
-MIN_PYTHON_VERSION = (2, 6)     # minimum supported python version
+GIT = 'git'                      # our git command
+MIN_GIT_VERSION = (1, 7, 2)      # minimum supported git version
+repodir = '.repo'                # name of repo's private directory
+S_repo = 'repo'                  # special repo repository
+S_manifests = 'manifests'        # special manifest repository
+REPO_MAIN = S_repo + '/main.py'  # main script
+MIN_PYTHON_VERSION = (2, 6)      # minimum supported python version
 GITC_CONFIG_FILE = '/gitc/.config'
+GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
 
 
 import errno
 import optparse
-import os
 import re
 import shutil
 import stat
@@ -213,17 +216,20 @@
                  dest='config_name', action="store_true", default=False,
                  help='Always prompt for name/e-mail')
 
-def _GitcInitOptions(init_optparse):
-  init_optparse.set_usage("repo gitc-init -u url -c client [options]")
-  g = init_optparse.add_option_group('GITC options')
+
+def _GitcInitOptions(init_optparse_arg):
+  init_optparse_arg.set_usage("repo gitc-init -u url -c client [options]")
+  g = init_optparse_arg.add_option_group('GITC options')
   g.add_option('-f', '--manifest-file',
                dest='manifest_file',
                help='Optional manifest file to use for this GITC client.')
   g.add_option('-c', '--gitc-client',
                dest='gitc_client',
-               help='The name for the new gitc_client instance.')
+               help='The name of the gitc_client instance to create or modify.')
 
 _gitc_manifest_dir = None
+
+
 def get_gitc_manifest_dir():
   global _gitc_manifest_dir
   if _gitc_manifest_dir is None:
@@ -238,7 +244,32 @@
       pass
   return _gitc_manifest_dir
 
+
+def gitc_parse_clientdir(gitc_fs_path):
+  """Parse a path in the GITC FS and return its client name.
+
+  @param gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR.
+
+  @returns: The GITC client name
+  """
+  if gitc_fs_path == GITC_FS_ROOT_DIR:
+    return None
+  if not gitc_fs_path.startswith(GITC_FS_ROOT_DIR):
+    manifest_dir = get_gitc_manifest_dir()
+    if manifest_dir == '':
+      return None
+    if manifest_dir[-1] != '/':
+      manifest_dir += '/'
+    if gitc_fs_path == manifest_dir:
+      return None
+    if not gitc_fs_path.startswith(manifest_dir):
+      return None
+    return gitc_fs_path.split(manifest_dir)[1].split('/')[0]
+  return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split('/')[0]
+
+
 class CloneFailure(Exception):
+
   """Indicate the remote clone of repo itself failed.
   """
 
@@ -276,10 +307,13 @@
         _print('fatal: GITC filesystem is not available. Exiting...',
                file=sys.stderr)
         sys.exit(1)
-      if not opt.gitc_client:
+      gitc_client = opt.gitc_client
+      if not gitc_client:
+        gitc_client = gitc_parse_clientdir(os.getcwd())
+      if not gitc_client:
         _print('fatal: GITC client (-c) is required.', file=sys.stderr)
         sys.exit(1)
-      client_dir = os.path.join(gitc_manifest_dir, opt.gitc_client)
+      client_dir = os.path.join(gitc_manifest_dir, gitc_client)
       if not os.path.exists(client_dir):
         os.makedirs(client_dir)
       os.chdir(client_dir)
@@ -403,8 +437,8 @@
   cmd = ['gpg', '--import']
   try:
     proc = subprocess.Popen(cmd,
-                            env = env,
-                            stdin = subprocess.PIPE)
+                            env=env,
+                            stdin=subprocess.PIPE)
   except OSError as e:
     if not quiet:
       _print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
@@ -430,7 +464,7 @@
   """Set a git configuration option to the specified value.
   """
   cmd = [GIT, 'config', name, value]
-  if subprocess.Popen(cmd, cwd = local).wait() != 0:
+  if subprocess.Popen(cmd, cwd=local).wait() != 0:
     raise CloneFailure()
 
 
@@ -443,9 +477,9 @@
     n = netrc.netrc()
     for host in n.hosts:
       p = n.hosts[host]
-      mgr.add_password(p[1], 'http://%s/'  % host, p[0], p[2])
+      mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2])
       mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
-  except:
+  except:  # pylint: disable=bare-except
     pass
   handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
   handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
@@ -458,6 +492,7 @@
     handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
   urllib.request.install_opener(urllib.request.build_opener(*handlers))
 
+
 def _Fetch(url, local, src, quiet):
   if not quiet:
     _print('Get %s' % url, file=sys.stderr)
@@ -472,22 +507,23 @@
   cmd.append('+refs/heads/*:refs/remotes/origin/*')
   cmd.append('refs/tags/*:refs/tags/*')
 
-  proc = subprocess.Popen(cmd, cwd = local, stderr = err)
+  proc = subprocess.Popen(cmd, cwd=local, stderr=err)
   if err:
     proc.stderr.read()
     proc.stderr.close()
   if proc.wait() != 0:
     raise CloneFailure()
 
+
 def _DownloadBundle(url, local, quiet):
   if not url.endswith('/'):
     url += '/'
   url += 'clone.bundle'
 
   proc = subprocess.Popen(
-    [GIT, 'config', '--get-regexp', 'url.*.insteadof'],
-    cwd = local,
-    stdout = subprocess.PIPE)
+      [GIT, 'config', '--get-regexp', 'url.*.insteadof'],
+      cwd=local,
+      stdout=subprocess.PIPE)
   for line in proc.stdout:
     m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line)
     if m:
@@ -529,6 +565,7 @@
   finally:
     dest.close()
 
+
 def _ImportBundle(local):
   path = os.path.join(local, '.git', 'clone.bundle')
   try:
@@ -536,6 +573,7 @@
   finally:
     os.remove(path)
 
+
 def _Clone(url, local, quiet):
   """Clones a git repository to a new subdirectory of repodir
   """
@@ -548,14 +586,14 @@
 
   cmd = [GIT, 'init', '--quiet']
   try:
-    proc = subprocess.Popen(cmd, cwd = local)
+    proc = subprocess.Popen(cmd, cwd=local)
   except OSError as e:
     _print(file=sys.stderr)
     _print("fatal: '%s' is not available" % GIT, file=sys.stderr)
     _print('fatal: %s' % e, file=sys.stderr)
     _print(file=sys.stderr)
     _print('Please make sure %s is installed and in your path.' % GIT,
-          file=sys.stderr)
+           file=sys.stderr)
     raise CloneFailure()
   if proc.wait() != 0:
     _print('fatal: could not create %s' % local, file=sys.stderr)
@@ -563,12 +601,12 @@
 
   _InitHttp()
   _SetConfig(local, 'remote.origin.url', url)
-  _SetConfig(local, 'remote.origin.fetch',
-                    '+refs/heads/*:refs/remotes/origin/*')
+  _SetConfig(local,
+             'remote.origin.fetch',
+             '+refs/heads/*:refs/remotes/origin/*')
   if _DownloadBundle(url, local, quiet):
     _ImportBundle(local)
-  else:
-    _Fetch(url, local, 'origin', quiet)
+  _Fetch(url, local, 'origin', quiet)
 
 
 def _Verify(cwd, branch, quiet):
@@ -578,7 +616,7 @@
   proc = subprocess.Popen(cmd,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
-                          cwd = cwd)
+                          cwd=cwd)
   cur = proc.stdout.read().strip()
   proc.stdout.close()
 
@@ -596,7 +634,7 @@
     if not quiet:
       _print(file=sys.stderr)
       _print("info: Ignoring branch '%s'; using tagged release '%s'"
-            % (branch, cur), file=sys.stderr)
+             % (branch, cur), file=sys.stderr)
       _print(file=sys.stderr)
 
   env = os.environ.copy()
@@ -604,10 +642,10 @@
 
   cmd = [GIT, 'tag', '-v', cur]
   proc = subprocess.Popen(cmd,
-                          stdout = subprocess.PIPE,
-                          stderr = subprocess.PIPE,
-                          cwd = cwd,
-                          env = env)
+                          stdout=subprocess.PIPE,
+                          stderr=subprocess.PIPE,
+                          cwd=cwd,
+                          env=env)
   out = proc.stdout.read()
   proc.stdout.close()
 
@@ -627,21 +665,21 @@
   """Checkout an upstream branch into the repository and track it.
   """
   cmd = [GIT, 'update-ref', 'refs/heads/default', rev]
-  if subprocess.Popen(cmd, cwd = cwd).wait() != 0:
+  if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
     raise CloneFailure()
 
   _SetConfig(cwd, 'branch.default.remote', 'origin')
   _SetConfig(cwd, 'branch.default.merge', 'refs/heads/%s' % branch)
 
   cmd = [GIT, 'symbolic-ref', 'HEAD', 'refs/heads/default']
-  if subprocess.Popen(cmd, cwd = cwd).wait() != 0:
+  if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
     raise CloneFailure()
 
   cmd = [GIT, 'read-tree', '--reset', '-u']
   if not quiet:
     cmd.append('-v')
   cmd.append('HEAD')
-  if subprocess.Popen(cmd, cwd = cwd).wait() != 0:
+  if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
     raise CloneFailure()
 
 
@@ -653,8 +691,8 @@
 
   olddir = None
   while curdir != '/' \
-    and curdir != olddir \
-    and not repo:
+          and curdir != olddir \
+          and not repo:
     repo = os.path.join(curdir, repodir, REPO_MAIN)
     if not os.path.isfile(repo):
       repo = None
@@ -663,7 +701,7 @@
   return (repo, os.path.join(curdir, repodir))
 
 
-class _Options:
+class _Options(object):
   help = False
 
 
@@ -690,7 +728,7 @@
     gitc_usage = "  gitc-init Initialize a GITC Client.\n"
 
   _print(
-"""usage: repo COMMAND [ARGS]
+      """usage: repo COMMAND [ARGS]
 
 repo is not yet installed.  Use "repo init" to install it here.
 
@@ -698,7 +736,7 @@
 
   init      Install repo in the current working directory
 """ + gitc_usage +
-"""  help      Display detailed help on a command
+      """  help      Display detailed help on a command
 
 For access to the full online help, install repo ("repo init").
 """, file=sys.stderr)
@@ -759,8 +797,8 @@
                            '--git-dir=%s' % gitdir,
                            'symbolic-ref',
                            'HEAD'],
-                          stdout = subprocess.PIPE,
-                          stderr = subprocess.PIPE)
+                          stdout=subprocess.PIPE,
+                          stderr=subprocess.PIPE)
   REPO_REV = proc.stdout.read().strip()
   proc.stdout.close()
 
@@ -773,9 +811,13 @@
 
 
 def main(orig_args):
-  repo_main, rel_repo_dir = _FindRepo()
   cmd, opt, args = _ParseArguments(orig_args)
 
+  repo_main, rel_repo_dir = None, None
+  # Don't use the local repo copy, make sure to switch to the gitc client first.
+  if cmd != 'gitc-init':
+    repo_main, rel_repo_dir = _FindRepo()
+
   wrapper_path = os.path.abspath(__file__)
   my_main, my_git = _RunSelf(wrapper_path)
 
@@ -783,7 +825,8 @@
   if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
     _print('error: repo cannot be used in the GITC local manifest directory.'
            '\nIf you want to work on this GITC client please rerun this '
-           'command from the corresponding client under /gitc/', file=sys.stderr)
+           'command from the corresponding client under /gitc/',
+           file=sys.stderr)
     sys.exit(1)
   if not repo_main:
     if opt.help:
diff --git a/subcmds/forall.py b/subcmds/forall.py
index 96dc99d..b10f34b 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -240,7 +240,8 @@
       rc = rc or errno.EINTR
     except Exception as e:
       # Catch any other exceptions raised
-      print('Got an error, terminating the pool: %r' % e,
+      print('Got an error, terminating the pool: %s: %s' %
+              (type(e).__name__, e),
             file=sys.stderr)
       pool.terminate()
       rc = rc or getattr(e, 'errno', 1)
@@ -254,7 +255,8 @@
       try:
         project = self._SerializeProject(p)
       except Exception as e:
-        print('Project list error: %r' % e,
+        print('Project list error on project %s: %s: %s' %
+                (p.name, type(e).__name__, e),
               file=sys.stderr)
         return
       except KeyboardInterrupt:
diff --git a/subcmds/gitc_delete.py b/subcmds/gitc_delete.py
new file mode 100644
index 0000000..7380c35
--- /dev/null
+++ b/subcmds/gitc_delete.py
@@ -0,0 +1,55 @@
+#
+# Copyright (C) 2015 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 __future__ import print_function
+import os
+import shutil
+import sys
+
+from command import Command, GitcClientCommand
+import gitc_utils
+
+from pyversion import is_python3
+if not is_python3():
+  # pylint:disable=W0622
+  input = raw_input
+  # pylint:enable=W0622
+
+class GitcDelete(Command, GitcClientCommand):
+  common = True
+  visible_everywhere = False
+  helpSummary = "Delete a GITC Client."
+  helpUsage = """
+%prog
+"""
+  helpDescription = """
+This subcommand deletes the current GITC client, deleting the GITC manifest
+and all locally downloaded sources.
+"""
+
+  def _Options(self, p):
+    p.add_option('-f', '--force',
+                 dest='force', action='store_true',
+                 help='Force the deletion (no prompt).')
+
+  def Execute(self, opt, args):
+    if not opt.force:
+      prompt = ('This will delete GITC client: %s\nAre you sure? (yes/no) ' %
+                self.gitc_manifest.gitc_client_name)
+      response = input(prompt).lower()
+      if not response == 'yes':
+        print('Response was not "yes"\n Exiting...')
+        sys.exit(1)
+    shutil.rmtree(self.gitc_manifest.gitc_client_dir)
diff --git a/subcmds/gitc_init.py b/subcmds/gitc_init.py
index e99affa..2726eae 100644
--- a/subcmds/gitc_init.py
+++ b/subcmds/gitc_init.py
@@ -18,11 +18,13 @@
 import sys
 
 import gitc_utils
-from command import RequiresGitcCommand
+from command import GitcAvailableCommand
+from manifest_xml import GitcManifest
 from subcmds import init
+import wrapper
 
 
-class GitcInit(init.Init, RequiresGitcCommand):
+class GitcInit(init.Init, GitcAvailableCommand):
   common = True
   helpSummary = "Initialize a GITC Client."
   helpUsage = """
@@ -34,7 +36,7 @@
 
 This command will setup the client directory, initialize repo, just
 like repo init does, and then downloads the manifest collection
-and installs in in the .repo/directory of the GITC client.
+and installs it in the .repo/directory of the GITC client.
 
 Once this is done, a GITC manifest is generated by pulling the HEAD
 SHA for each project and generates the properly formatted XML file
@@ -54,29 +56,27 @@
                  help='Optional manifest file to use for this GITC client.')
     g.add_option('-c', '--gitc-client',
                  dest='gitc_client',
-                 help='The name for the new gitc_client instance.')
+                 help='The name of the gitc_client instance to create or modify.')
 
   def Execute(self, opt, args):
-    if not opt.gitc_client:
-      print('fatal: gitc client (-c) is required', file=sys.stderr)
+    gitc_client = gitc_utils.parse_clientdir(os.getcwd())
+    if not gitc_client or (opt.gitc_client and gitc_client != opt.gitc_client):
+      print('fatal: Please update your repo command. See go/gitc for instructions.', file=sys.stderr)
       sys.exit(1)
     self.client_dir = os.path.join(gitc_utils.get_gitc_manifest_dir(),
-                                   opt.gitc_client)
-    if not os.path.exists(gitc_utils.get_gitc_manifest_dir()):
-      os.makedirs(gitc_utils.get_gitc_manifest_dir())
-    if not os.path.exists(self.client_dir):
-      os.mkdir(self.client_dir)
+                                   gitc_client)
     super(GitcInit, self).Execute(opt, args)
 
-    for name, remote in self.manifest.remotes.iteritems():
-      remote.fetchUrl = remote.resolvedFetchUrl
-
+    manifest_file = self.manifest.manifestFile
     if opt.manifest_file:
       if not os.path.exists(opt.manifest_file):
         print('fatal: Specified manifest file %s does not exist.' %
               opt.manifest_file)
         sys.exit(1)
-      self.manifest.Override(opt.manifest_file)
-    gitc_utils.generate_gitc_manifest(self.client_dir, self.manifest)
+      manifest_file = opt.manifest_file
+
+    manifest = GitcManifest(self.repodir, gitc_client)
+    manifest.Override(manifest_file)
+    gitc_utils.generate_gitc_manifest(None, manifest)
     print('Please run `cd %s` to view your GITC client.' %
-          os.path.join(gitc_utils.GITC_FS_ROOT_DIR, opt.gitc_client))
+          os.path.join(wrapper.Wrapper().GITC_FS_ROOT_DIR, gitc_client))
diff --git a/subcmds/help.py b/subcmds/help.py
index ae5b8f0..9bb4c8c 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -19,7 +19,7 @@
 from formatter import AbstractFormatter, DumbWriter
 
 from color import Coloring
-from command import PagedCommand, MirrorSafeCommand, RequiresGitcCommand
+from command import PagedCommand, MirrorSafeCommand, GitcAvailableCommand, GitcClientCommand
 import gitc_utils
 
 class Help(PagedCommand, MirrorSafeCommand):
@@ -57,8 +57,12 @@
     print('The most commonly used repo commands are:')
 
     def gitc_supported(cmd):
-      if not isinstance(cmd, RequiresGitcCommand):
+      if not isinstance(cmd, GitcAvailableCommand) and not isinstance(cmd, GitcClientCommand):
+        return True
+      if self.manifest.isGitcClient:
         return True
+      if isinstance(cmd, GitcClientCommand):
+        return False
       if gitc_utils.get_gitc_manifest_dir():
         return True
       return False
diff --git a/subcmds/init.py b/subcmds/init.py
index dbb6ddd..b8e3de5 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -179,7 +179,7 @@
       r.Save()
 
     groups = re.split(r'[,\s]+', opt.groups)
-    all_platforms = ['linux', 'darwin']
+    all_platforms = ['linux', 'darwin', 'windows']
     platformize = lambda x: 'platform-' + x
     if opt.platform == 'auto':
       if (not opt.mirror and
@@ -188,7 +188,7 @@
     elif opt.platform == 'all':
       groups.extend(map(platformize, all_platforms))
     elif opt.platform in all_platforms:
-      groups.extend(platformize(opt.platform))
+      groups.append(platformize(opt.platform))
     elif opt.platform != 'none':
       print('fatal: invalid platform flag', file=sys.stderr)
       sys.exit(1)
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index 1bdc1f0..7479697 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -54,6 +54,11 @@
     p.add_option('--auto-stash',
                  dest='auto_stash', action='store_true',
                  help='Stash local modifications before starting')
+    p.add_option('-m', '--onto-manifest',
+                 dest='onto_manifest', action='store_true',
+                 help='Rebase onto the manifest version instead of upstream '
+                      'HEAD.  This helps to make sure the local tree stays '
+                      'consistent if you previously synced to a manifest.')
 
   def Execute(self, opt, args):
     all_projects = self.GetProjects(args)
@@ -106,6 +111,10 @@
       if opt.interactive:
         args.append("-i")
 
+      if opt.onto_manifest:
+        args.append('--onto')
+        args.append(project.revisionExpr)
+
       args.append(upbranch.LocalMerge)
 
       print('# %s: rebasing %s -> %s'
diff --git a/subcmds/start.py b/subcmds/start.py
index 188fd7c..d1430a9 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -57,29 +57,35 @@
         print("error: at least one project must be specified", file=sys.stderr)
         sys.exit(1)
 
-    proj_name_to_gitc_proj_dict = {}
+    all_projects = self.GetProjects(projects,
+                                    missing_ok=bool(self.gitc_manifest))
+
+    # This must happen after we find all_projects, since GetProjects may need
+    # the local directory, which will disappear once we save the GITC manifest.
     if self.gitc_manifest:
-      all_projects = self.GetProjects(projects, manifest=self.gitc_manifest,
-                                      missing_ok=True)
-      for project in all_projects:
+      gitc_projects = self.GetProjects(projects, manifest=self.gitc_manifest,
+                                       missing_ok=True)
+      for project in gitc_projects:
         if project.old_revision:
           project.already_synced = True
         else:
           project.already_synced = False
           project.old_revision = project.revisionExpr
-        proj_name_to_gitc_proj_dict[project.name] = project
         project.revisionExpr = None
       # Save the GITC manifest.
       gitc_utils.save_manifest(self.gitc_manifest)
 
-    all_projects = self.GetProjects(projects,
-                                    missing_ok=bool(self.gitc_manifest))
+      # Make sure we have a valid CWD
+      if not os.path.exists(os.getcwd()):
+        os.chdir(self.manifest.topdir)
+
     pm = Progress('Starting %s' % nb, len(all_projects))
     for project in all_projects:
       pm.update()
+
       if self.gitc_manifest:
-        gitc_project = proj_name_to_gitc_proj_dict[project.name]
-        # Sync projects that have already been opened.
+        gitc_project = self.gitc_manifest.paths[project.relpath]
+        # Sync projects that have not been opened.
         if not gitc_project.already_synced:
           proj_localdir = os.path.join(self.gitc_manifest.gitc_client_dir,
                                        project.relpath)
@@ -89,7 +95,7 @@
           project.Sync_NetworkHalf()
           sync_buf = SyncBuffer(self.manifest.manifestProject.config)
           project.Sync_LocalHalf(sync_buf)
-          project.revisionExpr = gitc_project.old_revision
+          project.revisionId = gitc_project.old_revision
 
       # If the current revision is a specific SHA1 then we can't push back
       # to it; so substitute with dest_branch if defined, or with manifest
@@ -100,6 +106,7 @@
           branch_merge = project.dest_branch
         else:
           branch_merge = self.manifest.default.revisionExpr
+
       if not project.StartBranch(nb, branch_merge=branch_merge):
         err.append(project)
     pm.end()
diff --git a/subcmds/sync.py b/subcmds/sync.py
index a750a23..674569c 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -75,6 +75,7 @@
 from project import SyncBuffer
 from progress import Progress
 from wrapper import Wrapper
+from manifest_xml import GitcManifest
 
 _ONE_DAY_S = 24 * 60 * 60
 
@@ -150,6 +151,9 @@
 are fixed to a sha1 revision if the sha1 revision does not already
 exist locally.
 
+The --prune option can be used to remove any refs that no longer
+exist on the remote.
+
 SSH Connections
 ---------------
 
@@ -233,6 +237,8 @@
     p.add_option('--optimized-fetch',
                  dest='optimized_fetch', action='store_true',
                  help='only fetch projects fixed to sha1 if revision does not exist locally')
+    p.add_option('--prune', dest='prune', action='store_true',
+                 help='delete refs that no longer exist on the remote')
     if show_smart:
       p.add_option('-s', '--smart-sync',
                    dest='smart_sync', action='store_true',
@@ -307,7 +313,8 @@
           force_sync=opt.force_sync,
           clone_bundle=not opt.no_clone_bundle,
           no_tags=opt.no_tags, archive=self.manifest.IsArchive,
-          optimized_fetch=opt.optimized_fetch)
+          optimized_fetch=opt.optimized_fetch,
+          prune=opt.prune)
         self._fetch_times.Set(project, time.time() - start)
 
         # Lock around all the rest of the code, since printing, updating a set
@@ -316,6 +323,7 @@
         did_lock = True
 
         if not success:
+          err_event.set()
           print('error: Cannot fetch %s' % project.name, file=sys.stderr)
           if opt.force_broken:
             print('warn: --force-broken, continuing to sync',
@@ -326,7 +334,7 @@
         fetched.add(project.gitdir)
         pm.update()
       except _FetchError:
-        err_event.set()
+        pass
       except Exception as e:
         print('error: Cannot fetch %s (%s: %s)' \
             % (project.name, type(e).__name__, str(e)), file=sys.stderr)
@@ -673,32 +681,39 @@
       if opt.jobs is None:
         self.jobs = self.manifest.default.sync_j
 
-    # TODO (sbasi) - Add support for manifest changes, aka projects
-    # have been added or deleted from the manifest.
     if self.gitc_manifest:
       gitc_manifest_projects = self.GetProjects(args,
-                                                manifest=self.gitc_manifest,
                                                 missing_ok=True)
       gitc_projects = []
       opened_projects = []
       for project in gitc_manifest_projects:
-        if not project.old_revision:
-          gitc_projects.append(project)
+        if project.relpath in self.gitc_manifest.paths and \
+           self.gitc_manifest.paths[project.relpath].old_revision:
+          opened_projects.append(project.relpath)
         else:
-          opened_projects.append(project)
+          gitc_projects.append(project.relpath)
 
-      if gitc_projects and not opt.local_only:
+      if not args:
+        gitc_projects = None
+
+      if gitc_projects != [] and not opt.local_only:
         print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
-        gitc_utils.generate_gitc_manifest(self.gitc_manifest.gitc_client_dir,
-                                          self.gitc_manifest,
+        manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
+        if manifest_name:
+          manifest.Override(manifest_name)
+        else:
+          manifest.Override(self.manifest.manifestFile)
+        gitc_utils.generate_gitc_manifest(self.gitc_manifest,
+                                          manifest,
                                           gitc_projects)
         print('GITC client successfully synced.')
 
       # The opened projects need to be synced as normal, therefore we
       # generate a new args list to represent the opened projects.
-      args = []
-      for proj in opened_projects:
-        args.append(os.path.relpath(proj.worktree, os.getcwd()))
+      # TODO: make this more reliable -- if there's a project name/path overlap,
+      # this may choose the wrong project.
+      args = [os.path.relpath(self.manifest.paths[p].worktree, os.getcwd())
+              for p in opened_projects]
       if not args:
         return
     all_projects = self.GetProjects(args,
@@ -911,6 +926,7 @@
       # stripping those prefixes away.
       if cookiefile:
         tmpcookiefile = tempfile.NamedTemporaryFile()
+        tmpcookiefile.write("# HTTP Cookie File")
         try:
           with open(cookiefile) as f:
             for line in f:
@@ -920,7 +936,10 @@
           tmpcookiefile.flush()
 
           cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
-          cookiejar.load()
+          try:
+            cookiejar.load()
+          except cookielib.LoadError:
+            cookiejar = cookielib.CookieJar()
         finally:
           tmpcookiefile.close()
       else:
diff --git a/tests/fixtures/gitc_config b/tests/fixtures/gitc_config
new file mode 100644
index 0000000..a7f3d1c
--- /dev/null
+++ b/tests/fixtures/gitc_config
@@ -0,0 +1 @@
+gitc_dir=/test/usr/local/google/gitc
diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py
new file mode 100644
index 0000000..fb32e38
--- /dev/null
+++ b/tests/test_wrapper.py
@@ -0,0 +1,75 @@
+#
+# Copyright (C) 2015 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
+import unittest
+
+import wrapper
+
+def fixture(*paths):
+  """Return a path relative to tests/fixtures.
+  """
+  return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
+
+class RepoWrapperUnitTest(unittest.TestCase):
+  """Tests helper functions in the repo wrapper
+  """
+  def setUp(self):
+    """Load the wrapper module every time
+    """
+    wrapper._wrapper_module = None
+    self.wrapper = wrapper.Wrapper()
+
+  def test_get_gitc_manifest_dir_no_gitc(self):
+    """
+    Test reading a missing gitc config file
+    """
+    self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config')
+    val = self.wrapper.get_gitc_manifest_dir()
+    self.assertEqual(val, '')
+
+  def test_get_gitc_manifest_dir(self):
+    """
+    Test reading the gitc config file and parsing the directory
+    """
+    self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config')
+    val = self.wrapper.get_gitc_manifest_dir()
+    self.assertEqual(val, '/test/usr/local/google/gitc')
+
+  def test_gitc_parse_clientdir_no_gitc(self):
+    """
+    Test parsing the gitc clientdir without gitc running
+    """
+    self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None)
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test')
+
+  def test_gitc_parse_clientdir(self):
+    """
+    Test parsing the gitc clientdir
+    """
+    self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None)
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/'), 'test')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/extra'), 'test')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test'), 'test')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/'), 'test')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/extra'), 'test')
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/'), None)
+    self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/'), None)
+
+if __name__ == '__main__':
+  unittest.main()