Compare commits

..

13 Commits
v1.7 ... v1.7.3

Author SHA1 Message Date
0fc3a39829 Bump repo version to 1,10
Change-Id: Ifdc041e7152af31de413b9269f20000acd945b3b
2011-01-10 09:01:24 -08:00
c7c57e34db help: Don't show empty Summary or Description sections
Signed-off-by: Shawn O. Pearce <sop@google.com>
(cherry picked from commit 60e679209a)
2011-01-09 17:39:22 -08:00
0d2b61f11d sync: Run git gc --auto after fetch
Users may wind up with a lot of loose object content in projects they
don't frequently make changes in, but that are modified by others.

Since we bypass many git code paths that would have otherwise called
out to `git gc --auto`, its possible for these projects to have
their loose object database grow out of control.  To help prevent
that, we now invoke it ourselves during the network half of sync.

Signed-off-by: Shawn O. Pearce <sop@google.com>
(cherry picked from commit 1875ddd47c)
2011-01-09 17:39:22 -08:00
2bf9db0d3b Add "repo branch" as an alias for "repo branches"
For those of us that are used to typing "git branch".

Signed-off-by: Mike Lockwood <lockwood@android.com>
(cherry picked from commit 33f0e786bb)
2011-01-09 17:39:22 -08:00
f00e0ce556 upload: Catch and cleanly report connectivity errors
Instead of giving a Python backtrace when there is a connectivity
problem during repo upload, report that we cannot access the host,
and why, with a halfway decent error message.

Bug: REPO-45
Change-Id: I9a45b387e86e48073a2d99bd6d594c1a7d6d99d4
Signed-off-by: Shawn O. Pearce <sop@google.com>
(cherry picked from commit d2dfac81ad)
2011-01-09 17:39:22 -08:00
1b5a4a0c5d forall: Silently skip missing projects
If a project is missing locally, it might be OK to skip over it
and continue running the same command in other projects.

Bug: REPO-43
Change-Id: I64f97eb315f379ab2c51fc53d24ed340b3d09250
Signed-off-by: Shawn O. Pearce <sop@google.com>
(cherry picked from commit d4cd69bdef)
2011-01-09 17:39:22 -08:00
de8b2c4276 Fix to display the usage message of the command download when the user
don't provide any arguments to 'repo download'.

Signed-off-by: Thiago Farina <thiago.farina@gmail.com>
(cherry picked from commit 840ed0fab7)
2011-01-09 17:39:22 -08:00
727ee98a40 Use os.environ.copy() instead of dict()
Signed-off-by: Shawn O. Pearce <sop@google.com>
(cherry picked from commit 3218c13205)
2011-01-09 17:39:22 -08:00
df14a70c45 Make path references OS independent
Change-Id: I5573995adfd52fd54bddc62d1d1ea78fb1328130
(cherry picked from commit b0f9a02394)

Conflicts:

	command.py
2011-01-09 17:39:19 -08:00
f18cb76173 Encode the environment variables passed to git
Windows allows the environment to have unicode values.
This will cause Python to fail to execute the command.

Change-Id: I37d922c3d7ced0d5b4883f0220346ac42defc5e9
Signed-off-by: Shawn O. Pearce <sop@google.com>
2011-01-09 16:13:56 -08:00
d3fd537ea5 Exit with statuscode 0 for repo help init
The complete help text is printed, so the program executed successfully.

Some tools (like OpenGrok) detects the availibility of a program by
running it with a known set of options and check the return code.
It is an easy and portable way of checking for the existence of a program
instead of searching the path (and handle extensions) ourselves.

Change-Id: Ic13428c77be4a36d599ccb8c86d893308818eae3
2011-01-09 16:10:04 -08:00
0048b69c03 Fixed race condition in 'repo sync -jN' that would open multiple masters.
This fixes the SSH Control Masters to be managed in a thread-safe
fashion.  This is important because "repo sync -jN" uses threads to
sync more than one repository at the same time.  The problem didn't
show up earlier because it was masked if all of the threads tried to
connect to the same host that was used on the "repo init" line.
2010-12-21 13:39:23 -08:00
2b8db3ce3e Added feature to print a <notice> from manifest at the end of a sync.
This feature is used to convey information on a when a branch has
ceased development or if it is an experimental branch with a few
gotchas, etc.

