Merge commits up to 6e53844f1edd3c9a3898f0af74fcc8da04b7de48

6e53844f1edd 2018-12-10 11:33:16 -0800 Allow clobbering of existing tags from remote.
d26146de7f1b 2018-11-01 11:54:10 +0900 platform_utils: Fix exception handling in _walk_windows_impl
bd8f65882305 2018-10-31 13:48:01 -0700 Add option for git-repo to support 'silent' uploads
713c5872fb54 2018-11-05 13:21:52 -0800 upload: Unify option passing in ssh and other transports
bed8b62345e4 2018-09-27 10:46:58 -0700 Add support for long paths
09f0abb0efde 2018-10-19 15:07:05 +0500 init: --dissociate option to copy objects borrowed with --reference
3b24e7b5577f 2018-10-10 00:57:44 -0400 update homepage URIs
b8f7bb04d003 2018-10-10 01:05:11 -0400 update markdown/help header format
3891b7519d35 2018-10-05 19:26:15 -0400 manifest-format: convert to markdown
2b42d288c08b 2018-10-01 14:59:48 -0700 Windows: Add support for creating symlinks as an unprivileged user
e469a0c74183 2018-06-23 15:02:26 +0800 fix some sync error while using python3
65b0ba5aa044 2018-06-24 16:21:51 +0900 Remove unused pylint suppressions
993dcacd17c6 2018-07-13 10:25:52 +0200 Fix the initial existence check for "repo"
a9399846faa0 2018-07-13 11:47:10 +0200 Flush stderr on Windows
b10f0e5b9a9b 2018-02-28 23:12:04 +0100 hooks/pre-auto-gc-battery: allow gc to run on non-laptops
da40341a3e6e 2018-05-04 12:53:29 -0600 manifest: Support a default upstream value
ed429c9f6f49 2018-03-20 20:00:14 -0400 docs: repo-hooks: fix cwd details
0f2e45a3a69e 2018-03-24 12:27:05 +0530 Pass refs to ls-remote
cf7c0834cfc2 2018-03-15 21:56:30 +0530 Download latest patch when no patch is specified
7d52585ec471 2018-03-15 09:54:08 -0700 Add a way to override the revision of an <extend-project>
ce7e02601cfb 2018-02-26 08:49:36 +0900 Take care of a tilde on cookie file path
a32c92c206ad 2018-02-14 16:57:31 +0900 implement optional 'sync-tags' in the manifest file
5f0e57d2ca28 2018-01-22 11:00:24 -0600 init: Remove string concat in no-op os.path.join
baa00093557d 2018-01-22 10:57:29 -0600 Support relative paths in --reference

BUG=chromium:900461
TEST=`repo sync` locally

Change-Id: Ie95dbf84b4113a567153d10ce9813fef33c0d22e
diff --git a/README.md b/README.md
index 250d08e..39f6bb8 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,11 @@
 easier to work with Git.  The repo command is an executable Python script
 that you can put anywhere in your path.
 
-* Homepage: https://code.google.com/p/git-repo/
+* Homepage: https://gerrit.googlesource.com/git-repo/
 * Bug reports: https://code.google.com/p/git-repo/issues/
-* Source: https://code.google.com/p/git-repo/
+* Source: https://gerrit.googlesource.com/git-repo/
 * Overview: https://source.android.com/source/developing.html
 * Docs: https://source.android.com/source/using-repo.html
-* [repo Manifest Format](./docs/manifest-format.txt)
+* [repo Manifest Format](./docs/manifest-format.md)
 * [repo Hooks](./docs/repo-hooks.md)
 * [Submitting patches](./SUBMITTING_PATCHES.md)
diff --git a/command.py b/command.py
index 971f968..eb3527f 100644
--- a/command.py
+++ b/command.py
@@ -218,11 +218,6 @@
     return result
 
 
-# pylint: disable=W0223
-# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
-# override method `Execute` which is abstract in `Command`.  Since that method
-# is always implemented in classes derived from `InteractiveCommand` and
-# `PagedCommand`, this warning can be suppressed.
 class InteractiveCommand(Command):
   """Command which requires user interaction on the tty and
      must not run within a pager, even if the user asks to.
@@ -238,8 +233,6 @@
   def WantPager(self, _opt):
     return True
 
-# pylint: enable=W0223
-
 
 class MirrorSafeCommand(object):
   """Command permits itself to run within a mirror,
diff --git a/docs/manifest-format.txt b/docs/manifest-format.md
similarity index 70%
rename from docs/manifest-format.txt
rename to docs/manifest-format.md
index 7778409..c85726b 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.md
@@ -1,112 +1,116 @@
-repo Manifest Format
-====================
+# repo Manifest Format
 
 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 single 'default.xml' XML file in the top level directory.
+a single `default.xml` XML file in the top level directory.
 
 Manifests are inherently version controlled, since they are kept
 within a Git repository.  Updates to manifests are automatically
 obtained by clients during `repo sync`.
 
+[TOC]
 
-XML File Format
----------------
 
-A manifest XML file (e.g. 'default.xml') roughly conforms to the
+## XML File Format
+
+A manifest XML file (e.g. `default.xml`) roughly conforms to the
 following DTD:
 
