From bb444175a65b5aea320f68ecbf0b4c208b2bec2a Mon Sep 17 00:00:00 2001 From: Stefan Beller Date: Thu, 4 Aug 2016 16:13:11 -0700 Subject: [PATCH] SubmoduleOp: Allow all branches to be subscribed This eases the way of handling many different branches for the superproject subscription. As the superproject subscriptions are not yet in a release, we can still play around with them. When a user sees "refs = refs/heads/*:refs/heads/*" they cannot tell whether this is 1:1 matching or any match to any match without consulting the documentation. Differentiate the matching strategy by keys, such that we have "all = " as well as "matching = ". The "all" key implies there is no interaction between the wildcards on the left and right side, e.g. all=refs/heads/*:refs/heads* indicates that any branch can be subscribed to any other branch in the given superproject. "matching" however substitutes the wildcard on the right with the captured value on the left. matching=refs/heads/*:refs/heads* allows a subscription of refs/heads/foo in the submodule to the refs/heads/foo in the superproject. Change-Id: I84d3a72c00f76570798880adf54ce56f974466ff --- Documentation/user-submodules.txt | 37 +++++--- .../git/AbstractSubmoduleSubscription.java | 22 ++++- .../git/SubmoduleSubscriptionsIT.java | 93 +++++++++++++------ ...bmoduleSubscriptionsWholeTopicMergeIT.java | 34 +++---- .../gerrit/common/data/SubscribeSection.java | 52 ++++++++--- .../gerrit/server/git/ProjectConfig.java | 20 +++- .../google/gerrit/server/git/SubmoduleOp.java | 68 +++++++++----- .../gerrit/server/schema/Schema_120.java | 4 +- 8 files changed, 226 insertions(+), 104 deletions(-) diff --git a/Documentation/user-submodules.txt b/Documentation/user-submodules.txt index bccb074fbc..7b5f0a6dbe 100644 --- a/Documentation/user-submodules.txt +++ b/Documentation/user-submodules.txt @@ -86,7 +86,7 @@ the subscribe capabilities in the 'project.config' file: and add the following lines: ---- [allowSuperproject ""] - refs = + matching = ---- where the 'superproject' should be the exact project name of the superproject. The refspec defines which branches of the submodule are allowed to be @@ -104,7 +104,7 @@ The configuration is inherited from parent projects, such that you can have a configuration in the "All-Projects" project like: ---- [allowSuperproject "my-only-superproject"] - refs = refs/heads/*:refs/heads/* + matching = refs/heads/*:refs/heads/* ---- and then you don't have to worry about configuring the individual projects any more. Child projects cannot negate the parent's configuration. @@ -147,14 +147,17 @@ a `branch` field and a url pointing to this server. [[acl_refspec]] === The RefSpec in the allowSuperproject section -The RefSpec for defining the branch level access for subscriptions look similar -to Git style RefSpecs used for pushing in Git. Regular expressions -as found in the ACL configuration are not supported. The most restrictive -RefSpec is allowing one specific branch of the submodule to be subscribed -to one specific branch of the superproject via: +There are two options for specifying which branches can be subscribed +to. The most common is to set `allowSuperproject..matching` +to a Git-style refspec, which has the same syntax as the refspecs used +for pushing in Git. Regular expressions as found in the ACL configuration +are not supported. + +The most restrictive refspec is allowing one specific branch of the +submodule to be subscribed to one specific branch of the superproject: ---- [allowSuperproject ""] - refs = refs/heads/:refs/heads/ + matching = refs/heads/:refs/heads/ ---- If you want to allow for a 1:1 mapping, i.e. 'master' maps to 'master', @@ -162,14 +165,24 @@ If you want to allow for a 1:1 mapping, i.e. 'master' maps to 'master', 'stable': ---- [allowSuperproject ""] - refs = refs/heads/*:refs/heads/* + matching = refs/heads/*:refs/heads/* ---- -If you want to enable a branch to be subscribed to any other branch of -the superproject, omit the second part of the RefSpec: +To allow all refs matching one pattern to subscribe to all refs +matching another pattern, set `allowSuperproject..all` +to the patterns concatenated with a colon. For example, to make a +single branch available for subscription from all branches of the +superproject: ---- [allowSuperproject ""] - refs = refs/heads/ + all = refs/heads/:refs/heads/* +---- + +To make all branches available for subscription from all branches of +the superproject: +---- + [allowSuperproject ""] + all = refs/heads/*:refs/heads/* ---- === Subscription Limitations diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java index 2ca46910d2..28f7ff8ebf 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java @@ -130,18 +130,25 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest { return pushChangeTo(repo, "refs/heads/" + branch, "some change", ""); } - protected void allowSubmoduleSubscription(String submodule, String subBranch, - String superproject, String superBranch) throws Exception { + protected void allowSubmoduleSubscription(String submodule, + String subBranch, String superproject, String superBranch, boolean match) + throws Exception { Project.NameKey sub = new Project.NameKey(name(submodule)); Project.NameKey superName = new Project.NameKey(name(superproject)); try (MetaDataUpdate md = metaDataUpdateFactory.create(sub)) { md.setMessage("Added superproject subscription"); ProjectConfig pc = ProjectConfig.read(md); SubscribeSection s = new SubscribeSection(superName); + String refspec; if (superBranch == null) { - s.addRefSpec(subBranch); + refspec = subBranch; } else { - s.addRefSpec(subBranch + ":" + superBranch); + refspec = subBranch + ":" + superBranch; + } + if (match) { + s.addMatchingRefSpec(refspec); + } else { + s.addMultiMatchRefSpec(refspec); } pc.addSubscribeSection(s); ObjectId oldId = pc.getRevision(); @@ -151,6 +158,13 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest { } } + protected void allowMatchingSubmoduleSubscription(String submodule, + String subBranch, String superproject, String superBranch) + throws Exception { + allowSubmoduleSubscription(submodule, subBranch, superproject, + superBranch, true); + } + protected void createSubmoduleSubscription(TestRepository repo, String branch, String subscribeToRepo, String subscribeToBranch) throws Exception { Config config = new Config(); diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java index 5326262048..6684e85195 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java @@ -44,7 +44,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionWithoutGlobalServerSetting() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); createSubmoduleSubscription(superRepo, "master", @@ -70,7 +70,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionToEmptyRepo() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); createSubmoduleSubscription(superRepo, "master", @@ -87,7 +87,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionToExistingRepo() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -104,8 +104,8 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionWildcardACLForSingleBranch() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - // master is allowed to be subscribed to any superprojects branch: - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + // master is allowed to be subscribed to master branch only: + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", null); // create 'branch': pushChangeTo(superRepo, "branch"); @@ -120,14 +120,14 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project", subHEAD); - expectToHaveSubmoduleState(superRepo, "branch", - "subscribed-to-project", subHEAD); + assertThat(hasSubmodule(superRepo, "branch", + "subscribed-to-project")).isFalse(); } @Test public void testSubscriptionWildcardACLForMissingProject() throws Exception { TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/*", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/*", "not-existing-super-project", "refs/heads/*"); pushChangeTo(subRepo, "master"); } @@ -136,7 +136,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionWildcardACLForMissingBranch() throws Exception { createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/*", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/*", "super-project", "refs/heads/*"); pushChangeTo(subRepo, "foo"); } @@ -145,7 +145,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionWildcardACLForMissingGitmodules() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/*", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/*", "super-project", "refs/heads/*"); pushChangeTo(superRepo, "master"); pushChangeTo(subRepo, "master"); @@ -156,7 +156,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); // any branch is allowed to be subscribed to the same superprojects branch: - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/*", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/*", "super-project", "refs/heads/*"); // create 'branch' in both repos: @@ -188,12 +188,53 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { "subscribed-to-project", subHEAD3); } + @Test + public void testSubscriptionWildcardACLForManyBranches() throws Exception { + TestRepository superRepo = createProjectWithPush("super-project"); + TestRepository subRepo = createProjectWithPush("subscribed-to-project"); + + // Any branch is allowed to be subscribed to any superproject branch: + allowSubmoduleSubscription("subscribed-to-project", "refs/heads/*", + "super-project", null, false); + pushChangeTo(superRepo, "branch"); + pushChangeTo(subRepo, "another-branch"); + createSubmoduleSubscription(superRepo, "branch", + "subscribed-to-project", "another-branch"); + ObjectId subHEAD = pushChangeTo(subRepo, "another-branch"); + expectToHaveSubmoduleState(superRepo, "branch", + "subscribed-to-project", subHEAD); + } + + @Test + public void testSubscriptionWildcardACLOneToManyBranches() throws Exception { + TestRepository superRepo = createProjectWithPush("super-project"); + TestRepository subRepo = createProjectWithPush("subscribed-to-project"); + + // Any branch is allowed to be subscribed to any superproject branch: + allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + "super-project", "refs/heads/*", false); + pushChangeTo(superRepo, "branch"); + createSubmoduleSubscription(superRepo, "branch", + "subscribed-to-project", "master"); + ObjectId subHEAD = pushChangeTo(subRepo, "master"); + expectToHaveSubmoduleState(superRepo, "branch", + "subscribed-to-project", subHEAD); + + createSubmoduleSubscription(superRepo, "branch", + "subscribed-to-project", "branch"); + pushChangeTo(subRepo, "branch"); + + // no change expected, as only master is subscribed: + expectToHaveSubmoduleState(superRepo, "branch", + "subscribed-to-project", subHEAD); + } + @Test @GerritConfig(name = "submodule.verboseSuperprojectUpdate", value = "false") public void testSubmoduleShortCommitMessage() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -220,7 +261,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubmoduleSubjectCommitMessage() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -248,7 +289,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubmoduleCommitMessage() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -276,7 +317,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionUnsubscribe() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -303,7 +344,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -329,7 +370,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionToDifferentBranches() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/foo", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/foo", "super-project", "refs/heads/master"); createSubmoduleSubscription(superRepo, "master", @@ -345,9 +386,9 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testBranchCircularSubscription() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); - allowSubmoduleSubscription("super-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("super-project", "refs/heads/master", "subscribed-to-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -370,9 +411,9 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); - allowSubmoduleSubscription("super-project", "refs/heads/dev", + allowMatchingSubmoduleSubscription("super-project", "refs/heads/dev", "subscribed-to-project", "refs/heads/dev"); pushChangeTo(subRepo, "master"); @@ -414,7 +455,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionFailOnWrongProjectACL() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "wrong-super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -429,7 +470,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testSubscriptionFailOnWrongBranchACL() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/wrong-branch"); pushChangeTo(subRepo, "master"); @@ -448,7 +489,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project", new Project.NameKey(name("config-repo2"))); - allowSubmoduleSubscription("config-repo", "refs/heads/*", + allowMatchingSubmoduleSubscription("config-repo", "refs/heads/*", "super-project", "refs/heads/*"); pushChangeTo(subRepo, "master"); @@ -463,7 +504,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { public void testAllowedButNotSubscribed() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); pushChangeTo(subRepo, "master"); @@ -490,7 +531,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription { TestRepository subRepo = createProjectWithPush( "nested/subscribed-to-project"); // master is allowed to be subscribed to any superprojects branch: - allowSubmoduleSubscription("nested/subscribed-to-project", + allowMatchingSubmoduleSubscription("nested/subscribed-to-project", "refs/heads/master", "super-project", null); pushChangeTo(subRepo, "master"); diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java index dfce62e1f2..0ff3af5960 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java @@ -58,7 +58,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT public void testSubscriptionUpdateOfManyChanges() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master"); @@ -113,7 +113,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT public void testSubscriptionUpdateIncludingChangeInSuperproject() throws Exception { TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master"); @@ -181,11 +181,11 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT TestRepository sub2 = createProjectWithPush("sub2"); TestRepository sub3 = createProjectWithPush("sub3"); - allowSubmoduleSubscription("sub1", "refs/heads/master", + allowMatchingSubmoduleSubscription("sub1", "refs/heads/master", "super-project", "refs/heads/master"); - allowSubmoduleSubscription("sub2", "refs/heads/master", + allowMatchingSubmoduleSubscription("sub2", "refs/heads/master", "super-project", "refs/heads/master"); - allowSubmoduleSubscription("sub3", "refs/heads/master", + allowMatchingSubmoduleSubscription("sub3", "refs/heads/master", "super-project", "refs/heads/master"); Config config = new Config(); @@ -227,7 +227,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT TestRepository superRepo = createProjectWithPush("super-project"); TestRepository sub = createProjectWithPush("sub"); - allowSubmoduleSubscription("sub", "refs/heads/master", + allowMatchingSubmoduleSubscription("sub", "refs/heads/master", "super-project", "refs/heads/master"); Config config = new Config(); @@ -261,7 +261,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT TestRepository sub = createProjectWithPush("sub"); TestRepository standAlone = createProjectWithPush("standalone"); - allowSubmoduleSubscription("sub", "refs/heads/master", + allowMatchingSubmoduleSubscription("sub", "refs/heads/master", "super-project", "refs/heads/master"); createSubmoduleSubscription(superRepo, "master", "sub", "master"); @@ -301,9 +301,9 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT TestRepository midRepo = createProjectWithPush("mid-project"); TestRepository bottomRepo = createProjectWithPush("bottom-project"); - allowSubmoduleSubscription("mid-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("mid-project", "refs/heads/master", "top-project", "refs/heads/master"); - allowSubmoduleSubscription("bottom-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("bottom-project", "refs/heads/master", "mid-project", "refs/heads/master"); createSubmoduleSubscription(topRepo, "master", "mid-project", "master"); @@ -332,11 +332,11 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT TestRepository midRepo = createProjectWithPush("mid-project"); TestRepository bottomRepo = createProjectWithPush("bottom-project"); - allowSubmoduleSubscription("mid-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("mid-project", "refs/heads/master", "top-project", "refs/heads/master"); - allowSubmoduleSubscription("bottom-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("bottom-project", "refs/heads/master", "mid-project", "refs/heads/master"); - allowSubmoduleSubscription("bottom-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("bottom-project", "refs/heads/master", "top-project", "refs/heads/master"); createSubmoduleSubscription(midRepo, "master", "bottom-project", "master"); @@ -373,11 +373,11 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT createSubmoduleSubscription(topRepo, "master", "mid-project", "master"); createSubmoduleSubscription(bottomRepo, "master", "top-project", "master"); - allowSubmoduleSubscription("bottom-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("bottom-project", "refs/heads/master", "mid-project", "refs/heads/master"); - allowSubmoduleSubscription("mid-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("mid-project", "refs/heads/master", "top-project", "refs/heads/master"); - allowSubmoduleSubscription("top-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("top-project", "refs/heads/master", "bottom-project", "refs/heads/master"); ObjectId bottomMasterHead = @@ -401,9 +401,9 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT TestRepository superRepo = createProjectWithPush("super-project"); TestRepository subRepo = createProjectWithPush("subscribed-to-project"); - allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master", + allowMatchingSubmoduleSubscription("subscribed-to-project", "refs/heads/master", "super-project", "refs/heads/master"); - allowSubmoduleSubscription("super-project", "refs/heads/dev", + allowMatchingSubmoduleSubscription("super-project", "refs/heads/dev", "subscribed-to-project", "refs/heads/dev"); pushChangeTo(subRepo, "dev"); diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java index 6cee630ef2..3fdc331deb 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubscribeSection.java @@ -29,20 +29,28 @@ import java.util.List; @GwtIncompatible("Unemulated org.eclipse.jgit.transport.RefSpec") public class SubscribeSection { - private final List refSpecs; + private final List multiMatchRefSpecs; + private final List matchingRefSpecs; private final Project.NameKey project; public SubscribeSection(Project.NameKey p) { project = p; - refSpecs = new ArrayList<>(); + matchingRefSpecs = new ArrayList<>(); + multiMatchRefSpecs = new ArrayList<>(); } - public void addRefSpec(RefSpec spec) { - refSpecs.add(spec); + public void addMatchingRefSpec(RefSpec spec) { + matchingRefSpecs.add(spec); } - public void addRefSpec(String spec) { - refSpecs.add(new RefSpec(spec)); + public void addMatchingRefSpec(String spec) { + RefSpec r = new RefSpec(spec); + matchingRefSpecs.add(r); + } + + public void addMultiMatchRefSpec(String spec) { + RefSpec r = new RefSpec(spec, RefSpec.WildcardMode.ALLOW_MISMATCH); + multiMatchRefSpecs.add(r); } public Project.NameKey getProject() { @@ -57,7 +65,12 @@ public class SubscribeSection { * @return if the branch could trigger a superproject update */ public boolean appliesTo(Branch.NameKey branch) { - for (RefSpec r : refSpecs) { + for (RefSpec r : matchingRefSpecs) { + if (r.matchSource(branch.get())) { + return true; + } + } + for (RefSpec r : multiMatchRefSpecs) { if (r.matchSource(branch.get())) { return true; } @@ -65,8 +78,12 @@ public class SubscribeSection { return false; } - public Collection getRefSpecs() { - return Collections.unmodifiableCollection(refSpecs); + public Collection getMatchingRefSpecs() { + return Collections.unmodifiableCollection(matchingRefSpecs); + } + + public Collection getMultiMatchRefSpecs() { + return Collections.unmodifiableCollection(multiMatchRefSpecs); } @Override @@ -74,10 +91,19 @@ public class SubscribeSection { StringBuilder ret = new StringBuilder(); ret.append("[SubscribeSection, project="); ret.append(project); - ret.append(", refs=["); - for (RefSpec r : refSpecs) { - ret.append(r.toString()); - ret.append(", "); + if (!matchingRefSpecs.isEmpty()) { + ret.append(", matching=["); + for (RefSpec r : matchingRefSpecs) { + ret.append(r.toString()); + ret.append(", "); + } + } + if (!multiMatchRefSpecs.isEmpty()) { + ret.append(", all=["); + for (RefSpec r : multiMatchRefSpecs) { + ret.append(r.toString()); + ret.append(", "); + } } ret.append("]"); return ret.toString(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java index 6d9d7599b1..64d9a9cc1b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java @@ -132,7 +132,8 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError. private static final String KEY_STATE = "state"; private static final String SUBSCRIBE_SECTION = "allowSuperproject"; - private static final String SUBSCRIBE_REFS = "refs"; + private static final String SUBSCRIBE_MATCH_REFS = "matching"; + private static final String SUBSCRIBE_MULTI_MATCH_REFS = "all"; private static final String DASHBOARD = "dashboard"; private static final String KEY_DEFAULT = "default"; @@ -848,8 +849,12 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError. Project.NameKey p = new Project.NameKey(projectName); SubscribeSection ss = new SubscribeSection(p); for (String s : rc.getStringList(SUBSCRIBE_SECTION, - projectName, SUBSCRIBE_REFS)) { - ss.addRefSpec(s); + projectName, SUBSCRIBE_MULTI_MATCH_REFS)) { + ss.addMultiMatchRefSpec(s); + } + for (String s : rc.getStringList(SUBSCRIBE_SECTION, + projectName, SUBSCRIBE_MATCH_REFS)) { + ss.addMatchingRefSpec(s); } subscribeSections.put(p, ss); } @@ -1238,8 +1243,13 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError. private void saveSubscribeSections(Config rc) { for (Project.NameKey p : subscribeSections.keySet()) { SubscribeSection s = subscribeSections.get(p); - for (RefSpec r : s.getRefSpecs()) { - rc.setString(SUBSCRIBE_SECTION, p.get(), SUBSCRIBE_REFS, r.toString()); + for (RefSpec r : s.getMatchingRefSpecs()) { + rc.setString(SUBSCRIBE_SECTION, p.get(), + SUBSCRIBE_MATCH_REFS, r.toString()); + } + for (RefSpec r : s.getMultiMatchRefSpecs()) { + rc.setString(SUBSCRIBE_SECTION, p.get(), + SUBSCRIBE_MULTI_MATCH_REFS, r.toString()); } } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java index b23ff95147..76f74ed58a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java @@ -62,6 +62,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; @@ -222,33 +223,50 @@ public class SubmoduleOp { private Collection getDestinationBranches(Branch.NameKey src, SubscribeSection s) throws IOException { - Collection ret = new ArrayList<>(); + Collection ret = new HashSet<>(); logDebug("Inspecting SubscribeSection " + s); - for (RefSpec r : s.getRefSpecs()) { - logDebug("Inspecting ref " + r); - if (r.matchSource(src.get())) { - if (r.getDestination() == null) { - // no need to care for wildcard, as we matched already - OpenRepo or; - try { - or = orm.openRepo(s.getProject(), false); - } catch (NoSuchProjectException e) { - // A project listed a non existent project to be allowed - // to subscribe to it. Allow this for now. - continue; - } + for (RefSpec r : s.getMatchingRefSpecs()) { + logDebug("Inspecting [matching] ref " + r); + if (!r.matchSource(src.get())) { + continue; + } + if (r.isWildcard()) { + // refs/heads/*[:refs/somewhere/*] + ret.add(new Branch.NameKey(s.getProject(), + r.expandFromSource(src.get()).getDestination())); + } else { + // e.g. refs/heads/master[:refs/heads/stable] + String dest = r.getDestination(); + if (dest == null) { + dest = r.getSource(); + } + ret.add(new Branch.NameKey(s.getProject(), dest)); + } + } - for (Ref ref : or.repo.getRefDatabase().getRefs( - RefNames.REFS_HEADS).values()) { - ret.add(new Branch.NameKey(s.getProject(), ref.getName())); - } - } else if (r.isWildcard()) { - // refs/heads/*:refs/heads/* - ret.add(new Branch.NameKey(s.getProject(), - r.expandFromSource(src.get()).getDestination())); - } else { - // e.g. refs/heads/master:refs/heads/stable - ret.add(new Branch.NameKey(s.getProject(), r.getDestination())); + for (RefSpec r : s.getMultiMatchRefSpecs()) { + logDebug("Inspecting [all] ref " + r); + if (!r.matchSource(src.get())) { + continue; + } + OpenRepo or; + try { + or = orm.openRepo(s.getProject(), false); + } catch (NoSuchProjectException e) { + // A project listed a non existent project to be allowed + // to subscribe to it. Allow this for now, i.e. no exception is + // thrown. + continue; + } + + for (Ref ref : or.repo.getRefDatabase().getRefs( + RefNames.REFS_HEADS).values()) { + if (r.getDestination() != null && !r.matchDestination(ref.getName())) { + continue; + } + Branch.NameKey b = new Branch.NameKey(s.getProject(), ref.getName()); + if (!ret.contains(b)) { + ret.add(b); } } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_120.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_120.java index e95353adee..fc1b0cddee 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_120.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_120.java @@ -79,10 +79,10 @@ public class Schema_120 extends SchemaVersion { } RefSpec newRefSpec = new RefSpec(subbranch.get() + ":" + superBranch.get()); - if (!s.getRefSpecs().contains(newRefSpec)) { + if (!s.getMatchingRefSpecs().contains(newRefSpec)) { // For the migration we use only exact RefSpecs, we're not trying to // generalize it. - s.addRefSpec(newRefSpec); + s.addMatchingRefSpec(newRefSpec); } pc.commit(md);