From 8cf99f00e80e29319ff3c493e890a151184501a4 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Mon, 15 Jun 2026 11:23:13 -0700 Subject: [PATCH] Treat always-dynamic-branches projects as multi-branch Previously, if a project with one permanent branch listed some branches in always-dynamic-branches, we would treat it as a single-branch project and disable implied branch matchers. This is slightly unintuitive, as typically always-dynamic-branches is not used with shared job repositories (which is the use case that we want to handle by disabling implied branches). This change flips that behavior so that if a project has always-dynamic-branches set, we will still treat it as a multi-branch project. This also matches the idea that always-dynamic-branches are treated as branches that just happen not to have zuul config until someone proposes a change. In case someone prefers the old behavior, either behavior can be selected explicitly with the implied-branch-matchers tenant config option. Change-Id: I61c4e564f04102aa27f2d8c130abe63aeb3c2f5a --- doc/source/tenants.rst | 9 ++++ ...ways-dynamic-implied-555f1dc564e0c0f0.yaml | 17 ++++++++ .../git/org_project/zuul.yaml | 1 + tests/unit/test_v3.py | 42 +++++++++++++++++++ zuul/model.py | 6 +++ 5 files changed, 75 insertions(+) create mode 100644 releasenotes/notes/always-dynamic-implied-555f1dc564e0c0f0.yaml diff --git a/doc/source/tenants.rst b/doc/source/tenants.rst index 81de9c81d5..04f9635e64 100644 --- a/doc/source/tenants.rst +++ b/doc/source/tenants.rst @@ -297,6 +297,15 @@ configuration. Some examples of tenant definitions are: change as if it included a change to a ``zuul.yaml`` file, even if the change does not alter the configuration). + Even though branches specified by + `always-dynamic-branches` are not loaded into Zuul's + configuration, the fact that they may exist causes Zuul to + treat the project as a multi-branch project, and therefore + Zuul will use implied branch matchers even if the project + has only one permanent branch. This behavior may be + overridden with :attr:`pragma` or + :attr:`tenant.untrusted-projects..implied-branch-matchers`. + With all these caveats in mind, this can be useful for repos with large numbers of rarely used branches as it allows Zuul to omit their configuration in most diff --git a/releasenotes/notes/always-dynamic-implied-555f1dc564e0c0f0.yaml b/releasenotes/notes/always-dynamic-implied-555f1dc564e0c0f0.yaml new file mode 100644 index 0000000000..0a4d5fe1d9 --- /dev/null +++ b/releasenotes/notes/always-dynamic-implied-555f1dc564e0c0f0.yaml @@ -0,0 +1,17 @@ +--- +upgrade: + - | + The behavior of a single-branch project with + :attr:`tenant.untrusted-projects..always-dynamic-branches` + has changed. Previously, if (after applying includes and + excludes) a project had only one permanent branch it was treated + as a single-branch project even if it also had + `always-dynamic-branches` set. Now if a project has + `always-dynamic-branches` set, it will be treated as a + multi-branch project and therefore implied branch matchers are + enabled. + + To retain the previous behavior (or explicitly choose a behavior) + the + :attr:`tenant.untrusted-projects..implied-branch-matchers` + tenant configuration option may be used. diff --git a/tests/fixtures/config/dynamic-only-project/git/org_project/zuul.yaml b/tests/fixtures/config/dynamic-only-project/git/org_project/zuul.yaml index 9712c08280..fb361f2136 100644 --- a/tests/fixtures/config/dynamic-only-project/git/org_project/zuul.yaml +++ b/tests/fixtures/config/dynamic-only-project/git/org_project/zuul.yaml @@ -1,5 +1,6 @@ - job: name: project-test + tags: [master] # Note: this job is not expected to run - job: diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py index e910b0b6b2..a9e7f3593b 100644 --- a/tests/unit/test_v3.py +++ b/tests/unit/test_v3.py @@ -11752,6 +11752,48 @@ class TestDynamicBranchesProject(IncludeBranchesTestCase): dict(name='project-dynamic', result='SUCCESS', changes='1,1'), ], ordered=False) + def test_new_dynamic_branch_single_branch(self): + # In this test, the repo has only a single branch ("master"), + # but since it has always-dynamic-branches set, we treat it + # like a normal repo, not a shared-jobs repo, and behave as if + # implied-branch-matchers is set to true. + + # Create a new dynamic-only branch. Since it's dynamic only, + # it won't trigger a reconfiguration. + self.create_branch('org/project', 'feature/foo') + self.fake_gerrit.addEvent( + self.fake_gerrit.getFakeBranchCreatedEvent( + 'org/project', 'feature/foo')) + self.waitUntilSettled() + + # Test that we load configuration from the dynamic branch even + # though the tenant layout doesn't know about it yet (since + # there hasn't been a reconfiguration since it was created). + conf = textwrap.dedent( + """ + - job: + name: project-test + tags: [feature] + + - project: + check: + jobs: + - project-test + """) + file_dict = {'zuul.yaml': conf} + A = self.fake_gerrit.addFakeChange('org/project', 'feature/foo', 'A', + files=file_dict) + self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1)) + self.waitUntilSettled() + self.assertHistory([ + dict(name='central-test', result='SUCCESS', changes='1,1'), + dict(name='project-test', result='SUCCESS', changes='1,1'), + ], ordered=False) + + job = self.getJobFromHistory('project-test') + self.assertEqual(set(['feature']), + set(job.parameters['zuul']['jobtags'])) + def test_post_jobs(self): self.create_branch('org/project', 'feature/bar') self.fake_gerrit.addEvent( diff --git a/zuul/model.py b/zuul/model.py index 5d907bd48c..bb1be34902 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -5215,6 +5215,12 @@ class Job(ConfigObject): if source_tpc.trusted: return None + # If we have some always-dynamic-branches, then this is + # probably not a shared jobs repo, so we will use implied + # branches regardless of how many branches it has. + if source_tpc.always_dynamic_branches: + return self.implied_branch_matcher + # If this project only has one branch, don't use implied # branch matchers. This way central job repos can work. branches = tenant.getProjectBranches(