-  <!DOCTYPE manifest [
-    <!ELEMENT manifest (notice?,
-                        remote*,
-                        default?,
-                        manifest-server?,
-                        remove-project*,
-                        project*,
-                        extend-project*,
-                        repo-hooks?,
-                        include*)>
+```xml
+<!DOCTYPE manifest [
+  <!ELEMENT manifest (notice?,
+                      remote*,
+                      default?,
+                      manifest-server?,
+                      remove-project*,
+                      project*,
+                      extend-project*,
+                      repo-hooks?,
+                      include*)>
 
-    <!ELEMENT notice (#PCDATA)>
+  <!ELEMENT notice (#PCDATA)>
 
-    <!ELEMENT remote EMPTY>
-    <!ATTLIST remote name         ID    #REQUIRED>
-    <!ATTLIST remote alias        CDATA #IMPLIED>
-    <!ATTLIST remote fetch        CDATA #REQUIRED>
-    <!ATTLIST remote pushurl      CDATA #IMPLIED>
-    <!ATTLIST remote review       CDATA #IMPLIED>
-    <!ATTLIST remote revision     CDATA #IMPLIED>
+  <!ELEMENT remote EMPTY>
+  <!ATTLIST remote name         ID    #REQUIRED>
+  <!ATTLIST remote alias        CDATA #IMPLIED>
+  <!ATTLIST remote fetch        CDATA #REQUIRED>
+  <!ATTLIST remote pushurl      CDATA #IMPLIED>
+  <!ATTLIST remote review       CDATA #IMPLIED>
+  <!ATTLIST remote revision     CDATA #IMPLIED>
 
-    <!ELEMENT default EMPTY>
-    <!ATTLIST default remote      IDREF #IMPLIED>
-    <!ATTLIST default revision    CDATA #IMPLIED>
-    <!ATTLIST default dest-branch CDATA #IMPLIED>
-    <!ATTLIST default sync-j      CDATA #IMPLIED>
-    <!ATTLIST default sync-c      CDATA #IMPLIED>
-    <!ATTLIST default sync-s      CDATA #IMPLIED>
+  <!ELEMENT default EMPTY>
+  <!ATTLIST default remote      IDREF #IMPLIED>
+  <!ATTLIST default revision    CDATA #IMPLIED>
+  <!ATTLIST default dest-branch CDATA #IMPLIED>
+  <!ATTLIST default upstream    CDATA #IMPLIED>
+  <!ATTLIST default sync-j      CDATA #IMPLIED>
+  <!ATTLIST default sync-c      CDATA #IMPLIED>
+  <!ATTLIST default sync-s      CDATA #IMPLIED>
+  <!ATTLIST default sync-tags   CDATA #IMPLIED>
 
-    <!ELEMENT manifest-server EMPTY>
-    <!ATTLIST manifest-server url CDATA #REQUIRED>
+  <!ELEMENT manifest-server EMPTY>
+  <!ATTLIST manifest-server url CDATA #REQUIRED>
 
-    <!ELEMENT project (annotation*,
-                       project*,
-                       copyfile*,
-                       linkfile*)>
-    <!ATTLIST project name        CDATA #REQUIRED>
-    <!ATTLIST project path        CDATA #IMPLIED>
-    <!ATTLIST project remote      IDREF #IMPLIED>
-    <!ATTLIST project revision    CDATA #IMPLIED>
-    <!ATTLIST project dest-branch CDATA #IMPLIED>
-    <!ATTLIST project groups      CDATA #IMPLIED>
-    <!ATTLIST project sync-c      CDATA #IMPLIED>
-    <!ATTLIST project sync-s      CDATA #IMPLIED>
-    <!ATTLIST project upstream CDATA #IMPLIED>
-    <!ATTLIST project clone-depth CDATA #IMPLIED>
-    <!ATTLIST project force-path CDATA #IMPLIED>
+  <!ELEMENT project (annotation*,
+                     project*,
+                     copyfile*,
+                     linkfile*)>
+  <!ATTLIST project name        CDATA #REQUIRED>
+  <!ATTLIST project path        CDATA #IMPLIED>
+  <!ATTLIST project remote      IDREF #IMPLIED>
+  <!ATTLIST project revision    CDATA #IMPLIED>
+  <!ATTLIST project dest-branch CDATA #IMPLIED>
+  <!ATTLIST project groups      CDATA #IMPLIED>
+  <!ATTLIST project sync-c      CDATA #IMPLIED>
+  <!ATTLIST project sync-s      CDATA #IMPLIED>
+  <!ATTLIST default sync-tags   CDATA #IMPLIED>
+  <!ATTLIST project upstream CDATA #IMPLIED>
+  <!ATTLIST project clone-depth CDATA #IMPLIED>
+  <!ATTLIST project force-path CDATA #IMPLIED>
 
-    <!ELEMENT annotation EMPTY>
-    <!ATTLIST annotation name  CDATA #REQUIRED>
-    <!ATTLIST annotation value CDATA #REQUIRED>
-    <!ATTLIST annotation keep  CDATA "true">
+  <!ELEMENT annotation EMPTY>
+  <!ATTLIST annotation name  CDATA #REQUIRED>
+  <!ATTLIST annotation value CDATA #REQUIRED>
+  <!ATTLIST annotation keep  CDATA "true">
 
-    <!ELEMENT copyfile EMPTY>
-    <!ATTLIST copyfile src  CDATA #REQUIRED>
-    <!ATTLIST copyfile dest CDATA #REQUIRED>
+  <!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 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>
+  <!ELEMENT extend-project EMPTY>
+  <!ATTLIST extend-project name CDATA #REQUIRED>
+  <!ATTLIST extend-project path CDATA #IMPLIED>
+  <!ATTLIST extend-project groups CDATA #IMPLIED>
+  <!ATTLIST extend-project revision CDATA #IMPLIED>
 
-    <!ELEMENT remove-project EMPTY>
-    <!ATTLIST remove-project name  CDATA #REQUIRED>
+  <!ELEMENT remove-project EMPTY>
+  <!ATTLIST remove-project name  CDATA #REQUIRED>
 
-    <!ELEMENT repo-hooks EMPTY>
-    <!ATTLIST repo-hooks in-project CDATA #REQUIRED>
-    <!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
+  <!ELEMENT repo-hooks EMPTY>
+  <!ATTLIST repo-hooks in-project CDATA #REQUIRED>
+  <!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
 
-    <!ELEMENT include EMPTY>
-    <!ATTLIST include name CDATA #REQUIRED>
-  ]>
+  <!ELEMENT include EMPTY>
+  <!ATTLIST include name CDATA #REQUIRED>
+]>
+```
 
 A description of the elements and their attributes follows.
 
 
-Element manifest
-----------------
+### Element manifest
 
 The root element of the file.
 
 
-Element remote
---------------
+### Element remote
 
 One or more remote elements may be specified.  Each remote element
 specifies a Git URL shared by one or more projects and (optionally)
@@ -141,8 +145,7 @@
 `refs/heads/master`). Remotes with their own revision will override
 the default revision.
 
-Element default
----------------
+### Element default
 
 At most one default element may be specified.  Its remote and
 revision attributes are used when a project element does not
@@ -161,6 +164,11 @@
 this value. If this value is not set, projects will use `revision`
 by default instead.
 
+Attribute `upstream`: Name of the Git ref in which a sha1
+can be found.  Used when syncing a revision locked manifest in
+-c mode to avoid having to sync the entire ref space. Project elements
+not setting their own `upstream` will inherit this value.
+
 Attribute `sync-j`: Number of parallel jobs to use when synching.
 
 Attribute `sync-c`: Set to true to only sync the given Git
@@ -170,9 +178,12 @@
 
 Attribute `sync-s`: Set to true to also sync sub-projects.
 
+Attribute `sync-tags`: Set to false to only sync the given Git
+branch (specified in the `revision` attribute) rather than
+the other ref tags.
 
-Element manifest-server
------------------------
+
+### Element manifest-server
 
 At most one manifest-server may be specified. The url attribute
 is used to specify the URL of a manifest server, which is an
@@ -180,7 +191,7 @@
 
 The manifest server should implement the following RPC methods:
 
-  GetApprovedManifest(branch, target)
+    GetApprovedManifest(branch, target)
 
 Return a manifest in which each project is pegged to a known good revision
 for the current branch and target. This is used by repo sync when the
@@ -193,15 +204,14 @@
 GetApprovedManifest without the target parameter and the manifest server
 should choose a reasonable default target.
 
-  GetManifest(tag)
+    GetManifest(tag)
 
 Return a manifest in which each project is pegged to the revision at
 the specified tag. This is used by repo sync when the --smart-tag option
 is given.
 
 
-Element project
----------------
+### Element project
 
 One or more project elements may be specified.  Each element
 describes a single Git repository to be cloned into the repo
