summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2009-04-10 18:53:46 -0700
committerShawn O. Pearce <sop@google.com>2009-04-18 16:50:47 -0700
commitfb2316146f6e3036e0cc3e08653920964a428a15 (patch)
treef19c5c65a035f547ada03496f1576524e4602665
parent8bd5e60b16080008771afcaa7de7084487b84780 (diff)
downloadgit-repo-fb2316146f6e3036e0cc3e08653920964a428a15.tar.gz
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>
-rw-r--r--git_command.py26
-rw-r--r--git_config.py81
-rwxr-xr-xgit_ssh2
-rwxr-xr-xmain.py6
-rw-r--r--project.py10
5 files changed, 122 insertions, 3 deletions
diff --git a/git_command.py b/git_command.py
index b6a4a343..954bebad 100644
--- a/git_command.py
+++ b/git_command.py
@@ -16,6 +16,7 @@
16import os 16import os
17import sys 17import sys
18import subprocess 18import subprocess
19import tempfile
19from error import GitError 20from error import GitError
20from trace import REPO_TRACE, IsTrace, Trace 21from trace import REPO_TRACE, IsTrace, Trace
21 22
@@ -26,6 +27,27 @@ GIT_DIR = 'GIT_DIR'
26LAST_GITDIR = None 27LAST_GITDIR = None
27LAST_CWD = None 28LAST_CWD = None
28 29
30_ssh_proxy_path = None
31_ssh_sock_path = None
32
33def _ssh_sock(create=True):
34 global _ssh_sock_path
35 if _ssh_sock_path is None:
36 if not create:
37 return None
38 _ssh_sock_path = os.path.join(
39 tempfile.mkdtemp('', 'ssh-'),
40 'master-%r@%h:%p')
41 return _ssh_sock_path
42
43def _ssh_proxy():
44 global _ssh_proxy_path
45 if _ssh_proxy_path is None:
46 _ssh_proxy_path = os.path.join(
47 os.path.dirname(__file__),
48 'git_ssh')
49 return _ssh_proxy_path
50
29 51
30class _GitCall(object): 52class _GitCall(object):
31 def version(self): 53 def version(self):
@@ -52,6 +74,7 @@ class GitCommand(object):
52 capture_stdout = False, 74 capture_stdout = False,
53 capture_stderr = False, 75 capture_stderr = False,
54 disable_editor = False, 76 disable_editor = False,
77 ssh_proxy = False,
55 cwd = None, 78 cwd = None,
56 gitdir = None): 79 gitdir = None):
57 env = dict(os.environ) 80 env = dict(os.environ)
@@ -68,6 +91,9 @@ class GitCommand(object):
68 91
69 if disable_editor: 92 if disable_editor:
70 env['GIT_EDITOR'] = ':' 93 env['GIT_EDITOR'] = ':'
94 if ssh_proxy:
95 env['REPO_SSH_SOCK'] = _ssh_sock()
96 env['GIT_SSH'] = _ssh_proxy()
71 97
72 if project: 98 if project:
73 if not cwd: 99 if not cwd:
diff --git a/git_config.py b/git_config.py
index 7e642a4c..163b0809 100644
--- a/git_config.py
+++ b/git_config.py
@@ -16,11 +16,14 @@
16import cPickle 16import cPickle
17import os 17import os
18import re 18import re
19import subprocess
19import sys 20import sys
21import time
22from signal import SIGTERM
20from urllib2 import urlopen, HTTPError 23from urllib2 import urlopen, HTTPError
21from error import GitError, UploadError 24from error import GitError, UploadError
22from trace import Trace 25from trace import Trace
23from git_command import GitCommand 26from git_command import GitCommand, _ssh_sock
24 27
25R_HEADS = 'refs/heads/' 28R_HEADS = 'refs/heads/'
26R_TAGS = 'refs/tags/' 29R_TAGS = 'refs/tags/'
@@ -331,6 +334,79 @@ class RefSpec(object):
331 return s 334 return s
332 335
333 336
337_ssh_cache = {}
338_ssh_master = True
339
340def _open_ssh(host, port=None):
341 global _ssh_master
342
343 if port is None:
344 port = 22
345
346 key = '%s:%s' % (host, port)
347 if key in _ssh_cache:
348 return True
349
350 if not _ssh_master \
351 or 'GIT_SSH' in os.environ \
352 or sys.platform == 'win32':
353 # failed earlier, or cygwin ssh can't do this
354 #
355 return False
356
357 command = ['ssh',
358 '-o','ControlPath %s' % _ssh_sock(),
359 '-p',str(port),
360 '-M',
361 '-N',
362 host]
363 try:
364 Trace(': %s', ' '.join(command))
365 p = subprocess.Popen(command)
366 except Exception, e:
367 _ssh_master = False
368 print >>sys.stderr, \
369 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
370 % (host,port, str(e))
371 return False
372
373 _ssh_cache[key] = p
374 time.sleep(1)
375 return True
376
377def close_ssh():
378 for key,p in _ssh_cache.iteritems():
379 os.kill(p.pid, SIGTERM)
380 p.wait()
381 _ssh_cache.clear()
382
383 d = _ssh_sock(create=False)
384 if d:
385 try:
386 os.rmdir(os.path.dirname(d))
387 except OSError:
388 pass
389
390URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
391URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/])/')
392
393def _preconnect(url):
394 m = URI_ALL.match(url)
395 if m:
396 scheme = m.group(1)
397 host = m.group(2)
398 if ':' in host:
399 host, port = host.split(':')
400 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
401 return _open_ssh(host, port)
402 return False
403
404 m = URI_SCP.match(url)
405 if m:
406 host = m.group(1)
407 return _open_ssh(host)
408
409
334class Remote(object): 410class Remote(object):
335 """Configuration options related to a remote. 411 """Configuration options related to a remote.
336 """ 412 """
@@ -344,6 +420,9 @@ class Remote(object):
344 self._Get('fetch', all=True)) 420 self._Get('fetch', all=True))
345 self._review_protocol = None 421 self._review_protocol = None
346 422
423 def PreConnectFetch(self):
424 return _preconnect(self.url)
425
347 @property 426 @property
348 def ReviewProtocol(self): 427 def ReviewProtocol(self):
349 if self._review_protocol is None: 428 if self._review_protocol is None:
diff --git a/git_ssh b/git_ssh
new file mode 100755
index 00000000..63aa63c2
--- /dev/null
+++ b/git_ssh
@@ -0,0 +1,2 @@
1#!/bin/sh
2exec ssh -o "ControlPath $REPO_SSH_SOCK" "$@"
diff --git a/main.py b/main.py
index 6fa1e51b..774b9038 100755
--- a/main.py
+++ b/main.py
@@ -28,6 +28,7 @@ import re
28import sys 28import sys
29 29
30from trace import SetTrace 30from trace import SetTrace
31from git_config import close_ssh
31from command import InteractiveCommand 32from command import InteractiveCommand
32from command import MirrorSafeCommand 33from command import MirrorSafeCommand
33from command import PagedCommand 34from command import PagedCommand
@@ -212,7 +213,10 @@ def _Main(argv):
212 213
213 repo = _Repo(opt.repodir) 214 repo = _Repo(opt.repodir)
214 try: 215 try:
215 repo._Run(argv) 216 try:
217 repo._Run(argv)
218 finally:
219 close_ssh()
216 except KeyboardInterrupt: 220 except KeyboardInterrupt:
217 sys.exit(1) 221 sys.exit(1)
218 except RepoChangedException, rce: 222 except RepoChangedException, rce:
diff --git a/project.py b/project.py
index fd3f0b8d..304480a8 100644
--- a/project.py
+++ b/project.py
@@ -969,11 +969,19 @@ class Project(object):
969 def _RemoteFetch(self, name=None): 969 def _RemoteFetch(self, name=None):
970 if not name: 970 if not name:
971 name = self.remote.name 971 name = self.remote.name
972
973 ssh_proxy = False
974 if self.GetRemote(name).PreConnectFetch():
975 ssh_proxy = True
976
972 cmd = ['fetch'] 977 cmd = ['fetch']
973 if not self.worktree: 978 if not self.worktree:
974 cmd.append('--update-head-ok') 979 cmd.append('--update-head-ok')
975 cmd.append(name) 980 cmd.append(name)
976 return GitCommand(self, cmd, bare = True).Wait() == 0 981 return GitCommand(self,
982 cmd,
983 bare = True,
984 ssh_proxy = ssh_proxy).Wait() == 0
977 985
978 def _Checkout(self, rev, quiet=False): 986 def _Checkout(self, rev, quiet=False):
979 cmd = ['checkout'] 987 cmd = ['checkout']