Merge "CodeOwnerApprovalCheck: Allow to check for an account which paths it owns"
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
index 82fb351..22bc543 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
@@ -34,6 +34,7 @@
abstract class CodeOwnerConfigFilesRequest {
private boolean includeNonParsableFiles;
private String email;
+ private String path;
/** Includes non-parsable code owner config files into the result. */
public CodeOwnerConfigFilesRequest includeNonParsableFiles(boolean includeNonParsableFiles) {
@@ -62,6 +63,23 @@
return email;
}
+ /**
+ * Limits the returned code owner config files to those that have a path matching the given
+ * glob.
+ *
+ * @param path the path glob that should be matched
+ */
+ public CodeOwnerConfigFilesRequest withPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ /** Returns the path glob that should be matched by the returned code owner config files/ */
+ @Nullable
+ public String getPath() {
+ return path;
+ }
+
/** Executes the request and retrieves the paths of the requested code owner config file */
public abstract List<String> paths() throws RestApiException;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
index f6e4804..7052a1b 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
@@ -66,6 +66,7 @@
GetCodeOwnerConfigFiles getCodeOwnerConfigFiles = getCodeOwnerConfigFilesProvider.get();
getCodeOwnerConfigFiles.setIncludeNonParsableFiles(getIncludeNonParsableFiles());
getCodeOwnerConfigFiles.setEmail(getEmail());
+ getCodeOwnerConfigFiles.setPath(getPath());
return getCodeOwnerConfigFiles.apply(branchResource).value();
}
};
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java
index 561c3b1..7a4261a 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java
@@ -50,14 +50,6 @@
* <p>The syntax is described at in the {@code find-owners} plugin documentation at:
* https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/syntax.md
*
- * <p><strong>Note:</strong> Currently this class only supports a subset of the syntax. Only the
- * following syntax elements are supported:
- *
- * <ul>
- * <li>comment: a line can be a comment (comments must start with '#')
- * <li>code owner emails: a line can be the email of a code owner
- * </ul>
- *
* <p>Comment lines are silently ignored.
*
* <p>Invalid lines cause the parsing to fail and trigger a {@link CodeOwnerConfigParseException}.
diff --git a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java
index 2a29766..8f20fc1 100644
--- a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java
+++ b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java
@@ -14,8 +14,6 @@
package com.google.gerrit.plugins.codeowners.config;
-import static com.google.gerrit.server.project.ProjectCache.illegalState;
-
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Project;
@@ -30,7 +28,7 @@
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.ValidationMessage;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
-import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectLevelConfig;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
@@ -41,6 +39,7 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
/** Validates modifications to the {@code code-owners.config} file in {@code refs/meta/config}. */
@Singleton
@@ -48,8 +47,9 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final String pluginName;
- private final ProjectCache projectCache;
private final GitRepositoryManager repoManager;
+ private final ProjectConfig.Factory projectConfigFactory;
+ private final ProjectState.Factory projectStateFactory;
private final ChangedFiles changedFiles;
private final GeneralConfig generalConfig;
private final StatusConfig statusConfig;
@@ -60,8 +60,9 @@
@Inject
CodeOwnersPluginConfigValidator(
@PluginName String pluginName,
- ProjectCache projectCache,
GitRepositoryManager repoManager,
+ ProjectConfig.Factory projectConfigFactory,
+ ProjectState.Factory projectStateFactory,
ChangedFiles changedFiles,
GeneralConfig generalConfig,
StatusConfig statusConfig,
@@ -69,8 +70,9 @@
RequiredApprovalConfig requiredApprovalConfig,
OverrideApprovalConfig overrideApprovalConfig) {
this.pluginName = pluginName;
- this.projectCache = projectCache;
this.repoManager = repoManager;
+ this.projectConfigFactory = projectConfigFactory;
+ this.projectStateFactory = projectStateFactory;
this.changedFiles = changedFiles;
this.generalConfig = generalConfig;
this.statusConfig = statusConfig;
@@ -93,11 +95,11 @@
return ImmutableList.of();
}
- ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
+ ProjectState projectState = getProjectState(project, receiveEvent.commit);
ProjectLevelConfig.Bare cfg = loadConfig(project, fileName, receiveEvent.commit);
validateConfig(projectState, fileName, cfg);
return ImmutableList.of();
- } catch (IOException | PatchListNotAvailableException e) {
+ } catch (IOException | ConfigInvalidException | PatchListNotAvailableException e) {
String errorMessage =
String.format(
"failed to validate file %s for revision %s in ref %s of project %s",
@@ -107,6 +109,15 @@
}
}
+ private ProjectState getProjectState(Project.NameKey projectName, RevCommit commit)
+ throws IOException, ConfigInvalidException {
+ try (Repository repo = repoManager.openRepository(projectName)) {
+ ProjectConfig projectConfig = projectConfigFactory.create(projectName);
+ projectConfig.load(repo, commit);
+ return projectStateFactory.create(projectConfig.getCacheable());
+ }
+ }
+
/**
* Whether the given file was changed in the given revision.
*
diff --git a/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java b/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java
index 54ddf7c..8a15dcb 100644
--- a/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java
@@ -227,7 +227,7 @@
+ " plugin.%s.%s). Falling back to default value %s.",
pluginConfigFromGerritConfig.getString(KEY_FALLBACK_CODE_OWNERS),
pluginName,
- KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED,
+ KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.NONE);
return FallbackCodeOwners.NONE;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java
index 8190e7c..9ec7118 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java
@@ -51,6 +51,7 @@
private boolean includeNonParsableFiles;
private String email;
+ private String pathGlob;
@Option(
name = "--include-non-parsable-files",
@@ -67,6 +68,15 @@
this.email = email;
}
+ @Option(
+ name = "--path",
+ usage =
+ "limits the returned code owner config files to those that have a path matching"
+ + " this glob")
+ public void setPath(@Nullable String pathGlob) {
+ this.pathGlob = pathGlob;
+ }
+
@Inject
public GetCodeOwnerConfigFiles(
CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
@@ -108,7 +118,8 @@
? (codeOwnerConfigFilePath, configInvalidException) -> {
codeOwnerConfigs.add(codeOwnerConfigFilePath);
}
- : CodeOwnerConfigScanner.ignoreInvalidCodeOwnerConfigFiles());
+ : CodeOwnerConfigScanner.ignoreInvalidCodeOwnerConfigFiles(),
+ pathGlob);
return Response.ok(
codeOwnerConfigs.build().stream().map(Path::toString).collect(toImmutableList()));
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
index 93504a7..1ea4183 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
@@ -37,8 +37,10 @@
import com.google.gerrit.plugins.codeowners.config.StatusConfig;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.eclipse.jgit.util.RawParseUtils;
import org.junit.Before;
import org.junit.Test;
@@ -369,6 +371,52 @@
}
@Test
+ public void defineAndConfigureOverrideLabelInSameCommit() throws Exception {
+ fetchRefsMetaConfig();
+
+ RevCommit head = getHead(testRepo.getRepository(), RefNames.REFS_CONFIG);
+ RevObject blob = testRepo.get(head.getTree(), "project.config");
+ byte[] data = testRepo.getRepository().open(blob).getCachedBytes(Integer.MAX_VALUE);
+ String projectConfigText = RawParseUtils.decode(data);
+
+ Config projectConfig = new Config();
+ projectConfig.fromText(projectConfigText);
+ String labelName = "Owners-Override";
+ projectConfig.setString("label", labelName, "function", "NoOp");
+ projectConfig.setStringList(
+ "label", labelName, "value", ImmutableList.of("0 Not Override", "+1 Override"));
+
+ Config codeOwnersConfig = new Config();
+ codeOwnersConfig.setString(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
+ "Owners-Override+1");
+
+ RevCommit commit =
+ testRepo.update(
+ RefNames.REFS_CONFIG,
+ testRepo
+ .commit()
+ .parent(head)
+ .message("Add test code owner config")
+ .author(admin.newIdent())
+ .committer(admin.newIdent())
+ .add("code-owners.config", codeOwnersConfig.toText())
+ .add("project.config", projectConfig.toText()));
+
+ testRepo.reset(commit);
+
+ PushResult r = pushRefsMetaConfig();
+ assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
+ ImmutableSet<RequiredApproval> overrideApproval =
+ codeOwnersPluginConfiguration.getOverrideApproval(project);
+ assertThat(overrideApproval).hasSize(1);
+ assertThat(overrideApproval).element(0).hasLabelNameThat().isEqualTo("Owners-Override");
+ assertThat(overrideApproval).element(0).hasValueThat().isEqualTo(1);
+ }
+
+ @Test
public void configureMergeCommitStrategy() throws Exception {
fetchRefsMetaConfig();
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
index d6bf9ab..a838795 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
@@ -21,7 +21,6 @@
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -32,16 +31,10 @@
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
import com.google.gerrit.plugins.codeowners.config.BackendConfig;
-import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.config.GeneralConfig;
import com.google.gerrit.plugins.codeowners.config.OverrideApprovalConfig;
import com.google.gerrit.plugins.codeowners.config.RequiredApprovalConfig;
import com.google.gerrit.plugins.codeowners.config.StatusConfig;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@@ -232,7 +225,7 @@
createOwnersOverrideLabel();
setCodeOwnersConfig(
project,
- null,
+ /* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
ImmutableList.of("Owners-Override+1", "Code-Review+2"));
CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
@@ -263,70 +256,70 @@
private void configureFileExtension(Project.NameKey project, String fileExtension)
throws Exception {
- setConfig(project, null, GeneralConfig.KEY_FILE_EXTENSION, fileExtension);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, GeneralConfig.KEY_FILE_EXTENSION, fileExtension);
}
private void configureOverrideInfoUrl(Project.NameKey project, String overrideInfoUrl)
throws Exception {
- setConfig(project, null, GeneralConfig.KEY_OVERRIDE_INFO_URL, overrideInfoUrl);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, GeneralConfig.KEY_OVERRIDE_INFO_URL, overrideInfoUrl);
}
private void configureMergeCommitStrategy(
Project.NameKey project, MergeCommitStrategy mergeCommitStrategy) throws Exception {
- setConfig(project, null, GeneralConfig.KEY_MERGE_COMMIT_STRATEGY, mergeCommitStrategy.name());
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ GeneralConfig.KEY_MERGE_COMMIT_STRATEGY,
+ mergeCommitStrategy.name());
}
private void configureFallbackCodeOwners(
Project.NameKey project, FallbackCodeOwners fallbackCodeOwners) throws Exception {
- setConfig(project, null, GeneralConfig.KEY_FALLBACK_CODE_OWNERS, fallbackCodeOwners.name());
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
+ fallbackCodeOwners.name());
}
private void configureDisabledBranch(Project.NameKey project, String disabledBranch)
throws Exception {
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED_BRANCH, disabledBranch);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, StatusConfig.KEY_DISABLED_BRANCH, disabledBranch);
}
private void configureBackend(Project.NameKey project, String backendName) throws Exception {
- configureBackend(project, null, backendName);
+ configureBackend(project, /* branch= */ null, backendName);
}
private void configureBackend(
Project.NameKey project, @Nullable String branch, String backendName) throws Exception {
- setConfig(project, branch, BackendConfig.KEY_BACKEND, backendName);
+ setCodeOwnersConfig(project, branch, BackendConfig.KEY_BACKEND, backendName);
}
private void configureRequiredApproval(Project.NameKey project, String requiredApproval)
throws Exception {
- setConfig(project, null, RequiredApprovalConfig.KEY_REQUIRED_APPROVAL, requiredApproval);
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
+ requiredApproval);
}
private void configureOverrideApproval(Project.NameKey project, String overrideApproval)
throws Exception {
- setConfig(project, null, OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL, overrideApproval);
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
+ overrideApproval);
}
private void configureImplicitApprovals(Project.NameKey project) throws Exception {
- setConfig(project, null, GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS, "true");
- }
-
- private void setConfig(Project.NameKey project, String subsection, String key, String value)
- throws Exception {
- Config codeOwnersConfig = new Config();
- codeOwnersConfig.setString(
- CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, subsection, key, value);
- try (TestRepository<Repository> testRepo =
- new TestRepository<>(repoManager.openRepository(project))) {
- Ref ref = testRepo.getRepository().exactRef(RefNames.REFS_CONFIG);
- RevCommit head = testRepo.getRevWalk().parseCommit(ref.getObjectId());
- testRepo.update(
- RefNames.REFS_CONFIG,
- testRepo
- .commit()
- .parent(head)
- .message("Configure code owner backend")
- .add("code-owners.config", codeOwnersConfig.toText()));
- }
- projectCache.evict(project);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS, "true");
}
/** Returns the ID of a code owner backend that is not the given backend. */
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
index 284f01b..31d1b87 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
@@ -373,6 +373,68 @@
.isEmpty();
}
+ @Test
+ public void getCodeOwnerConfigFilesWithPathGlob() throws Exception {
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ CodeOwnerConfig.Key codeOwnerConfigKey3 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/bar/")
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ assertThat(
+ projectCodeOwnersApiFactory
+ .project(project)
+ .branch("master")
+ .codeOwnerConfigFiles()
+ .withPath("/foo/bar/" + getCodeOwnerConfigFileName())
+ .paths())
+ .containsExactly(
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey3).getFilePath());
+
+ assertThat(
+ projectCodeOwnersApiFactory
+ .project(project)
+ .branch("master")
+ .codeOwnerConfigFiles()
+ .withPath("/foo/bar/*")
+ .paths())
+ .containsExactly(
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey3).getFilePath())
+ .inOrder();
+
+ assertThat(
+ projectCodeOwnersApiFactory
+ .project(project)
+ .branch("master")
+ .codeOwnerConfigFiles()
+ .withPath("/foo/**")
+ .paths())
+ .containsExactly(
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getFilePath(),
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey3).getFilePath())
+ .inOrder();
+ }
+
private String getCodeOwnerConfigFileName() {
CodeOwnerBackend backend = backendConfig.getDefaultBackend();
if (backend instanceof FindOwnersBackend) {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java
index 0a14b8e..fe6c044 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java
@@ -25,7 +25,6 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -36,17 +35,11 @@
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
import com.google.gerrit.plugins.codeowners.config.BackendConfig;
-import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.config.GeneralConfig;
import com.google.gerrit.plugins.codeowners.config.OverrideApprovalConfig;
import com.google.gerrit.plugins.codeowners.config.RequiredApprovalConfig;
import com.google.gerrit.plugins.codeowners.config.StatusConfig;
import com.google.inject.Inject;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@@ -259,7 +252,7 @@
createOwnersOverrideLabel();
setCodeOwnersConfig(
project,
- null,
+ /* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
ImmutableList.of("Owners-Override+1", "Code-Review+2"));
CodeOwnerProjectConfigInfo codeOwnerProjectConfigInfo =
@@ -282,70 +275,70 @@
private void configureFileExtension(Project.NameKey project, String fileExtension)
throws Exception {
- setConfig(project, null, GeneralConfig.KEY_FILE_EXTENSION, fileExtension);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, GeneralConfig.KEY_FILE_EXTENSION, fileExtension);
}
private void configureOverrideInfoUrl(Project.NameKey project, String overrideInfoUrl)
throws Exception {
- setConfig(project, null, GeneralConfig.KEY_OVERRIDE_INFO_URL, overrideInfoUrl);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, GeneralConfig.KEY_OVERRIDE_INFO_URL, overrideInfoUrl);
}
private void configureMergeCommitStrategy(
Project.NameKey project, MergeCommitStrategy mergeCommitStrategy) throws Exception {
- setConfig(project, null, GeneralConfig.KEY_MERGE_COMMIT_STRATEGY, mergeCommitStrategy.name());
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ GeneralConfig.KEY_MERGE_COMMIT_STRATEGY,
+ mergeCommitStrategy.name());
}
private void configureFallbackCodeOwners(
Project.NameKey project, FallbackCodeOwners fallbackCodeOwners) throws Exception {
- setConfig(project, null, GeneralConfig.KEY_FALLBACK_CODE_OWNERS, fallbackCodeOwners.name());
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
+ fallbackCodeOwners.name());
}
private void configureDisabledBranch(Project.NameKey project, String disabledBranch)
throws Exception {
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED_BRANCH, disabledBranch);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, StatusConfig.KEY_DISABLED_BRANCH, disabledBranch);
}
private void configureBackend(Project.NameKey project, String backendName) throws Exception {
- configureBackend(project, null, backendName);
+ configureBackend(project, /* branch= */ null, backendName);
}
private void configureBackend(
Project.NameKey project, @Nullable String branch, String backendName) throws Exception {
- setConfig(project, branch, BackendConfig.KEY_BACKEND, backendName);
+ setCodeOwnersConfig(project, branch, BackendConfig.KEY_BACKEND, backendName);
}
private void configureRequiredApproval(Project.NameKey project, String requiredApproval)
throws Exception {
- setConfig(project, null, RequiredApprovalConfig.KEY_REQUIRED_APPROVAL, requiredApproval);
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
+ requiredApproval);
}
private void configureOverrideApproval(Project.NameKey project, String overrideApproval)
throws Exception {
- setConfig(project, null, OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL, overrideApproval);
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
+ overrideApproval);
}
private void configureImplicitApprovals(Project.NameKey project) throws Exception {
- setConfig(project, null, GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS, "true");
- }
-
- private void setConfig(Project.NameKey project, String subsection, String key, String value)
- throws Exception {
- Config codeOwnersConfig = new Config();
- codeOwnersConfig.setString(
- CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, subsection, key, value);
- try (TestRepository<Repository> testRepo =
- new TestRepository<>(repoManager.openRepository(project))) {
- Ref ref = testRepo.getRepository().exactRef(RefNames.REFS_CONFIG);
- RevCommit head = testRepo.getRevWalk().parseCommit(ref.getObjectId());
- testRepo.update(
- RefNames.REFS_CONFIG,
- testRepo
- .commit()
- .parent(head)
- .message("Configure code owner backend")
- .add("code-owners.config", codeOwnersConfig.toText()));
- }
- projectCache.evict(project);
+ setCodeOwnersConfig(
+ project, /* subsection= */ null, GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS, "true");
}
/** Returns the ID of a code owner backend that is not the given backend. */
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigReferenceTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigReferenceTest.java
index 747bd83..42d3696 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigReferenceTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigReferenceTest.java
@@ -14,10 +14,15 @@
package com.google.gerrit.plugins.codeowners.backend;
+import static com.google.common.truth.PathSubject.paths;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Optional;
import org.junit.Test;
@@ -45,4 +50,23 @@
.build());
assertThat(exception).hasMessageThat().isEqualTo("branch must be full name: master");
}
+
+ @Test
+ public void absoluteFilePathCanBeSpecifiedInDifferentFormats() throws Exception {
+ Path expectedPath = Paths.get("/foo/OWNERS");
+ for (String inputPath : new String[] {"/foo/OWNERS", "//foo/OWNERS"}) {
+ Path path =
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, inputPath).filePath();
+ assertWithMessage(inputPath).about(paths()).that(path).isEqualTo(expectedPath);
+ assertThat(path.isAbsolute()).isTrue();
+ }
+ }
+
+ @Test
+ public void relativeFilePathCanBeSpecified() throws Exception {
+ Path path =
+ CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "foo/OWNERS").filePath();
+ assertThat(path).isEqualTo(Paths.get("foo/OWNERS"));
+ assertThat(path.isAbsolute()).isFalse();
+ }
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
index 0677ca4..066fcbb 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersTest.java
@@ -81,7 +81,9 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () -> pathCodeOwnersFactory.create(null, Paths.get("/foo/bar/baz.md")));
+ () ->
+ pathCodeOwnersFactory.create(
+ /* codeOwnerConfig= */ null, Paths.get("/foo/bar/baz.md")));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfig");
}
@@ -90,7 +92,8 @@
CodeOwnerConfig codeOwnerConfig = createCodeOwnerBuilder().build();
NullPointerException npe =
assertThrows(
- NullPointerException.class, () -> pathCodeOwnersFactory.create(codeOwnerConfig, null));
+ NullPointerException.class,
+ () -> pathCodeOwnersFactory.create(codeOwnerConfig, /* absolutePath= */ null));
assertThat(npe).hasMessageThat().isEqualTo("path");
}
@@ -133,7 +136,7 @@
NullPointerException.class,
() ->
pathCodeOwnersFactory.create(
- null,
+ /* codeOwnerConfigKey= */ null,
projectOperations.project(project).getHead("master"),
Paths.get("/foo/bar/baz.md")));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigKey");
@@ -148,7 +151,7 @@
pathCodeOwnersFactory.create(
CodeOwnerConfig.Key.create(
BranchNameKey.create(project, "master"), Paths.get("/")),
- null,
+ /* revision= */ null,
Paths.get("/foo/bar/baz.md")));
assertThat(npe).hasMessageThat().isEqualTo("revision");
}
@@ -171,7 +174,7 @@
pathCodeOwnersFactory.create(
codeOwnerConfigKey,
projectOperations.project(project).getHead("master"),
- null));
+ /* absolutePath= */ null));
assertThat(npe).hasMessageThat().isEqualTo("path");
}
@@ -265,7 +268,7 @@
@GerritConfig(name = "plugin.code-owners.backend", value = TestCodeOwnerBackend.ID)
public void codeOwnerSetsWithPathExpressionsAreIgnoredIfBackendDoesntSupportPathExpressions()
throws Exception {
- try (AutoCloseable registration = registerTestBackend(null)) {
+ try (AutoCloseable registration = registerTestBackend(/* pathExpressionMatcher= */ null)) {
CodeOwnerConfig codeOwnerConfig =
createCodeOwnerBuilder()
.addCodeOwnerSet(
@@ -1664,7 +1667,9 @@
NullPointerException.class,
() ->
PathCodeOwners.matches(
- null, Paths.get("bar/baz.md"), mock(PathExpressionMatcher.class)));
+ /* codeOwnerSet= */ null,
+ Paths.get("bar/baz.md"),
+ mock(PathExpressionMatcher.class)));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerSet");
}
@@ -1676,7 +1681,7 @@
() ->
PathCodeOwners.matches(
CodeOwnerSet.createWithoutPathExpressions(admin.email()),
- null,
+ /* relativePath= */ null,
mock(PathExpressionMatcher.class)));
assertThat(npe).hasMessageThat().isEqualTo("relativePath");
}
@@ -1706,7 +1711,7 @@
PathCodeOwners.matches(
CodeOwnerSet.createWithoutPathExpressions(admin.email()),
Paths.get("bar/baz.md"),
- null));
+ /* matcher= */ null));
assertThat(npe).hasMessageThat().isEqualTo("matcher");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java
index 5b6e80b..6acb268 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java
@@ -14,6 +14,7 @@
package com.google.gerrit.plugins.codeowners.config;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
@@ -702,14 +703,13 @@
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
ImmutableList.of("Owners-Override+1", "Other-Override+1"));
- // If multiple values are set for a key, the last value wins.
- ImmutableSet<RequiredApproval> requiredApproval =
+ ImmutableSet<RequiredApproval> requiredApprovals =
codeOwnersPluginConfiguration.getOverrideApproval(project);
- assertThat(requiredApproval).hasSize(2);
- assertThat(requiredApproval).element(0).hasLabelNameThat().isEqualTo("Owners-Override");
- assertThat(requiredApproval).element(0).hasValueThat().isEqualTo(1);
- assertThat(requiredApproval).element(1).hasLabelNameThat().isEqualTo("Other-Override");
- assertThat(requiredApproval).element(1).hasValueThat().isEqualTo(1);
+ assertThat(
+ requiredApprovals.stream()
+ .map(requiredApproval -> requiredApproval.toString())
+ .collect(toImmutableSet()))
+ .containsExactly("Owners-Override+1", "Other-Override+1");
}
@Test
diff --git a/resources/Documentation/config-guide.md b/resources/Documentation/config-guide.md
new file mode 100644
index 0000000..0a6904d
--- /dev/null
+++ b/resources/Documentation/config-guide.md
@@ -0,0 +1,125 @@
+# Config Guide
+
+The `@PLUGIN@` plugin has many configuration parameters that can be used to
+customize its behavior. These configuration parameters are described in the
+[config](config.html) documentation. This guide gives some additional
+recommendations for the configuration, but doesn't cover all configuration
+parameters.
+
+## <a id="requiredConfiguration">Required Configuration
+
+**Before** installing/enabling the plugin, or enabling the code owners
+functionality for further projects, it is important to do some basic
+configuration. This includes choosing a [code owner backend](backends.html),
+defining the approvals that count as code owner approval and as code owner
+override, opting-out projects or branches and configuring the allowed email
+domain. All this configuration is covered in detail by the [setup
+guide](setup-guide.html).
+
+## <a id="workflow">Workflow Configuration
+
+Some of the configuration parameters have an effect on the user workflow.
+
+### <a id="implicitApprovals">Implicit code owner approvals
+
+It's possible to [enable implicit approvals](config.html#pluginCodeOwnersEnableImplicitApprovals)
+of code owners on their own changes. If enabled and the uploader of a patch set
+is a code owner, an approval of the uploader is assumed for all owned files.
+This means if a code owner uploads a change / patch set that only touches files
+that they own, no approval from other code owners is required for submitting the
+change.
+
+If implicit approvals are enabled, paths can be exempted from requiring code
+owner approvals by assigning the code ownership to [all
+users](backend-find-owners.html#allUsers), as then any modification to the path
+is always implicitly approved by the uploader.
+
+**NOTE:** If implicit approvals are disabled, users can still self-approve their
+own changes by voting on the required label.
+
+**IMPORTANT**: Enabling implicit approvals is considered unsafe, see [security
+pitfalls](#securityImplicitApprovals) below.
+
+### <a id="mergeCommits">Required code owner approvals on merge commits
+
+For merge commits the list of modified files depends on the base against which
+the merge commit is compared:
+
+1. comparison against the destination branch (aka first parent commit):
+ All files which differ between the merge commit and the destination branch.
+ This includes all files which have been modified in the source branch since
+ the last merge into the destination branch has been done.
+
+2. comparison against the Auto-Merge (Auto-Merge = result of automatically
+ merging the source branch into the destination branch):
+ Only shows files for which a conflict resolution has been done.
+
+Which files a users sees on the change screen depends on their base selection.
+
+For the `@PLUGIN@` plugin it can be configured [which files of a merge commit
+require code owner approvals](config.html#pluginCodeOwnersMergeCommitStrategy),
+all files that differ with the destination branch (case 1) or only files that
+differ with the Auto-Merge (case 2). If case 1 is configured, all file diffs
+that have been approved in one branch must be re-approved when they are merged
+into another branch. If case 2 is configured, only conflict resolutions have to
+be approved when a merge is done.
+
+**IMPORTANT**: Requiring code owner approvals only for files that differ with
+the Auto-Merge (case 2) is considered unsafe, see [security
+pitfalls](#securityMergeCommits) below.
+
+## <a id="securityPitfalls">Security pitfalls
+
+While requiring code owner approvals is primarily considered as a code quality
+feature and not a security feature, many admins / projects owners are concerned
+about possibilities to bypass code owner approvals. These admins / projects
+owners should be aware that some configuration settings may make it possible to
+bypass code owner approvals, and hence using them is not recommended.
+
+### <a id="securityImplicitApprovals">Implicit approvals
+
+If [implicit approvals](#implicitApprovals) are enabled, it is important that
+code owners are aware of their implicit approval when they upload new patch sets
+for other users. E.g. if a contributor pushes a change to a wrong branch and a
+code owner helps them to get it rebased onto the correct branch, the rebased
+change has implicit approvals from the code owner, since the code owner is the
+uploader. To avoid situations like this it is recommended to not enable implicit
+approvals.
+
+### <a id="securityMergeCommits">Required code owner approvals on merge commits
+
+If any branch doesn't require code owner approvals or if the code owners in any
+branch are not trusted, it is not safe to [configure for merge commits that they
+only require code owner approvals for files that differ with the
+Auto-Merge](#mergeCommits). E.g. if there is a branch that doesn't require code
+owner approvals, with this setting the code owners check can be bypassed by:
+
+1. setting the branch that doesn't require code owner approvals to the same
+ commit as the main branch that does require code owner approvals
+2. making a change in the branch that doesn't require code owner approvals
+3. merging this change back into the main branch that does require code owner
+ approvals
+4. since it's a clean merge, all files are merged automatically and no code
+ owner approval is required
+
+### <a id="securityFallbackCodeOwners">Setting all users as fallback code owners
+
+As soon as the code owners functionality is enabled for a project / branch, all
+files in it require code owner approvals. This means if any path doesn't have
+any code owners defined, submitting changes to the path is only possible with
+
+1. a code owner override
+2. an approval from a fallback code owners (only if enabled)
+
+[Configuring all users as fallback code
+owners](config.html#pluginCodeOwnersFallbackCodeOwners) is problematic, as it
+can happen easily that code owner config files are misconfigured so that some
+paths are accidentally not covered by code owners. In this case, the affected
+paths would suddenly be open to all users, which may not be wanted. This is why
+configuring all users as fallback code owners is not recommended.
+
+---
+
+Back to [@PLUGIN@ documentation index](index.html)
+
+Part of [Gerrit Code Review](../../../Documentation/index.html)
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index 24ed4b9..b01532a 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -3,6 +3,9 @@
The global configuration of the @PLUGIN@ plugin is stored in the `gerrit.config`
file in the `plugin.@PLUGIN@` subsection.
+This page describes all available configuration parameters. For configuration
+recommendations please consult the [config guide](#config-guide.html).
+
## <a id="projectLevelConfigFile">
In addition some configuration can be done on the project level in
`@PLUGIN@.config` files that are stored in the `refs/meta/config` branches of
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index da00ae4..bd25b37 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -188,6 +188,7 @@
| ----------- | -------- | ----------- |
| `include-non-parsable-files` | optional | Includes non-parseable code owner config files in the response. By default `false`. Cannot be used in combination with the `email` option.
| `email` | optional | Code owner email that must appear in the returned code owner config files.
+| `path` | optional | Path glob that must be matched by the returned code owner config files.
#### Request
diff --git a/resources/Documentation/setup-guide.md b/resources/Documentation/setup-guide.md
index c662be7..7449164 100644
--- a/resources/Documentation/setup-guide.md
+++ b/resources/Documentation/setup-guide.md
@@ -27,6 +27,9 @@
* [How to update the code-owners.config file for a project](#updateCodeOwnersConfig)
* [How to check if the code owners functionality is enabled for a project or branch](#checkIfEnabled)
+Recommendations about further configuration parameters can be found in the
+[config guide](config-guide.html).
+
### <a id="configureCodeOwnersBackend">1. Configure the code owners backend that should be used
The `code-owners` plugin supports multiple [code owner backends](backends.html)