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/
"""