Merge "Update hacking dependency"
diff --git a/requirements.txt b/requirements.txt
index 872b8f0..052d069 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,7 +4,7 @@
 Paste
 WebOb>=1.2.3
 paramiko>=1.8.0,<2.0.0
-GitPython>=0.3.3
+GitPython>=0.3.3,<2.1.2
 ordereddict
 python-daemon>=2.0.4,<2.1.0
 extras
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index d205395..6a16b37 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -3903,19 +3903,23 @@
         "Test cross-repo dependencies in multiple branches"
         A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
         B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
-        C = self.fake_gerrit.addFakeChange('org/project2', 'mp', 'C')
-        C.data['id'] = B.data['id']
+        C1 = self.fake_gerrit.addFakeChange('org/project2', 'mp', 'C1')
+        C2 = self.fake_gerrit.addFakeChange('org/project2', 'mp', 'C2',
+                                            status="ABANDONED")
+        C1.data['id'] = B.data['id']
+        C2.data['id'] = B.data['id']
+
         A.addApproval('CRVW', 2)
         B.addApproval('CRVW', 2)
-        C.addApproval('CRVW', 2)
+        C1.addApproval('CRVW', 2)
 
-        # A Depends-On: B+C
+        # A Depends-On: B+C1
         A.data['commitMessage'] = '%s\n\nDepends-On: %s\n' % (
             A.subject, B.data['id'])
 
         self.worker.hold_jobs_in_build = True
         B.addApproval('APRV', 1)
-        C.addApproval('APRV', 1)
+        C1.addApproval('APRV', 1)
         self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
         self.waitUntilSettled()
 
@@ -3931,10 +3935,10 @@
 
         self.assertEqual(A.data['status'], 'MERGED')
         self.assertEqual(B.data['status'], 'MERGED')
-        self.assertEqual(C.data['status'], 'MERGED')
+        self.assertEqual(C1.data['status'], 'MERGED')
         self.assertEqual(A.reported, 2)
         self.assertEqual(B.reported, 2)
-        self.assertEqual(C.reported, 2)
+        self.assertEqual(C1.reported, 2)
 
         self.assertEqual(self.getJobFromHistory('project1-merge').changes,
                          '2,1 3,1 1,1')
diff --git a/zuul/connection/gerrit.py b/zuul/connection/gerrit.py
index 6e8d085..85ca051 100644
--- a/zuul/connection/gerrit.py
+++ b/zuul/connection/gerrit.py
@@ -192,13 +192,16 @@
                     stdout.channel.close()
             ret = stdout.channel.recv_exit_status()
             self.log.debug("SSH exit status: %s" % ret)
-            client.close()
 
             if ret and ret not in [-1, 130]:
                 raise Exception("Gerrit error executing stream-events")
         except:
             self.log.exception("Exception on ssh event stream:")
             time.sleep(5)
+        finally:
+            # If we don't close on exceptions to connect we can leak the
+            # connection and DoS Gerrit.
+            client.close()
 
     def run(self):
         while not self._stopped:
@@ -353,16 +356,25 @@
         return alldata
 
     def _open(self):
-        client = paramiko.SSHClient()
-        client.load_system_host_keys()
-        client.set_missing_host_key_policy(paramiko.WarningPolicy())
-        client.connect(self.server,
-                       username=self.user,
-                       port=self.port,
-                       key_filename=self.keyfile)
-        transport = client.get_transport()
-        transport.set_keepalive(self.keepalive)
-        self.client = client
+        if self.client:
+            # Paramiko needs explicit closes, its possible we will open even
+            # with an unclosed client so explicitly close here.
+            self.client.close()
+        try:
+            client = paramiko.SSHClient()
+            client.load_system_host_keys()
+            client.set_missing_host_key_policy(paramiko.WarningPolicy())
+            client.connect(self.server,
+                           username=self.user,
+                           port=self.port,
+                           key_filename=self.keyfile)
+            transport = client.get_transport()
+            transport.set_keepalive(self.keepalive)
+            self.client = client
+        except Exception:
+            client.close()
+            self.client = None
+            raise
 
     def _ssh(self, command, stdin_data=None):
         if not self.client:
diff --git a/zuul/source/gerrit.py b/zuul/source/gerrit.py
index 828e201..fa49505 100644
--- a/zuul/source/gerrit.py
+++ b/zuul/source/gerrit.py
@@ -295,6 +295,9 @@
             # cycle, we won't detect it.  By explicitly performing a
             # walk of the dependency tree, we will.
             detect_cycle(dep, history)
+            # This is a git commit dependency. So we only ignore it if it is
+            # already merged. So even if it is "ABANDONED", we should not
+            # ignore it.
             if (not dep.is_merged) and dep not in needs_changes:
                 needs_changes.append(dep)
 
@@ -315,7 +318,7 @@
             # cycle, we won't detect it.  By explicitly performing a
             # walk of the dependency tree, we will.
             detect_cycle(dep, history)
-            if (not dep.is_merged) and dep not in needs_changes:
+            if dep.open and dep not in needs_changes:
                 needs_changes.append(dep)
         change.needs_changes = needs_changes
 
@@ -327,7 +330,7 @@
                 self.log.debug("Updating %s: Getting git-needed change %s,%s" %
                                (change, dep_num, dep_ps))
                 dep = self._getChange(dep_num, dep_ps)
-                if (not dep.is_merged) and dep.is_current_patchset:
+                if dep.open and dep.is_current_patchset:
                     needed_by_changes.append(dep)
 
         for record in self._getNeededByFromCommit(data['id'], change):
@@ -340,7 +343,7 @@
             # reference the latest patchset of its Depends-On (this
             # change).
             dep = self._getChange(dep_num, dep_ps, refresh=True)
-            if (not dep.is_merged) and dep.is_current_patchset:
+            if dep.open and dep.is_current_patchset:
                 needed_by_changes.append(dep)
         change.needed_by_changes = needed_by_changes