@@ -214,7 +224,7 @@
 name is appended onto its remote's fetch URL to generate the actual
 URL to configure the Git remote with.  The URL gets formed as:
 
-  ${remote_fetch}/${project_name}.git
+    ${remote_fetch}/${project_name}.git
 
 where ${remote_fetch} is the remote's fetch attribute and
 ${project_name} is the project's name attribute.  The suffix ".git"
@@ -278,8 +288,7 @@
 local mirrors syncing, it will be ignored when syncing the projects in a
 client working directory.
 
-Element extend-project
-----------------------
+### Element extend-project
 
 Modify the attributes of the named project.
 
@@ -294,8 +303,10 @@
 Attribute `groups`: List of additional groups to which this project
 belongs.  Same syntax as the corresponding element of `project`.
 
-Element annotation
-------------------
+Attribute `revision`: If specified, overrides the revision of the original
+project.  Same syntax as the corresponding element of `project`.
+
+### Element annotation
 
 Zero or more annotation elements may be specified as children of a
 project element. Each element describes a name-value pair that will be
@@ -305,23 +316,20 @@
 "false".  This attribute determines whether or not the annotation will
 be kept when exported with the manifest subcommand.
 
-Element copyfile
-----------------
+### 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'
+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
-----------------
+### 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
-----------------------
+### Element remove-project
 
 Deletes the named project from the internal manifest table, possibly
 allowing a subsequent project element in the same manifest file to
@@ -331,8 +339,7 @@
 the user can remove a project, and possibly replace it with their
 own definition.
 
-Element include
----------------
+### Element include
 
 This element provides the capability of including another manifest
 file into the originating manifest.  Normal rules apply for the
@@ -342,26 +349,25 @@
 the manifest repository's root.
 
 
-Local Manifests
-===============
+## Local Manifests
 
 Additional remotes and projects may be added through local manifest
 files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
 
 For example:
 
-  $ ls .repo/local_manifests
-  local_manifest.xml
-  another_local_manifest.xml
+    $ ls .repo/local_manifests
+    local_manifest.xml
+    another_local_manifest.xml
 
-  $ cat .repo/local_manifests/local_manifest.xml
-  <?xml version="1.0" encoding="UTF-8"?>
-  <manifest>
-    <project path="manifest"
-             name="tools/manifest" />
-    <project path="platform-manifest"
-             name="platform/manifest" />
-  </manifest>
+    $ cat .repo/local_manifests/local_manifest.xml
+    <?xml version="1.0" encoding="UTF-8"?>
+    <manifest>
+      <project path="manifest"
+               name="tools/manifest" />
+      <project path="platform-manifest"
+               name="platform/manifest" />
+    </manifest>
 
 Users may add projects to the local manifest(s) prior to a `repo sync`
 invocation, instructing repo to automatically download and manage
diff --git a/docs/repo-hooks.md b/docs/repo-hooks.md
index c8eb945..e198b39 100644
--- a/docs/repo-hooks.md
+++ b/docs/repo-hooks.md
@@ -24,7 +24,7 @@
 
 ## Manifest Settings
 
