Merge "Protect line endings in shell scripts"
diff --git a/.pylintrc b/.pylintrc
index 9f81ee1..9e8882e 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -53,7 +53,7 @@
enable=RP0004
# Disable the message(s) with the given id(s).
-disable=R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,C0323,C0322,C0324,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,W0311,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801
+disable=R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801
[REPORTS]
diff --git a/color.py b/color.py
index 9200a29..d856313 100644
--- a/color.py
+++ b/color.py
@@ -36,50 +36,51 @@
'blink' : 5,
'reverse': 7}
-RESET = "\033[m"
+RESET = "\033[m" # pylint: disable=W1401
+ # backslash is not anomalous
def is_color(s):
- return s in COLORS
+ return s in COLORS
def is_attr(s):
- return s in ATTRS
+ return s in ATTRS
def _Color(fg = None, bg = None, attr = None):
- fg = COLORS[fg]
- bg = COLORS[bg]
- attr = ATTRS[attr]
+ fg = COLORS[fg]
+ bg = COLORS[bg]
+ attr = ATTRS[attr]
- if attr >= 0 or fg >= 0 or bg >= 0:
- need_sep = False
- code = "\033["
+ if attr >= 0 or fg >= 0 or bg >= 0:
+ need_sep = False
+ code = "\033[" #pylint: disable=W1401
- if attr >= 0:
- code += chr(ord('0') + attr)
- need_sep = True
+ if attr >= 0:
+ code += chr(ord('0') + attr)
+ need_sep = True
- if fg >= 0:
- if need_sep:
- code += ';'
- need_sep = True
+ if fg >= 0:
+ if need_sep:
+ code += ';'
+ need_sep = True
- if fg < 8:
- code += '3%c' % (ord('0') + fg)
- else:
- code += '38;5;%d' % fg
+ if fg < 8:
+ code += '3%c' % (ord('0') + fg)
+ else:
+ code += '38;5;%d' % fg
- if bg >= 0:
- if need_sep:
- code += ';'
- need_sep = True
+ if bg >= 0:
+ if need_sep:
+ code += ';'
+ need_sep = True
- if bg < 8:
- code += '4%c' % (ord('0') + bg)
- else:
- code += '48;5;%d' % bg
- code += 'm'
- else:
- code = ''
- return code
+ if bg < 8:
+ code += '4%c' % (ord('0') + bg)
+ else:
+ code += '48;5;%d' % bg
+ code += 'm'
+ else:
+ code = ''
+ return code
class Coloring(object):
diff --git a/command.py b/command.py
index 0c3b360..96d7848 100644
--- a/command.py
+++ b/command.py
@@ -22,6 +22,7 @@
from error import NoSuchProjectError
from error import InvalidProjectGroupsError
+
class Command(object):
"""Base class for any command line action in repo.
"""
@@ -33,6 +34,27 @@
def WantPager(self, opt):
return False
+ def ReadEnvironmentOptions(self, opts):
+ """ Set options from environment variables. """
+
+ env_options = self._RegisteredEnvironmentOptions()
+
+ for env_key, opt_key in env_options.items():
+ # Get the user-set option value if any
+ opt_value = getattr(opts, opt_key)
+
+ # If the value is set, it means the user has passed it as a command
+ # line option, and we should use that. Otherwise we can try to set it
+ # with the value from the corresponding environment variable.
+ if opt_value is not None:
+ continue
+
+ env_value = os.environ.get(env_key)
+ if env_value is not None:
+ setattr(opts, opt_key, env_value)
+
+ return opts
+
@property
def OptionParser(self):
if self._optparse is None:
@@ -49,6 +71,24 @@
"""Initialize the option parser.
"""
+ def _RegisteredEnvironmentOptions(self):
+ """Get options that can be set from environment variables.
+
+ Return a dictionary mapping environment variable name
+ to option key name that it can override.
+
+ Example: {'REPO_MY_OPTION': 'my_option'}
+
+ Will allow the option with key value 'my_option' to be set
+ from the value in the environment variable named 'REPO_MY_OPTION'.
+
+ Note: This does not work properly for options that are explicitly
+ set to None by the user, or options that are defined with a
+ default value other than None.
+
+ """
+ return {}
+
def Usage(self):
"""Display usage and terminate.
"""
@@ -60,7 +100,33 @@
"""
raise NotImplementedError
- def GetProjects(self, args, missing_ok=False):
+ def _ResetPathToProjectMap(self, projects):
+ self._by_path = dict((p.worktree, p) for p in projects)
+
+ def _UpdatePathToProjectMap(self, project):
+ self._by_path[project.worktree] = project
+
+ def _GetProjectByPath(self, path):
+ project = None
+ if os.path.exists(path):
+ oldpath = None
+ while path \
+ and path != oldpath \
+ and path != self.manifest.topdir:
+ try:
+ project = self._by_path[path]
+ break
+ except KeyError:
+ oldpath = path
+ path = os.path.dirname(path)
+ else:
+ try:
+ project = self._by_path[path]
+ except KeyError:
+ pass
+ return project
+
+ def GetProjects(self, args, missing_ok=False, submodules_ok=False):
"""A list of projects that match the arguments.
"""
all_projects = self.manifest.projects
@@ -71,43 +137,40 @@
groups = mp.config.GetString('manifest.groups')
if not groups:
groups = 'all,-notdefault,platform-' + platform.system().lower()
- groups = [x for x in re.split('[,\s]+', groups) if x]
+ groups = [x for x in re.split(r'[,\s]+', groups) if x]
if not args:
- for project in all_projects.values():
+ all_projects_list = all_projects.values()
+ derived_projects = {}
+ for project in all_projects_list:
+ if submodules_ok or project.sync_s:
+ derived_projects.update((p.name, p)
+ for p in project.GetDerivedSubprojects())
+ all_projects_list.extend(derived_projects.values())
+ for project in all_projects_list:
if ((missing_ok or project.Exists) and
project.MatchesGroups(groups)):
result.append(project)
else:
- by_path = None
+ self._ResetPathToProjectMap(all_projects.values())
for arg in args:
project = all_projects.get(arg)
if not project:
path = os.path.abspath(arg).replace('\\', '/')
+ project = self._GetProjectByPath(path)
- if not by_path:
- by_path = dict()
- for p in all_projects.values():
- by_path[p.worktree] = p
-
- if os.path.exists(path):
- oldpath = None
- while path \
- and path != oldpath \
- and path != self.manifest.topdir:
- try:
- project = by_path[path]
- break
- except KeyError:
- oldpath = path
- path = os.path.dirname(path)
- else:
- try:
- project = by_path[path]
- except KeyError:
- pass
+ # If it's not a derived project, update path->project mapping and
+ # search again, as arg might actually point to a derived subproject.
+ if (project and not project.Derived and
+ (submodules_ok or project.sync_s)):
+ search_again = False
+ for subproject in project.GetDerivedSubprojects():
+ self._UpdatePathToProjectMap(subproject)
+ search_again = True
+ if search_again:
+ project = self._GetProjectByPath(path) or project
if not project:
raise NoSuchProjectError(arg)
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index f499868..0bf09f6 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -41,17 +41,21 @@
<!ATTLIST default revision CDATA #IMPLIED>
<!ATTLIST default sync-j CDATA #IMPLIED>
<!ATTLIST default sync-c CDATA #IMPLIED>
+ <!ATTLIST default sync-s CDATA #IMPLIED>
<!ELEMENT manifest-server (EMPTY)>
<!ATTLIST url CDATA #REQUIRED>
- <!ELEMENT project (annotation?)>
+ <!ELEMENT project (annotation?,
+ project*)>
<!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED>
<!ATTLIST project remote IDREF #IMPLIED>
<!ATTLIST project revision CDATA #IMPLIED>
<!ATTLIST project groups CDATA #IMPLIED>
<!ATTLIST project sync-c CDATA #IMPLIED>
+ <!ATTLIST project sync-s CDATA #IMPLIED>
+ <!ATTLIST project upstream CDATA #IMPLIED>
<!ELEMENT annotation (EMPTY)>
<!ATTLIST annotation name CDATA #REQUIRED>
@@ -119,6 +123,15 @@
`refs/heads/master`). Project elements lacking their own
revision attribute will use this revision.
+Attribute `sync_j`: Number of parallel jobs to use when synching.
+
+Attribute `sync_c`: Set to true to only sync the given Git
+branch (specified in the `revision` attribute) rather than the
+whole ref space. Project elements lacking a sync_c element of
+their own will use this value.
+
+Attribute `sync_s`: Set to true to also sync sub-projects.
+
Element manifest-server
-----------------------
@@ -152,7 +165,10 @@
One or more project elements may be specified. Each element
describes a single Git repository to be cloned into the repo
-client workspace.
+client workspace. You may specify Git-submodules by creating a
+nested project. Git-submodules will be automatically
+recognized and inherit their parent's attributes, but those
+may be overridden by an explicitly specified project element.
Attribute `name`: A unique name for this project. The project's
name is appended onto its remote's fetch URL to generate the actual
@@ -163,7 +179,8 @@
where ${remote_fetch} is the remote's fetch attribute and
${project_name} is the project's name attribute. The suffix ".git"
is always appended as repo assumes the upstream is a forest of
-bare Git repositories.
+bare Git repositories. If the project has a parent element, its
+name will be prefixed by the parent's.
The project name must match the name Gerrit knows, if Gerrit is
being used for code reviews.
@@ -171,6 +188,8 @@
Attribute `path`: An optional path relative to the top directory
of the repo client where the Git working directory for this project
should be placed. If not supplied the project name is used.
+If the project has a parent element, its path will be prefixed
+by the parent's.
Attribute `remote`: Name of a previously defined remote element.
If not supplied the remote given by the default element is used.
@@ -190,6 +209,18 @@
definition is implicitly in the following manifest groups:
default, name:monkeys, and path:barrel-of. If you place a project in the
group "notdefault", it will not be automatically downloaded by repo.
+If the project has a parent element, the `name` and `path` here
+are the prefixed ones.
+
+Attribute `sync_c`: Set to true to only sync the given Git
+branch (specified in the `revision` attribute) rather than the
+whole ref space.
+
+Attribute `sync_s`: Set to true to also sync sub-projects.
+
+Attribute `upstream`: Name of the Git branch 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.
Element annotation
------------------
@@ -209,7 +240,7 @@
allowing a subsequent project element in the same manifest file to
replace the project with a different source.
-This element is mostly useful in the local_manifest.xml, where
+This element is mostly useful in a local manifest file, where
the user can remove a project, and possibly replace it with their
own definition.
@@ -218,21 +249,25 @@
This element provides the capability of including another manifest
file into the originating manifest. Normal rules apply for the
-target manifest to include- it must be a usable manifest on it's own.
+target manifest to include - it must be a usable manifest on its own.
-Attribute `name`; the manifest to include, specified relative to
-the manifest repositories root.
+Attribute `name`: the manifest to include, specified relative to
+the manifest repository's root.
-Local Manifest
-==============
+Local Manifests
+===============
-Additional remotes and projects may be added through a local
-manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`.
+Additional remotes and projects may be added through local manifest
+files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
For example:
- $ cat .repo/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"
@@ -241,6 +276,17 @@
name="platform/manifest" />
</manifest>
-Users may add projects to the local manifest prior to a `repo sync`
+Users may add projects to the local manifest(s) prior to a `repo sync`
invocation, instructing repo to automatically download and manage
these extra projects.
+
+Manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml` will
+be loaded in alphabetical order.
+
+Additional remotes and projects may also be added through a local
+manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`. This method
+is deprecated in favor of using multiple manifest files as mentioned
+above.
+
+If `$TOP_DIR/.repo/local_manifest.xml` exists, it will be loaded before
+any manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
diff --git a/editor.py b/editor.py
index 489c6cd..883a1a8 100644
--- a/editor.py
+++ b/editor.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import os
import re
import sys
@@ -53,10 +54,10 @@
return e
if os.getenv('TERM') == 'dumb':
- print >>sys.stderr,\
+ print(
"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
Tried to fall back to vi but terminal is dumb. Please configure at
-least one of these before using this command."""
+least one of these before using this command.""", file=sys.stderr)
sys.exit(1)
return 'vi'
@@ -67,7 +68,7 @@
Args:
data : the text to edit
-
+
Returns:
new value of edited text; None if editing did not succeed
"""
diff --git a/error.py b/error.py
index 2148248..7e52b01 100644
--- a/error.py
+++ b/error.py
@@ -21,6 +21,10 @@
"""The revision value in a project is incorrect.
"""
+class NoManifestException(Exception):
+ """The required manifest does not exist.
+ """
+
class EditorError(Exception):
"""Unspecified error from the user's text editor.
"""
diff --git a/git_command.py b/git_command.py
index a40e6c0..d347dd6 100644
--- a/git_command.py
+++ b/git_command.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import os
import sys
import subprocess
@@ -88,11 +89,11 @@
ver_str = git.version()
if ver_str.startswith('git version '):
_git_version = tuple(
- map(lambda x: int(x),
+ map(int,
ver_str[len('git version '):].strip().split('-')[0].split('.')[0:3]
))
else:
- print >>sys.stderr, 'fatal: "%s" unsupported' % ver_str
+ print('fatal: "%s" unsupported' % ver_str, file=sys.stderr)
sys.exit(1)
return _git_version
@@ -110,8 +111,8 @@
if min_version <= git_version:
return True
if fail:
- need = '.'.join(map(lambda x: str(x), min_version))
- print >>sys.stderr, 'fatal: git %s or later required' % need
+ need = '.'.join(map(str, min_version))
+ print('fatal: git %s or later required' % need, file=sys.stderr)
sys.exit(1)
return False
@@ -132,15 +133,15 @@
gitdir = None):
env = os.environ.copy()
- for e in [REPO_TRACE,
+ for key in [REPO_TRACE,
GIT_DIR,
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_OBJECT_DIRECTORY',
'GIT_WORK_TREE',
'GIT_GRAFT_FILE',
'GIT_INDEX_FILE']:
- if e in env:
- del env[e]
+ if key in env:
+ del env[key]
if disable_editor:
_setenv(env, 'GIT_EDITOR', ':')
diff --git a/git_config.py b/git_config.py
index ae28855..56cc6a2 100644
--- a/git_config.py
+++ b/git_config.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import cPickle
import os
import re
@@ -23,7 +24,18 @@
except ImportError:
import dummy_threading as _threading
import time
-import urllib2
+try:
+ import urllib2
+except ImportError:
+ # For python3
+ import urllib.request
+ import urllib.error
+else:
+ # For python2
+ import imp
+ urllib = imp.new_module('urllib')
+ urllib.request = urllib2
+ urllib.error = urllib2
from signal import SIGTERM
from error import GitError, UploadError
@@ -35,7 +47,7 @@
R_HEADS = 'refs/heads/'
R_TAGS = 'refs/tags/'
-ID_RE = re.compile('^[0-9a-f]{40}$')
+ID_RE = re.compile(r'^[0-9a-f]{40}$')
REVIEW_CACHE = dict()
@@ -157,7 +169,7 @@
elif old != value:
self._cache[key] = list(value)
self._do('--replace-all', name, value[0])
- for i in xrange(1, len(value)):
+ for i in range(1, len(value)):
self._do('--add', name, value[i])
elif len(old) != 1 or old[0] != value:
@@ -288,12 +300,13 @@
d = self._do('--null', '--list')
if d is None:
return c
- for line in d.rstrip('\0').split('\0'):
+ for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
+ # Backslash is not anomalous
if '\n' in line:
- key, val = line.split('\n', 1)
+ key, val = line.split('\n', 1)
else:
- key = line
- val = None
+ key = line
+ val = None
if key in c:
c[key].append(val)
@@ -418,7 +431,7 @@
'-o','ControlPath %s' % ssh_sock(),
host]
if port is not None:
- command_base[1:1] = ['-p',str(port)]
+ command_base[1:1] = ['-p', str(port)]
# Since the key wasn't in _master_keys, we think that master isn't running.
# ...but before actually starting a master, we'll double-check. This can
@@ -451,9 +464,8 @@
p = subprocess.Popen(command)
except Exception as e:
_ssh_master = False
- print >>sys.stderr, \
- '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
- % (host,port, str(e))
+ print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
+ % (host,port, str(e)), file=sys.stderr)
return False
_master_processes.append(p)
@@ -525,7 +537,7 @@
self.url = self._Get('url')
self.review = self._Get('review')
self.projectname = self._Get('projectname')
- self.fetch = map(lambda x: RefSpec.FromString(x),
+ self.fetch = map(RefSpec.FromString,
self._Get('fetch', all_keys=True))
self._review_url = None
@@ -579,7 +591,7 @@
else:
try:
info_url = u + 'ssh_info'
- info = urllib2.urlopen(info_url).read()
+ info = urllib.request.urlopen(info_url).read()
if '<' in info:
# Assume the server gave us some sort of HTML
# response back, like maybe a login page.
@@ -592,9 +604,9 @@
else:
host, port = info.split()
self._review_url = self._SshReviewUrl(userEmail, host, port)
- except urllib2.HTTPError as e:
+ except urllib.error.HTTPError as e:
raise UploadError('%s: %s' % (self.review, str(e)))
- except urllib2.URLError as e:
+ except urllib.error.URLError as e:
raise UploadError('%s: %s' % (self.review, str(e)))
REVIEW_CACHE[u] = self._review_url
@@ -645,7 +657,7 @@
self._Set('url', self.url)
self._Set('review', self.review)
self._Set('projectname', self.projectname)
- self._Set('fetch', map(lambda x: str(x), self.fetch))
+ self._Set('fetch', map(str, self.fetch))
def _Set(self, key, value):
key = 'remote.%s.%s' % (self.name, key)
diff --git a/git_refs.py b/git_refs.py
index 18c9230..cfeffba 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -138,14 +138,14 @@
def _ReadLoose1(self, path, name):
try:
fd = open(path, 'rb')
- except:
+ except IOError:
return
try:
try:
mtime = os.path.getmtime(path)
ref_id = fd.readline()
- except:
+ except (IOError, OSError):
return
finally:
fd.close()
diff --git a/hooks/commit-msg b/hooks/commit-msg
index 172a178..b37dfaa 100755
--- a/hooks/commit-msg
+++ b/hooks/commit-msg
@@ -1,5 +1,5 @@
#!/bin/sh
-# From Gerrit Code Review 2.5-rc0
+# From Gerrit Code Review 2.5.2
#
# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
#
@@ -18,6 +18,8 @@
# limitations under the License.
#
+unset GREP_OPTIONS
+
CHANGE_ID_AFTER="Bug|Issue"
MSG="$1"
diff --git a/main.py b/main.py
index ba40d56..9cc2639 100755
--- a/main.py
+++ b/main.py
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/env python
#
# Copyright (C) 2008 The Android Open Source Project
#
@@ -14,23 +14,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-magic='--calling-python-from-/bin/sh--'
-"""exec" python -E "$0" "$@" """#$magic"
-if __name__ == '__main__':
- import sys
- if sys.argv[-1] == '#%s' % magic:
- del sys.argv[-1]
-del magic
-
+from __future__ import print_function
import getpass
import imp
import netrc
import optparse
import os
-import re
import sys
import time
-import urllib2
+try:
+ import urllib2
+except ImportError:
+ # For python3
+ import urllib.request
+else:
+ # For python2
+ urllib = imp.new_module('urllib')
+ urllib.request = urllib2
from trace import SetTrace
from git_command import git, GitCommand
@@ -41,6 +41,8 @@
from editor import Editor
from error import DownloadError
from error import ManifestInvalidRevisionError
+from error import ManifestParseError
+from error import NoManifestException
from error import NoSuchProjectError
from error import RepoChangedException
from manifest_xml import XmlManifest
@@ -79,7 +81,7 @@
name = None
glob = []
- for i in xrange(0, len(argv)):
+ for i in range(len(argv)):
if not argv[i].startswith('-'):
name = argv[i]
if i > 0:
@@ -98,15 +100,14 @@
if name == 'help':
name = 'version'
else:
- print >>sys.stderr, 'fatal: invalid usage of --version'
+ print('fatal: invalid usage of --version', file=sys.stderr)
return 1
try:
cmd = self.commands[name]
except KeyError:
- print >>sys.stderr,\
- "repo: '%s' is not a repo command. See 'repo help'."\
- % name
+ print("repo: '%s' is not a repo command. See 'repo help'." % name,
+ file=sys.stderr)
return 1
cmd.repodir = self.repodir
@@ -114,12 +115,12 @@
Editor.globalConfig = cmd.manifest.globalConfig
if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
- print >>sys.stderr, \
- "fatal: '%s' requires a working directory"\
- % name
+ print("fatal: '%s' requires a working directory" % name,
+ file=sys.stderr)
return 1
copts, cargs = cmd.OptionParser.parse_args(argv)
+ copts = cmd.ReadEnvironmentOptions(copts)
if not gopts.no_pager and not isinstance(cmd, InteractiveCommand):
config = cmd.manifest.globalConfig
@@ -132,33 +133,35 @@
if use_pager:
RunPager(config)
+ start = time.time()
try:
- start = time.time()
- try:
- result = cmd.Execute(copts, cargs)
- finally:
- elapsed = time.time() - start
- hours, remainder = divmod(elapsed, 3600)
- minutes, seconds = divmod(remainder, 60)
- if gopts.time:
- if hours == 0:
- print >>sys.stderr, 'real\t%dm%.3fs' \
- % (minutes, seconds)
- else:
- print >>sys.stderr, 'real\t%dh%dm%.3fs' \
- % (hours, minutes, seconds)
+ result = cmd.Execute(copts, cargs)
except DownloadError as e:
- print >>sys.stderr, 'error: %s' % str(e)
- return 1
+ print('error: %s' % str(e), file=sys.stderr)
+ result = 1
except ManifestInvalidRevisionError as e:
- print >>sys.stderr, 'error: %s' % str(e)
- return 1
+ print('error: %s' % str(e), file=sys.stderr)
+ result = 1
+ except NoManifestException as e:
+ print('error: manifest required for this command -- please run init',
+ file=sys.stderr)
+ result = 1
except NoSuchProjectError as e:
if e.name:
- print >>sys.stderr, 'error: project %s not found' % e.name
+ print('error: project %s not found' % e.name, file=sys.stderr)
else:
- print >>sys.stderr, 'error: no project in current directory'
- return 1
+ print('error: no project in current directory', file=sys.stderr)
+ result = 1
+ finally:
+ elapsed = time.time() - start
+ hours, remainder = divmod(elapsed, 3600)
+ minutes, seconds = divmod(remainder, 60)
+ if gopts.time:
+ if hours == 0:
+ print('real\t%dm%.3fs' % (minutes, seconds), file=sys.stderr)
+ else:
+ print('real\t%dh%dm%.3fs' % (hours, minutes, seconds),
+ file=sys.stderr)
return result
@@ -183,36 +186,35 @@
repo_path = '~/bin/repo'
if not ver:
- print >>sys.stderr, 'no --wrapper-version argument'
+ print('no --wrapper-version argument', file=sys.stderr)
sys.exit(1)
exp = _CurrentWrapperVersion()
- ver = tuple(map(lambda x: int(x), ver.split('.')))
+ ver = tuple(map(int, ver.split('.')))
if len(ver) == 1:
ver = (0, ver[0])
+ exp_str = '.'.join(map(str, exp))
if exp[0] > ver[0] or ver < (0, 4):
- exp_str = '.'.join(map(lambda x: str(x), exp))
- print >>sys.stderr, """
+ print("""
!!! A new repo command (%5s) is available. !!!
!!! You must upgrade before you can continue: !!!
cp %s %s
-""" % (exp_str, _MyWrapperPath(), repo_path)
+""" % (exp_str, _MyWrapperPath(), repo_path), file=sys.stderr)
sys.exit(1)
if exp > ver:
- exp_str = '.'.join(map(lambda x: str(x), exp))
- print >>sys.stderr, """
+ print("""
... A new repo command (%5s) is available.
... You should upgrade soon:
cp %s %s
-""" % (exp_str, _MyWrapperPath(), repo_path)
+""" % (exp_str, _MyWrapperPath(), repo_path), file=sys.stderr)
def _CheckRepoDir(repo_dir):
if not repo_dir:
- print >>sys.stderr, 'no --repo-dir argument'
+ print('no --repo-dir argument', file=sys.stderr)
sys.exit(1)
def _PruneOptions(argv, opt):
@@ -264,11 +266,11 @@
_user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
repo_version,
os_name,
- '.'.join(map(lambda d: str(d), git.version_tuple())),
+ '.'.join(map(str, git.version_tuple())),
py_version[0], py_version[1], py_version[2])
return _user_agent
-class _UserAgentHandler(urllib2.BaseHandler):
+class _UserAgentHandler(urllib.request.BaseHandler):
def http_request(self, req):
req.add_header('User-Agent', _UserAgent())
return req
@@ -278,22 +280,22 @@
return req
def _AddPasswordFromUserInput(handler, msg, req):
- # If repo could not find auth info from netrc, try to get it from user input
- url = req.get_full_url()
- user, password = handler.passwd.find_user_password(None, url)
- if user is None:
- print msg
- try:
- user = raw_input('User: ')
- password = getpass.getpass()
- except KeyboardInterrupt:
- return
- handler.passwd.add_password(None, url, user, password)
+ # If repo could not find auth info from netrc, try to get it from user input
+ url = req.get_full_url()
+ user, password = handler.passwd.find_user_password(None, url)
+ if user is None:
+ print(msg)
+ try:
+ user = raw_input('User: ')
+ password = getpass.getpass()
+ except KeyboardInterrupt:
+ return
+ handler.passwd.add_password(None, url, user, password)
-class _BasicAuthHandler(urllib2.HTTPBasicAuthHandler):
+class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req)
- return urllib2.HTTPBasicAuthHandler.http_error_401(
+ return urllib.request.HTTPBasicAuthHandler.http_error_401(
self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, authreq, host, req, headers):
@@ -303,7 +305,7 @@
val = val.replace('\n', '')
old_add_header(name, val)
req.add_header = _add_header
- return urllib2.AbstractBasicAuthHandler.http_error_auth_reqed(
+ return urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed(
self, authreq, host, req, headers)
except:
reset = getattr(self, 'reset_retry_count', None)
@@ -313,10 +315,10 @@
self.retried = 0
raise
-class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
+class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req)
- return urllib2.HTTPDigestAuthHandler.http_error_401(
+ return urllib.request.HTTPDigestAuthHandler.http_error_401(
self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, auth_header, host, req, headers):
@@ -326,7 +328,7 @@
val = val.replace('\n', '')
old_add_header(name, val)
req.add_header = _add_header
- return urllib2.AbstractDigestAuthHandler.http_error_auth_reqed(
+ return urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed(
self, auth_header, host, req, headers)
except:
reset = getattr(self, 'reset_retry_count', None)
@@ -339,7 +341,7 @@
def init_http():
handlers = [_UserAgentHandler()]
- mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+ mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
try:
n = netrc.netrc()
for host in n.hosts:
@@ -355,11 +357,11 @@
if 'http_proxy' in os.environ:
url = os.environ['http_proxy']
- handlers.append(urllib2.ProxyHandler({'http': url, 'https': url}))
+ handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
if 'REPO_CURL_VERBOSE' in os.environ:
- handlers.append(urllib2.HTTPHandler(debuglevel=1))
- handlers.append(urllib2.HTTPSHandler(debuglevel=1))
- urllib2.install_opener(urllib2.build_opener(*handlers))
+ handlers.append(urllib.request.HTTPHandler(debuglevel=1))
+ handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
+ urllib.request.install_opener(urllib.request.build_opener(*handlers))
def _Main(argv):
result = 0
@@ -389,6 +391,10 @@
finally:
close_ssh()
except KeyboardInterrupt:
+ print('aborted by user', file=sys.stderr)
+ result = 1
+ except ManifestParseError as mpe:
+ print('fatal: %s' % mpe, file=sys.stderr)
result = 1
except RepoChangedException as rce:
# If repo changed, re-exec ourselves.
@@ -398,8 +404,8 @@
try:
os.execv(__file__, argv)
except OSError as e:
- print >>sys.stderr, 'fatal: cannot restart repo after upgrade'
- print >>sys.stderr, 'fatal: %s' % e
+ print('fatal: cannot restart repo after upgrade', file=sys.stderr)
+ print('fatal: %s' % e, file=sys.stderr)
result = 128
sys.exit(result)
diff --git a/manifest_xml.py b/manifest_xml.py
index dd163be..53f3353 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import itertools
import os
import re
@@ -27,6 +28,7 @@
MANIFEST_FILE_NAME = 'manifest.xml'
LOCAL_MANIFEST_NAME = 'local_manifest.xml'
+LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
urlparse.uses_relative.extend(['ssh', 'git'])
urlparse.uses_netloc.extend(['ssh', 'git'])
@@ -38,6 +40,7 @@
remote = None
sync_j = 1
sync_c = False
+ sync_s = False
class _XmlRemote(object):
def __init__(self,
@@ -53,15 +56,28 @@
self.reviewUrl = review
self.resolvedFetchUrl = self._resolveFetchUrl()
+ def __eq__(self, other):
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ return self.__dict__ != other.__dict__
+
def _resolveFetchUrl(self):
url = self.fetchUrl.rstrip('/')
manifestUrl = self.manifestUrl.rstrip('/')
+ p = manifestUrl.startswith('persistent-http')
+ if p:
+ manifestUrl = manifestUrl[len('persistent-'):]
+
# urljoin will get confused if there is no scheme in the base url
# ie, if manifestUrl is of the form <hostname:port>
if manifestUrl.find(':') != manifestUrl.find('/') - 1:
- manifestUrl = 'gopher://' + manifestUrl
+ manifestUrl = 'gopher://' + manifestUrl
url = urlparse.urljoin(manifestUrl, url)
- return re.sub(r'^gopher://', '', url)
+ url = re.sub(r'^gopher://', '', url)
+ if p:
+ url = 'persistent-' + url
+ return url
def ToRemoteSpec(self, projectName):
url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
@@ -110,11 +126,11 @@
self.Override(name)
try:
- if os.path.exists(self.manifestFile):
+ if os.path.lexists(self.manifestFile):
os.remove(self.manifestFile)
os.symlink('manifests/%s' % name, self.manifestFile)
- except OSError:
- raise ManifestParseError('cannot link manifest %s' % name)
+ except OSError as e:
+ raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e)))
def _RemoteToXml(self, r, doc, root):
e = doc.createElement('remote')
@@ -130,9 +146,8 @@
mp = self.manifestProject
groups = mp.config.GetString('manifest.groups')
- if not groups:
- groups = 'all'
- groups = [x for x in re.split(r'[,\s]+', groups) if x]
+ if groups:
+ groups = [x for x in re.split(r'[,\s]+', groups) if x]
doc = xml.dom.minidom.Document()
root = doc.createElement('manifest')
@@ -170,6 +185,9 @@
if d.sync_c:
have_default = True
e.setAttribute('sync-c', 'true')
+ if d.sync_s:
+ have_default = True
+ e.setAttribute('sync-s', 'true')
if have_default:
root.appendChild(e)
root.appendChild(doc.createTextNode(''))
@@ -180,20 +198,25 @@
root.appendChild(e)
root.appendChild(doc.createTextNode(''))
- sort_projects = list(self.projects.keys())
- sort_projects.sort()
+ def output_projects(parent, parent_node, projects):
+ for p in projects:
+ output_project(parent, parent_node, self.projects[p])
- for p in sort_projects:
- p = self.projects[p]
-
+ def output_project(parent, parent_node, p):
if not p.MatchesGroups(groups):
- continue
+ return
+
+ name = p.name
+ relpath = p.relpath
+ if parent:
+ name = self._UnjoinName(parent.name, name)
+ relpath = self._UnjoinRelpath(parent.relpath, relpath)
e = doc.createElement('project')
- root.appendChild(e)
- e.setAttribute('name', p.name)
- if p.relpath != p.name:
- e.setAttribute('path', p.relpath)
+ parent_node.appendChild(e)
+ e.setAttribute('name', name)
+ if relpath != name:
+ e.setAttribute('path', relpath)
if not d.remote or p.remote.name != d.remote.name:
e.setAttribute('remote', p.remote.name)
if peg_rev:
@@ -231,6 +254,19 @@
if p.sync_c:
e.setAttribute('sync-c', 'true')
+ if p.sync_s:
+ e.setAttribute('sync-s', 'true')
+
+ if p.subprojects:
+ sort_projects = [subp.name for subp in p.subprojects]
+ sort_projects.sort()
+ output_projects(p, e, sort_projects)
+
+ sort_projects = [key for key in self.projects.keys()
+ if not self.projects[key].parent]
+ sort_projects.sort()
+ output_projects(None, root, sort_projects)
+
if self._repo_hooks_project:
root.appendChild(doc.createTextNode(''))
e = doc.createElement('repo-hooks')
@@ -299,9 +335,30 @@
local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
if os.path.exists(local):
+ print('warning: %s is deprecated; put local manifests in %s instead'
+ % (LOCAL_MANIFEST_NAME, LOCAL_MANIFESTS_DIR_NAME),
+ file=sys.stderr)
nodes.append(self._ParseManifestXml(local, self.repodir))
- self._ParseManifest(nodes)
+ local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
+ try:
+ for local_file in sorted(os.listdir(local_dir)):
+ if local_file.endswith('.xml'):
+ try:
+ local = os.path.join(local_dir, local_file)
+ nodes.append(self._ParseManifestXml(local, self.repodir))
+ except ManifestParseError as e:
+ print('%s' % str(e), file=sys.stderr)
+ except OSError:
+ pass
+
+ try:
+ self._ParseManifest(nodes)
+ except ManifestParseError as e:
+ # There was a problem parsing, unload ourselves in case they catch
+ # this error and try again later, we will show the correct error
+ self._Unload()
+ raise e
if self.IsMirror:
self._AddMetaProjectMirror(self.repoProject)
@@ -310,7 +367,11 @@
self._loaded = True
def _ParseManifestXml(self, path, include_root):
- root = xml.dom.minidom.parse(path)
+ try:
+ root = xml.dom.minidom.parse(path)
+ except (OSError, xml.parsers.expat.ExpatError) as e:
+ raise ManifestParseError("error parsing manifest %s: %s" % (path, e))
+
if not root or not root.childNodes:
raise ManifestParseError("no root node in %s" % (path,))
@@ -323,35 +384,38 @@
nodes = []
for node in manifest.childNodes: # pylint:disable=W0631
# We only get here if manifest is initialised
- if node.nodeName == 'include':
- name = self._reqatt(node, 'name')
- fp = os.path.join(include_root, name)
- if not os.path.isfile(fp):
- raise ManifestParseError, \
- "include %s doesn't exist or isn't a file" % \
- (name,)
- try:
- nodes.extend(self._ParseManifestXml(fp, include_root))
- # should isolate this to the exact exception, but that's
- # tricky. actual parsing implementation may vary.
- except (KeyboardInterrupt, RuntimeError, SystemExit):
- raise
- except Exception as e:
- raise ManifestParseError(
- "failed parsing included manifest %s: %s", (name, e))
- else:
- nodes.append(node)
+ if node.nodeName == 'include':
+ name = self._reqatt(node, 'name')
+ fp = os.path.join(include_root, name)
+ if not os.path.isfile(fp):
+ raise ManifestParseError, \
+ "include %s doesn't exist or isn't a file" % \
+ (name,)
+ try:
+ nodes.extend(self._ParseManifestXml(fp, include_root))
+ # should isolate this to the exact exception, but that's
+ # tricky. actual parsing implementation may vary.
+ except (KeyboardInterrupt, RuntimeError, SystemExit):
+ raise
+ except Exception as e:
+ raise ManifestParseError(
+ "failed parsing included manifest %s: %s", (name, e))
+ else:
+ nodes.append(node)
return nodes
def _ParseManifest(self, node_list):
for node in itertools.chain(*node_list):
if node.nodeName == 'remote':
remote = self._ParseRemote(node)
- if self._remotes.get(remote.name):
- raise ManifestParseError(
- 'duplicate remote %s in %s' %
- (remote.name, self.manifestFile))
- self._remotes[remote.name] = remote
+ if remote:
+ if remote.name in self._remotes:
+ if remote != self._remotes[remote.name]:
+ raise ManifestParseError(
+ 'remote %s already exists with different attributes' %
+ (remote.name))
+ else:
+ self._remotes[remote.name] = remote
for node in itertools.chain(*node_list):
if node.nodeName == 'default':
@@ -375,19 +439,24 @@
if node.nodeName == 'manifest-server':
url = self._reqatt(node, 'url')
if self._manifest_server is not None:
- raise ManifestParseError(
- 'duplicate manifest-server in %s' %
- (self.manifestFile))
+ raise ManifestParseError(
+ 'duplicate manifest-server in %s' %
+ (self.manifestFile))
self._manifest_server = url
+ def recursively_add_projects(project):
+ if self._projects.get(project.name):
+ raise ManifestParseError(
+ 'duplicate project %s in %s' %
+ (project.name, self.manifestFile))
+ self._projects[project.name] = project
+ for subproject in project.subprojects:
+ recursively_add_projects(subproject)
+
for node in itertools.chain(*node_list):
if node.nodeName == 'project':
project = self._ParseProject(node)
- if self._projects.get(project.name):
- raise ManifestParseError(
- 'duplicate project %s in %s' %
- (project.name, self.manifestFile))
- self._projects[project.name] = project
+ recursively_add_projects(project)
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')
@@ -414,9 +483,8 @@
try:
del self._projects[name]
except KeyError:
- raise ManifestParseError(
- 'project %s not found' %
- (name))
+ raise ManifestParseError('remove-project element specifies non-existent '
+ 'project: %s' % name)
# If the manifest removes the hooks project, treat it as if it deleted
# the repo-hooks element too.
@@ -496,6 +564,12 @@
d.sync_c = False
else:
d.sync_c = sync_c.lower() in ("yes", "true", "1")
+
+ sync_s = node.getAttribute('sync-s')
+ if not sync_s:
+ d.sync_s = False
+ else:
+ d.sync_s = sync_s.lower() in ("yes", "true", "1")
return d
def _ParseNotice(self, node):
@@ -537,11 +611,19 @@
return '\n'.join(cleanLines)
- def _ParseProject(self, node):
+ def _JoinName(self, parent_name, name):
+ return os.path.join(parent_name, name)
+
+ def _UnjoinName(self, parent_name, name):
+ return os.path.relpath(name, parent_name)
+
+ def _ParseProject(self, node, parent = None):
"""
reads a <project> element from the manifest file
"""
name = self._reqatt(node, 'name')
+ if parent:
+ name = self._JoinName(parent.name, name)
remote = self._get_remote(node)
if remote is None:
@@ -579,44 +661,80 @@
else:
sync_c = sync_c.lower() in ("yes", "true", "1")
+ sync_s = node.getAttribute('sync-s')
+ if not sync_s:
+ sync_s = self._default.sync_s
+ else:
+ sync_s = sync_s.lower() in ("yes", "true", "1")
+
upstream = node.getAttribute('upstream')
groups = ''
if node.hasAttribute('groups'):
groups = node.getAttribute('groups')
- groups = [x for x in re.split('[,\s]+', groups) if x]
+ groups = [x for x in re.split(r'[,\s]+', groups) if x]
- default_groups = ['all', 'name:%s' % name, 'path:%s' % path]
- groups.extend(set(default_groups).difference(groups))
-
- if self.IsMirror:
- worktree = None
- gitdir = os.path.join(self.topdir, '%s.git' % name)
+ if parent is None:
+ relpath, worktree, gitdir = self.GetProjectPaths(name, path)
else:
- worktree = os.path.join(self.topdir, path).replace('\\', '/')
- gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
+ relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
+
+ default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
+ groups.extend(set(default_groups).difference(groups))
project = Project(manifest = self,
name = name,
remote = remote.ToRemoteSpec(name),
gitdir = gitdir,
worktree = worktree,
- relpath = path,
+ relpath = relpath,
revisionExpr = revisionExpr,
revisionId = None,
rebase = rebase,
groups = groups,
sync_c = sync_c,
- upstream = upstream)
+ sync_s = sync_s,
+ upstream = upstream,
+ parent = parent)
for n in node.childNodes:
if n.nodeName == 'copyfile':
self._ParseCopyFile(project, n)
if n.nodeName == 'annotation':
self._ParseAnnotation(project, n)
+ if n.nodeName == 'project':
+ project.subprojects.append(self._ParseProject(n, parent = project))
return project
+ def GetProjectPaths(self, name, path):
+ relpath = path
+ if self.IsMirror:
+ worktree = None
+ gitdir = os.path.join(self.topdir, '%s.git' % name)
+ else:
+ worktree = os.path.join(self.topdir, path).replace('\\', '/')
+ gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
+ return relpath, worktree, gitdir
+
+ def GetSubprojectName(self, parent, submodule_path):
+ return os.path.join(parent.name, submodule_path)
+
+ def _JoinRelpath(self, parent_relpath, relpath):
+ return os.path.join(parent_relpath, relpath)
+
+ def _UnjoinRelpath(self, parent_relpath, relpath):
+ return os.path.relpath(relpath, parent_relpath)
+
+ def GetSubprojectPaths(self, parent, path):
+ relpath = self._JoinRelpath(parent.relpath, path)
+ gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
+ if self.IsMirror:
+ worktree = None
+ else:
+ worktree = os.path.join(parent.worktree, path).replace('\\', '/')
+ return relpath, worktree, gitdir
+
def _ParseCopyFile(self, project, node):
src = self._reqatt(node, 'src')
dest = self._reqatt(node, 'dest')
diff --git a/pager.py b/pager.py
index 888b108..c621141 100755
--- a/pager.py
+++ b/pager.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import os
import select
import sys
@@ -49,7 +50,7 @@
_BecomePager(pager)
except Exception:
- print >>sys.stderr, "fatal: cannot start pager '%s'" % pager
+ print("fatal: cannot start pager '%s'" % pager, file=sys.stderr)
sys.exit(255)
def _SelectPager(globalConfig):
diff --git a/project.py b/project.py
index 2f47169..ba7898e 100644
--- a/project.py
+++ b/project.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import traceback
import errno
import filecmp
@@ -22,13 +23,15 @@
import stat
import subprocess
import sys
+import tempfile
import time
from color import Coloring
-from git_command import GitCommand
+from git_command import GitCommand, git_require
from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
from error import GitError, HookError, UploadError
from error import ManifestInvalidRevisionError
+from error import NoManifestException
from trace import IsTrace, Trace
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
@@ -50,7 +53,7 @@
def _error(fmt, *args):
msg = fmt % args
- print >>sys.stderr, 'error: %s' % msg
+ print('error: %s' % msg, file=sys.stderr)
def not_rev(r):
return '^' + r
@@ -359,7 +362,7 @@
'(yes/yes-never-ask-again/NO)? ') % (
self._GetMustVerb(), self._script_fullpath)
response = raw_input(prompt).lower()
- print
+ print()
# User is doing a one-time approval.
if response in ('y', 'yes'):
@@ -484,7 +487,30 @@
rebase = True,
groups = None,
sync_c = False,
- upstream = None):
+ sync_s = False,
+ upstream = None,
+ parent = None,
+ is_derived = False):
+ """Init a Project object.
+
+ Args:
+ manifest: The XmlManifest object.
+ name: The `name` attribute of manifest.xml's project element.
+ remote: RemoteSpec object specifying its remote's properties.
+ gitdir: Absolute path of git directory.
+ worktree: Absolute path of git working tree.
+ relpath: Relative path of git working tree to repo's top directory.
+ revisionExpr: The `revision` attribute of manifest.xml's project element.
+ revisionId: git commit id for checking out.
+ rebase: The `rebase` attribute of manifest.xml's project element.
+ 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.
+ 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;
+ True if the project is a discovered submodule.
+ """
self.manifest = manifest
self.name = name
self.remote = remote
@@ -506,7 +532,11 @@
self.rebase = rebase
self.groups = groups
self.sync_c = sync_c
+ self.sync_s = sync_s
self.upstream = upstream
+ self.parent = parent
+ self.is_derived = is_derived
+ self.subprojects = []
self.snapshots = {}
self.copyfiles = []
@@ -527,6 +557,10 @@
self.enabled_repo_hooks = []
@property
+ def Derived(self):
+ return self.is_derived
+
+ @property
def Exists(self):
return os.path.isdir(self.gitdir)
@@ -555,7 +589,7 @@
'--unmerged',
'--ignore-missing',
'--refresh')
- if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
+ if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
return True
if self.work_git.DiffZ('diff-files'):
return True
@@ -584,14 +618,14 @@
return self._userident_email
def _LoadUserIdentity(self):
- u = self.bare_git.var('GIT_COMMITTER_IDENT')
- m = re.compile("^(.*) <([^>]*)> ").match(u)
- if m:
- self._userident_name = m.group(1)
- self._userident_email = m.group(2)
- else:
- self._userident_name = ''
- self._userident_email = ''
+ u = self.bare_git.var('GIT_COMMITTER_IDENT')
+ m = re.compile("^(.*) <([^>]*)> ").match(u)
+ if m:
+ self._userident_name = m.group(1)
+ self._userident_email = m.group(2)
+ else:
+ self._userident_name = ''
+ self._userident_email = ''
def GetRemote(self, name):
"""Get the configuration for a single remote.
@@ -683,9 +717,9 @@
if not os.path.isdir(self.worktree):
if output_redir == None:
output_redir = sys.stdout
- print >>output_redir, ''
- print >>output_redir, 'project %s/' % self.relpath
- print >>output_redir, ' missing (run "repo sync")'
+ print(file=output_redir)
+ print('project %s/' % self.relpath, file=output_redir)
+ print(' missing (run "repo sync")', file=output_redir)
return
self.work_git.update_index('-q',
@@ -785,7 +819,7 @@
out.project('project %s/' % self.relpath)
out.nl()
has_diff = True
- print line[:-1]
+ print(line[:-1])
p.Wait()
@@ -1012,6 +1046,10 @@
self.CleanPublishedCache(all_refs)
revid = self.GetRevisionId(all_refs)
+ def _doff():
+ self._FastForward(revid)
+ self._CopyFiles()
+
self._InitWorkTree()
head = self.work_git.GetHead()
if head.startswith(R_HEADS):
@@ -1090,9 +1128,6 @@
# All published commits are merged, and thus we are a
# strict subset. We can fast-forward safely.
#
- def _doff():
- self._FastForward(revid)
- self._CopyFiles()
syncbuf.later1(self, _doff)
return
@@ -1155,9 +1190,6 @@
syncbuf.fail(self, e)
return
else:
- def _doff():
- self._FastForward(revid)
- self._CopyFiles()
syncbuf.later1(self, _doff)
def AddCopyFile(self, src, dest, absdest):
@@ -1177,7 +1209,7 @@
cmd = ['fetch', remote.name]
cmd.append('refs/changes/%2.2d/%d/%d' \
% (change_id % 100, change_id, patch_id))
- cmd.extend(map(lambda x: str(x), remote.fetch))
+ cmd.extend(map(str, remote.fetch))
if GitCommand(self, cmd, bare=True).Wait() != 0:
return None
return DownloadedChange(self,
@@ -1370,6 +1402,149 @@
return kept
+## Submodule Management ##
+
+ def GetRegisteredSubprojects(self):
+ result = []
+ def rec(subprojects):
+ if not subprojects:
+ return
+ result.extend(subprojects)
+ for p in subprojects:
+ rec(p.subprojects)
+ rec(self.subprojects)
+ return result
+
+ def _GetSubmodules(self):
+ # Unfortunately we cannot call `git submodule status --recursive` here
+ # because the working tree might not exist yet, and it cannot be used
+ # without a working tree in its current implementation.
+
+ def get_submodules(gitdir, rev):
+ # Parse .gitmodules for submodule sub_paths and sub_urls
+ sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
+ if not sub_paths:
+ return []
+ # Run `git ls-tree` to read SHAs of submodule object, which happen to be
+ # revision of submodule repository
+ sub_revs = git_ls_tree(gitdir, rev, sub_paths)
+ submodules = []
+ for sub_path, sub_url in zip(sub_paths, sub_urls):
+ try:
+ sub_rev = sub_revs[sub_path]
+ except KeyError:
+ # Ignore non-exist submodules
+ continue
+ submodules.append((sub_rev, sub_path, sub_url))
+ return submodules
+
+ re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
+ re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
+ def parse_gitmodules(gitdir, rev):
+ cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
+ try:
+ p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
+ bare = True, gitdir = gitdir)
+ except GitError:
+ return [], []
+ if p.Wait() != 0:
+ return [], []
+
+ gitmodules_lines = []
+ fd, temp_gitmodules_path = tempfile.mkstemp()
+ try:
+ os.write(fd, p.stdout)
+ os.close(fd)
+ cmd = ['config', '--file', temp_gitmodules_path, '--list']
+ p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
+ bare = True, gitdir = gitdir)
+ if p.Wait() != 0:
+ return [], []
+ gitmodules_lines = p.stdout.split('\n')
+ except GitError:
+ return [], []
+ finally:
+ os.remove(temp_gitmodules_path)
+
+ names = set()
+ paths = {}
+ urls = {}
+ for line in gitmodules_lines:
+ if not line:
+ continue
+ m = re_path.match(line)
+ if m:
+ names.add(m.group(1))
+ paths[m.group(1)] = m.group(2)
+ continue
+ m = re_url.match(line)
+ if m:
+ names.add(m.group(1))
+ urls[m.group(1)] = m.group(2)
+ continue
+ names = sorted(names)
+ return ([paths.get(name, '') for name in names],
+ [urls.get(name, '') for name in names])
+
+ def git_ls_tree(gitdir, rev, paths):
+ cmd = ['ls-tree', rev, '--']
+ cmd.extend(paths)
+ try:
+ p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
+ bare = True, gitdir = gitdir)
+ except GitError:
+ return []
+ if p.Wait() != 0:
+ return []
+ objects = {}
+ for line in p.stdout.split('\n'):
+ if not line.strip():
+ continue
+ object_rev, object_path = line.split()[2:4]
+ objects[object_path] = object_rev
+ return objects
+
+ try:
+ rev = self.GetRevisionId()
+ except GitError:
+ return []
+ return get_submodules(self.gitdir, rev)
+
+ def GetDerivedSubprojects(self):
+ result = []
+ if not self.Exists:
+ # If git repo does not exist yet, querying its submodules will
+ # mess up its states; so return here.
+ return result
+ for rev, path, url in self._GetSubmodules():
+ name = self.manifest.GetSubprojectName(self, path)
+ project = self.manifest.projects.get(name)
+ if project:
+ result.extend(project.GetDerivedSubprojects())
+ continue
+ relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
+ remote = RemoteSpec(self.remote.name,
+ url = url,
+ review = self.remote.review)
+ subproject = Project(manifest = self.manifest,
+ name = name,
+ remote = remote,
+ gitdir = gitdir,
+ worktree = worktree,
+ relpath = relpath,
+ revisionExpr = self.revisionExpr,
+ revisionId = rev,
+ rebase = self.rebase,
+ groups = self.groups,
+ sync_c = self.sync_c,
+ sync_s = self.sync_s,
+ parent = self,
+ is_derived = True)
+ result.append(subproject)
+ result.extend(subproject.GetDerivedSubprojects())
+ return result
+
+
## Direct Git Commands ##
def _RemoteFetch(self, name=None,
@@ -1382,14 +1557,14 @@
tag_name = None
def CheckForSha1():
- try:
- # if revision (sha or tag) is not present then following function
- # throws an error.
- self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
- return True
- except GitError:
- # There is no such persistent revision. We have to fetch it.
- return False
+ try:
+ # if revision (sha or tag) is not present then following function
+ # throws an error.
+ self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
+ return True
+ except GitError:
+ # There is no such persistent revision. We have to fetch it.
+ return False
if current_branch_only:
if ID_RE.match(self.revisionExpr) is not None:
@@ -1571,6 +1746,9 @@
os.remove(tmpPath)
if 'http_proxy' in os.environ and 'darwin' == sys.platform:
cmd += ['--proxy', os.environ['http_proxy']]
+ cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
+ if cookiefile:
+ cmd += ['--cookie', cookiefile]
cmd += [srcUrl]
if IsTrace():
@@ -1588,7 +1766,8 @@
# returned another error with the HTTP error code being 400 or above.
# This return code only appears if -f, --fail is used.
if not quiet:
- print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
+ print("Server does not provide clone.bundle; ignoring.",
+ file=sys.stderr)
return False
if os.path.exists(tmpPath):
@@ -1836,7 +2015,8 @@
if p.Wait() == 0:
out = p.stdout
if out:
- return out[:-1].split("\0")
+ return out[:-1].split('\0') # pylint: disable=W1401
+ # Backslash is not anomalous
return []
def DiffZ(self, name, *args):
@@ -1852,7 +2032,7 @@
out = p.process.stdout.read()
r = {}
if out:
- out = iter(out[:-1].split('\0'))
+ out = iter(out[:-1].split('\0')) # pylint: disable=W1401
while out:
try:
info = out.next()
@@ -1879,7 +2059,7 @@
self.level = self.level[1:]
info = info[1:].split(' ')
- info =_Info(path, *info)
+ info = _Info(path, *info)
if info.status in ('R', 'C'):
info.src_path = info.path
info.path = out.next()
@@ -1893,7 +2073,10 @@
path = os.path.join(self._project.gitdir, HEAD)
else:
path = os.path.join(self._project.worktree, '.git', HEAD)
- fd = open(path, 'rb')
+ try:
+ fd = open(path, 'rb')
+ except IOError:
+ raise NoManifestException(path)
try:
line = fd.read()
finally:
@@ -1988,6 +2171,9 @@
raise TypeError('%s() got an unexpected keyword argument %r'
% (name, k))
if config is not None:
+ if not git_require((1, 7, 2)):
+ raise ValueError('cannot set config on command line for %s()'
+ % name)
for k, v in config.iteritems():
cmdv.append('-c')
cmdv.append('%s=%s' % (k, v))
diff --git a/repo b/repo
index 70f4157..6b374f7 100755
--- a/repo
+++ b/repo
@@ -1,9 +1,10 @@
-#!/bin/sh
+#!/usr/bin/env python
## repo default configuration
##
-REPO_URL='https://gerrit.googlesource.com/git-repo'
-REPO_REV='stable'
+from __future__ import print_function
+REPO_URL = 'https://gerrit.googlesource.com/git-repo'
+REPO_REV = 'stable'
# Copyright (C) 2008 Google Inc.
#
@@ -19,19 +20,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-magic='--calling-python-from-/bin/sh--'
-"""exec" python -E "$0" "$@" """#$magic"
-if __name__ == '__main__':
- import sys
- if sys.argv[-1] == '#%s' % magic:
- del sys.argv[-1]
-del magic
-
# increment this whenever we make important changes to this script
-VERSION = (1, 18)
+VERSION = (1, 19)
# increment this if the MAINTAINER_KEYS block is modified
-KEYRING_VERSION = (1,1)
+KEYRING_VERSION = (1, 1)
MAINTAINER_KEYS = """
Repo Maintainer <repo@android.kernel.org>
@@ -110,7 +103,7 @@
"""
GIT = 'git' # our git command
-MIN_GIT_VERSION = (1, 5, 4) # minimum supported git version
+MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
repodir = '.repo' # name of repo's private directory
S_repo = 'repo' # special repo repository
S_manifests = 'manifests' # special manifest repository
@@ -120,9 +113,21 @@
import optparse
import os
import re
+import stat
import subprocess
import sys
-import urllib2
+try:
+ import urllib2
+except ImportError:
+ # For python3
+ import urllib.request
+ import urllib.error
+else:
+ # For python2
+ import imp
+ urllib = imp.new_module('urllib')
+ urllib.request = urllib2
+ urllib.error = urllib2
home_dot_repo = os.path.expanduser('~/.repoconfig')
gpg_dir = os.path.join(home_dot_repo, 'gnupg')
@@ -149,7 +154,8 @@
help='initial manifest file', metavar='NAME.xml')
group.add_option('--mirror',
dest='mirror', action='store_true',
- help='mirror the forrest')
+ help='create a replica of the remote repositories '
+ 'rather than a client working directory')
group.add_option('--reference',
dest='reference',
help='location of mirror directory', metavar='DIR')
@@ -211,17 +217,16 @@
if branch.startswith('refs/heads/'):
branch = branch[len('refs/heads/'):]
if branch.startswith('refs/'):
- print >>sys.stderr, "fatal: invalid branch name '%s'" % branch
+ print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
raise CloneFailure()
if not os.path.isdir(repodir):
try:
os.mkdir(repodir)
except OSError as e:
- print >>sys.stderr, \
- 'fatal: cannot make %s directory: %s' % (
- repodir, e.strerror)
- # Don't faise CloneFailure; that would delete the
+ print('fatal: cannot make %s directory: %s'
+ % (repodir, e.strerror), file=sys.stderr)
+ # Don't raise CloneFailure; that would delete the
# name. Instead exit immediately.
#
sys.exit(1)
@@ -244,8 +249,8 @@
_Checkout(dst, branch, rev, opt.quiet)
except CloneFailure:
if opt.quiet:
- print >>sys.stderr, \
- 'fatal: repo init failed; run without --quiet to see why'
+ print('fatal: repo init failed; run without --quiet to see why',
+ file=sys.stderr)
raise
@@ -254,12 +259,12 @@
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
except OSError as e:
- print >>sys.stderr
- print >>sys.stderr, "fatal: '%s' is not available" % GIT
- print >>sys.stderr, 'fatal: %s' % e
- print >>sys.stderr
- print >>sys.stderr, 'Please make sure %s is installed'\
- ' and in your path.' % GIT
+ print(file=sys.stderr)
+ print("fatal: '%s' is not available" % GIT, file=sys.stderr)
+ print('fatal: %s' % e, file=sys.stderr)
+ print(file=sys.stderr)
+ print('Please make sure %s is installed and in your path.' % GIT,
+ file=sys.stderr)
raise CloneFailure()
ver_str = proc.stdout.read().strip()
@@ -267,14 +272,14 @@
proc.wait()
if not ver_str.startswith('git version '):
- print >>sys.stderr, 'error: "%s" unsupported' % ver_str
+ print('error: "%s" unsupported' % ver_str, file=sys.stderr)
raise CloneFailure()
ver_str = ver_str[len('git version '):].strip()
- ver_act = tuple(map(lambda x: int(x), ver_str.split('.')[0:3]))
+ ver_act = tuple(map(int, ver_str.split('.')[0:3]))
if ver_act < MIN_GIT_VERSION:
- need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION))
- print >>sys.stderr, 'fatal: git %s or later required' % need
+ need = '.'.join(map(str, MIN_GIT_VERSION))
+ print('fatal: git %s or later required' % need, file=sys.stderr)
raise CloneFailure()
@@ -290,7 +295,7 @@
if not kv:
return True
- kv = tuple(map(lambda x: int(x), kv.split('.')))
+ kv = tuple(map(int, kv.split('.')))
if kv < KEYRING_VERSION:
return True
return False
@@ -301,18 +306,16 @@
try:
os.mkdir(home_dot_repo)
except OSError as e:
- print >>sys.stderr, \
- 'fatal: cannot make %s directory: %s' % (
- home_dot_repo, e.strerror)
+ print('fatal: cannot make %s directory: %s'
+ % (home_dot_repo, e.strerror), file=sys.stderr)
sys.exit(1)
if not os.path.isdir(gpg_dir):
try:
- os.mkdir(gpg_dir, 0700)
+ os.mkdir(gpg_dir, stat.S_IRWXU)
except OSError as e:
- print >>sys.stderr, \
- 'fatal: cannot make %s directory: %s' % (
- gpg_dir, e.strerror)
+ print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
+ file=sys.stderr)
sys.exit(1)
env = os.environ.copy()
@@ -325,21 +328,21 @@
stdin = subprocess.PIPE)
except OSError as e:
if not quiet:
- print >>sys.stderr, 'warning: gpg (GnuPG) is not available.'
- print >>sys.stderr, 'warning: Installing it is strongly encouraged.'
- print >>sys.stderr
+ print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
+ print('warning: Installing it is strongly encouraged.', file=sys.stderr)
+ print(file=sys.stderr)
return False
proc.stdin.write(MAINTAINER_KEYS)
proc.stdin.close()
if proc.wait() != 0:
- print >>sys.stderr, 'fatal: registering repo maintainer keys failed'
+ print('fatal: registering repo maintainer keys failed', file=sys.stderr)
sys.exit(1)
- print
+ print()
fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
- fd.write('.'.join(map(lambda x: str(x), KEYRING_VERSION)) + '\n')
+ fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
fd.close()
return True
@@ -355,7 +358,7 @@
def _InitHttp():
handlers = []
- mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+ mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
try:
import netrc
n = netrc.netrc()
@@ -365,20 +368,20 @@
mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
except:
pass
- handlers.append(urllib2.HTTPBasicAuthHandler(mgr))
- handlers.append(urllib2.HTTPDigestAuthHandler(mgr))
+ handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
+ handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
if 'http_proxy' in os.environ:
url = os.environ['http_proxy']
- handlers.append(urllib2.ProxyHandler({'http': url, 'https': url}))
+ handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
if 'REPO_CURL_VERBOSE' in os.environ:
- handlers.append(urllib2.HTTPHandler(debuglevel=1))
- handlers.append(urllib2.HTTPSHandler(debuglevel=1))
- urllib2.install_opener(urllib2.build_opener(*handlers))
+ handlers.append(urllib.request.HTTPHandler(debuglevel=1))
+ handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
+ urllib.request.install_opener(urllib.request.build_opener(*handlers))
def _Fetch(url, local, src, quiet):
if not quiet:
- print >>sys.stderr, 'Get %s' % url
+ print('Get %s' % url, file=sys.stderr)
cmd = [GIT, 'fetch']
if quiet:
@@ -423,20 +426,20 @@
dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b')
try:
try:
- r = urllib2.urlopen(url)
- except urllib2.HTTPError as e:
- if e.code == 404:
+ r = urllib.request.urlopen(url)
+ except urllib.error.HTTPError as e:
+ if e.code in [403, 404]:
return False
- print >>sys.stderr, 'fatal: Cannot get %s' % url
- print >>sys.stderr, 'fatal: HTTP error %s' % e.code
+ print('fatal: Cannot get %s' % url, file=sys.stderr)
+ print('fatal: HTTP error %s' % e.code, file=sys.stderr)
raise CloneFailure()
- except urllib2.URLError as e:
- print >>sys.stderr, 'fatal: Cannot get %s' % url
- print >>sys.stderr, 'fatal: error %s' % e.reason
+ except urllib.error.URLError as e:
+ print('fatal: Cannot get %s' % url, file=sys.stderr)
+ print('fatal: error %s' % e.reason, file=sys.stderr)
raise CloneFailure()
try:
if not quiet:
- print >>sys.stderr, 'Get %s' % url
+ print('Get %s' % url, file=sys.stderr)
while True:
buf = r.read(8192)
if buf == '':
@@ -460,24 +463,23 @@
try:
os.mkdir(local)
except OSError as e:
- print >>sys.stderr, \
- 'fatal: cannot make %s directory: %s' \
- % (local, e.strerror)
+ print('fatal: cannot make %s directory: %s' % (local, e.strerror),
+ file=sys.stderr)
raise CloneFailure()
cmd = [GIT, 'init', '--quiet']
try:
proc = subprocess.Popen(cmd, cwd = local)
except OSError as e:
- print >>sys.stderr
- print >>sys.stderr, "fatal: '%s' is not available" % GIT
- print >>sys.stderr, 'fatal: %s' % e
- print >>sys.stderr
- print >>sys.stderr, 'Please make sure %s is installed'\
- ' and in your path.' % GIT
+ print(file=sys.stderr)
+ print("fatal: '%s' is not available" % GIT, file=sys.stderr)
+ print('fatal: %s' % e, file=sys.stderr)
+ print(file=sys.stderr)
+ print('Please make sure %s is installed and in your path.' % GIT,
+ file=sys.stderr)
raise CloneFailure()
if proc.wait() != 0:
- print >>sys.stderr, 'fatal: could not create %s' % local
+ print('fatal: could not create %s' % local, file=sys.stderr)
raise CloneFailure()
_InitHttp()
@@ -505,21 +507,18 @@
proc.stderr.close()
if proc.wait() != 0 or not cur:
- print >>sys.stderr
- print >>sys.stderr,\
- "fatal: branch '%s' has not been signed" \
- % branch
+ print(file=sys.stderr)
+ print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
raise CloneFailure()
m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
if m:
cur = m.group(1)
if not quiet:
- print >>sys.stderr
- print >>sys.stderr, \
- "info: Ignoring branch '%s'; using tagged release '%s'" \
- % (branch, cur)
- print >>sys.stderr
+ print(file=sys.stderr)
+ print("info: Ignoring branch '%s'; using tagged release '%s'"
+ % (branch, cur), file=sys.stderr)
+ print(file=sys.stderr)
env = os.environ.copy()
env['GNUPGHOME'] = gpg_dir.encode()
@@ -537,10 +536,10 @@
proc.stderr.close()
if proc.wait() != 0:
- print >>sys.stderr
- print >>sys.stderr, out
- print >>sys.stderr, err
- print >>sys.stderr
+ print(file=sys.stderr)
+ print(out, file=sys.stderr)
+ print(err, file=sys.stderr)
+ print(file=sys.stderr)
raise CloneFailure()
return '%s^0' % cur
@@ -594,7 +593,7 @@
opt = _Options()
arg = []
- for i in xrange(0, len(args)):
+ for i in range(len(args)):
a = args[i]
if a == '-h' or a == '--help':
opt.help = True
@@ -607,7 +606,7 @@
def _Usage():
- print >>sys.stderr,\
+ print(
"""usage: repo COMMAND [ARGS]
repo is not yet installed. Use "repo init" to install it here.
@@ -618,7 +617,7 @@
help Display detailed help on a command
For access to the full online help, install repo ("repo init").
-"""
+""", file=sys.stderr)
sys.exit(1)
@@ -628,25 +627,23 @@
init_optparse.print_help()
sys.exit(0)
else:
- print >>sys.stderr,\
- "error: '%s' is not a bootstrap command.\n"\
- ' For access to online help, install repo ("repo init").'\
- % args[0]
+ print("error: '%s' is not a bootstrap command.\n"
+ ' For access to online help, install repo ("repo init").'
+ % args[0], file=sys.stderr)
else:
_Usage()
sys.exit(1)
def _NotInstalled():
- print >>sys.stderr,\
-'error: repo is not installed. Use "repo init" to install it here.'
+ print('error: repo is not installed. Use "repo init" to install it here.',
+ file=sys.stderr)
sys.exit(1)
def _NoCommands(cmd):
- print >>sys.stderr,\
-"""error: command '%s' requires repo to be installed first.
- Use "repo init" to install it here.""" % cmd
+ print("""error: command '%s' requires repo to be installed first.
+ Use "repo init" to install it here.""" % cmd, file=sys.stderr)
sys.exit(1)
@@ -683,7 +680,7 @@
proc.stderr.close()
if proc.wait() != 0:
- print >>sys.stderr, 'fatal: %s has no current branch' % gitdir
+ print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
sys.exit(1)
@@ -721,7 +718,7 @@
if my_main:
repo_main = my_main
- ver_str = '.'.join(map(lambda x: str(x), VERSION))
+ ver_str = '.'.join(map(str, VERSION))
me = [repo_main,
'--repo-dir=%s' % rel_repo_dir,
'--wrapper-version=%s' % ver_str,
@@ -732,8 +729,8 @@
try:
os.execv(repo_main, me)
except OSError as e:
- print >>sys.stderr, "fatal: unable to start %s" % repo_main
- print >>sys.stderr, "fatal: %s" % e
+ print("fatal: unable to start %s" % repo_main, file=sys.stderr)
+ print("fatal: %s" % e, file=sys.stderr)
sys.exit(148)
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index e17ab2b..b94ccdd 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from command import Command
from git_command import git
@@ -36,7 +37,7 @@
nb = args[0]
if not git.check_ref_format('heads/%s' % nb):
- print >>sys.stderr, "error: '%s' is not a valid name" % nb
+ print("error: '%s' is not a valid name" % nb, file=sys.stderr)
sys.exit(1)
nb = args[0]
@@ -58,13 +59,13 @@
if err:
for p in err:
- print >>sys.stderr,\
- "error: %s/: cannot abandon %s" \
- % (p.relpath, nb)
+ print("error: %s/: cannot abandon %s" % (p.relpath, nb),
+ file=sys.stderr)
sys.exit(1)
elif not success:
- print >>sys.stderr, 'error: no project has branch %s' % nb
+ print('error: no project has branch %s' % nb, file=sys.stderr)
sys.exit(1)
else:
- print >>sys.stderr, 'Abandoned in %d project(s):\n %s' % (
- len(success), '\n '.join(p.relpath for p in success))
+ print('Abandoned in %d project(s):\n %s'
+ % (len(success), '\n '.join(p.relpath for p in success)),
+ file=sys.stderr)
diff --git a/subcmds/branches.py b/subcmds/branches.py
index a7ba3d6..06d45ab 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from color import Coloring
from command import Command
@@ -107,7 +108,7 @@
names.sort()
if not names:
- print >>sys.stderr, ' (no branches)'
+ print(' (no branches)', file=sys.stderr)
return
width = 25
diff --git a/subcmds/checkout.py b/subcmds/checkout.py
index bfbe992..cbbca10 100644
--- a/subcmds/checkout.py
+++ b/subcmds/checkout.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from command import Command
from progress import Progress
@@ -55,10 +56,9 @@
if err:
for p in err:
- print >>sys.stderr,\
- "error: %s/: cannot checkout %s" \
- % (p.relpath, nb)
+ print("error: %s/: cannot checkout %s" % (p.relpath, nb),
+ file=sys.stderr)
sys.exit(1)
elif not success:
- print >>sys.stderr, 'error: no project has branch %s' % nb
+ print('error: no project has branch %s' % nb, file=sys.stderr)
sys.exit(1)
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py
index 7a6d4c2..01b97e0 100644
--- a/subcmds/cherry_pick.py
+++ b/subcmds/cherry_pick.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import re
import sys
from command import Command
@@ -46,13 +47,13 @@
capture_stdout = True,
capture_stderr = True)
if p.Wait() != 0:
- print >>sys.stderr, p.stderr
+ print(p.stderr, file=sys.stderr)
sys.exit(1)
sha1 = p.stdout.strip()
p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True)
if p.Wait() != 0:
- print >>sys.stderr, "error: Failed to retrieve old commit message"
+ print("error: Failed to retrieve old commit message", file=sys.stderr)
sys.exit(1)
old_msg = self._StripHeader(p.stdout)
@@ -62,8 +63,8 @@
capture_stderr = True)
status = p.Wait()
- print >>sys.stdout, p.stdout
- print >>sys.stderr, p.stderr
+ print(p.stdout, file=sys.stdout)
+ print(p.stderr, file=sys.stderr)
if status == 0:
# The cherry-pick was applied correctly. We just need to edit the
@@ -76,16 +77,14 @@
capture_stderr = True)
p.stdin.write(new_msg)
if p.Wait() != 0:
- print >>sys.stderr, "error: Failed to update commit message"
+ print("error: Failed to update commit message", file=sys.stderr)
sys.exit(1)
else:
- print >>sys.stderr, """\
-NOTE: When committing (please see above) and editing the commit message,
-please remove the old Change-Id-line and add:
-"""
- print >>sys.stderr, self._GetReference(sha1)
- print >>sys.stderr
+ print('NOTE: When committing (please see above) and editing the commit'
+ 'message, please remove the old Change-Id-line and add:')
+ print(self._GetReference(sha1), file=sys.stderr)
+ print(file=sys.stderr)
def _IsChangeId(self, line):
return CHANGE_ID_RE.match(line)
diff --git a/subcmds/download.py b/subcmds/download.py
index 0abe90d..471e88b 100644
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import re
import sys
@@ -32,13 +33,13 @@
"""
def _Options(self, p):
- p.add_option('-c','--cherry-pick',
+ p.add_option('-c', '--cherry-pick',
dest='cherrypick', action='store_true',
help="cherry-pick instead of checkout")
- p.add_option('-r','--revert',
+ p.add_option('-r', '--revert',
dest='revert', action='store_true',
help="revert instead of checkout")
- p.add_option('-f','--ff-only',
+ p.add_option('-f', '--ff-only',
dest='ffonly', action='store_true',
help="force fast-forward merge")
@@ -68,23 +69,23 @@
for project, change_id, ps_id in self._ParseChangeIds(args):
dl = project.DownloadPatchSet(change_id, ps_id)
if not dl:
- print >>sys.stderr, \
- '[%s] change %d/%d not found' \
- % (project.name, change_id, ps_id)
+ print('[%s] change %d/%d not found'
+ % (project.name, change_id, ps_id),
+ file=sys.stderr)
sys.exit(1)
if not opt.revert and not dl.commits:
- print >>sys.stderr, \
- '[%s] change %d/%d has already been merged' \
- % (project.name, change_id, ps_id)
+ print('[%s] change %d/%d has already been merged'
+ % (project.name, change_id, ps_id),
+ file=sys.stderr)
continue
if len(dl.commits) > 1:
- print >>sys.stderr, \
- '[%s] %d/%d depends on %d unmerged changes:' \
- % (project.name, change_id, ps_id, len(dl.commits))
+ print('[%s] %d/%d depends on %d unmerged changes:' \
+ % (project.name, change_id, ps_id, len(dl.commits)),
+ file=sys.stderr)
for c in dl.commits:
- print >>sys.stderr, ' %s' % (c)
+ print(' %s' % (c), file=sys.stderr)
if opt.cherrypick:
project._CherryPick(dl.commit)
elif opt.revert:
diff --git a/subcmds/forall.py b/subcmds/forall.py
index b633b7d..4c1c9ff 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import fcntl
import re
import os
@@ -92,6 +93,9 @@
Unless -p is used, stdin, stdout, stderr are inherited from the
terminal and are not redirected.
+
+If -e is used, when a command exits unsuccessfully, '%prog' will abort
+without iterating through the remaining projects.
"""
def _Options(self, p):
@@ -104,6 +108,9 @@
dest='command',
action='callback',
callback=cmd)
+ p.add_option('-e', '--abort-on-errors',
+ dest='abort_on_errors', action='store_true',
+ help='Abort if a command exits unsuccessfully')
g = p.add_option_group('Output')
g.add_option('-p',
@@ -183,7 +190,7 @@
if not os.path.exists(cwd):
if (opt.project_header and opt.verbose) \
or not opt.project_header:
- print >>sys.stderr, 'skipping %s/' % project.relpath
+ print('skipping %s/' % project.relpath, file=sys.stderr)
continue
if opt.project_header:
@@ -254,7 +261,12 @@
s.dest.flush()
r = p.wait()
- if r != 0 and r != rc:
- rc = r
+ if r != 0:
+ if r != rc:
+ rc = r
+ if opt.abort_on_errors:
+ print("error: %s: Aborting due to previous error" % project.relpath,
+ file=sys.stderr)
+ sys.exit(r)
if rc != 0:
sys.exit(rc)
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 0dc8f9f..dd391cf 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from color import Coloring
from command import PagedCommand
@@ -51,7 +52,7 @@
Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
- repo grep -e '#define' --and -\( -e MAX_PATH -e PATH_MAX \)
+ repo grep -e '#define' --and -\\( -e MAX_PATH -e PATH_MAX \\)
Look for a line that has 'NODE' or 'Unexpected' in files that
contain a line that matches both expressions:
@@ -84,7 +85,7 @@
g.add_option('--cached',
action='callback', callback=carry,
help='Search the index, instead of the work tree')
- g.add_option('-r','--revision',
+ g.add_option('-r', '--revision',
dest='revision', action='append', metavar='TREEish',
help='Search TREEish, instead of the work tree')
@@ -96,7 +97,7 @@
g.add_option('-i', '--ignore-case',
action='callback', callback=carry,
help='Ignore case differences')
- g.add_option('-a','--text',
+ g.add_option('-a', '--text',
action='callback', callback=carry,
help="Process binary files as if they were text")
g.add_option('-I',
@@ -125,7 +126,7 @@
g.add_option('--and', '--or', '--not',
action='callback', callback=carry,
help='Boolean operators to combine patterns')
- g.add_option('-(','-)',
+ g.add_option('-(', '-)',
action='callback', callback=carry,
help='Boolean operator grouping')
@@ -145,10 +146,10 @@
action='callback', callback=carry,
metavar='CONTEXT', type='str',
help='Show CONTEXT lines after match')
- g.add_option('-l','--name-only','--files-with-matches',
+ g.add_option('-l', '--name-only', '--files-with-matches',
action='callback', callback=carry,
help='Show only file names containing matching lines')
- g.add_option('-L','--files-without-match',
+ g.add_option('-L', '--files-without-match',
action='callback', callback=carry,
help='Show only file names not containing matching lines')
@@ -157,9 +158,9 @@
out = GrepColoring(self.manifest.manifestProject.config)
cmd_argv = ['grep']
- if out.is_on and git_require((1,6,3)):
+ if out.is_on and git_require((1, 6, 3)):
cmd_argv.append('--color')
- cmd_argv.extend(getattr(opt,'cmd_argv',[]))
+ cmd_argv.extend(getattr(opt, 'cmd_argv', []))
if '-e' not in cmd_argv:
if not args:
@@ -178,8 +179,7 @@
have_rev = False
if opt.revision:
if '--cached' in cmd_argv:
- print >>sys.stderr,\
- 'fatal: cannot combine --cached and --revision'
+ print('fatal: cannot combine --cached and --revision', file=sys.stderr)
sys.exit(1)
have_rev = True
cmd_argv.extend(opt.revision)
@@ -230,13 +230,13 @@
out.nl()
else:
for line in r:
- print line
+ print(line)
if have_match:
sys.exit(0)
elif have_rev and bad_rev:
for r in opt.revision:
- print >>sys.stderr, "error: can't search revision %s" % r
+ print("error: can't search revision %s" % r, file=sys.stderr)
sys.exit(1)
else:
sys.exit(1)
diff --git a/subcmds/help.py b/subcmds/help.py
index 375d04d..15aab7f 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import re
import sys
from formatter import AbstractFormatter, DumbWriter
@@ -31,10 +32,8 @@
"""
def _PrintAllCommands(self):
- print 'usage: repo COMMAND [ARGS]'
- print """
-The complete list of recognized repo commands are:
-"""
+ print('usage: repo COMMAND [ARGS]')
+ print('The complete list of recognized repo commands are:')
commandNames = self.commands.keys()
commandNames.sort()
@@ -49,17 +48,14 @@
summary = command.helpSummary.strip()
except AttributeError:
summary = ''
- print fmt % (name, summary)
- print """
-See 'repo help <command>' for more information on a specific command.
-"""
+ print(fmt % (name, summary))
+ print("See 'repo help <command>' for more information on a"
+ 'specific command.')
def _PrintCommonCommands(self):
- print 'usage: repo COMMAND [ARGS]'
- print """
-The most commonly used repo commands are:
-"""
- commandNames = [name
+ print('usage: repo COMMAND [ARGS]')
+ print('The most commonly used repo commands are:')
+ commandNames = [name
for name in self.commands.keys()
if self.commands[name].common]
commandNames.sort()
@@ -75,11 +71,10 @@
summary = command.helpSummary.strip()
except AttributeError:
summary = ''
- print fmt % (name, summary)
- print """
-See 'repo help <command>' for more information on a specific command.
-See 'repo help --all' for a complete list of recognized commands.
-"""
+ print(fmt % (name, summary))
+ print(
+"See 'repo help <command>' for more information on a specific command.\n"
+"See 'repo help --all' for a complete list of recognized commands.")
def _PrintCommandHelp(self, cmd):
class _Out(Coloring):
@@ -131,7 +126,7 @@
p('%s', title)
self.nl()
- p('%s', ''.ljust(len(title),section_type[0]))
+ p('%s', ''.ljust(len(title), section_type[0]))
self.nl()
continue
@@ -162,7 +157,7 @@
try:
cmd = self.commands[name]
except KeyError:
- print >>sys.stderr, "repo: '%s' is not a repo command." % name
+ print("repo: '%s' is not a repo command." % name, file=sys.stderr)
sys.exit(1)
cmd.manifest = self.manifest
diff --git a/subcmds/info.py b/subcmds/info.py
new file mode 100644
index 0000000..a6eba88
--- /dev/null
+++ b/subcmds/info.py
@@ -0,0 +1,195 @@
+#
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from command import PagedCommand
+from color import Coloring
+from error import NoSuchProjectError
+from git_refs import R_M
+
+class _Coloring(Coloring):
+ def __init__(self, config):
+ Coloring.__init__(self, config, "status")
+
+class Info(PagedCommand):
+ common = True
+ helpSummary = "Get info on the manifest branch, current branch or unmerged branches"
+ helpUsage = "%prog [-dl] [-o [-b]] [<project>...]"
+
+ def _Options(self, p, show_smart=True):
+ p.add_option('-d', '--diff',
+ dest='all', action='store_true',
+ help="show full info and commit diff including remote branches")
+ p.add_option('-o', '--overview',
+ dest='overview', action='store_true',
+ help='show overview of all local commits')
+ p.add_option('-b', '--current-branch',
+ dest="current_branch", action="store_true",
+ help="consider only checked out branches")
+ p.add_option('-l', '--local-only',
+ dest="local", action="store_true",
+ help="Disable all remote operations")
+
+
+ def Execute(self, opt, args):
+ self.out = _Coloring(self.manifest.globalConfig)
+ self.heading = self.out.printer('heading', attr = 'bold')
+ self.headtext = self.out.printer('headtext', fg = 'yellow')
+ self.redtext = self.out.printer('redtext', fg = 'red')
+ self.sha = self.out.printer("sha", fg = 'yellow')
+ self.text = self.out.printer('text')
+ self.dimtext = self.out.printer('dimtext', attr = 'dim')
+
+ self.opt = opt
+
+ mergeBranch = self.manifest.manifestProject.config.GetBranch("default").merge
+
+ self.heading("Manifest branch: ")
+ self.headtext(self.manifest.default.revisionExpr)
+ self.out.nl()
+ self.heading("Manifest merge branch: ")
+ self.headtext(mergeBranch)
+ self.out.nl()
+
+ self.printSeparator()
+
+ if not opt.overview:
+ self.printDiffInfo(args)
+ else:
+ self.printCommitOverview(args)
+
+ def printSeparator(self):
+ self.text("----------------------------")
+ self.out.nl()
+
+ def printDiffInfo(self, args):
+ try:
+ projs = self.GetProjects(args)
+ except NoSuchProjectError:
+ return
+
+ for p in projs:
+ self.heading("Project: ")
+ self.headtext(p.name)
+ self.out.nl()
+
+ self.heading("Mount path: ")
+ self.headtext(p.worktree)
+ self.out.nl()
+
+ self.heading("Current revision: ")
+ self.headtext(p.revisionExpr)
+ self.out.nl()
+
+ localBranches = p.GetBranches().keys()
+ self.heading("Local Branches: ")
+ self.redtext(str(len(localBranches)))
+ if len(localBranches) > 0:
+ self.text(" [")
+ self.text(", ".join(localBranches))
+ self.text("]")
+ self.out.nl()
+
+ if self.opt.all:
+ self.findRemoteLocalDiff(p)
+
+ self.printSeparator()
+
+ def findRemoteLocalDiff(self, project):
+ #Fetch all the latest commits
+ if not self.opt.local:
+ project.Sync_NetworkHalf(quiet=True, current_branch_only=True)
+
+ logTarget = R_M + self.manifest.manifestProject.config.GetBranch("default").merge
+
+ bareTmp = project.bare_git._bare
+ project.bare_git._bare = False
+ localCommits = project.bare_git.rev_list(
+ '--abbrev=8',
+ '--abbrev-commit',
+ '--pretty=oneline',
+ logTarget + "..",
+ '--')
+
+ originCommits = project.bare_git.rev_list(
+ '--abbrev=8',
+ '--abbrev-commit',
+ '--pretty=oneline',
+ ".." + logTarget,
+ '--')
+ project.bare_git._bare = bareTmp
+
+ self.heading("Local Commits: ")
+ self.redtext(str(len(localCommits)))
+ self.dimtext(" (on current branch)")
+ self.out.nl()
+
+ for c in localCommits:
+ split = c.split()
+ self.sha(split[0] + " ")
+ self.text(" ".join(split[1:]))
+ self.out.nl()
+
+ self.printSeparator()
+
+ self.heading("Remote Commits: ")
+ self.redtext(str(len(originCommits)))
+ self.out.nl()
+
+ for c in originCommits:
+ split = c.split()
+ self.sha(split[0] + " ")
+ self.text(" ".join(split[1:]))
+ self.out.nl()
+
+ def printCommitOverview(self, args):
+ all_branches = []
+ for project in self.GetProjects(args):
+ br = [project.GetUploadableBranch(x)
+ for x in project.GetBranches().keys()]
+ br = [x for x in br if x]
+ if self.opt.current_branch:
+ br = [x for x in br if x.name == project.CurrentBranch]
+ all_branches.extend(br)
+
+ if not all_branches:
+ return
+
+ self.out.nl()
+ self.heading('Projects Overview')
+ project = None
+
+ for branch in all_branches:
+ if project != branch.project:
+ project = branch.project
+ self.out.nl()
+ self.headtext(project.relpath)
+ self.out.nl()
+
+ commits = branch.commits
+ date = branch.date
+ self.text('%s %-33s (%2d commit%s, %s)' % (
+ branch.name == project.CurrentBranch and '*' or ' ',
+ branch.name,
+ len(commits),
+ len(commits) != 1 and 's' or '',
+ date))
+ self.out.nl()
+
+ for commit in commits:
+ split = commit.split()
+ self.text('{0:38}{1} '.format('','-'))
+ self.sha(split[0] + " ")
+ self.text(" ".join(split[1:]))
+ self.out.nl()
diff --git a/subcmds/init.py b/subcmds/init.py
index b6b9807..1131260 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import os
import platform
import re
@@ -117,18 +118,22 @@
dest='config_name', action="store_true", default=False,
help='Always prompt for name/e-mail')
+ def _RegisteredEnvironmentOptions(self):
+ return {'REPO_MANIFEST_URL': 'manifest_url',
+ 'REPO_MIRROR_LOCATION': 'reference'}
+
def _SyncManifest(self, opt):
m = self.manifest.manifestProject
is_new = not m.Exists
if is_new:
if not opt.manifest_url:
- print >>sys.stderr, 'fatal: manifest url (-u) is required.'
+ print('fatal: manifest url (-u) is required.', file=sys.stderr)
sys.exit(1)
if not opt.quiet:
- print >>sys.stderr, 'Get %s' \
- % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url)
+ print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url),
+ file=sys.stderr)
m._InitGitDir()
if opt.manifest_branch:
@@ -147,7 +152,7 @@
r.ResetFetch()
r.Save()
- groups = re.split('[,\s]+', opt.groups)
+ groups = re.split(r'[,\s]+', opt.groups)
all_platforms = ['linux', 'darwin']
platformize = lambda x: 'platform-' + x
if opt.platform == 'auto':
@@ -159,7 +164,7 @@
elif opt.platform in all_platforms:
groups.extend(platformize(opt.platform))
elif opt.platform != 'none':
- print >>sys.stderr, 'fatal: invalid platform flag'
+ print('fatal: invalid platform flag', file=sys.stderr)
sys.exit(1)
groups = [x for x in groups if x]
@@ -175,12 +180,15 @@
if is_new:
m.config.SetString('repo.mirror', 'true')
else:
- print >>sys.stderr, 'fatal: --mirror not supported on existing client'
+ print('fatal: --mirror is only supported when initializing a new '
+ 'workspace.', file=sys.stderr)
+ print('Either delete the .repo folder in this workspace, or initialize '
+ 'in another location.', file=sys.stderr)
sys.exit(1)
if not m.Sync_NetworkHalf(is_new=is_new):
r = m.GetRemote(m.remote.name)
- print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url
+ print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
# Better delete the manifest git dir if we created it; otherwise next
# time (when user fixes problems) we won't go through the "is_new" logic.
@@ -197,19 +205,19 @@
if is_new or m.CurrentBranch is None:
if not m.StartBranch('default'):
- print >>sys.stderr, 'fatal: cannot create default in manifest'
+ print('fatal: cannot create default in manifest', file=sys.stderr)
sys.exit(1)
def _LinkManifest(self, name):
if not name:
- print >>sys.stderr, 'fatal: manifest name (-m) is required.'
+ print('fatal: manifest name (-m) is required.', file=sys.stderr)
sys.exit(1)
try:
self.manifest.Link(name)
except ManifestParseError as e:
- print >>sys.stderr, "fatal: manifest '%s' not available" % name
- print >>sys.stderr, 'fatal: %s' % str(e)
+ print("fatal: manifest '%s' not available" % name, file=sys.stderr)
+ print('fatal: %s' % str(e), file=sys.stderr)
sys.exit(1)
def _Prompt(self, prompt, value):
@@ -231,24 +239,24 @@
mp.config.SetString('user.name', gc.GetString('user.name'))
mp.config.SetString('user.email', gc.GetString('user.email'))
- print ''
- print 'Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
- mp.config.GetString('user.email'))
- print 'If you want to change this, please re-run \'repo init\' with --config-name'
+ print()
+ print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
+ mp.config.GetString('user.email')))
+ print('If you want to change this, please re-run \'repo init\' with --config-name')
return False
def _ConfigureUser(self):
mp = self.manifest.manifestProject
while True:
- print ''
+ print()
name = self._Prompt('Your Name', mp.UserName)
email = self._Prompt('Your Email', mp.UserEmail)
- print ''
- print 'Your identity is: %s <%s>' % (name, email)
+ print()
+ print('Your identity is: %s <%s>' % (name, email))
sys.stdout.write('is this correct [y/N]? ')
- a = sys.stdin.readline().strip()
+ a = sys.stdin.readline().strip().lower()
if a in ('yes', 'y', 't', 'true'):
break
@@ -274,17 +282,17 @@
self._on = True
out = _Test()
- print ''
- print "Testing colorized output (for 'repo diff', 'repo status'):"
+ print()
+ print("Testing colorized output (for 'repo diff', 'repo status'):")
- for c in ['black','red','green','yellow','blue','magenta','cyan']:
+ for c in ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan']:
out.write(' ')
out.printer(fg=c)(' %-6s ', c)
out.write(' ')
out.printer(fg='white', bg='black')(' %s ' % 'white')
out.nl()
- for c in ['bold','dim','ul','reverse']:
+ for c in ['bold', 'dim', 'ul', 'reverse']:
out.write(' ')
out.printer(fg='black', attr=c)(' %-6s ', c)
out.nl()
@@ -313,6 +321,23 @@
# We store the depth in the main manifest project.
self.manifest.manifestProject.config.SetString('repo.depth', depth)
+ def _DisplayResult(self):
+ if self.manifest.IsMirror:
+ init_type = 'mirror '
+ else:
+ init_type = ''
+
+ print()
+ print('repo %shas been initialized in %s'
+ % (init_type, self.manifest.topdir))
+
+ current_dir = os.getcwd()
+ if current_dir != self.manifest.topdir:
+ print('If this is not the directory in which you want to initialize '
+ 'repo, please run:')
+ print(' rm -r %s/.repo' % self.manifest.topdir)
+ print('and try again.')
+
def Execute(self, opt, args):
git_require(MIN_GIT_VERSION, fail=True)
@@ -329,10 +354,4 @@
self._ConfigureDepth(opt)
- if self.manifest.IsMirror:
- init_type = 'mirror '
- else:
- init_type = ''
-
- print ''
- print 'repo %sinitialized in %s' % (init_type, self.manifest.topdir)
+ self._DisplayResult()
diff --git a/subcmds/list.py b/subcmds/list.py
index 6058a75..0d5c27f 100644
--- a/subcmds/list.py
+++ b/subcmds/list.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import re
from command import Command, MirrorSafeCommand
@@ -64,7 +65,7 @@
lines.append("%s : %s" % (_getpath(project), project.name))
lines.sort()
- print '\n'.join(lines)
+ print('\n'.join(lines))
def FindProjects(self, args):
result = []
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index 5592a37..5ceeb12 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import os
import sys
@@ -69,7 +70,7 @@
peg_rev_upstream = opt.peg_rev_upstream)
fd.close()
if opt.output_file != '-':
- print >>sys.stderr, 'Saved manifest to %s' % opt.output_file
+ print('Saved manifest to %s' % opt.output_file, file=sys.stderr)
def Execute(self, opt, args):
if args:
@@ -79,6 +80,6 @@
self._Output(opt)
return
- print >>sys.stderr, 'error: no operation to perform'
- print >>sys.stderr, 'error: see repo help manifest'
+ print('error: no operation to perform', file=sys.stderr)
+ print('error: see repo help manifest', file=sys.stderr)
sys.exit(1)
diff --git a/subcmds/overview.py b/subcmds/overview.py
index a509bd9..418459a 100644
--- a/subcmds/overview.py
+++ b/subcmds/overview.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
from color import Coloring
from command import PagedCommand
@@ -54,8 +55,11 @@
def __init__(self, config):
Coloring.__init__(self, config, 'status')
self.project = self.printer('header', attr='bold')
+ self.text = self.printer('text')
out = Report(all_branches[0].project.config)
+ out.text("Deprecated. See repo info -o.")
+ out.nl()
out.project('Projects Overview')
out.nl()
@@ -70,11 +74,11 @@
commits = branch.commits
date = branch.date
- print '%s %-33s (%2d commit%s, %s)' % (
+ print('%s %-33s (%2d commit%s, %s)' % (
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
len(commits),
len(commits) != 1 and 's' or ' ',
- date)
+ date))
for commit in commits:
- print '%-35s - %s' % ('', commit)
+ print('%-35s - %s' % ('', commit))
diff --git a/subcmds/prune.py b/subcmds/prune.py
index c50a550..39c571a 100644
--- a/subcmds/prune.py
+++ b/subcmds/prune.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
from color import Coloring
from command import PagedCommand
@@ -51,9 +52,9 @@
commits = branch.commits
date = branch.date
- print '%s %-33s (%2d commit%s, %s)' % (
+ print('%s %-33s (%2d commit%s, %s)' % (
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
len(commits),
len(commits) != 1 and 's' or ' ',
- date)
+ date))
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index a8d58cd..06cda22 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from command import Command
@@ -59,14 +60,16 @@
one_project = len(all_projects) == 1
if opt.interactive and not one_project:
- print >>sys.stderr, 'error: interactive rebase not supported with multiple projects'
+ print('error: interactive rebase not supported with multiple projects',
+ file=sys.stderr)
return -1
for project in all_projects:
cb = project.CurrentBranch
if not cb:
if one_project:
- print >>sys.stderr, "error: project %s has a detatched HEAD" % project.relpath
+ print("error: project %s has a detatched HEAD" % project.relpath,
+ file=sys.stderr)
return -1
# ignore branches with detatched HEADs
continue
@@ -74,7 +77,8 @@
upbranch = project.GetBranch(cb)
if not upbranch.LocalMerge:
if one_project:
- print >>sys.stderr, "error: project %s does not track any remote branches" % project.relpath
+ print("error: project %s does not track any remote branches"
+ % project.relpath, file=sys.stderr)
return -1
# ignore branches without remotes
continue
@@ -101,8 +105,8 @@
args.append(upbranch.LocalMerge)
- print >>sys.stderr, '# %s: rebasing %s -> %s' % \
- (project.relpath, cb, upbranch.LocalMerge)
+ print('# %s: rebasing %s -> %s'
+ % (project.relpath, cb, upbranch.LocalMerge), file=sys.stderr)
needs_stash = False
if opt.auto_stash:
diff --git a/subcmds/selfupdate.py b/subcmds/selfupdate.py
index 46aa3a1..d12e08d 100644
--- a/subcmds/selfupdate.py
+++ b/subcmds/selfupdate.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
from optparse import SUPPRESS_HELP
import sys
@@ -52,7 +53,7 @@
else:
if not rp.Sync_NetworkHalf():
- print >>sys.stderr, "error: can't update repo"
+ print("error: can't update repo", file=sys.stderr)
sys.exit(1)
rp.bare_git.gc('--auto')
diff --git a/subcmds/stage.py b/subcmds/stage.py
index 2ec4806..ff15ee0 100644
--- a/subcmds/stage.py
+++ b/subcmds/stage.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from color import Coloring
@@ -50,7 +51,7 @@
def _Interactive(self, opt, args):
all_projects = filter(lambda x: x.IsDirty(), self.GetProjects(args))
if not all_projects:
- print >>sys.stderr,'no projects have uncommitted modifications'
+ print('no projects have uncommitted modifications', file=sys.stderr)
return
out = _ProjectList(self.manifest.manifestProject.config)
@@ -58,7 +59,7 @@
out.header(' %s', 'project')
out.nl()
- for i in xrange(0, len(all_projects)):
+ for i in range(len(all_projects)):
p = all_projects[i]
out.write('%3d: %s', i + 1, p.relpath + '/')
out.nl()
@@ -101,7 +102,7 @@
if len(p) == 1:
_AddI(p[0])
continue
- print 'Bye.'
+ print('Bye.')
def _AddI(project):
p = GitCommand(project, ['add', '--interactive'], bare=False)
diff --git a/subcmds/start.py b/subcmds/start.py
index be64531..2d723fc 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from command import Command
from git_config import IsId
@@ -41,7 +42,7 @@
nb = args[0]
if not git.check_ref_format('heads/%s' % nb):
- print >>sys.stderr, "error: '%s' is not a valid name" % nb
+ print("error: '%s' is not a valid name" % nb, file=sys.stderr)
sys.exit(1)
err = []
@@ -49,7 +50,7 @@
if not opt.all:
projects = args[1:]
if len(projects) < 1:
- print >>sys.stderr, "error: at least one project must be specified"
+ print("error: at least one project must be specified", file=sys.stderr)
sys.exit(1)
all_projects = self.GetProjects(projects)
@@ -67,7 +68,6 @@
if err:
for p in err:
- print >>sys.stderr,\
- "error: %s/: cannot start %s" \
- % (p.relpath, nb)
+ print("error: %s/: cannot start %s" % (p.relpath, nb),
+ file=sys.stderr)
sys.exit(1)
diff --git a/subcmds/status.py b/subcmds/status.py
index 7611621..cce00c8 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -20,10 +20,14 @@
except ImportError:
import dummy_threading as _threading
+import glob
import itertools
+import os
import sys
import StringIO
+from color import Coloring
+
class Status(PagedCommand):
common = True
helpSummary = "Show the working tree status"
@@ -39,6 +43,13 @@
The -j/--jobs option can be used to run multiple status queries
in parallel.
+The -o/--orphans option can be used to show objects that are in
+the working directory, but not associated with a repo project.
+This includes unmanaged top-level files and directories, but also
+includes deeper items. For example, if dir/subdir/proj1 and
+dir/subdir/proj2 are repo projects, dir/subdir/proj3 will be shown
+if it is not known to repo.
+
Status Display
--------------
@@ -76,6 +87,9 @@
p.add_option('-j', '--jobs',
dest='jobs', action='store', type='int', default=2,
help="number of projects to check simultaneously")
+ p.add_option('-o', '--orphans',
+ dest='orphans', action='store_true',
+ help="include objects in working directory outside of repo projects")
def _StatusHelper(self, project, clean_counter, sem, output):
"""Obtains the status for a specific project.
@@ -97,6 +111,22 @@
finally:
sem.release()
+ def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring):
+ """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):
+ outstring.write(''.join([status_header, item]))
+ continue
+ if item in proj_dirs:
+ continue
+ if item in proj_dirs_parents:
+ self._FindOrphans(glob.glob('%s/.*' % item) + \
+ glob.glob('%s/*' % item), \
+ proj_dirs, proj_dirs_parents, outstring)
+ continue
+ outstring.write(''.join([status_header, item, '/']))
+
def Execute(self, opt, args):
all_projects = self.GetProjects(args)
counter = itertools.count()
@@ -129,4 +159,46 @@
output.dump(sys.stdout)
output.close()
if len(all_projects) == counter.next():
- print 'nothing to commit (working directory clean)'
+ print('nothing to commit (working directory clean)')
+
+ if opt.orphans:
+ proj_dirs = set()
+ proj_dirs_parents = set()
+ for project in self.GetProjects(None, missing_ok=True):
+ proj_dirs.add(project.relpath)
+ (head, _tail) = os.path.split(project.relpath)
+ while head != "":
+ proj_dirs_parents.add(head)
+ (head, _tail) = os.path.split(head)
+ proj_dirs.add('.repo')
+
+ class StatusColoring(Coloring):
+ def __init__(self, config):
+ Coloring.__init__(self, config, 'status')
+ self.project = self.printer('header', attr = 'bold')
+ self.untracked = self.printer('untracked', fg = 'red')
+
+ orig_path = os.getcwd()
+ try:
+ os.chdir(self.manifest.topdir)
+
+ outstring = StringIO.StringIO()
+ self._FindOrphans(glob.glob('.*') + \
+ glob.glob('*'), \
+ proj_dirs, proj_dirs_parents, outstring)
+
+ if outstring.buflist:
+ output = StatusColoring(self.manifest.globalConfig)
+ output.project('Objects not within a project (orphans)')
+ output.nl()
+ for entry in outstring.buflist:
+ output.untracked(entry)
+ output.nl()
+ else:
+ print('No orphan files or directories')
+
+ outstring.close()
+
+ finally:
+ # Restore CWD.
+ os.chdir(orig_path)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index d4637d0..228a279 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import netrc
from optparse import SUPPRESS_HELP
import os
@@ -44,13 +45,13 @@
except ImportError:
multiprocessing = None
-from git_command import GIT
+from git_command import GIT, git_require
from git_refs import R_HEADS, HEAD
from main import WrapperModule
from project import Project
from project import RemoteSpec
from command import Command, MirrorSafeCommand
-from error import RepoChangedException, GitError
+from error import RepoChangedException, GitError, ManifestParseError
from project import SyncBuffer
from progress import Progress
@@ -113,6 +114,9 @@
may be necessary if there are problems with the local Python
HTTP client or proxy configuration, but the Git binary works.
+The --fetch-submodules option enables fetching Git submodules
+of a project from server.
+
SSH Connections
---------------
@@ -144,27 +148,30 @@
"""
def _Options(self, p, show_smart=True):
- self.jobs = self.manifest.default.sync_j
+ try:
+ self.jobs = self.manifest.default.sync_j
+ except ManifestParseError:
+ self.jobs = 1
p.add_option('-f', '--force-broken',
dest='force_broken', action='store_true',
help="continue sync even if a project fails to sync")
- p.add_option('-l','--local-only',
+ p.add_option('-l', '--local-only',
dest='local_only', action='store_true',
help="only update working tree, don't fetch")
- p.add_option('-n','--network-only',
+ p.add_option('-n', '--network-only',
dest='network_only', action='store_true',
help="fetch only, don't update working tree")
- p.add_option('-d','--detach',
+ p.add_option('-d', '--detach',
dest='detach_head', action='store_true',
help='detach projects back to manifest revision')
- p.add_option('-c','--current-branch',
+ p.add_option('-c', '--current-branch',
dest='current_branch_only', action='store_true',
help='fetch only current branch from server')
- p.add_option('-q','--quiet',
+ p.add_option('-q', '--quiet',
dest='quiet', action='store_true',
help='be more quiet')
- p.add_option('-j','--jobs',
+ p.add_option('-j', '--jobs',
dest='jobs', action='store', type='int',
help="projects to fetch simultaneously (default %d)" % self.jobs)
p.add_option('-m', '--manifest-name',
@@ -173,6 +180,15 @@
p.add_option('--no-clone-bundle',
dest='no_clone_bundle', action='store_true',
help='disable use of /clone.bundle on HTTP/HTTPS')
+ p.add_option('-u', '--manifest-server-username', action='store',
+ dest='manifest_server_username',
+ help='username to authenticate with the manifest server')
+ p.add_option('-p', '--manifest-server-password', action='store',
+ dest='manifest_server_password',
+ help='password to authenticate with the manifest server')
+ p.add_option('--fetch-submodules',
+ dest='fetch_submodules', action='store_true',
+ help='fetch submodules from server')
if show_smart:
p.add_option('-s', '--smart-sync',
dest='smart_sync', action='store_true',
@@ -180,12 +196,6 @@
p.add_option('-t', '--smart-tag',
dest='smart_tag', action='store',
help='smart sync using manifest from a known tag')
- p.add_option('-u', '--manifest-server-username', action='store',
- dest='manifest_server_username',
- help='username to authenticate with the manifest server')
- p.add_option('-p', '--manifest-server-password', action='store',
- dest='manifest_server_password',
- help='password to authenticate with the manifest server')
g = p.add_option_group('repo Version options')
g.add_option('--no-repo-verify',
@@ -196,61 +206,62 @@
help=SUPPRESS_HELP)
def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
- """Main function of the fetch threads when jobs are > 1.
+ """Main function of the fetch threads when jobs are > 1.
- Args:
- opt: Program options returned from optparse. See _Options().
- project: Project object for the project to fetch.
- lock: Lock for accessing objects that are shared amongst multiple
- _FetchHelper() threads.
- fetched: set object that we will add project.gitdir to when we're done
- (with our lock held).
- pm: Instance of a Project object. We will call pm.update() (with our
- lock held).
- sem: We'll release() this semaphore when we exit so that another thread
- can be started up.
- err_event: We'll set this event in the case of an error (after printing
- out info about the error).
- """
- # We'll set to true once we've locked the lock.
- did_lock = False
+ Args:
+ opt: Program options returned from optparse. See _Options().
+ project: Project object for the project to fetch.
+ lock: Lock for accessing objects that are shared amongst multiple
+ _FetchHelper() threads.
+ fetched: set object that we will add project.gitdir to when we're done
+ (with our lock held).
+ pm: Instance of a Project object. We will call pm.update() (with our
+ lock held).
+ sem: We'll release() this semaphore when we exit so that another thread
+ can be started up.
+ err_event: We'll set this event in the case of an error (after printing
+ out info about the error).
+ """
+ # We'll set to true once we've locked the lock.
+ did_lock = False
- # Encapsulate everything in a try/except/finally so that:
- # - We always set err_event in the case of an exception.
- # - We always make sure we call sem.release().
- # - We always make sure we unlock the lock if we locked it.
+ # Encapsulate everything in a try/except/finally so that:
+ # - We always set err_event in the case of an exception.
+ # - We always make sure we call sem.release().
+ # - We always make sure we unlock the lock if we locked it.
+ try:
try:
- try:
- start = time.time()
- success = project.Sync_NetworkHalf(
- quiet=opt.quiet,
- current_branch_only=opt.current_branch_only,
- clone_bundle=not opt.no_clone_bundle)
- self._fetch_times.Set(project, time.time() - start)
+ start = time.time()
+ success = project.Sync_NetworkHalf(
+ quiet=opt.quiet,
+ current_branch_only=opt.current_branch_only,
+ clone_bundle=not opt.no_clone_bundle)
+ self._fetch_times.Set(project, time.time() - start)
- # Lock around all the rest of the code, since printing, updating a set
- # and Progress.update() are not thread safe.
- lock.acquire()
- did_lock = True
+ # Lock around all the rest of the code, since printing, updating a set
+ # and Progress.update() are not thread safe.
+ lock.acquire()
+ did_lock = True
- if not success:
- print >>sys.stderr, 'error: Cannot fetch %s' % project.name
- if opt.force_broken:
- print >>sys.stderr, 'warn: --force-broken, continuing to sync'
- else:
- raise _FetchError()
+ if not success:
+ print('error: Cannot fetch %s' % project.name, file=sys.stderr)
+ if opt.force_broken:
+ print('warn: --force-broken, continuing to sync',
+ file=sys.stderr)
+ else:
+ raise _FetchError()
- fetched.add(project.gitdir)
- pm.update()
- except _FetchError:
- err_event.set()
- except:
- err_event.set()
- raise
- finally:
- if did_lock:
- lock.release()
- sem.release()
+ fetched.add(project.gitdir)
+ pm.update()
+ except _FetchError:
+ err_event.set()
+ except:
+ err_event.set()
+ raise
+ finally:
+ if did_lock:
+ lock.release()
+ sem.release()
def _Fetch(self, projects, opt):
fetched = set()
@@ -265,9 +276,9 @@
clone_bundle=not opt.no_clone_bundle):
fetched.add(project.gitdir)
else:
- print >>sys.stderr, 'error: Cannot fetch %s' % project.name
+ print('error: Cannot fetch %s' % project.name, file=sys.stderr)
if opt.force_broken:
- print >>sys.stderr, 'warn: --force-broken, continuing to sync'
+ print('warn: --force-broken, continuing to sync', file=sys.stderr)
else:
sys.exit(1)
else:
@@ -300,7 +311,7 @@
# If we saw an error, exit with code 1 so that other scripts can check.
if err_event.isSet():
- print >>sys.stderr, '\nerror: Exited sync due to fetch errors'
+ print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
sys.exit(1)
pm.end()
@@ -310,7 +321,8 @@
return fetched
def _GCProjects(self, projects):
- if multiprocessing:
+ has_dash_c = git_require((1, 7, 2))
+ if multiprocessing and has_dash_c:
cpu_count = multiprocessing.cpu_count()
else:
cpu_count = 1
@@ -352,7 +364,7 @@
t.join()
if err_event.isSet():
- print >>sys.stderr, '\nerror: Exited sync due to gc errors'
+ print('\nerror: Exited sync due to gc errors', file=sys.stderr)
sys.exit(1)
def UpdateProjectList(self):
@@ -376,34 +388,36 @@
if path not in new_project_paths:
# If the path has already been deleted, we don't need to do it
if os.path.exists(self.manifest.topdir + '/' + path):
- project = Project(
- manifest = self.manifest,
- name = path,
- remote = RemoteSpec('origin'),
- gitdir = os.path.join(self.manifest.topdir,
- path, '.git'),
- worktree = os.path.join(self.manifest.topdir, path),
- relpath = path,
- revisionExpr = 'HEAD',
- revisionId = None,
- groups = None)
+ project = Project(
+ manifest = self.manifest,
+ name = path,
+ remote = RemoteSpec('origin'),
+ gitdir = os.path.join(self.manifest.topdir,
+ path, '.git'),
+ worktree = os.path.join(self.manifest.topdir, path),
+ relpath = path,
+ revisionExpr = 'HEAD',
+ revisionId = None,
+ groups = None)
- if project.IsDirty():
- print >>sys.stderr, 'error: Cannot remove project "%s": \
-uncommitted changes are present' % project.relpath
- print >>sys.stderr, ' commit changes, then run sync again'
- return -1
- else:
- print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
- shutil.rmtree(project.worktree)
- # Try deleting parent subdirs if they are empty
- project_dir = os.path.dirname(project.worktree)
- while project_dir != self.manifest.topdir:
- try:
- os.rmdir(project_dir)
- except OSError:
- break
- project_dir = os.path.dirname(project_dir)
+ if project.IsDirty():
+ print('error: Cannot remove project "%s": uncommitted changes'
+ 'are present' % project.relpath, file=sys.stderr)
+ print(' commit changes, then run sync again',
+ file=sys.stderr)
+ return -1
+ else:
+ print('Deleting obsolete path %s' % project.worktree,
+ file=sys.stderr)
+ shutil.rmtree(project.worktree)
+ # Try deleting parent subdirs if they are empty
+ project_dir = os.path.dirname(project.worktree)
+ while project_dir != self.manifest.topdir:
+ try:
+ os.rmdir(project_dir)
+ except OSError:
+ break
+ project_dir = os.path.dirname(project_dir)
new_project_paths.sort()
fd = open(file_path, 'w')
@@ -422,24 +436,24 @@
self.jobs = min(self.jobs, (soft_limit - 5) / 3)
if opt.network_only and opt.detach_head:
- print >>sys.stderr, 'error: cannot combine -n and -d'
+ print('error: cannot combine -n and -d', file=sys.stderr)
sys.exit(1)
if opt.network_only and opt.local_only:
- print >>sys.stderr, 'error: cannot combine -n and -l'
+ print('error: cannot combine -n and -l', file=sys.stderr)
sys.exit(1)
if opt.manifest_name and opt.smart_sync:
- print >>sys.stderr, 'error: cannot combine -m and -s'
+ print('error: cannot combine -m and -s', file=sys.stderr)
sys.exit(1)
if opt.manifest_name and opt.smart_tag:
- print >>sys.stderr, 'error: cannot combine -m and -t'
+ print('error: cannot combine -m and -t', file=sys.stderr)
sys.exit(1)
if opt.manifest_server_username or opt.manifest_server_password:
if not (opt.smart_sync or opt.smart_tag):
- print >>sys.stderr, 'error: -u and -p may only be combined with ' \
- '-s or -t'
+ print('error: -u and -p may only be combined with -s or -t',
+ file=sys.stderr)
sys.exit(1)
if None in [opt.manifest_server_username, opt.manifest_server_password]:
- print >>sys.stderr, 'error: both -u and -p must be given'
+ print('error: both -u and -p must be given', file=sys.stderr)
sys.exit(1)
if opt.manifest_name:
@@ -447,8 +461,8 @@
if opt.smart_sync or opt.smart_tag:
if not self.manifest.manifest_server:
- print >>sys.stderr, \
- 'error: cannot smart sync: no manifest server defined in manifest'
+ print('error: cannot smart sync: no manifest server defined in'
+ 'manifest', file=sys.stderr)
sys.exit(1)
manifest_server = self.manifest.manifest_server
@@ -463,7 +477,8 @@
try:
info = netrc.netrc()
except IOError:
- print >>sys.stderr, '.netrc file does not exist or could not be opened'
+ print('.netrc file does not exist or could not be opened',
+ file=sys.stderr)
else:
try:
parse_result = urlparse.urlparse(manifest_server)
@@ -473,10 +488,10 @@
except TypeError:
# TypeError is raised when the given hostname is not present
# in the .netrc file.
- print >>sys.stderr, 'No credentials found for %s in .netrc' % \
- parse_result.hostname
+ print('No credentials found for %s in .netrc'
+ % parse_result.hostname, file=sys.stderr)
except netrc.NetrcParseError as e:
- print >>sys.stderr, 'Error parsing .netrc file: %s' % e
+ print('Error parsing .netrc file: %s' % e, file=sys.stderr)
if (username and password):
manifest_server = manifest_server.replace('://', '://%s:%s@' %
@@ -515,20 +530,21 @@
finally:
f.close()
except IOError:
- print >>sys.stderr, 'error: cannot write manifest to %s' % \
- manifest_path
+ print('error: cannot write manifest to %s' % manifest_path,
+ file=sys.stderr)
sys.exit(1)
self.manifest.Override(manifest_name)
else:
- print >>sys.stderr, 'error: %s' % manifest_str
+ print('error: %s' % manifest_str, file=sys.stderr)
sys.exit(1)
except (socket.error, IOError, xmlrpclib.Fault) as e:
- print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%s' % (
- self.manifest.manifest_server, e)
+ print('error: cannot connect to manifest server %s:\n%s'
+ % (self.manifest.manifest_server, e), file=sys.stderr)
sys.exit(1)
except xmlrpclib.ProtocolError as e:
- print >>sys.stderr, 'error: cannot connect to manifest server %s:\n%d %s' % (
- self.manifest.manifest_server, e.errcode, e.errmsg)
+ print('error: cannot connect to manifest server %s:\n%d %s'
+ % (self.manifest.manifest_server, e.errcode, e.errmsg),
+ file=sys.stderr)
sys.exit(1)
rp = self.manifest.repoProject
@@ -552,7 +568,9 @@
self.manifest._Unload()
if opt.jobs is None:
self.jobs = self.manifest.default.sync_j
- all_projects = self.GetProjects(args, missing_ok=True)
+ all_projects = self.GetProjects(args,
+ missing_ok=True,
+ submodules_ok=opt.fetch_submodules)
self._fetch_times = _FetchTimes(self.manifest)
if not opt.local_only:
@@ -563,12 +581,33 @@
to_fetch.extend(all_projects)
to_fetch.sort(key=self._fetch_times.Get, reverse=True)
- self._Fetch(to_fetch, opt)
+ fetched = self._Fetch(to_fetch, opt)
_PostRepoFetch(rp, opt.no_repo_verify)
if opt.network_only:
# bail out now; the rest touches the working tree
return
+ # Iteratively fetch missing and/or nested unregistered submodules
+ previously_missing_set = set()
+ while True:
+ self.manifest._Unload()
+ all_projects = self.GetProjects(args,
+ missing_ok=True,
+ submodules_ok=opt.fetch_submodules)
+ missing = []
+ for project in all_projects:
+ if project.gitdir not in fetched:
+ missing.append(project)
+ if not missing:
+ break
+ # Stop us from non-stopped fetching actually-missing repos: If set of
+ # missing repos has not been changed from last fetch, we break.
+ missing_set = set(p.name for p in missing)
+ if previously_missing_set == missing_set:
+ break
+ previously_missing_set = missing_set
+ fetched.update(self._Fetch(missing, opt))
+
if self.manifest.IsMirror:
# bail out now, we have no working tree
return
@@ -584,14 +623,14 @@
if project.worktree:
project.Sync_LocalHalf(syncbuf)
pm.end()
- print >>sys.stderr
+ print(file=sys.stderr)
if not syncbuf.Finish():
sys.exit(1)
# If there's a notice that's supposed to print at the end of the sync, print
# it now...
if self.manifest.notice:
- print self.manifest.notice
+ print(self.manifest.notice)
def _PostRepoUpgrade(manifest, quiet=False):
wrapper = WrapperModule()
@@ -603,27 +642,28 @@
def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
if rp.HasChanges:
- print >>sys.stderr, 'info: A new version of repo is available'
- print >>sys.stderr, ''
+ print('info: A new version of repo is available', file=sys.stderr)
+ print(file=sys.stderr)
if no_repo_verify or _VerifyTag(rp):
syncbuf = SyncBuffer(rp.config)
rp.Sync_LocalHalf(syncbuf)
if not syncbuf.Finish():
sys.exit(1)
- print >>sys.stderr, 'info: Restarting repo with latest version'
+ print('info: Restarting repo with latest version', file=sys.stderr)
raise RepoChangedException(['--repo-upgraded'])
else:
- print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
+ print('warning: Skipped upgrade to unverified version', file=sys.stderr)
else:
if verbose:
- print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
+ print('repo version %s is current' % rp.work_git.describe(HEAD),
+ file=sys.stderr)
def _VerifyTag(project):
gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
if not os.path.exists(gpg_dir):
- print >>sys.stderr,\
-"""warning: GnuPG was not available during last "repo init"
-warning: Cannot automatically authenticate repo."""
+ print('warning: GnuPG was not available during last "repo init"\n'
+ 'warning: Cannot automatically authenticate repo."""',
+ file=sys.stderr)
return True
try:
@@ -637,10 +677,9 @@
if rev.startswith(R_HEADS):
rev = rev[len(R_HEADS):]
- print >>sys.stderr
- print >>sys.stderr,\
- "warning: project '%s' branch '%s' is not signed" \
- % (project.name, rev)
+ print(file=sys.stderr)
+ print("warning: project '%s' branch '%s' is not signed"
+ % (project.name, rev), file=sys.stderr)
return False
env = os.environ.copy()
@@ -659,10 +698,10 @@
proc.stderr.close()
if proc.wait() != 0:
- print >>sys.stderr
- print >>sys.stderr, out
- print >>sys.stderr, err
- print >>sys.stderr
+ print(file=sys.stderr)
+ print(out, file=sys.stderr)
+ print(err, file=sys.stderr)
+ print(file=sys.stderr)
return False
return True
@@ -696,7 +735,7 @@
try:
try:
self._times = pickle.load(f)
- except:
+ except IOError:
try:
os.remove(self._path)
except OSError:
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 84a5e44..e314032 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import copy
import re
import sys
@@ -26,16 +27,18 @@
def _ConfirmManyUploads(multiple_branches=False):
if multiple_branches:
- print "ATTENTION: One or more branches has an unusually high number of commits."
+ print('ATTENTION: One or more branches has an unusually high number'
+ 'of commits.')
else:
- print "ATTENTION: You are uploading an unusually high number of commits."
- print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
+ print('ATTENTION: You are uploading an unusually high number of commits.')
+ print('YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across'
+ 'branches?)')
answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
return answer == "yes"
def _die(fmt, *args):
msg = fmt % args
- print >>sys.stderr, 'error: %s' % msg
+ print('error: %s' % msg, file=sys.stderr)
sys.exit(1)
def _SplitEmails(values):
@@ -47,7 +50,7 @@
class Upload(InteractiveCommand):
common = True
helpSummary = "Upload changes for code review"
- helpUsage="""
+ helpUsage = """
%prog [--re --cc] [<project>]...
"""
helpDescription = """
@@ -176,18 +179,18 @@
date = branch.date
commit_list = branch.commits
- print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr)
- print ' branch %s (%2d commit%s, %s):' % (
+ print('Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr))
+ print(' branch %s (%2d commit%s, %s):' % (
name,
len(commit_list),
len(commit_list) != 1 and 's' or '',
- date)
+ date))
for commit in commit_list:
- print ' %s' % commit
+ print(' %s' % commit)
sys.stdout.write('to %s (y/N)? ' % remote.review)
- answer = sys.stdin.readline().strip()
- answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
+ answer = sys.stdin.readline().strip().lower()
+ answer = answer in ('y', 'yes', '1', 'true', 't')
if answer:
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
@@ -297,7 +300,7 @@
try:
# refs/changes/XYZ/N --> XYZ
return refs.get(last_pub).split('/')[-2]
- except:
+ except (AttributeError, IndexError):
return ""
def _UploadAndReport(self, opt, todo, original_people):
@@ -309,23 +312,23 @@
# Check if there are local changes that may have been forgotten
if branch.project.HasChanges():
- key = 'review.%s.autoupload' % branch.project.remote.review
- answer = branch.project.config.GetBoolean(key)
+ key = 'review.%s.autoupload' % branch.project.remote.review
+ answer = branch.project.config.GetBoolean(key)
- # if they want to auto upload, let's not ask because it could be automated
- if answer is None:
- sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
- a = sys.stdin.readline().strip().lower()
- if a not in ('y', 'yes', 't', 'true', 'on'):
- print >>sys.stderr, "skipping upload"
- branch.uploaded = False
- branch.error = 'User aborted'
- continue
+ # if they want to auto upload, let's not ask because it could be automated
+ if answer is None:
+ sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
+ a = sys.stdin.readline().strip().lower()
+ if a not in ('y', 'yes', 't', 'true', 'on'):
+ print("skipping upload", file=sys.stderr)
+ branch.uploaded = False
+ branch.error = 'User aborted'
+ continue
# Check if topic branches should be sent to the server during upload
if opt.auto_topic is not True:
- key = 'review.%s.uploadtopic' % branch.project.remote.review
- opt.auto_topic = branch.project.config.GetBoolean(key)
+ key = 'review.%s.uploadtopic' % branch.project.remote.review
+ opt.auto_topic = branch.project.config.GetBoolean(key)
branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
branch.uploaded = True
@@ -334,8 +337,8 @@
branch.uploaded = False
have_errors = True
- print >>sys.stderr, ''
- print >>sys.stderr, '----------------------------------------------------------------------'
+ print(file=sys.stderr)
+ print('----------------------------------------------------------------------', file=sys.stderr)
if have_errors:
for branch in todo:
@@ -344,17 +347,19 @@
fmt = ' (%s)'
else:
fmt = '\n (%s)'
- print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
+ print(('[FAILED] %-15s %-15s' + fmt) % (
branch.project.relpath + '/', \
branch.name, \
- str(branch.error))
- print >>sys.stderr, ''
+ str(branch.error)),
+ file=sys.stderr)
+ print()
for branch in todo:
- if branch.uploaded:
- print >>sys.stderr, '[OK ] %-15s %s' % (
- branch.project.relpath + '/',
- branch.name)
+ if branch.uploaded:
+ print('[OK ] %-15s %s' % (
+ branch.project.relpath + '/',
+ branch.name),
+ file=sys.stderr)
if have_errors:
sys.exit(1)
@@ -385,17 +390,17 @@
try:
hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
except HookError as e:
- print >>sys.stderr, "ERROR: %s" % str(e)
+ print("ERROR: %s" % str(e), file=sys.stderr)
return
if opt.reviewers:
reviewers = _SplitEmails(opt.reviewers)
if opt.cc:
cc = _SplitEmails(opt.cc)
- people = (reviewers,cc)
+ people = (reviewers, cc)
if not pending:
- print >>sys.stdout, "no branches ready for upload"
+ print("no branches ready for upload", file=sys.stderr)
elif len(pending) == 1 and len(pending[0][1]) == 1:
self._SingleBranch(opt, pending[0][1][0], people)
else:
diff --git a/subcmds/version.py b/subcmds/version.py
index 243e367..01b7fd8 100644
--- a/subcmds/version.py
+++ b/subcmds/version.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
from command import Command, MirrorSafeCommand
from git_command import git
@@ -32,12 +33,12 @@
rp = self.manifest.repoProject
rem = rp.GetRemote(rp.remote.name)
- print 'repo version %s' % rp.work_git.describe(HEAD)
- print ' (from %s)' % rem.url
+ print('repo version %s' % rp.work_git.describe(HEAD))
+ print(' (from %s)' % rem.url)
if Version.wrapper_path is not None:
- print 'repo launcher version %s' % Version.wrapper_version
- print ' (from %s)' % Version.wrapper_path
+ print('repo launcher version %s' % Version.wrapper_version)
+ print(' (from %s)' % Version.wrapper_path)
- print git.version().strip()
- print 'Python %s' % sys.version
+ print(git.version().strip())
+ print('Python %s' % sys.version)
diff --git a/tests/test_git_config.py b/tests/test_git_config.py
index 5b1770e..3d4b997 100644
--- a/tests/test_git_config.py
+++ b/tests/test_git_config.py
@@ -4,49 +4,49 @@
import git_config
def fixture(*paths):
- """Return a path relative to test/fixtures.
- """
- return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
+ """Return a path relative to test/fixtures.
+ """
+ return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
class GitConfigUnitTest(unittest.TestCase):
- """Tests the GitConfig class.
+ """Tests the GitConfig class.
+ """
+ def setUp(self):
+ """Create a GitConfig object using the test.gitconfig fixture.
"""
- def setUp(self):
- """Create a GitConfig object using the test.gitconfig fixture.
- """
- config_fixture = fixture('test.gitconfig')
- self.config = git_config.GitConfig(config_fixture)
+ config_fixture = fixture('test.gitconfig')
+ self.config = git_config.GitConfig(config_fixture)
- def test_GetString_with_empty_config_values(self):
- """
- Test config entries with no value.
+ def test_GetString_with_empty_config_values(self):
+ """
+ Test config entries with no value.
- [section]
- empty
+ [section]
+ empty
- """
- val = self.config.GetString('section.empty')
- self.assertEqual(val, None)
+ """
+ val = self.config.GetString('section.empty')
+ self.assertEqual(val, None)
- def test_GetString_with_true_value(self):
- """
- Test config entries with a string value.
+ def test_GetString_with_true_value(self):
+ """
+ Test config entries with a string value.
- [section]
- nonempty = true
+ [section]
+ nonempty = true
- """
- val = self.config.GetString('section.nonempty')
- self.assertEqual(val, 'true')
+ """
+ val = self.config.GetString('section.nonempty')
+ self.assertEqual(val, 'true')
- def test_GetString_from_missing_file(self):
- """
- Test missing config file
- """
- config_fixture = fixture('not.present.gitconfig')
- config = git_config.GitConfig(config_fixture)
- val = config.GetString('empty')
- self.assertEqual(val, None)
+ def test_GetString_from_missing_file(self):
+ """
+ Test missing config file
+ """
+ config_fixture = fixture('not.present.gitconfig')
+ config = git_config.GitConfig(config_fixture)
+ val = config.GetString('empty')
+ self.assertEqual(val, None)
if __name__ == '__main__':
- unittest.main()
+ unittest.main()
diff --git a/trace.py b/trace.py
index 0376d2b..db42a68 100644
--- a/trace.py
+++ b/trace.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import print_function
import sys
import os
REPO_TRACE = 'REPO_TRACE'
@@ -31,4 +32,4 @@
def Trace(fmt, *args):
if IsTrace():
- print >>sys.stderr, fmt % args
+ print(fmt % args, file=sys.stderr)