Ignore duplicate project-template definitions

Let the first one win, so that they can't be updated in other branches or
repos.

Change-Id: I5d0c2d428b0714935949d1e0350ab98ef2969293
diff --git a/tests/fixtures/config/in-repo/git/common-config/playbooks/template-job.yaml b/tests/fixtures/config/in-repo/git/common-config/playbooks/template-job.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/in-repo/git/common-config/playbooks/template-job.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+  tasks: []
diff --git a/tests/fixtures/config/in-repo/git/common-config/zuul.yaml b/tests/fixtures/config/in-repo/git/common-config/zuul.yaml
index 5623467..a97af51 100644
--- a/tests/fixtures/config/in-repo/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/in-repo/git/common-config/zuul.yaml
@@ -76,6 +76,15 @@
 - job:
     name: common-config-test
 
+- job:
+    name: template-job
+
+- project-template:
+    name: common-config-template
+    check:
+      jobs:
+        - template-job
+
 - project:
     name: common-config
     check:
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index b30d710..20f39bb 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -283,6 +283,31 @@
             dict(name='project-test2', result='SUCCESS', changes='1,1'),
             dict(name='project-test2', result='SUCCESS', changes='2,1')])
 
+    def test_dynamic_template(self):
+        in_repo_conf = textwrap.dedent(
+            """
+            - job:
+                name: project-test1
+
+            - project-template:
+                name: common-config-template
+                check:
+                  jobs:
+                    - project-test1
+
+            - project:
+                name: org/project
+                templates: [common-config-template]
+            """)
+
+        file_dict = {'.zuul.yaml': in_repo_conf}
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+                                           files=file_dict)
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.assertHistory([
+            dict(name='template-job', result='SUCCESS', changes='1,1')])
+
     def test_dynamic_config_non_existing_job(self):
         """Test that requesting a non existent job fails"""
         in_repo_conf = textwrap.dedent(
diff --git a/zuul/model.py b/zuul/model.py
index 26adb93..d2595c3 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -2423,6 +2423,11 @@
         self.pipelines[pipeline.name] = pipeline
 
     def addProjectTemplate(self, project_template):
+        if project_template.name in self.project_templates:
+            # TODO(jeblair): issue a warning to the logs on loading
+            # the config, and an error when this hits in a proposed
+            # change.
+            return
         self.project_templates[project_template.name] = project_template
 
     def addProjectConfig(self, project_config):