mirror of
https://gerrit.googlesource.com/git-repo
synced 2024-12-21 07:16:21 +00:00
Automatically use SSH control master support during sync
By creating a background ssh "control master" process which lives for the duration of our sync cycle we can easily cut the time for a no-op sync of 132 projects from 60s to 18s. Bug: REPO-11 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
parent
8bd5e60b16
commit
fb2316146f
@ -16,6 +16,7 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
from error import GitError
|
||||
from trace import REPO_TRACE, IsTrace, Trace
|
||||
|
||||
@ -26,6 +27,27 @@ GIT_DIR = 'GIT_DIR'
|
||||
LAST_GITDIR = None
|
||||
LAST_CWD = None
|
||||
|
||||
_ssh_proxy_path = None
|
||||
_ssh_sock_path = None
|
||||
|
||||
def _ssh_sock(create=True):
|
||||
global _ssh_sock_path
|
||||
if _ssh_sock_path is None:
|
||||
if not create:
|
||||
return None
|
||||
_ssh_sock_path = os.path.join(
|
||||
tempfile.mkdtemp('', 'ssh-'),
|
||||
'master-%r@%h:%p')
|
||||
return _ssh_sock_path
|
||||
|
||||
def _ssh_proxy():
|
||||
global _ssh_proxy_path
|
||||
if _ssh_proxy_path is None:
|
||||
_ssh_proxy_path = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'git_ssh')
|
||||
return _ssh_proxy_path
|
||||
|
||||
|
||||
class _GitCall(object):
|
||||
def version(self):
|
||||
@ -52,6 +74,7 @@ class GitCommand(object):
|
||||
capture_stdout = False,
|
||||
capture_stderr = False,
|
||||
disable_editor = False,
|
||||
ssh_proxy = False,
|
||||
cwd = None,
|
||||
gitdir = None):
|
||||
env = dict(os.environ)
|
||||
@ -68,6 +91,9 @@ class GitCommand(object):
|
||||
|
||||
if disable_editor:
|
||||
env['GIT_EDITOR'] = ':'
|
||||
if ssh_proxy:
|
||||
env['REPO_SSH_SOCK'] = _ssh_sock()
|
||||
env['GIT_SSH'] = _ssh_proxy()
|
||||
|
||||
if project:
|
||||
if not cwd:
|
||||
|
@ -16,11 +16,14 @@
|
||||
import cPickle
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from signal import SIGTERM
|
||||
from urllib2 import urlopen, HTTPError
|
||||
from error import GitError, UploadError
|
||||
from trace import Trace
|
||||
from git_command import GitCommand
|
||||
from git_command import GitCommand, _ssh_sock
|
||||
|
||||
R_HEADS = 'refs/heads/'
|
||||
R_TAGS = 'refs/tags/'
|
||||
@ -331,6 +334,79 @@ class RefSpec(object):
|
||||
return s
|
||||
|
||||
|
||||
_ssh_cache = {}
|
||||
_ssh_master = True
|
||||
|
||||
def _open_ssh(host, port=None):
|
||||
global _ssh_master
|
||||
|
||||
if port is None:
|
||||
port = 22
|
||||
|
||||
key = '%s:%s' % (host, port)
|
||||
if key in _ssh_cache:
|
||||
return True
|
||||
|
||||
if not _ssh_master \
|
||||
or 'GIT_SSH' in os.environ \
|
||||
or sys.platform == 'win32':
|
||||
# failed earlier, or cygwin ssh can't do this
|
||||
#
|
||||
return False
|
||||
|
||||
command = ['ssh',
|
||||
'-o','ControlPath %s' % _ssh_sock(),
|
||||
'-p',str(port),
|
||||
'-M',
|
||||
'-N',
|
||||
host]
|
||||
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
|
||||
|
||||
_ssh_cache[key] = p
|
||||
time.sleep(1)
|
||||
return True
|
||||
|
||||
def close_ssh():
|
||||
for key,p in _ssh_cache.iteritems():
|
||||
os.kill(p.pid, SIGTERM)
|
||||
p.wait()
|
||||
_ssh_cache.clear()
|
||||
|
||||
d = _ssh_sock(create=False)
|
||||
if d:
|
||||
try:
|
||||
os.rmdir(os.path.dirname(d))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
|
||||
URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/])/')
|
||||
|
||||
def _preconnect(url):
|
||||
m = URI_ALL.match(url)
|
||||
if m:
|
||||
scheme = m.group(1)
|
||||
host = m.group(2)
|
||||
if ':' in host:
|
||||
host, port = host.split(':')
|
||||
if scheme in ('ssh', 'git+ssh', 'ssh+git'):
|
||||
return _open_ssh(host, port)
|
||||
return False
|
||||
|
||||
m = URI_SCP.match(url)
|
||||
if m:
|
||||
host = m.group(1)
|
||||
return _open_ssh(host)
|
||||
|
||||
|
||||
class Remote(object):
|
||||
"""Configuration options related to a remote.
|
||||
"""
|
||||
@ -344,6 +420,9 @@ class Remote(object):
|
||||
self._Get('fetch', all=True))
|
||||
self._review_protocol = None
|
||||
|
||||
def PreConnectFetch(self):
|
||||
return _preconnect(self.url)
|
||||
|
||||
@property
|
||||
def ReviewProtocol(self):
|
||||
if self._review_protocol is None:
|
||||
|
2
git_ssh
Executable file
2
git_ssh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec ssh -o "ControlPath $REPO_SSH_SOCK" "$@"
|
4
main.py
4
main.py
@ -28,6 +28,7 @@ import re
|
||||
import sys
|
||||
|
||||
from trace import SetTrace
|
||||
from git_config import close_ssh
|
||||
from command import InteractiveCommand
|
||||
from command import MirrorSafeCommand
|
||||
from command import PagedCommand
|
||||
@ -211,8 +212,11 @@ def _Main(argv):
|
||||
_CheckRepoDir(opt.repodir)
|
||||
|
||||
repo = _Repo(opt.repodir)
|
||||
try:
|
||||
try:
|
||||
repo._Run(argv)
|
||||
finally:
|
||||
close_ssh()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
except RepoChangedException, rce:
|
||||
|
10
project.py
10
project.py
@ -969,11 +969,19 @@ class Project(object):
|
||||
def _RemoteFetch(self, name=None):
|
||||
if not name:
|
||||
name = self.remote.name
|
||||
|
||||
ssh_proxy = False
|
||||
if self.GetRemote(name).PreConnectFetch():
|
||||
ssh_proxy = True
|
||||
|
||||
cmd = ['fetch']
|
||||
if not self.worktree:
|
||||
cmd.append('--update-head-ok')
|
||||
cmd.append(name)
|
||||
return GitCommand(self, cmd, bare = True).Wait() == 0
|
||||
return GitCommand(self,
|
||||
cmd,
|
||||
bare = True,
|
||||
ssh_proxy = ssh_proxy).Wait() == 0
|
||||
|
||||
def _Checkout(self, rev, quiet=False):
|
||||
cmd = ['checkout']
|
||||
|
Loading…
Reference in New Issue
Block a user