Be less agressive with implied branch matchers

We want to be more liberal with use of project-repos for holding
centralized configuration than I was anticipating when I wrote
the implied branch matcher functionality.  The over-use of implied
branch matchers makes this hard.

The goal of implied branch matching is so that if you, say, define
a devstack-gate job on master, then create a new stable branch from
that, the job definition in the stable branch should automatically
apply to changes on the stable branch.

Currently, that is enacted by forcing a job definition to always
have a branch matcher for the branch it is defined in if it is in
a project repo.

This changes that so that the first definition of a job (known as
the reference definition) never receives an implied branch matcher.
It can still have explicit branch matchers.  Later definitions of
a job (variants) on the same branch as the reference definition
still don't get implied branch matchers (so you can safely add
a file-matcher variant right below the reference job and not be
surprised by an implied branch matcher).  They too can still have
explicit branch matchers.

Only if we are defining a variant that is on a different branch
than the reference do we add an implicit branch matcher, and only
then if there is still not an explicit branch matcher.

This, hopefully, will provide the least surprise, yet still
facilitate the goal of automatic branching of jobs.

However, the behavior is slightly different for project-pipeline
job variants.  These are job variants which appear in the project
definition.  While not yet enforced, we only expect these to appear
in two circumstances: in a config repo (in which case there is no
branch context) or within the project repo itself (eg, the project
definition for the zuul repo should only appear in project-config
or zuul; not cloudkitty).

In the case of a project definition appearing in a config repo,
we will not apply the implied branch matcher because it is not an
untrusted repo.  That behavior is the same as for a normal job
definition.

In the case of a project definition appearing in a project repo,
we want to more strongly favor the implied branch.  That is because
since a project definition should only appear in its own repo, we
can expect such a definition on each branch, so we don't have to
worry as much about a job which is meant to be able to run on any
branch mistakenly only running on the branch it's defined in.
Therefore, in this case, use the implied branch matcher if there
is no explicit branch matcher.  In other words, in the case of a
project-pipeline job variant, we ignore the "same-branch" test
we added above for standard job definitions.

To implement that, we use the JobParser.fromYaml method even for
project-pipeline job variants with no attributes -- that way we can
more easily use the same implied branch logic.  However, since in
those cases we were previously simply constructing model.Job objects,
we need to clean up a couple of cases where we were explictly setting
Job attributes which were not actually being set in the config
(otherwise we will fail if we try to run a final job).  This would
eventually have caused trouble anyway if we had a project-pipeline
variant that changed anything (even safe attributes) on a final job;
this change corrects that as well.

Change-Id: I06dca12511227d6ec79ed53d4cf6caa23d81427d
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 3919418..71d94c2 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -191,6 +191,61 @@
             dict(name='project-test1', result='SUCCESS', changes='2,1'),
             dict(name='project-test2', result='SUCCESS', changes='3,1')])
 
+    def test_crd_dynamic_config_branch(self):
+        # Test that we can create a job in one repo and be able to use
+        # it from a different branch on a different repo.
+
+        self.create_branch('org/project1', 'stable')
+
+        in_repo_conf = textwrap.dedent(
+            """
+            - job:
+                name: project-test2
+
+            - project:
+                name: org/project
+                check:
+                  jobs:
+                    - project-test2
+            """)
+
+        in_repo_playbook = textwrap.dedent(
+            """
+            - hosts: all
+              tasks: []
+            """)
+
+        file_dict = {'.zuul.yaml': in_repo_conf,
+                     'playbooks/project-test2.yaml': in_repo_playbook}
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+                                           files=file_dict)
+
+        second_repo_conf = textwrap.dedent(
+            """
+            - project:
+                name: org/project1
+                check:
+                  jobs:
+                    - project-test2
+            """)
+
+        second_file_dict = {'.zuul.yaml': second_repo_conf}
+        B = self.fake_gerrit.addFakeChange('org/project1', 'stable', 'B',
+                                           files=second_file_dict)
+        B.data['commitMessage'] = '%s\n\nDepends-On: %s\n' % (
+            B.subject, A.data['id'])
+
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+
+        self.assertEqual(A.reported, 1, "A should report")
+        self.assertHistory([
+            dict(name='project-test2', result='SUCCESS', changes='1,1'),
+            dict(name='project-test2', result='SUCCESS', changes='1,1 2,1'),
+        ])
+
     def test_untrusted_syntax_error(self):
         in_repo_conf = textwrap.dedent(
             """