Check for existing SSH ControlMaster

Be more thorough about checking for an existing ssh master by
running a test command first, and only opening up a new master
if the test fails to connect.

Change-Id: I56fe8e7b4dbc123675b7f259e81d359ed0cd55cf
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/git_config.py b/git_config.py
index 1382d5d..138470c 100644
--- a/git_config.py
+++ b/git_config.py
@@ -356,18 +356,21 @@
     return s
 
 
-_ssh_cache = {}
+_master_processes = []
+_master_keys = set()
 _ssh_master = True
 
 def _open_ssh(host, port=None):
   global _ssh_master
 
+  # Check to see whether we already think that the master is running; if we
+  # think it's already running, return right away.
   if port is not None:
     key = '%s:%s' % (host, port)
   else:
     key = host
 
-  if key in _ssh_cache:
+  if key in _master_keys:
     return True
 
   if not _ssh_master \
@@ -377,15 +380,39 @@
     #
     return False
 
-  command = ['ssh',
-             '-o','ControlPath %s' % ssh_sock(),
-             '-M',
-             '-N',
-             host]
-
+  # 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[3:3] = ['-p',str(port)]
+    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)
@@ -396,20 +423,22 @@
       % (host,port, str(e))
     return False
 
-  _ssh_cache[key] = p
+  _master_processes.append(p)
+  _master_keys.add(key)
   time.sleep(1)
   return True
 
 def close_ssh():
   terminate_ssh_clients()
 
-  for key,p in _ssh_cache.iteritems():
+  for p in _master_processes:
     try:
       os.kill(p.pid, SIGTERM)
       p.wait()
     except OSError:
       pass
-  _ssh_cache.clear()
+  del _master_processes[:]
+  _master_keys.clear()
 
   d = ssh_sock(create=False)
   if d: