Compare commits

...

13 Commits

Author SHA1 Message Date
0ce6ca9c7b Fix mirror clients with no worktree
Commit "Make path references OS independent" (df14a70c45)
broke mirror clients by trying to invoke replace() on None
when there is no worktree.

Change-Id: Ie0a187058358f7dcdf83119e45cc65409c980f11
2011-01-10 13:26:34 -08:00
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
13 changed files with 154 additions and 88 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

@ -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,76 +367,97 @@ 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
# Check to see whether we already think that the master is running; if we # Acquire the lock. This is needed to prevent opening multiple masters for
# think it's already running, return right away. # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
if port is not None: # manifest <remote fetch="ssh://xyz"> specifies a different host from the
key = '%s:%s' % (host, port) # one that was passed to repo init.
else: _master_keys_lock.acquire()
key = host
if key in _master_keys:
return True
if not _ssh_master \
or 'GIT_SSH' in os.environ \
or sys.platform in ('win32', 'cygwin'):
# failed earlier, or cygwin ssh can't do this
#
return False
# We will make two calls to ssh; this is the common part of both calls.
command_base = ['ssh',
'-o','ControlPath %s' % ssh_sock(),
host]
if port is not None:
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
# be important because we can't tell that that 'git@myhost.com' is the same
# as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
check_command = command_base + ['-O','check']
try: try:
Trace(': %s', ' '.join(check_command))
check_process = subprocess.Popen(check_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
check_process.communicate() # read output, but ignore it...
isnt_running = check_process.wait()
if not isnt_running: # Check to see whether we already think that the master is running; if we
# Our double-check found that the master _was_ infact running. Add to # think it's already running, return right away.
# the list of keys. if port is not None:
_master_keys.add(key) key = '%s:%s' % (host, port)
else:
key = host
if key in _master_keys:
return True return True
except Exception:
# Ignore excpetions. We we will fall back to the normal command and print
# to the log there.
pass
command = command_base[:1] + \ if not _ssh_master \
['-M', '-N'] + \ or 'GIT_SSH' in os.environ \
command_base[1:] or sys.platform in ('win32', 'cygwin'):
try: # failed earlier, or cygwin ssh can't do this
Trace(': %s', ' '.join(command)) #
p = subprocess.Popen(command) return False
except Exception, e:
_ssh_master = False
print >>sys.stderr, \
'\nwarn: cannot enable ssh control master for %s:%s\n%s' \
% (host,port, str(e))
return False
_master_processes.append(p) # We will make two calls to ssh; this is the common part of both calls.
_master_keys.add(key) command_base = ['ssh',
time.sleep(1) '-o','ControlPath %s' % ssh_sock(),
return True host]
if port is not None:
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
# be important because we can't tell that that 'git@myhost.com' is the same
# as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
check_command = command_base + ['-O','check']
try:
Trace(': %s', ' '.join(check_command))
check_process = subprocess.Popen(check_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
check_process.communicate() # read output, but ignore it...
isnt_running = check_process.wait()
if not isnt_running:
# Our double-check found that the master _was_ infact running. Add to
# the list of keys.
_master_keys.add(key)
return True
except Exception:
# Ignore excpetions. We we will fall back to the normal command and print
# to the log there.
pass
command = command_base[:1] + \
['-M', '-N'] + \
command_base[1:]
try:
Trace(': %s', ' '.join(command))
p = subprocess.Popen(command)
except Exception, e:
_ssh_master = False
print >>sys.stderr, \
'\nwarn: cannot enable ssh control master for %s:%s\n%s' \
% (host,port, str(e))
return False
_master_processes.append(p)
_master_keys.add(key)
time.sleep(1)
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

@ -435,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,11 @@ 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 if worktree:
self.worktree = worktree.replace('\\', '/')
else:
self.worktree = None
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'],
@ -413,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: