Combine branch templates and pipeline branch matchers

In a situation where a projec-template is defined on multiple branches
and then used by a project on multiple branches, the behavior was not
as one might expect.

Currently, assuming no explicit branch matchers are in play, all the
jobs listed in the project-template will get implied branch matchers
attached.  So the resulting template looks like

 - job @ master
 - job @ stable/newton
 - job @ stable/ocata

Then when that template is applied to the project, since the jobs within
already have branch matchers, the implied branch matcher for the project
pipeline definition is not applied.  When the template is added to the
project on the ocata branch, all 3 of those jobs are added to the project,
and when it is added on the newton branch, all 3 jobs are added to the
project again.  That's invisible to the user, until they attempt to remove
the template from one of the branches.  Because the other branches still
add the template, which contains all the jobs, they still run.

To correct this, either replace the branch matcher obtained from the job
in the project template (if it is a simple match of a single branch), or
combine it using a boolean "and" (if it is something more complex) with a
branch matcher for the project definition.  When the above template is added
to a project on the newton branch, only the following job will be added to
the project-pipeline:

 - job @ stable/newton

When the template is added on the ocata branch, likewise only the ocata job
will be added.

If, instead, the project template had an explicit branch matcher, the
resulting template might be:

 - job @ ^(?!stable/diablo).*$

After adding that to the project on the newton branch, the resulting project
pipeline would be:

 - job @ { ^(?!stable/diablo).*$ AND stable/newton }

Ensuring that since the template was only added to the project on newton,
its jobs only run on newton changes.

Change-Id: I1969d588bc47b8ab5a54a885a68f98178b16b9d5
diff --git a/tests/fixtures/config/branch-templates/git/project-config/zuul.yaml b/tests/fixtures/config/branch-templates/git/project-config/zuul.yaml
new file mode 100644
index 0000000..ce08877
--- /dev/null
+++ b/tests/fixtures/config/branch-templates/git/project-config/zuul.yaml
@@ -0,0 +1,26 @@
+- pipeline:
+    name: check
+    manager: independent
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        Verified: 1
+    failure:
+      gerrit:
+        Verified: -1
+
+- job:
+    name: base
+    parent: null
+
+- project:
+    name: project-config
+    check:
+      jobs: []
+
+- project:
+    name: puppet-integration
+    check:
+      jobs: []
diff --git a/tests/fixtures/config/branch-templates/git/puppet-integration/.zuul.yaml b/tests/fixtures/config/branch-templates/git/puppet-integration/.zuul.yaml
new file mode 100644
index 0000000..dfea632
--- /dev/null
+++ b/tests/fixtures/config/branch-templates/git/puppet-integration/.zuul.yaml
@@ -0,0 +1,25 @@
+- job:
+    name: puppet-unit-base
+    run: playbooks/run-unit-tests.yaml
+
+- job:
+    name: puppet-unit-3.8
+    parent: puppet-unit-base
+    branches: ^(stable/(newton|ocata)).*$
+    vars:
+      puppet_gem_version: 3.8
+
+- job:
+    name: puppet-something
+    run: playbooks/run-unit-tests.yaml
+
+- project-template:
+    name: puppet-unit
+    check:
+      jobs:
+        - puppet-unit-3.8
+
+- project:
+    name: puppet-integration
+    templates:
+      - puppet-unit
diff --git a/tests/fixtures/config/branch-templates/git/puppet-integration/README b/tests/fixtures/config/branch-templates/git/puppet-integration/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/branch-templates/git/puppet-integration/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/branch-templates/git/puppet-integration/playbooks/run-unit-tests.yaml b/tests/fixtures/config/branch-templates/git/puppet-integration/playbooks/run-unit-tests.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/branch-templates/git/puppet-integration/playbooks/run-unit-tests.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+  tasks: []
diff --git a/tests/fixtures/config/branch-templates/git/puppet-tripleo/.zuul.yaml b/tests/fixtures/config/branch-templates/git/puppet-tripleo/.zuul.yaml
new file mode 100644
index 0000000..4be8146
--- /dev/null
+++ b/tests/fixtures/config/branch-templates/git/puppet-tripleo/.zuul.yaml
@@ -0,0 +1,4 @@
+- project:
+    name: puppet-tripleo
+    templates:
+      - puppet-unit
diff --git a/tests/fixtures/config/branch-templates/git/puppet-tripleo/README b/tests/fixtures/config/branch-templates/git/puppet-tripleo/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/branch-templates/git/puppet-tripleo/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/branch-templates/main.yaml b/tests/fixtures/config/branch-templates/main.yaml
new file mode 100644
index 0000000..f7677a3
--- /dev/null
+++ b/tests/fixtures/config/branch-templates/main.yaml
@@ -0,0 +1,9 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - project-config
+        untrusted-projects:
+          - puppet-integration
+          - puppet-tripleo
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index c04604d..e2da808 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -157,6 +157,115 @@
         self.assertIn('Unable to modify final job', A.messages[0])
 
 
+class TestBranchTemplates(ZuulTestCase):
+    tenant_config_file = 'config/branch-templates/main.yaml'
+
+    def test_template_removal_from_branch(self):
+        # Test that a template can be removed from one branch but not
+        # another.
+        # This creates a new branch with a copy of the config in master
+        self.create_branch('puppet-integration', 'stable/newton')
+        self.create_branch('puppet-integration', 'stable/ocata')
+        self.create_branch('puppet-tripleo', 'stable/newton')
+        self.create_branch('puppet-tripleo', 'stable/ocata')
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-integration', 'stable/newton'))
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-integration', 'stable/ocata'))
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-tripleo', 'stable/newton'))
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-tripleo', 'stable/ocata'))
+        self.waitUntilSettled()
+
+        in_repo_conf = textwrap.dedent(
+            """
+            - project:
+                name: puppet-tripleo
+                check:
+                  jobs:
+                    - puppet-something
+            """)
+
+        file_dict = {'.zuul.yaml': in_repo_conf}
+        A = self.fake_gerrit.addFakeChange('puppet-tripleo', 'stable/newton',
+                                           'A', files=file_dict)
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.assertHistory([
+            dict(name='puppet-something', result='SUCCESS', changes='1,1')])
+
+    def test_template_change_on_branch(self):
+        # Test that the contents of a template can be changed on one
+        # branch without affecting another.
+
+        # This creates a new branch with a copy of the config in master
+        self.create_branch('puppet-integration', 'stable/newton')
+        self.create_branch('puppet-integration', 'stable/ocata')
+        self.create_branch('puppet-tripleo', 'stable/newton')
+        self.create_branch('puppet-tripleo', 'stable/ocata')
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-integration', 'stable/newton'))
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-integration', 'stable/ocata'))
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-tripleo', 'stable/newton'))
+        self.fake_gerrit.addEvent(
+            self.fake_gerrit.getFakeBranchCreatedEvent(
+                'puppet-tripleo', 'stable/ocata'))
+        self.waitUntilSettled()
+
+        in_repo_conf = textwrap.dedent("""
+            - job:
+                name: puppet-unit-base
+                run: playbooks/run-unit-tests.yaml
+
+            - job:
+                name: puppet-unit-3.8
+                parent: puppet-unit-base
+                branches: ^(stable/(newton|ocata)).*$
+                vars:
+                  puppet_gem_version: 3.8
+
+            - job:
+                name: puppet-something
+                run: playbooks/run-unit-tests.yaml
+
+            - project-template:
+                name: puppet-unit
+                check:
+                  jobs:
+                    - puppet-something
+
+            - project:
+                name: puppet-integration
+                templates:
+                  - puppet-unit
+        """)
+
+        file_dict = {'.zuul.yaml': in_repo_conf}
+        A = self.fake_gerrit.addFakeChange('puppet-integration',
+                                           'stable/newton',
+                                           'A', files=file_dict)
+        B = self.fake_gerrit.addFakeChange('puppet-tripleo',
+                                           'stable/newton',
+                                           'B')
+        B.data['commitMessage'] = '%s\n\nDepends-On: %s\n' % (
+            B.subject, A.data['id'])
+        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.assertHistory([
+            dict(name='puppet-something', result='SUCCESS',
+                 changes='1,1 2,1')])
+
+
 class TestBranchVariants(ZuulTestCase):
     tenant_config_file = 'config/branch-variants/main.yaml'