-For the full syntax, see the [repo manifest format](./manifest-format.txt).
+For the full syntax, see the [repo manifest format](./manifest-format.md).
 
 Here's a short example from
 [Android](https://android.googlesource.com/platform/manifest/+/master/default.xml).
@@ -61,9 +61,14 @@
 long running operations occur, but long/verbose output should be used only if
 the hook ultimately fails.
 
-The hook runs from the top level of the git repo where the operation is started.
-e.g. If you're in the git repo `src/foo/`, that is where the hook runs, even if
-the `repo` command was started from a subdir like `src/foo/bar/`.
+The hook runs from the top level of the repo client where the operation is
+started.
+For example, if the repo client is under `~/tree/`, then that is where the hook
+runs, even if you ran repo in a git repository at `~/tree/src/foo/`, or in a
+subdirectory of that git repository in `~/tree/src/foo/bar/`.
+Hooks frequently start off by doing a `os.chdir` to the specific project they're
+called on (see below) and then changing back to the original dir when they're
+finished.
 
 Python's `sys.path` is modified so that the top of repohooks directory comes
 first.  This should help simplify the hook logic to easily allow importing of
diff --git a/event_log.py b/event_log.py
index 45a2c26..2f1b180 100644
--- a/event_log.py
+++ b/event_log.py
@@ -18,6 +18,8 @@
 import json
 import multiprocessing
 
+from pyversion import is_python3
+
 TASK_COMMAND = 'command'
 TASK_SYNC_NETWORK = 'sync-network'
 TASK_SYNC_LOCAL = 'sync-local'
@@ -71,7 +73,7 @@
       A dictionary of the event added to the log.
     """
     event = {
-        'id': (kind, self._next_id.next()),
+        'id': (kind, self._next_id.__next__() if is_python3() else self._next_id.next()),
         'name': name,
         'task_name': task_name,
         'start_time': start,
diff --git a/git_config.py b/git_config.py
index 3ba9dbd..aac0885 100644
--- a/git_config.py
+++ b/git_config.py
@@ -306,8 +306,9 @@
     d = self._do('--null', '--list')
     if d is None:
       return c
-    for line in d.decode('utf-8').rstrip('\0').split('\0'):  # pylint: disable=W1401
-                                                             # Backslash is not anomalous
+    if not is_python3():
+      d = d.decode('utf-8')
+    for line in d.rstrip('\0').split('\0'):
       if '\n' in line:
         key, val = line.split('\n', 1)
       else:
@@ -502,7 +503,7 @@
   d = ssh_sock(create=False)
   if d:
     try:
-      os.rmdir(os.path.dirname(d))
+      platform_utils.rmdir(os.path.dirname(d))
     except OSError:
       pass
 
@@ -534,7 +535,7 @@
         for line in p.stdout:
           line = line.strip()
           if line.startswith(cookieprefix):
-            cookiefile = line[len(cookieprefix):]
+            cookiefile = os.path.expanduser(line[len(cookieprefix):])
           if line.startswith(proxyprefix):
             proxy = line[len(proxyprefix):]
         # Leave subprocess open, as cookie file may be transient.
@@ -553,7 +554,10 @@
       if e.errno == errno.ENOENT:
         pass  # No persistent proxy.
       raise
-  yield GitConfig.ForUser().GetString('http.cookiefile'), None
+  cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
+  if cookiefile:
+    cookiefile = os.path.expanduser(cookiefile)
+  yield cookiefile, None
 
 def _preconnect(url):
   m = URI_ALL.match(url)
diff --git a/git_refs.py b/git_refs.py
index 7feaffb..e0a85d7 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -15,6 +15,7 @@
 
 import os
 from trace import Trace
+import platform_utils
 
 HEAD      = 'HEAD'
 R_CHANGES = 'refs/changes/'
@@ -127,9 +128,9 @@
 
   def _ReadLoose(self, prefix):
     base = os.path.join(self._gitdir, prefix)
-    for name in os.listdir(base):
+    for name in platform_utils.listdir(base):
       p = os.path.join(base, name)
-      if os.path.isdir(p):
+      if platform_utils.isdir(p):
         self._mtime[prefix] = os.path.getmtime(base)
         self._ReadLoose(prefix + name + '/')
       elif name.endswith('.lock'):
diff --git a/hooks/pre-auto-gc b/hooks/pre-auto-gc
index c4107f5..ec29be4 100755
--- a/hooks/pre-auto-gc
+++ b/hooks/pre-auto-gc
@@ -29,7 +29,7 @@
 	exit 0
 fi
 
-if test -x /sbin/on_ac_power && /sbin/on_ac_power
+if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1)
 then
 	exit 0
 elif test "$(cat /sys/class/power_supply/AC/online 2>/dev/null)" = 1
diff --git a/main.py b/main.py
index a6538c2..be5e313 100755
--- a/main.py
+++ b/main.py
@@ -61,9 +61,7 @@
 from subcmds import all_commands
 
 if not is_python3():
-  # pylint:disable=W0622
   input = raw_input
-  # pylint:enable=W0622
 
 global_options = optparse.OptionParser(
                  usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]"
@@ -396,7 +394,7 @@
     self.context = None
     self.handler_order = urllib.request.BaseHandler.handler_order - 50
 
-  def http_error_401(self, req, fp, code, msg, headers): # pylint:disable=unused-argument
+  def http_error_401(self, req, fp, code, msg, headers):
     host = req.get_host()
     retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
     return retry
diff --git a/manifest_xml.py b/manifest_xml.py
index 9b5d784..f37732c 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -59,10 +59,12 @@
 
   revisionExpr = None
   destBranchExpr = None
+  upstreamExpr = None
   remote = None
   sync_j = 1
   sync_c = False
   sync_s = False
+  sync_tags = True
 
   def __eq__(self, other):
     return self.__dict__ == other.__dict__
@@ -229,6 +231,9 @@
     if d.destBranchExpr:
       have_default = True
       e.setAttribute('dest-branch', d.destBranchExpr)
+    if d.upstreamExpr:
+      have_default = True
+      e.setAttribute('upstream', d.upstreamExpr)
     if d.sync_j > 1:
       have_default = True
       e.setAttribute('sync-j', '%d' % d.sync_j)
@@ -238,6 +243,9 @@
     if d.sync_s:
       have_default = True
       e.setAttribute('sync-s', 'true')
+    if not d.sync_tags:
+      have_default = True
+      e.setAttribute('sync-tags', 'false')
     if have_default:
       root.appendChild(e)
       root.appendChild(doc.createTextNode(''))
@@ -291,7 +299,8 @@
         revision = self.remotes[p.remote.orig_name].revision or d.revisionExpr
         if not revision or revision != p.revisionExpr:
           e.setAttribute('revision', p.revisionExpr)
-        if p.upstream and p.upstream != p.revisionExpr:
+        if (p.upstream and (p.upstream != p.revisionExpr or
+                            p.upstream != d.upstreamExpr)):
           e.setAttribute('upstream', p.upstream)
 
       if p.dest_branch and p.dest_branch != d.destBranchExpr:
@@ -327,6 +336,9 @@
       if p.sync_s:
         e.setAttribute('sync-s', 'true')
 
+      if not p.sync_tags:
+        e.setAttribute('sync-tags', 'false')
+
       if p.clone_depth:
         e.setAttribute('clone-depth', str(p.clone_depth))
 
@@ -434,7 +446,7 @@
 
       local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
       try:
-        for local_file in sorted(os.listdir(local_dir)):
+        for local_file in sorted(platform_utils.listdir(local_dir)):
           if local_file.endswith('.xml'):
             local = os.path.join(local_dir, local_file)
             nodes.append(self._ParseManifestXml(local, self.repodir))
@@ -471,8 +483,7 @@
       raise ManifestParseError("no <manifest> in %s" % (path,))
 
     nodes = []
-    for node in manifest.childNodes:  # pylint:disable=W0631
-                                      # We only get here if manifest is initialised
+    for node in manifest.childNodes:
       if node.nodeName == 'include':
         name = self._reqatt(node, 'name')
         fp = os.path.join(include_root, name)
@@ -564,12 +575,15 @@
         groups = node.getAttribute('groups')
         if groups:
           groups = self._ParseGroups(groups)
+        revision = node.getAttribute('revision')
 
         for p in self._projects[name]:
           if path and p.relpath != path:
             continue
           if groups:
             p.groups.extend(groups)
+          if revision:
+            p.revisionExpr = revision
       if node.nodeName == 'repo-hooks':
         # Get the name of the project and the (space-separated) list of enabled.
         repo_hooks_project = self._reqatt(node, 'in-project')
@@ -684,6 +698,7 @@
       d.revisionExpr = None
 
     d.destBranchExpr = node.getAttribute('dest-branch') or None
+    d.upstreamExpr = node.getAttribute('upstream') or None
 
     sync_j = node.getAttribute('sync-j')
     if sync_j == '' or sync_j is None:
@@ -702,6 +717,12 @@
       d.sync_s = False
     else:
       d.sync_s = sync_s.lower() in ("yes", "true", "1")
+
+    sync_tags = node.getAttribute('sync-tags')
+    if not sync_tags:
+      d.sync_tags = True
+    else:
+      d.sync_tags = sync_tags.lower() in ("yes", "true", "1")
     return d
 
   def _ParseNotice(self, node):
@@ -796,6 +817,12 @@
     else:
       sync_s = sync_s.lower() in ("yes", "true", "1")
 
+    sync_tags = node.getAttribute('sync-tags')
+    if not sync_tags:
+      sync_tags = self._default.sync_tags
+    else:
+      sync_tags = sync_tags.lower() in ("yes", "true", "1")
+
     clone_depth = node.getAttribute('clone-depth')
     if clone_depth:
       try:
@@ -808,7 +835,7 @@
 
     dest_branch = node.getAttribute('dest-branch') or self._default.destBranchExpr
 
-    upstream = node.getAttribute('upstream')
+    upstream = node.getAttribute('upstream') or self._default.upstreamExpr
 
     groups = ''
     if node.hasAttribute('groups'):
@@ -841,6 +868,7 @@
                       groups = groups,
                       sync_c = sync_c,
                       sync_s = sync_s,
+                      sync_tags = sync_tags,
                       clone_depth = clone_depth,
                       upstream = upstream,
                       parent = parent,
diff --git a/platform_utils.py b/platform_utils.py
index 33cb2ec..8af25d2 100644
--- a/platform_utils.py
+++ b/platform_utils.py
@@ -20,7 +20,12 @@
 import shutil
 import stat
 
-from Queue import Queue
+from pyversion import is_python3
+if is_python3():
+  from queue import Queue
+else:
+  from Queue import Queue
+
 from threading import Thread
 
 
@@ -182,10 +187,10 @@
     source = _validate_winpath(source)
     link_name = _validate_winpath(link_name)
     target = os.path.join(os.path.dirname(link_name), source)
-    if os.path.isdir(target):
-      platform_utils_win32.create_dirsymlink(source, link_name)
+    if isdir(target):
+      platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name)
     else:
-      platform_utils_win32.create_filesymlink(source, link_name)
+      platform_utils_win32.create_filesymlink(_makelongpath(source), link_name)
   else:
     return os.symlink(source, link_name)
 
@@ -215,9 +220,32 @@
     return not drive  # "x:" is invalid
 
 
-def rmtree(path):
+def _makelongpath(path):
+  """Return the input path normalized to support the Windows long path syntax
+  ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
+  MAX_PATH limit.
+  """
   if isWindows():
-    shutil.rmtree(path, onerror=handle_rmtree_error)
+    # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246.
+    if len(path) < 246:
+      return path
+    if path.startswith(u"\\\\?\\"):
+      return path
+    if not os.path.isabs(path):
+      return path
+    # Append prefix and ensure unicode so that the special longpath syntax
+    # is supported by underlying Win32 API calls
+    return u"\\\\?\\" + os.path.normpath(path)
+  else:
+    return path
+
+
+def rmtree(path):
+  """shutil.rmtree(path) wrapper with support for long paths on Windows.
+
+  Availability: Unix, Windows."""
+  if isWindows():
+    shutil.rmtree(_makelongpath(path), onerror=handle_rmtree_error)
   else:
     shutil.rmtree(path)
 
@@ -229,15 +257,18 @@
 
 
 def rename(src, dst):
+  """os.rename(src, dst) wrapper with support for long paths on Windows.
+
+  Availability: Unix, Windows."""
   if isWindows():
     # On Windows, rename fails if destination exists, see
     # https://docs.python.org/2/library/os.html#os.rename
     try:
-      os.rename(src, dst)
+      os.rename(_makelongpath(src), _makelongpath(dst))
     except OSError as e:
       if e.errno == errno.EEXIST:
-        os.remove(dst)
-        os.rename(src, dst)
+        os.remove(_makelongpath(dst))
+        os.rename(_makelongpath(src), _makelongpath(dst))
       else:
         raise
   else:
@@ -245,30 +276,98 @@
 
 
 def remove(path):
-  """Remove (delete) the file path. This is a replacement for os.remove, but
-  allows deleting read-only files on Windows.
-  """
+  """Remove (delete) the file path. This is a replacement for os.remove that
+  allows deleting read-only files on Windows, with support for long paths and
+  for deleting directory symbolic links.
+
+  Availability: Unix, Windows."""
   if isWindows():
+    longpath = _makelongpath(path)
     try:
-      os.remove(path)
+      os.remove(longpath)
     except OSError as e:
       if e.errno == errno.EACCES:
-        os.chmod(path, stat.S_IWRITE)
-        os.remove(path)
+        os.chmod(longpath, stat.S_IWRITE)
+        # Directory symbolic links must be deleted with 'rmdir'.
+        if islink(longpath) and isdir(longpath):
+          os.rmdir(longpath)
+        else:
+          os.remove(longpath)
       else:
         raise
   else:
     os.remove(path)
 
 
+def walk(top, topdown=True, onerror=None, followlinks=False):
+  """os.walk(path) wrapper with support for long paths on Windows.
+
+  Availability: Windows, Unix.
+  """
+  if isWindows():
+    return _walk_windows_impl(top, topdown, onerror, followlinks)
+  else:
+    return os.walk(top, topdown, onerror, followlinks)
+
+
+def _walk_windows_impl(top, topdown, onerror, followlinks):
+  try:
+    names = listdir(top)
+  except Exception as err:
+    if onerror is not None:
+      onerror(err)
+    return
+
+  dirs, nondirs = [], []
+  for name in names:
+    if isdir(os.path.join(top, name)):
+      dirs.append(name)
+    else:
+      nondirs.append(name)
+
+  if topdown:
+    yield top, dirs, nondirs
+  for name in dirs:
+    new_path = os.path.join(top, name)
+    if followlinks or not islink(new_path):
+      for x in _walk_windows_impl(new_path, topdown, onerror, followlinks):
+        yield x
+  if not topdown:
+    yield top, dirs, nondirs
+
+
+def listdir(path):
+  """os.listdir(path) wrapper with support for long paths on Windows.
+
+  Availability: Windows, Unix.
+  """
+  return os.listdir(_makelongpath(path))
+
+
+def rmdir(path):
+  """os.rmdir(path) wrapper with support for long paths on Windows.
+
+  Availability: Windows, Unix.
+  """
+  os.rmdir(_makelongpath(path))
+
+
+def isdir(path):
+  """os.path.isdir(path) wrapper with support for long paths on Windows.
+
+  Availability: Windows, Unix.
+  """
+  return os.path.isdir(_makelongpath(path))
+
+
 def islink(path):
-  """Test whether a path is a symbolic link.
+  """os.path.islink(path) wrapper with support for long paths on Windows.
 
   Availability: Windows, Unix.
   """
   if isWindows():
     import platform_utils_win32
-    return platform_utils_win32.islink(path)
+    return platform_utils_win32.islink(_makelongpath(path))
   else:
     return os.path.islink(path)
 
@@ -283,7 +382,7 @@
   """
   if isWindows():
     import platform_utils_win32
-    return platform_utils_win32.readlink(path)
+    return platform_utils_win32.readlink(_makelongpath(path))
   else:
     return os.readlink(path)
 
diff --git a/platform_utils_win32.py b/platform_utils_win32.py
index fe76b3d..a643121 100644
--- a/platform_utils_win32.py
+++ b/platform_utils_win32.py
@@ -41,6 +41,8 @@
 # Symbolic link creation flags
 SYMBOLIC_LINK_FLAG_FILE = 0x00
 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
+# symlink support for CreateSymbolicLink() starting with Windows 10 (1703, v10.0.14972)
+SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02
 
 GetFileAttributesW = kernel32.GetFileAttributesW
 GetFileAttributesW.restype = DWORD
@@ -147,15 +149,21 @@
   # On success, the function returns "1".
   # On error, the function returns some random value (e.g. 1280).
   # The best bet seems to use "GetLastError" and check for error/success.
-  CreateSymbolicLinkW(link_name, source, dwFlags)
+  CreateSymbolicLinkW(link_name, source, dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
   code = get_last_error()
   if code != ERROR_SUCCESS:
-    error_desc = FormatError(code).strip()
-    if code == ERROR_PRIVILEGE_NOT_HELD:
-      raise OSError(errno.EPERM, error_desc, link_name)
-    _raise_winerror(
-        code,
-        'Error creating symbolic link \"%s\"'.format(link_name))
+    # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
+    # "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
+    # retry without it."
+    CreateSymbolicLinkW(link_name, source, dwFlags)
+    code = get_last_error()
+    if code != ERROR_SUCCESS:
+      error_desc = FormatError(code).strip()
+      if code == ERROR_PRIVILEGE_NOT_HELD:
+        raise OSError(errno.EPERM, error_desc, link_name)
+      _raise_winerror(
+          code,
+          'Error creating symbolic link \"%s\"'.format(link_name))
 
 
 def islink(path):
diff --git a/project.py b/project.py
old mode 100644
new mode 100755
index a21f52f..c9679d7
--- a/project.py
+++ b/project.py
@@ -49,9 +49,7 @@
   import urlparse
   urllib = imp.new_module('urllib')
   urllib.parse = urlparse
-  # pylint:disable=W0622
   input = raw_input
-  # pylint:enable=W0622
 
 
 def _lwrite(path, content):
@@ -106,7 +104,7 @@
   if _project_hook_list is None:
     d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
     d = os.path.join(d, 'hooks')
-    _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
+    _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
   return _project_hook_list
 
 
@@ -258,7 +256,7 @@
           platform_utils.remove(dest)
         else:
           dest_dir = os.path.dirname(dest)
-          if not os.path.isdir(dest_dir):
+          if not platform_utils.isdir(dest_dir):
             os.makedirs(dest_dir)
         shutil.copy(src, dest)
         # make the file read-only
@@ -287,7 +285,7 @@
           platform_utils.remove(absDest)
         else:
           dest_dir = os.path.dirname(absDest)
-          if not os.path.isdir(dest_dir):
+          if not platform_utils.isdir(dest_dir):
             os.makedirs(dest_dir)
         platform_utils.symlink(relSrc, absDest)
       except IOError:
@@ -307,7 +305,7 @@
     else:
       # Entity doesn't exist assume there is a wild card
       absDestDir = self.abs_dest
-      if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
+      if os.path.exists(absDestDir) and not platform_utils.isdir(absDestDir):
         _error('Link error: src with wildcard, %s must be a directory',
                absDestDir)
       else:
@@ -663,6 +661,7 @@
                groups=None,
                sync_c=False,
                sync_s=False,
+               sync_tags=True,
                clone_depth=None,
                upstream=None,
                parent=None,
@@ -686,6 +685,7 @@
       groups: The `groups` attribute of manifest.xml's project element.
       sync_c: The `sync-c` attribute of manifest.xml's project element.
       sync_s: The `sync-s` attribute of manifest.xml's project element.
+      sync_tags: The `sync-tags` attribute of manifest.xml's project element.
       upstream: The `upstream` attribute of manifest.xml's project element.
       parent: The parent Project object.
       is_derived: False if the project was explicitly defined in the manifest;
@@ -718,6 +718,7 @@
     self.groups = groups
     self.sync_c = sync_c
     self.sync_s = sync_s
+    self.sync_tags = sync_tags
     self.clone_depth = clone_depth
     self.upstream = upstream
     self.parent = parent
@@ -752,7 +753,7 @@
 
   @property
   def Exists(self):
-    return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
+    return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
 
   @property
   def CurrentBranch(self):
@@ -933,7 +934,7 @@
       quiet:  If True then only print the project name.  Do not print
               the modified files, branch name, etc.
     """
-    if not os.path.isdir(self.worktree):
+    if not platform_utils.isdir(self.worktree):
       if output_redir is None:
         output_redir = sys.stdout
       print(file=output_redir)
@@ -1330,7 +1331,8 @@
       try:
         fd = open(alt)
         try:
-          alt_dir = fd.readline().rstrip()
+          # This works for both absolute and relative alternate directories.
+          alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
         finally:
           fd.close()
       except IOError:
@@ -1367,6 +1369,10 @@
       elif self.manifest.default.sync_c:
         current_branch_only = True
 
+    if not no_tags:
+      if not self.sync_tags:
+        no_tags = True
+
     if self.clone_depth:
       depth = self.clone_depth
     else:
@@ -1382,6 +1388,16 @@
                               submodules=submodules)):
       return False
 
