Support cross-repo-dependencies in independent pipelines

Changes supplied by the Depends-On header in git commit messages
will now additionally pull in changes from any project, on any
branch into independent pipelines.  They are considered "non-live"
changes, that is, changes that should not have any tests run on
them.

To accomodate this, the internal structure of an independent
pipeline is now a dynamically adjusting list of ChangeQueue objects
where a new ChangeQueue is created for each change added to the
pipeline (and that ChangeQueue might contain non-live changes
enqueued ahead of the change we are interested in for the purpose
of stacking commits in the associated Zuul refs).  This actually
more closely matches the visual and intuitive understanding of
independent pipelines.

Change-Id: I8ba0bc0918263f297666a50c607bca4f87c903b8
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index 3837cfa..059f155 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -1921,7 +1921,7 @@
         status_jobs = set()
         for p in data['pipelines']:
             for q in p['change_queues']:
-                if q['dependent']:
+                if p['name'] in ['gate', 'conflict']:
                     self.assertEqual(q['window'], 20)
                 else:
                     self.assertEqual(q['window'], 0)
@@ -3014,3 +3014,45 @@
         self.assertEqual(B.reported, 0)
         self.assertEqual(A.data['status'], 'NEW')
         self.assertEqual(B.data['status'], 'NEW')
+
+    def test_crd_check(self):
+        "Test cross-repo dependencies in independent pipelines"
+
+        self.gearman_server.hold_jobs_in_queue = True
+        A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
+        B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
+
+        # A Depends-On: B
+        A.data['commitMessage'] = '%s\n\nDepends-On: %s\n' % (
+            A.subject, B.data['id'])
+
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+
+        queue = self.gearman_server.getQueue()
+        ref = self.getParameter(queue[-1], 'ZUUL_REF')
+        self.gearman_server.hold_jobs_in_queue = False
+        self.gearman_server.release()
+        self.waitUntilSettled()
+
+        path = os.path.join(self.git_root, "org/project1")
+        repo = git.Repo(path)
+        repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
+        repo_messages.reverse()
+        correct_messages = ['initial commit', 'A-1']
+        self.assertEqual(repo_messages, correct_messages)
+
+        path = os.path.join(self.git_root, "org/project2")
+        repo = git.Repo(path)
+        repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
+        repo_messages.reverse()
+        correct_messages = ['initial commit', 'B-1']
+        self.assertEqual(repo_messages, correct_messages)
+
+        self.assertEqual(A.data['status'], 'NEW')
+        self.assertEqual(B.data['status'], 'NEW')
+        self.assertEqual(A.reported, 1)
+        self.assertEqual(B.reported, 0)
+
+        self.assertEqual(self.history[0].changes, '2,1 1,1')
+        self.assertEqual(len(self.sched.layout.pipelines['check'].queues), 0)