You add it to your manifest XML by doing something like this:
<manifest>
  <notice>
    NOTE TO DEVELOPERS:
      If you checkin code, you have to pinky-swear that it contains no bugs.
      Anyone who breaks their promise will have tomatoes thrown at them in the
      team meeting.  Be sure to bring an extra set of clothes.
  </notice>

  <remote ... />
  ...
</manifest>

Carriage returns and indentation are relevant for the text in this tag.

This feature was requested by Anush Elangovan on the ChromiumOS team.
2010-11-01 15:08:06 -07:00
14 changed files with 222 additions and 89 deletions

View File

@ -74,7 +74,7 @@ class Command(object):
project = all.get(arg) project = all.get(arg)
if not project: if not project:
path = os.path.abspath(arg) path = os.path.abspath(arg).replace('\\', '/')
if not by_path: if not by_path:
by_path = dict() by_path = dict()
@ -82,13 +82,15 @@ class Command(object):
by_path[p.worktree] = p by_path[p.worktree] = p
if os.path.exists(path): if os.path.exists(path):
oldpath = None
while path \ while path \
and path != '/' \ and path != oldpath \
and path != self.manifest.topdir: and path != self.manifest.topdir:
try: try:
project = by_path[path] project = by_path[path]
break break
except KeyError: except KeyError:
oldpath = path
path = os.path.dirname(path) path = os.path.dirname(path)
else: else:
try: try:

View File

