Support wildcard matching in repository configuration Per repository configuration was only supporting "*" as the repository name so it was only possible to configure one default submit type and same owner groups for all the new repositories. [repository "*"] ownerGroup = Registered Users defaultSubmitType = MERGE_IF_NECESSARY Now supports different repository configuration based on the name. The only matching patterns supported are exact match or wildcard matching which can be specified by ending the name by a *. Obviously, repository name "*" still represents all repositories. If a project matches more than one repository configuration, then the configuration from the more precise match will be used. In the following example, the default submit type for a project named project/plugins/a would be CHERRY_PICK. [repository "project/*"] defaultSubmitType = MERGE_IF_NECESSARY [repository "project/plugins/*"] defaultSubmitType = CHERRY_PICK Change-Id: I8b9c157f60a3ad1c6f542cef62e5de8fe9333126
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index 742d996..61c764a 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt
@@ -2844,9 +2844,22 @@ ownerGroup = Registered Users ---- -[NOTE] -Currently only the repository name `*` is supported. -This is a wildcard designating all repositories. +The only matching patterns supported are exact match or wildcard matching which +can be specified by ending the name with a `*`. If a project matches more than one +repository configuration, then the configuration from the more precise match +will be used. In the following example, the default submit type for a project +named `project/plugins/a` would be `CHERRY_PICK`. + +---- +[repository "project/*"] + defaultSubmitType = MERGE_IF_NECESSARY +[repository "project/plugins/*"] + defaultSubmitType = CHERRY_PICK +---- + +[NOTE] All properties are used from the matching repository configuration. In +the previous example, all properties will be used from `project/plugins/\*` +section and no properties will be inherited nor overridden from `project/*`. [[repository.name.defaultSubmitType]]repository.<name>.defaultSubmitType:: +
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index 06d73a8..86785ef 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -38,7 +38,6 @@ import com.google.gerrit.extensions.webui.PatchSetWebLink; import com.google.gerrit.extensions.webui.ProjectWebLink; import com.google.gerrit.extensions.webui.TopMenu; -import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.rules.PrologModule; import com.google.gerrit.rules.RulesCache; import com.google.gerrit.server.AnonymousUser; @@ -137,7 +136,6 @@ import org.eclipse.jgit.transport.PreUploadHook; import java.util.List; -import java.util.Set; /** Starts global state with standard dependencies. */ @@ -203,9 +201,8 @@ bind(AccountVisibility.class) .toProvider(AccountVisibilityProvider.class) .in(SINGLETON); - bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) - .annotatedWith(ProjectOwnerGroups.class) - .toProvider(ProjectOwnerGroupsProvider.class).in(SINGLETON); + factory(ProjectOwnerGroupsProvider.Factory.class); + bind(RepositoryConfig.class); bind(AuthBackend.class).to(UniversalAuthBackend.class).in(SINGLETON); DynamicSet.setOf(binder(), AuthBackend.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java index 5e2f71f..5dd2784 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
@@ -30,7 +30,8 @@ @GerritServerConfig Config config, ThreadLocalRequestContext threadContext, ServerRequestContext serverCtx) { - super(gb, config, threadContext, serverCtx, "receive", null, "allowGroup"); + super(gb, threadContext, serverCtx, config.getStringList("receive", null, + "allowGroup")); // If no group was set, default to "registered users" //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java index 79cfd88..545f48b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
@@ -29,7 +29,8 @@ @GerritServerConfig Config config, ThreadLocalRequestContext threadContext, ServerRequestContext serverCtx) { - super(gb, config, threadContext, serverCtx, "upload", null, "allowGroup"); + super(gb, threadContext, serverCtx, config.getStringList("upload", null, + "allowGroup")); // If no group was set, default to "registered users" and "anonymous" //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java index 5c3ec39..9e55a7f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -25,7 +25,6 @@ import com.google.inject.Inject; import com.google.inject.Provider; -import org.eclipse.jgit.lib.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,13 +39,10 @@ @Inject protected GroupSetProvider(GroupBackend groupBackend, - @GerritServerConfig Config config, ThreadLocalRequestContext threadContext, - ServerRequestContext serverCtx, String section, - String subsection, String name) { + ServerRequestContext serverCtx, String[] groupNames) { RequestContext ctx = threadContext.setContext(serverCtx); try { - String[] groupNames = config.getStringList(section, subsection, name); ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder(); for (String n : groupNames) { GroupReference g = GroupBackends.findBestSuggestion(groupBackend, n);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroups.java deleted file mode 100644 index 876c51f..0000000 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroups.java +++ /dev/null
@@ -1,30 +0,0 @@ -// Copyright (C) 2010 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.gerrit.server.config; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.google.inject.BindingAnnotation; - -import java.lang.annotation.Retention; - -/** - * Marker on a {@code Set<AccountGroup.Id>} for the configured groups which - * should become owners of a created project. - */ -@Retention(RUNTIME) -@BindingAnnotation -public @interface ProjectOwnerGroups { -} \ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java index 0189de3..23615d6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -14,30 +14,37 @@ package com.google.gerrit.server.config; +import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.account.GroupBackend; import com.google.gerrit.server.util.ServerRequestContext; import com.google.gerrit.server.util.ThreadLocalRequestContext; -import com.google.inject.Inject; - -import org.eclipse.jgit.lib.Config; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; /** * Provider of the group(s) which should become owners of a newly created - * project. Currently only supports {@code ownerGroup} declarations in the - * {@code "*"} repository, like so: + * project. The only matching patterns supported are exact match or wildcard + * matching which can be specified by ending the name with a {@code *}. * * <pre> * [repository "*"] * ownerGroup = Registered Users * ownerGroup = Administrators + * [repository "project/*"] + * ownerGroup = Administrators * </pre> */ public class ProjectOwnerGroupsProvider extends GroupSetProvider { - @Inject + + public interface Factory { + public ProjectOwnerGroupsProvider create(Project.NameKey project); + } + + @AssistedInject public ProjectOwnerGroupsProvider(GroupBackend gb, - @GerritServerConfig final Config config, - ThreadLocalRequestContext context, - ServerRequestContext serverCtx) { - super(gb, config, context, serverCtx, "repository", "*", "ownerGroup"); + ThreadLocalRequestContext context, ServerRequestContext serverCtx, + RepositoryConfig repositoryCfg, + @Assisted Project.NameKey project) { + super(gb, context, serverCtx, repositoryCfg.getOwnerGroups(project)); } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/RepositoryConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/RepositoryConfig.java new file mode 100644 index 0000000..c34b8a6 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/RepositoryConfig.java
@@ -0,0 +1,85 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.gerrit.extensions.client.SubmitType; +import com.google.gerrit.reviewdb.client.Project; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.jgit.lib.Config; + +@Singleton +public class RepositoryConfig { + + static final String SECTION_NAME = "repository"; + static final String OWNER_GROUP_NAME = "ownerGroup"; + static final String DEFAULT_SUBMIT_TYPE_NAME = "defaultSubmitType"; + + private final Config cfg; + + @Inject + public RepositoryConfig(@GerritServerConfig Config cfg) { + this.cfg = cfg; + } + + public SubmitType getDefaultSubmitType(Project.NameKey project) { + return cfg.getEnum(SECTION_NAME, findSubSection(project.get()), + DEFAULT_SUBMIT_TYPE_NAME, SubmitType.MERGE_IF_NECESSARY); + } + + public String[] getOwnerGroups(Project.NameKey project) { + return cfg.getStringList(SECTION_NAME, findSubSection(project.get()), + OWNER_GROUP_NAME); + } + + /** + * Find the subSection to get repository configuration from. + * <p> + * SubSection can use the * pattern so if project name matches more than one + * section, return the more precise one. E.g if the following subSections are + * defined: + * + * <pre> + * [repository "somePath/*"] + * name = value + * [repository "somePath/somePath/*"] + * name = value + * </pre> + * + * and this method is called with "somePath/somePath/someProject" as project + * name, it will return the subSection "somePath/somePath/*" + * + * @param project Name of the project + * @return the name of the subSection, null if none is found + */ + private String findSubSection(String project) { + String subSectionFound = null; + for (String subSection : cfg.getSubsections(SECTION_NAME)) { + if (isMatch(subSection, project) + && (subSectionFound == null || subSectionFound.length() < subSection + .length())) { + subSectionFound = subSection; + } + } + return subSectionFound; + } + + private boolean isMatch(String subSection, String project) { + return project.equals(subSection) + || (subSection.endsWith("*") && project.startsWith(subSection + .substring(0, subSection.length() - 1))); + } +}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java index ee2e564..5b159fe 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -44,8 +44,8 @@ import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.account.GroupBackend; -import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.ProjectOwnerGroups; +import com.google.gerrit.server.config.ProjectOwnerGroupsProvider; +import com.google.gerrit.server.config.RepositoryConfig; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MetaDataUpdate; @@ -61,7 +61,6 @@ import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.CommitBuilder; -import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -77,7 +76,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; @RequiresCapability(GlobalCapability.CREATE_PROJECT) public class CreateProject implements RestModifyView<TopLevelResource, ProjectInput> { @@ -97,10 +95,10 @@ private final DynamicSet<NewProjectCreatedListener> createdListener; private final ProjectCache projectCache; private final GroupBackend groupBackend; - private final Set<AccountGroup.UUID> projectOwnerGroups; + private final ProjectOwnerGroupsProvider.Factory projectOwnerGroups; private final MetaDataUpdate.User metaDataUpdateFactory; private final GitReferenceUpdated referenceUpdated; - private final Config cfg; + private final RepositoryConfig repositoryCfg; private final PersonIdent serverIdent; private final Provider<CurrentUser> currentUser; private final Provider<PutConfig> putConfig; @@ -115,10 +113,10 @@ DynamicSet<NewProjectCreatedListener> createdListener, ProjectCache projectCache, GroupBackend groupBackend, - @ProjectOwnerGroups Set<AccountGroup.UUID> projectOwnerGroups, + ProjectOwnerGroupsProvider.Factory projectOwnerGroups, MetaDataUpdate.User metaDataUpdateFactory, GitReferenceUpdated referenceUpdated, - @GerritServerConfig Config cfg, + RepositoryConfig repositoryCfg, @GerritPersonIdent PersonIdent serverIdent, Provider<CurrentUser> currentUser, Provider<PutConfig> putConfig, @@ -135,7 +133,7 @@ this.projectOwnerGroups = projectOwnerGroups; this.metaDataUpdateFactory = metaDataUpdateFactory; this.referenceUpdated = referenceUpdated; - this.cfg = cfg; + this.repositoryCfg = repositoryCfg; this.serverIdent = serverIdent; this.currentUser = currentUser; this.putConfig = putConfig; @@ -166,7 +164,8 @@ args.submitType = input.submitType; args.branch = normalizeBranchNames(input.branches); if (input.owners == null || input.owners.isEmpty()) { - args.ownerIds = new ArrayList<>(projectOwnerGroups); + args.ownerIds = + new ArrayList<>(projectOwnerGroups.create(args.getProject()).get()); } else { args.ownerIds = Lists.newArrayListWithCapacity(input.owners.size()); @@ -306,7 +305,7 @@ Project newProject = config.getProject(); newProject.setDescription(args.projectDescription); newProject.setSubmitType(MoreObjects.firstNonNull(args.submitType, - cfg.getEnum("repository", "*", "defaultSubmitType", SubmitType.MERGE_IF_NECESSARY))); + repositoryCfg.getDefaultSubmitType(args.getProject()))); newProject .setUseContributorAgreements(args.contributorAgreements); newProject.setUseSignedOffBy(args.signedOffBy);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java new file mode 100644 index 0000000..1fb6d81 --- /dev/null +++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
@@ -0,0 +1,147 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.Lists; +import com.google.gerrit.extensions.client.SubmitType; +import com.google.gerrit.reviewdb.client.Project.NameKey; + +import org.eclipse.jgit.lib.Config; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; + +public class RepositoryConfigTest { + + private Config cfg; + private RepositoryConfig repoCfg; + + @Before + public void setUp() throws Exception { + cfg = new Config(); + repoCfg = new RepositoryConfig(cfg); + } + + @Test + public void testDefaultSubmitTypeWhenNotConfigured() { + assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject"))) + .isEqualTo(SubmitType.MERGE_IF_NECESSARY); + } + + @Test + public void testDefaultSubmitTypeForStarFilter() { + configureDefaultSubmitType("*", SubmitType.CHERRY_PICK); + assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject"))) + .isEqualTo(SubmitType.CHERRY_PICK); + + configureDefaultSubmitType("*", SubmitType.FAST_FORWARD_ONLY); + assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject"))) + .isEqualTo(SubmitType.FAST_FORWARD_ONLY); + + configureDefaultSubmitType("*", SubmitType.REBASE_IF_NECESSARY); + assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject"))) + .isEqualTo(SubmitType.REBASE_IF_NECESSARY); + } + + @Test + public void testDefaultSubmitTypeForSpecificFilter() { + configureDefaultSubmitType("someProject", SubmitType.CHERRY_PICK); + assertThat(repoCfg.getDefaultSubmitType(new NameKey("someOtherProject"))) + .isEqualTo(SubmitType.MERGE_IF_NECESSARY); + assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject"))) + .isEqualTo(SubmitType.CHERRY_PICK); + } + + @Test + public void testDefaultSubmitTypeForStartWithFilter() { + configureDefaultSubmitType("somePath/somePath/*", + SubmitType.REBASE_IF_NECESSARY); + configureDefaultSubmitType("somePath/*", SubmitType.CHERRY_PICK); + configureDefaultSubmitType("*", SubmitType.MERGE_ALWAYS); + + assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject"))) + .isEqualTo(SubmitType.MERGE_ALWAYS); + + assertThat( + repoCfg.getDefaultSubmitType(new NameKey("somePath/someProject"))) + .isEqualTo(SubmitType.CHERRY_PICK); + + assertThat( + repoCfg.getDefaultSubmitType(new NameKey( + "somePath/somePath/someProject"))).isEqualTo( + SubmitType.REBASE_IF_NECESSARY); + } + + private void configureDefaultSubmitType(String projectFilter, + SubmitType submitType) { + cfg.setString(RepositoryConfig.SECTION_NAME, projectFilter, + RepositoryConfig.DEFAULT_SUBMIT_TYPE_NAME, submitType.toString()); + } + + @Test + public void testOwnerGroupsWhenNotConfigured() { + assertThat(repoCfg.getOwnerGroups(new NameKey("someProject"))).isEqualTo( + new String[] {}); + } + + @Test + public void testOwnerGroupsForStarFilter() { + String[] ownerGroups = new String[] {"group1", "group2"}; + configureOwnerGroups("*", Lists.newArrayList(ownerGroups)); + assertThat(repoCfg.getOwnerGroups(new NameKey("someProject"))).isEqualTo( + ownerGroups); + } + + @Test + public void testOwnerGroupsForSpecificFilter() { + String[] ownerGroups = new String[] {"group1", "group2"}; + configureOwnerGroups("someProject", Lists.newArrayList(ownerGroups)); + assertThat(repoCfg.getOwnerGroups(new NameKey("someOtherProject"))) + .isEqualTo(new String[] {}); + assertThat(repoCfg.getOwnerGroups(new NameKey("someProject"))).isEqualTo( + ownerGroups); + } + + @Test + public void testOwnerGroupsForStartWithFilter() { + String[] ownerGroups1 = new String[] {"group1"}; + String[] ownerGroups2 = new String[] {"group2"}; + String[] ownerGroups3 = new String[] {"group3"}; + + configureOwnerGroups("*", Lists.newArrayList(ownerGroups1)); + configureOwnerGroups("somePath/*", Lists.newArrayList(ownerGroups2)); + configureOwnerGroups("somePath/somePath/*", + Lists.newArrayList(ownerGroups3)); + + assertThat(repoCfg.getOwnerGroups(new NameKey("someProject"))).isEqualTo( + ownerGroups1); + + assertThat(repoCfg.getOwnerGroups(new NameKey("somePath/someProject"))) + .isEqualTo(ownerGroups2); + + assertThat( + repoCfg.getOwnerGroups(new NameKey("somePath/somePath/someProject"))) + .isEqualTo(ownerGroups3); + } + + private void configureOwnerGroups(String projectFilter, + List<String> ownerGroups) { + cfg.setStringList(RepositoryConfig.SECTION_NAME, projectFilter, + RepositoryConfig.OWNER_GROUP_NAME, ownerGroups); + } +}