Merge "Support wildcard matching in repository configuration"
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 6e4e2ed..99ff581 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;
@@ -138,7 +137,6 @@
import org.eclipse.jgit.transport.PreUploadHook;
import java.util.List;
-import java.util.Set;
/** Starts global state with standard dependencies. */
@@ -204,9 +202,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);
+ }
+}