@ -20,12 +20,15 @@ A manifest XML file (e.g. 'default.xml') roughly conforms to the
following DTD: following DTD:
<!DOCTYPE manifest [ <!DOCTYPE manifest [
<!ELEMENT manifest (remote*, <!ELEMENT manifest (notice?,
remote*,
default?, default?,
manifest-server?, manifest-server?,
remove-project*, remove-project*,
project*)> project*)>
<!ELEMENT notice (#PCDATA)>
<!ELEMENT remote (EMPTY)> <!ELEMENT remote (EMPTY)>
<!ATTLIST remote name ID #REQUIRED> <!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote fetch CDATA #REQUIRED> <!ATTLIST remote fetch CDATA #REQUIRED>

View File

@ -112,6 +112,9 @@ def git_require(min_version, fail=False):
sys.exit(1) sys.exit(1)
return False return False
def _setenv(env, name, value):
env[name] = value.encode()
class GitCommand(object): class GitCommand(object):
def __init__(self, def __init__(self,
project, project,
@ -124,7 +127,7 @@ class GitCommand(object):
ssh_proxy = False, ssh_proxy = False,
cwd = None, cwd = None,
gitdir = None): gitdir = None):
env = dict(os.environ) env = os.environ.copy()
for e in [REPO_TRACE, for e in [REPO_TRACE,
GIT_DIR, GIT_DIR,
@ -137,10 +140,10 @@ class GitCommand(object):
del env[e] del env[e]
if disable_editor: if disable_editor:
env['GIT_EDITOR'] = ':' _setenv(env, 'GIT_EDITOR', ':')
if ssh_proxy: if ssh_proxy:
env['REPO_SSH_SOCK'] = ssh_sock() _setenv(env, 'REPO_SSH_SOCK', ssh_sock())
env['GIT_SSH'] = _ssh_proxy() _setenv(env, 'GIT_SSH', _ssh_proxy())
if project: if project:
if not cwd: if not cwd:
@ -151,7 +154,7 @@ class GitCommand(object):
command = [GIT] command = [GIT]
if bare: if bare:
if gitdir: if gitdir:
env[GIT_DIR] = gitdir _setenv(env, GIT_DIR, gitdir)
cwd = None cwd = None
command.extend(cmdv) command.extend(cmdv)

View File

@ -18,7 +18,13 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
try:
import threading as _threading
except ImportError:
import dummy_threading as _threading
import time import time
import urllib2
from signal import SIGTERM from signal import SIGTERM
from urllib2 import urlopen, HTTPError from urllib2 import urlopen, HTTPError
from error import GitError, UploadError from error import GitError, UploadError
@ -361,10 +367,27 @@ class RefSpec(object):
_master_processes = [] _master_processes = []
_master_keys = set() _master_keys = set()
_ssh_master = True _ssh_master = True
_master_keys_lock = None
def init_ssh():
"""Should be called once at the start of repo to init ssh master handling.
At the moment, all we do is to create our lock.
"""
global _master_keys_lock
assert _master_keys_lock is None, "Should only call init_ssh once"
_master_keys_lock = _threading.Lock()
def _open_ssh(host, port=None): def _open_ssh(host, port=None):
global _ssh_master global _ssh_master
# Acquire the lock. This is needed to prevent opening multiple masters for
# the same host when we're running "repo sync -jN" (for N > 1) _and_ the
# manifest <remote fetch="ssh://xyz"> specifies a different host from the
# one that was passed to repo init.
_master_keys_lock.acquire()
try:
# Check to see whether we already think that the master is running; if we # Check to see whether we already think that the master is running; if we
# think it's already running, return right away. # think it's already running, return right away.
if port is not None: if port is not None:
@ -429,8 +452,12 @@ def _open_ssh(host, port=None):
_master_keys.add(key) _master_keys.add(key)
time.sleep(1) time.sleep(1)
return True return True
finally:
_master_keys_lock.release()
def close_ssh(): def close_ssh():
global _master_keys_lock
terminate_ssh_clients() terminate_ssh_clients()
for p in _master_processes: for p in _master_processes:
@ -449,6 +476,9 @@ def close_ssh():
except OSError: except OSError:
pass pass
# We're done with the lock, so we can delete it.
_master_keys_lock = None
URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):') URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/') URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
@ -535,23 +565,25 @@ class Remote(object):
try: try:
info = urlopen(u).read() info = urlopen(u).read()
if info == 'NOT_AVAILABLE': if info == 'NOT_AVAILABLE':
raise UploadError('Upload over ssh unavailable') raise UploadError('%s: SSH disabled' % self.review)
if '<' in info: if '<' in info:
# Assume the server gave us some sort of HTML # Assume the server gave us some sort of HTML
# response back, like maybe a login page. # response back, like maybe a login page.
# #
raise UploadError('Cannot read %s:\n%s' % (u, info)) raise UploadError('%s: Cannot parse response' % u)
self._review_protocol = 'ssh' self._review_protocol = 'ssh'
self._review_host = info.split(" ")[0] self._review_host = info.split(" ")[0]
self._review_port = info.split(" ")[1] self._review_port = info.split(" ")[1]
except urllib2.URLError, e:
raise UploadError('%s: %s' % (self.review, e.reason[1]))
except HTTPError, e: except HTTPError, e:
if e.code == 404: if e.code == 404:
self._review_protocol = 'http-post' self._review_protocol = 'http-post'
self._review_host = None self._review_host = None
self._review_port = None self._review_port = None
else: else:
raise UploadError('Cannot guess Gerrit version') raise UploadError('Upload over ssh unavailable')
REVIEW_CACHE[u] = ( REVIEW_CACHE[u] = (
self._review_protocol, self._review_protocol,

View File

@ -28,7 +28,7 @@ import re
import sys import sys
from trace import SetTrace from trace import SetTrace
from git_config import close_ssh from git_config import init_ssh, close_ssh
from command import InteractiveCommand from command import InteractiveCommand
from command import MirrorSafeCommand from command import MirrorSafeCommand
from command import PagedCommand from command import PagedCommand
@ -61,6 +61,8 @@ class _Repo(object):
def __init__(self, repodir): def __init__(self, repodir):
self.repodir = repodir self.repodir = repodir
self.commands = all_commands self.commands = all_commands
# add 'branch' as an alias for 'branches'
all_commands['branch'] = all_commands['branches']
def _Run(self, argv): def _Run(self, argv):
name = None name = None
@ -214,6 +216,7 @@ def _Main(argv):
repo = _Repo(opt.repodir) repo = _Repo(opt.repodir)
try: try:
try: try:
init_ssh()
repo._Run(argv) repo._Run(argv)
finally: finally:
close_ssh() close_ssh()

View File

@ -107,6 +107,15 @@ class XmlManifest(object):
root = doc.createElement('manifest') root = doc.createElement('manifest')
doc.appendChild(root) doc.appendChild(root)
# Save out the notice. There's a little bit of work here to give it the
# right whitespace, which assumes that the notice is automatically indented
# by 4 by minidom.
if self.notice:
notice_element = root.appendChild(doc.createElement('notice'))
notice_lines = self.notice.splitlines()
indented_notice = ('\n'.join(" "*4 + line for line in notice_lines))[4:]
notice_element.appendChild(doc.createTextNode(indented_notice))
d = self.default d = self.default
sort_remotes = list(self.remotes.keys()) sort_remotes = list(self.remotes.keys())
sort_remotes.sort() sort_remotes.sort()
@ -179,6 +188,11 @@ class XmlManifest(object):
self._Load() self._Load()
return self._default return self._default
@property
def notice(self):
self._Load()
return self._notice
@property @property
def manifest_server(self): def manifest_server(self):
self._Load() self._Load()
@ -193,6 +207,7 @@ class XmlManifest(object):
self._projects = {} self._projects = {}
self._remotes = {} self._remotes = {}
self._default = None self._default = None
self._notice = None
self.branch = None self.branch = None
self._manifest_server = None self._manifest_server = None
@ -263,6 +278,14 @@ class XmlManifest(object):
if self._default is None: if self._default is None:
self._default = _Default() self._default = _Default()
for node in config.childNodes:
if node.nodeName == 'notice':
if self._notice is not None:
raise ManifestParseError, \
'duplicate notice in %s' % \
(self.manifestFile)
self._notice = self._ParseNotice(node)
for node in config.childNodes: for node in config.childNodes:
if node.nodeName == 'manifest-server': if node.nodeName == 'manifest-server':
url = self._reqatt(node, 'url') url = self._reqatt(node, 'url')
@ -338,6 +361,45 @@ class XmlManifest(object):
d.revisionExpr = None d.revisionExpr = None
return d return d
def _ParseNotice(self, node):
"""
reads a <notice> element from the manifest file
The <notice> element is distinct from other tags in the XML in that the
data is conveyed between the start and end tag (it's not an empty-element
tag).
The white space (carriage returns, indentation) for the notice element is
relevant and is parsed in a way that is based on how python docstrings work.
In fact, the code is remarkably similar to here:
http://www.python.org/dev/peps/pep-0257/
"""
# Get the data out of the node...
notice = node.childNodes[0].data
# Figure out minimum indentation, skipping the first line (the same line
# as the <notice> tag)...
minIndent = sys.maxint
lines = notice.splitlines()
for line in lines[1:]:
lstrippedLine = line.lstrip()
if lstrippedLine:
indent = len(line) - len(lstrippedLine)
minIndent = min(indent, minIndent)
# Strip leading / trailing blank lines and also indentation.
cleanLines = [lines[0].strip()]
for line in lines[1:]:
cleanLines.append(line[minIndent:].rstrip())
# Clear completely blank lines from front and back...
while cleanLines and not cleanLines[0]:
del cleanLines[0]
while cleanLines and not cleanLines[-1]:
del cleanLines[-1]
return '\n'.join(cleanLines)
def _ParseProject(self, node): def _ParseProject(self, node):
""" """
reads a <project> element from the manifest file reads a <project> element from the manifest file
@ -373,7 +435,7 @@ class XmlManifest(object):
worktree = None worktree = None
gitdir = os.path.join(self.topdir, '%s.git' % name) gitdir = os.path.join(self.topdir, '%s.git' % name)
else: else:
worktree = os.path.join(self.topdir, path) worktree = os.path.join(self.topdir, path).replace('\\', '/')
gitdir = os.path.join(self.repodir, 'projects/%s.git' % path) gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
project = Project(manifest = self, project = Project(manifest = self,

View File

@ -236,8 +236,8 @@ class Project(object):
self.manifest = manifest self.manifest = manifest
self.name = name self.name = name
self.remote = remote self.remote = remote
self.gitdir = gitdir self.gitdir = gitdir.replace('\\', '/')
self.worktree = worktree self.worktree = worktree.replace('\\', '/')
self.relpath = relpath self.relpath = relpath
self.revisionExpr = revisionExpr self.revisionExpr = revisionExpr

17
repo
View File

@ -28,7 +28,7 @@ if __name__ == '__main__':
del magic del magic
# increment this whenever we make important changes to this script # increment this whenever we make important changes to this script
VERSION = (1, 9) VERSION = (1, 10)
# increment this if the MAINTAINER_KEYS block is modified # increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (1,0) KEYRING_VERSION = (1,0)
@ -259,8 +259,8 @@ def _SetupGnuPG(quiet):
gpg_dir, e.strerror) gpg_dir, e.strerror)
sys.exit(1) sys.exit(1)
env = dict(os.environ) env = os.environ.copy()
env['GNUPGHOME'] = gpg_dir env['GNUPGHOME'] = gpg_dir.encode()
cmd = ['gpg', '--import'] cmd = ['gpg', '--import']
try: try:
@ -378,8 +378,8 @@ def _Verify(cwd, branch, quiet):
% (branch, cur) % (branch, cur)
print >>sys.stderr print >>sys.stderr
env = dict(os.environ) env = os.environ.copy()
env['GNUPGHOME'] = gpg_dir env['GNUPGHOME'] = gpg_dir.encode()
cmd = [GIT, 'tag', '-v', cur] cmd = [GIT, 'tag', '-v', cur]
proc = subprocess.Popen(cmd, proc = subprocess.Popen(cmd,
@ -430,10 +430,14 @@ def _FindRepo():
dir = os.getcwd() dir = os.getcwd()
repo = None repo = None
while dir != '/' and not repo: olddir = None
while dir != '/' \
and dir != olddir \
and not repo:
repo = os.path.join(dir, repodir, REPO_MAIN) repo = os.path.join(dir, repodir, REPO_MAIN)
if not os.path.isfile(repo): if not os.path.isfile(repo):
repo = None repo = None
olddir = dir
dir = os.path.dirname(dir) dir = os.path.dirname(dir)
return (repo, os.path.join(dir, repodir)) return (repo, os.path.join(dir, repodir))
@ -479,6 +483,7 @@ def _Help(args):
if args: if args:
if args[0] == 'init': if args[0] == 'init':
init_optparse.print_help() init_optparse.print_help()
sys.exit(0)
else: else:
print >>sys.stderr,\ print >>sys.stderr,\
"error: '%s' is not a bootstrap command.\n"\ "error: '%s' is not a bootstrap command.\n"\

View File

@ -36,6 +36,9 @@ makes it available in your project's local working directory.
pass pass
def _ParseChangeIds(self, args): def _ParseChangeIds(self, args):
if not args:
self.Usage()
to_get = [] to_get = []
project = None project = None

View File

@ -151,11 +151,11 @@ terminal and are not redirected.
first = True first = True
for project in self.GetProjects(args): for project in self.GetProjects(args):
env = dict(os.environ.iteritems()) env = os.environ.copy()
def setenv(name, val): def setenv(name, val):
if val is None: if val is None:
val = '' val = ''
env[name] = val env[name] = val.encode()
setenv('REPO_PROJECT', project.name) setenv('REPO_PROJECT', project.name)
setenv('REPO_PATH', project.relpath) setenv('REPO_PATH', project.relpath)
@ -169,6 +169,12 @@ terminal and are not redirected.
else: else:
cwd = project.worktree cwd = project.worktree
if not os.path.exists(cwd):
if (opt.project_header and opt.verbose) \
or not opt.project_header:
print >>sys.stderr, 'skipping %s/' % project.relpath
continue
if opt.project_header: if opt.project_header:
stdin = subprocess.PIPE stdin = subprocess.PIPE
stdout = subprocess.PIPE stdout = subprocess.PIPE

View File

@ -94,6 +94,8 @@ See 'repo help --all' for a complete list of recognized commands.
body = getattr(cmd, bodyAttr) body = getattr(cmd, bodyAttr)
except AttributeError: except AttributeError:
return return
if body == '' or body is None:
return
self.nl() self.nl()

View File

@ -55,6 +55,7 @@ need to be performed by an end-user.
print >>sys.stderr, "error: can't update repo" print >>sys.stderr, "error: can't update repo"
sys.exit(1) sys.exit(1)
rp.bare_git.gc('--auto')
_PostRepoFetch(rp, _PostRepoFetch(rp,
no_repo_verify = opt.no_repo_verify, no_repo_verify = opt.no_repo_verify,
verbose = True) verbose = True)

View File

@ -185,6 +185,8 @@ later is required to fix a server side protocol bug.
t.join() t.join()
pm.end() pm.end()
for project in projects:
project.bare_git.gc('--auto')
return fetched return fetched
def UpdateProjectList(self): def UpdateProjectList(self):
@ -269,7 +271,7 @@ uncommitted changes are present' % project.relpath
if branch.startswith(R_HEADS): if branch.startswith(R_HEADS):
branch = branch[len(R_HEADS):] branch = branch[len(R_HEADS):]
env = dict(os.environ) env = os.environ.copy()
if (env.has_key('TARGET_PRODUCT') and if (env.has_key('TARGET_PRODUCT') and
env.has_key('TARGET_BUILD_VARIANT')): env.has_key('TARGET_BUILD_VARIANT')):
target = '%s-%s' % (env['TARGET_PRODUCT'], target = '%s-%s' % (env['TARGET_PRODUCT'],
@ -361,6 +363,11 @@ uncommitted changes are present' % project.relpath
if not syncbuf.Finish(): if not syncbuf.Finish():
sys.exit(1) 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
def _PostRepoUpgrade(manifest): def _PostRepoUpgrade(manifest):
for project in manifest.projects.values(): for project in manifest.projects.values():
if project.Exists: if project.Exists:
@ -408,9 +415,9 @@ warning: Cannot automatically authenticate repo."""
% (project.name, rev) % (project.name, rev)
return False return False
env = dict(os.environ) env = os.environ.copy()
env['GIT_DIR'] = project.gitdir env['GIT_DIR'] = project.gitdir.encode()
env['GNUPGHOME'] = gpg_dir env['GNUPGHOME'] = gpg_dir.encode()
cmd = [GIT, 'tag', '-v', cur] cmd = [GIT, 'tag', '-v', cur]
proc = subprocess.Popen(cmd, proc = subprocess.Popen(cmd,

View File

@ -283,15 +283,19 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
have_errors = True have_errors = True
print >>sys.stderr, '' print >>sys.stderr, ''
print >>sys.stderr, '--------------------------------------------' print >>sys.stderr, '----------------------------------------------------------------------'
if have_errors: if have_errors:
for branch in todo: for branch in todo:
if not branch.uploaded: if not branch.uploaded:
print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % ( if len(str(branch.error)) <= 30:
fmt = ' (%s)'
else:
fmt = '\n (%s)'
print >>sys.stderr, ('[FAILED] %-15s %-15s' + fmt) % (
branch.project.relpath + '/', \ branch.project.relpath + '/', \
branch.name, \ branch.name, \
branch.error) str(branch.error))
print >>sys.stderr, '' print >>sys.stderr, ''
for branch in todo: for branch in todo: