1
0
Fork 0

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:
Shawn O. Pearce 2009-04-10 18:53:46 -07:00
parent 161f445a4d
commit af26748add
5 changed files with 96 additions and 2 deletions

View file

@ -30,6 +30,13 @@ try:
except KeyError:
TRACE = False
_ssh_proxy_path = None
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):
@ -56,6 +63,7 @@ class GitCommand(object):
capture_stdout = False,
capture_stderr = False,
disable_editor = False,
ssh_proxy = False,
cwd = None,
gitdir = None):
env = dict(os.environ)
@ -72,6 +80,8 @@ class GitCommand(object):
if disable_editor:
env['GIT_EDITOR'] = ':'
if ssh_proxy:
env['GIT_SSH'] = _ssh_proxy()
if project:
if not cwd:

View file

@ -15,7 +15,10 @@
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 git_command import GitCommand
@ -253,6 +256,70 @@ 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 ~/.ssh/master-%r@%h:%p',
'-p',port,
'-M',
'-N',
host]
try:
p = subprocess.Popen(command)
except Exception, e:
_ssh_master = False
print >>sys.stderr, \
'\nwarn: cannot enable ssh control master for %s:%s' \
% (host,port)
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)
_ssh_cache.clear()
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.
"""
@ -266,6 +333,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
View file

@ -0,0 +1,2 @@
#!/bin/sh
exec ssh -o 'ControlPath ~/.ssh/master-%r@%h:%p' "$@"

View file

@ -28,6 +28,7 @@ import re
import sys
import git_command
from git_config import close_ssh
from command import InteractiveCommand
from command import MirrorSafeCommand
from command import PagedCommand
@ -211,7 +212,10 @@ def _Main(argv):
repo = _Repo(opt.repodir)
try:
repo._Run(argv)
try:
repo._Run(argv)
finally:
close_ssh()
except KeyboardInterrupt:
sys.exit(1)
except RepoChangedException, rce:

View file

@ -868,11 +868,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']