+    mp = self.manifest.manifestProject
+    dissociate = mp.config.GetBoolean('repo.dissociate')
+    if dissociate:
+      alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
+      if os.path.exists(alternates_file):
+        cmd = ['repack', '-a', '-d']
+        if GitCommand(self, cmd, bare=True).Wait() != 0:
+          return False
+        platform_utils.remove(alternates_file)
+
     if self.worktree:
       self._InitMRef()
     else:
@@ -1978,6 +1994,7 @@
                            groups=self.groups,
                            sync_c=self.sync_c,
                            sync_s=self.sync_s,
+                           sync_tags=self.sync_tags,
                            parent=self,
                            is_derived=True)
       result.append(subproject)
@@ -2249,7 +2266,7 @@
     cmd.append(bundle_dst)
     for f in remote.fetch:
       cmd.append(str(f))
-    cmd.append('refs/tags/*:refs/tags/*')
+    cmd.append('+refs/tags/*:refs/tags/*')
 
     ok = GitCommand(self, cmd, bare=True).Wait() == 0
     if os.path.exists(bundle_dst):
@@ -2339,6 +2356,16 @@
       if self._allrefs:
         raise GitError('%s cherry-pick %s ' % (self.name, rev))
 
+  def _LsRemote(self, refs):
+    cmd = ['ls-remote', self.remote.name, refs]
+    p = GitCommand(self, cmd, capture_stdout=True)
+    if p.Wait() == 0:
+      if hasattr(p.stdout, 'decode'):
+        return p.stdout.decode('utf-8')
+      else:
+        return p.stdout
+    return None
+
   def _Revert(self, rev):
     cmd = ['revert']
     cmd.append('--no-edit')
@@ -2431,6 +2458,10 @@
             ref_dir = None
 
           if ref_dir:
+            if not os.path.isabs(ref_dir):
+              # The alternate directory is relative to the object database.
+              ref_dir = os.path.relpath(ref_dir,
+                                        os.path.join(self.objdir, 'objects'))
             _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
                     os.path.join(ref_dir, 'objects') + '\n')
 
@@ -2567,7 +2598,7 @@
 
     to_copy = []
     if copy_all:
-      to_copy = os.listdir(gitdir)
+      to_copy = platform_utils.listdir(gitdir)
 
     dotgit = platform_utils.realpath(dotgit)
     for name in set(to_copy).union(to_symlink):
@@ -2586,7 +2617,7 @@
           platform_utils.symlink(
               os.path.relpath(src, os.path.dirname(dst)), dst)
         elif copy_all and not platform_utils.islink(dst):
-          if os.path.isdir(src):
+          if platform_utils.isdir(src):
             shutil.copytree(src, dst)
           elif os.path.isfile(src):
             shutil.copy(src, dst)
@@ -2726,7 +2757,7 @@
         out = p.stdout
         if out:
           # Backslash is not anomalous
-          return out[:-1].split('\0')  # pylint: disable=W1401
+          return out[:-1].split('\0')
       return []
 
     def DiffZ(self, name, *args):
@@ -2743,7 +2774,7 @@
         out = p.process.stdout.read()
         r = {}
         if out:
-          out = iter(out[:-1].split('\0'))  # pylint: disable=W1401
+          out = iter(out[:-1].split('\0'))
           while out:
             try:
               info = next(out)
diff --git a/repo b/repo
index abd74ca..9d3e577 100755
--- a/repo
+++ b/repo
@@ -271,6 +271,10 @@
   out = kwargs.get('file', sys.stdout)
   out.write(sep.join(objects) + end)
 
+  # On Windows stderr is buffered, so flush to maintain the order of error messages.
+  if out == sys.stderr and platform.system() == "Windows":
+    out.flush()
+
 
 # Python version check
 ver = sys.version_info
@@ -313,6 +317,9 @@
 group.add_option('--reference',
                  dest='reference',
                  help='location of mirror directory', metavar='DIR')
+group.add_option('--dissociate',
+                 dest='dissociate', action='store_true',
+                 help='dissociate from reference mirrors after clone')
 group.add_option('--depth', type='int', default=None,
                  dest='depth',
                  help='create a shallow clone with given depth; see git clone')
@@ -484,16 +491,17 @@
     dst = os.path.abspath(os.path.join(repodir, S_repo))
     _Clone(url, dst, opt.quiet, not opt.no_clone_bundle)
 
-    if not os.path.isfile('%s/repo' % dst):
-      _print("warning: '%s' does not look like a git-repo repository, is "
-             "REPO_URL set correctly?" % url, file=sys.stderr)
-
     if can_verify and not opt.no_repo_verify:
       rev = _Verify(dst, branch, opt.quiet)
     else:
       rev = 'refs/remotes/origin/%s^0' % branch
 
     _Checkout(dst, branch, rev, opt.quiet)
+
+    if not os.path.isfile(os.path.join(dst, 'repo')):
+      _print("warning: '%s' does not look like a git-repo repository, is "
+             "REPO_URL set correctly?" % url, file=sys.stderr)
+
   except CloneFailure:
     if opt.quiet:
       _print('fatal: repo init failed; run without --quiet to see why',
@@ -629,7 +637,7 @@
       p = n.hosts[host]
       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:  # pylint: disable=bare-except
+  except:
     pass
   handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
   handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
@@ -655,7 +663,7 @@
     err = None
   cmd.append(src)
   cmd.append('+refs/heads/*:refs/remotes/origin/*')
-  cmd.append('refs/tags/*:refs/tags/*')
+  cmd.append('+refs/tags/*:refs/tags/*')
 
   proc = subprocess.Popen(cmd, cwd=local, stderr=err)
   if err:
diff --git a/subcmds/branches.py b/subcmds/branches.py
index 2902684..fa1dff6 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -67,8 +67,7 @@
 
 Summarizes the currently available topic branches.
 
-Branch Display
---------------
+# Branch Display
 
 The branch display output by this command is organized into four
 columns of information; for example:
diff --git a/subcmds/download.py b/subcmds/download.py
old mode 100644
new mode 100755
index e1010aa..dba70ff
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -62,6 +62,15 @@
           ps_id = int(m.group(2))
         else:
           ps_id = 1
+          refs = 'refs/changes/%2.2d/%d/' % (chg_id % 100, chg_id)
+          output = project._LsRemote(refs + '*')
+          if output:
+            regex = refs + r'(\d+)'
+            rcomp = re.compile(regex, re.I)
+            for line in output.splitlines():
+              match = rcomp.search(line)
+              if match:
+                ps_id = max(int(match.group(1)), ps_id)
         to_get.append((project, chg_id, ps_id))
       else:
         project = self.GetProjects([a])[0]
diff --git a/subcmds/forall.py b/subcmds/forall.py
index 52eb5e2..6fb16f1 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -53,8 +53,7 @@
 The -r option allows running the command only on projects matching
 regex or wildcard expression.
 
-Output Formatting
------------------
+# Output Formatting
 
 The -p option causes '%prog' to bind pipes to the command's stdin,
 stdout and stderr streams, and pipe all output into a continuous
@@ -71,8 +70,7 @@
 causes command output to be suppressed until the command produces
 at least one byte of output on stdout.
 
-Environment
------------
+# Environment
 
 pwd is the project's working directory.  If the current client is
 a mirror client, then pwd is the Git repository.
@@ -205,14 +203,12 @@
           break
       else:
         cn = None
-      # pylint: disable=W0631
       if cn and cn in _CAN_COLOR:
         class ColorCmd(Coloring):
           def __init__(self, config, cmd):
             Coloring.__init__(self, config, cmd)
         if ColorCmd(self.manifest.manifestProject.config, cn).is_on:
           cmd.insert(cmd.index(cn) + 1, '--color')
-      # pylint: enable=W0631
 
     mirror = self.manifest.IsMirror
     rc = 0
diff --git a/subcmds/gitc_delete.py b/subcmds/gitc_delete.py
index 54f62f4..4d8cd8c 100644
--- a/subcmds/gitc_delete.py
+++ b/subcmds/gitc_delete.py
@@ -21,9 +21,7 @@
 
 from pyversion import is_python3
 if not is_python3():
-  # pylint:disable=W0622
   input = raw_input
-  # pylint:enable=W0622
 
 class GitcDelete(Command, GitcClientCommand):
   common = True
diff --git a/subcmds/grep.py b/subcmds/grep.py
index dd391cf..1157355 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -33,8 +33,7 @@
   helpDescription = """
 Search for the specified patterns in all project files.
 
-Boolean Options
----------------
+# Boolean Options
 
 The following options can appear as often as necessary to express
 the pattern to locate:
@@ -47,8 +46,7 @@
 than one tree, only the first result is reported, prefixed by the
 revision name it was found under.
 
-Examples
--------
+# Examples
 
 Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
 
diff --git a/subcmds/help.py b/subcmds/help.py
index 9bb4c8c..67a225e 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -107,15 +107,13 @@
 
         self.heading('%s', heading)
         self.nl()
-
-        self.heading('%s', ''.ljust(len(heading), '-'))
         self.nl()
 
         me = 'repo %s' % cmd.NAME
         body = body.strip()
         body = body.replace('%prog', me)
 
-        asciidoc_hdr = re.compile(r'^\n?([^\n]{1,})\n([=~-]{2,})$')
+        asciidoc_hdr = re.compile(r'^\n?#+ (.+)$')
         for para in body.split("\n\n"):
           if para.startswith(' '):
             self.write('%s', para)
@@ -125,19 +123,8 @@
 
           m = asciidoc_hdr.match(para)
           if m:
-            title = m.group(1)
-            section_type = m.group(2)
-            if section_type[0] in ('=', '-'):
-              p = self.heading
-            else:
-              def _p(fmt, *args):
-                self.write('  ')
-                self.heading(fmt, *args)
-              p = _p
-
-            p('%s', title)
+            self.heading(m.group(1))
             self.nl()
-            p('%s', ''.ljust(len(title), section_type[0]))
             self.nl()
             continue
 
diff --git a/subcmds/init.py b/subcmds/init.py
index eeddca0..6e99658 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -61,14 +61,18 @@
 directory when fetching from the server. This will make the sync
 go a lot faster by reducing data traffic on the network.
 
+The --dissociate option can be used to borrow the objects from
+the directory specified with the --reference option only to reduce
+network transfer, and stop borrowing from them after a first clone
+is made by making necessary local copies of borrowed objects.
+
 The --no-clone-bundle option disables any attempt to use
 $URL/clone.bundle to bootstrap a new Git repository from a
 resumeable bundle file on a content delivery network. This
 may be necessary if there are problems with the local Python
 HTTP client or proxy configuration, but the Git binary works.
 
-Switching Manifest Branches
----------------------------
+# Switching Manifest Branches
 
 To switch to another manifest branch, `repo init -b otherbranch`
 may be used in an existing client.  However, as this only updates the
@@ -104,6 +108,9 @@
     g.add_option('--reference',
                  dest='reference',
                  help='location of mirror directory', metavar='DIR')
+    g.add_option('--dissociate',
+                 dest='dissociate', action='store_true',
+                 help='dissociate from reference mirrors after clone')
     g.add_option('--depth', type='int', default=None,
                  dest='depth',
                  help='create a shallow clone with given depth; see git clone')
@@ -175,7 +182,8 @@
         if not mirrored_manifest_git.endswith(".git"):
           mirrored_manifest_git += ".git"
         if not os.path.exists(mirrored_manifest_git):
-          mirrored_manifest_git = os.path.join(opt.reference + '/.repo/manifests.git')
+          mirrored_manifest_git = os.path.join(opt.reference,
+                                               '.repo/manifests.git')
 
       m._InitGitDir(mirror_git=mirrored_manifest_git)
 
@@ -219,6 +227,9 @@
     if opt.reference:
       m.config.SetString('repo.reference', opt.reference)
 
+    if opt.dissociate:
+      m.config.SetString('repo.dissociate', 'true')
+
     if opt.archive:
       if is_new:
         m.config.SetString('repo.archive', 'true')
@@ -401,7 +412,7 @@
     git_require(MIN_GIT_VERSION, fail=True)
 
     if opt.reference:
-      opt.reference = os.path.abspath(os.path.expanduser(opt.reference))
+      opt.reference = os.path.expanduser(opt.reference)
 
     # Check this here, else manifest will be tagged "not new" and init won't be
     # possible anymore without removing the .repo/manifests directory.
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index 5ceeb12..4a5228b 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -39,7 +39,7 @@
     helptext = self._helpDescription + '\n'
     r = os.path.dirname(__file__)
     r = os.path.dirname(r)
-    fd = open(os.path.join(r, 'docs', 'manifest-format.txt'))
+    fd = open(os.path.join(r, 'docs', 'manifest-format.md'))
     for line in fd:
       helptext += line
     fd.close()
diff --git a/subcmds/status.py b/subcmds/status.py
index 60e26ff..773f22d 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -26,6 +26,7 @@
 import os
 
 from color import Coloring
+import platform_utils
 
 class Status(PagedCommand):
   common = True
@@ -49,8 +50,7 @@
 dir/subdir/proj2 are repo projects, dir/subdir/proj3 will be shown
 if it is not known to repo.
 
-Status Display
---------------
+# Status Display
 
 The status display is organized into three columns of information,
 for example if the file 'subcmds/status.py' is modified in the
@@ -116,7 +116,7 @@
     """find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'"""
     status_header = ' --\t'
     for item in dirs:
-      if not os.path.isdir(item):
+      if not platform_utils.isdir(item):
         outstring.append(''.join([status_header, item]))
         continue
       if item in proj_dirs:
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 7ef409f..9b2387c 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -156,8 +156,7 @@
 The --prune option can be used to remove any refs that no longer
 exist on the remote.
 
-SSH Connections
----------------
+# SSH Connections
 
 If at least one project remote URL uses an SSH connection (ssh://,
 git+ssh://, or user@host:path syntax) repo will automatically
@@ -171,8 +170,7 @@
   export GIT_SSH=ssh
   %prog
 
-Compatibility
-~~~~~~~~~~~~~
+# Compatibility
 
 This feature is automatically disabled on Windows, due to the lack
 of UNIX domain socket support.
@@ -483,8 +481,8 @@
     # so rmtree works.
     try:
       platform_utils.rmtree(os.path.join(path, '.git'))
-    except OSError:
-      print('Failed to remove %s' % os.path.join(path, '.git'), file=sys.stderr)
+    except OSError as e:
+      print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
       print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
       print('       remove manually, then run sync again', file=sys.stderr)
       return -1
@@ -493,12 +491,12 @@
     # another git project
     dirs_to_remove = []
     failed = False
-    for root, dirs, files in os.walk(path):
+    for root, dirs, files in platform_utils.walk(path):
       for f in files:
         try:
           platform_utils.remove(os.path.join(root, f))
-        except OSError:
-          print('Failed to remove %s' % os.path.join(root, f), file=sys.stderr)
+        except OSError as e:
+          print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr)
           failed = True
       dirs[:] = [d for d in dirs
                  if not os.path.lexists(os.path.join(root, d, '.git'))]
@@ -508,14 +506,14 @@
       if platform_utils.islink(d):
         try:
           platform_utils.remove(d)
-        except OSError:
-          print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
+        except OSError as e:
+          print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
           failed = True
-      elif len(os.listdir(d)) == 0:
+      elif len(platform_utils.listdir(d)) == 0:
         try:
-          os.rmdir(d)
-        except OSError:
-          print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
+          platform_utils.rmdir(d)
+        except OSError as e:
+          print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
           failed = True
           continue
     if failed:
@@ -526,8 +524,8 @@
     # Try deleting parent dirs if they are empty
     project_dir = path
     while project_dir != self.manifest.topdir:
-      if len(os.listdir(project_dir)) == 0:
-        os.rmdir(project_dir)
+      if len(platform_utils.listdir(project_dir)) == 0:
+        platform_utils.rmdir(project_dir)
       else:
         break
       project_dir = os.path.dirname(project_dir)
diff --git a/subcmds/upload.py b/subcmds/upload.py
index ecdcc88..acb9d7f 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -25,12 +25,10 @@
 from project import RepoHook
 
 from pyversion import is_python3
-# pylint:disable=W0622
 if not is_python3():
   input = raw_input
 else:
   unicode = str
-# pylint:enable=W0622
 
 UNUSUAL_COMMIT_THRESHOLD = 5
 
@@ -80,8 +78,7 @@
 new users.  Users passed as --reviewers must already be registered
 with the code review system, or the upload will fail.
 
-Configuration
--------------
+# Configuration
 
 review.URL.autoupload:
 
@@ -128,10 +125,9 @@
 of the -t option to the repo command. If unset or set to "false" then
 repo will make use of only the command line option.
 
-References
-----------
+# References
 
-Gerrit Code Review:  http://code.google.com/p/gerrit/
+Gerrit Code Review:  https://www.gerritcodereview.com/
 
 """