Merge changes If8f371e7,I968977a0,I84560336
* changes:
User guide: Document how to submit changes with files that have no code owners
Document that imports and global owners are always read from the current revisions
GetCodeOwnerBranchConfig: Include a flag if no code owner config file exists yet
diff --git a/java/com/google/gerrit/plugins/codeowners/BatchModule.java b/java/com/google/gerrit/plugins/codeowners/BatchModule.java
new file mode 100644
index 0000000..3267d31
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/BatchModule.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 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.plugins.codeowners;
+
+import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.plugins.codeowners.backend.BackendModule;
+import com.google.inject.AbstractModule;
+
+/**
+ * Binds a subset of the code-owners plugin functionality that should be available in batch jobs.
+ */
+@UsedAt(UsedAt.Project.GOOGLE)
+public class BatchModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ // We only need the CodeOwnerSubmitRule in batch jobs, but since the CodeOwnerSubmitRule depends
+ // on the CodeOwnerBackend, CodeOwnerBackend must be bound too. This means we can simply install
+ // the whole BackendModule.
+ install(new BackendModule());
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java
index 45c4156..daad0df 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java
@@ -14,6 +14,8 @@
package com.google.gerrit.plugins.codeowners.api;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
+
/**
* The input for the {@link
* com.google.gerrit.plugins.codeowners.restapi.CheckCodeOwnerConfigFilesInRevision} REST endpoint.
@@ -26,4 +28,19 @@
* <p>By default unset, which means that all code owner config files should be validated.
*/
public String path;
+
+ /**
+ * Level that controls which code owner config file issues are returned.
+ *
+ * <p>The following values are supported:
+ *
+ * <ul>
+ * <li>{@code FATAL}: only fatal issues are returned
+ * <li>{@code ERROR}: only fatal and error issues are returned
+ * <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+ * </ul>
+ *
+ * <p>If unset, {@code WARNING} is used.
+ */
+ public ConsistencyProblemInfo.Status verbosity;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
index fd865b8..37f6757 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
@@ -14,6 +14,7 @@
package com.google.gerrit.plugins.codeowners.api;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import java.util.List;
/**
@@ -46,4 +47,19 @@
* <p>By default unset, which means that all code owner config files should be validated.
*/
public String path;
+
+ /**
+ * Level that controls which code owner config file issues are returned.
+ *
+ * <p>The following values are supported:
+ *
+ * <ul>
+ * <li>{@code FATAL}: only fatal issues are returned
+ * <li>{@code ERROR}: only fatal and error issues are returned
+ * <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+ * </ul>
+ *
+ * <p>If unset, {@code WARNING} is used.
+ */
+ public ConsistencyProblemInfo.Status verbosity;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
index 4d5159f..ccce1ed 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
@@ -39,6 +39,7 @@
private boolean validateDisabledBranches;
private ImmutableList<String> branches;
private String path;
+ private ConsistencyProblemInfo.Status verbosity;
/**
* Includes code owner config files in branches for which the code owners functionality is
@@ -101,6 +102,30 @@
}
/**
+ * Sets the verbosity level that controls which kind of issues should be returned.
+ *
+ * <p>The following values are supported:
+ *
+ * <ul>
+ * <li>{@code FATAL}: only fatal issues are returned
+ * <li>{@code ERROR}: only fatal and error issues are returned
+ * <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+ * </ul>
+ *
+ * <p>If unset, {@code WARNING} is used.
+ */
+ public CheckCodeOwnerConfigFilesRequest setVerbosity(
+ @Nullable ConsistencyProblemInfo.Status verbosity) {
+ this.verbosity = verbosity;
+ return this;
+ }
+
+ /** Gets the verbosity level that controls which kind of issues should be returned. */
+ public ConsistencyProblemInfo.Status getVerbosity() {
+ return verbosity;
+ }
+
+ /**
* Executes the request to check the code owner config files and retrieves the result of the
* validation.
*/
diff --git a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
index 6011572..54b37ec 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
@@ -86,6 +86,7 @@
input.validateDisabledBranches = isValidateDisabledBranches();
input.branches = getBranches();
input.path = getPath();
+ input.verbosity = getVerbosity();
return checkCodeOwnerConfigFiles.apply(projectResource, input).value();
} catch (Exception e) {
throw asRestApiException("Cannot check code owner config files", e);
diff --git a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java
index 1471280..df9ed04 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java
@@ -37,6 +37,7 @@
/** Request to check code owner config files. */
abstract class CheckCodeOwnerConfigFilesRequest {
private String path;
+ private ConsistencyProblemInfo.Status verbosity;
/**
* Sets a glob that limits the validation to code owner config files that have a path that
@@ -57,6 +58,30 @@
}
/**
+ * Sets the verbosity level that controls which kind of issues should be returned.
+ *
+ * <p>The following values are supported:
+ *
+ * <ul>
+ * <li>{@code FATAL}: only fatal issues are returned
+ * <li>{@code ERROR}: only fatal and error issues are returned
+ * <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+ * </ul>
+ *
+ * <p>If unset, {@code WARNING} is used.
+ */
+ public CheckCodeOwnerConfigFilesRequest setVerbosity(
+ @Nullable ConsistencyProblemInfo.Status verbosity) {
+ this.verbosity = verbosity;
+ return this;
+ }
+
+ /** Gets the verbosity level that controls which kind of issues should be returned. */
+ public ConsistencyProblemInfo.Status getVerbosity() {
+ return verbosity;
+ }
+
+ /**
* Executes the request to check the code owner config files and retrieves the result of the
* validation.
*/
diff --git a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java
index e05b281..817639f 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java
@@ -51,6 +51,7 @@
CheckCodeOwnerConfigFilesInRevisionInput input =
new CheckCodeOwnerConfigFilesInRevisionInput();
input.path = getPath();
+ input.verbosity = getVerbosity();
return checkCodeOwnerConfigFilesInRevision.apply(revisionResource, input).value();
} catch (Exception e) {
throw asRestApiException("Cannot check code owner config files", e);
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
index 5f55f62..7e402c7 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
@@ -191,8 +191,7 @@
codeOwnerResolver
.get()
.enforceVisibility(false)
- .resolve(
- codeOwnersPluginConfiguration.getGlobalCodeOwners(changeNotes.getProjectName()));
+ .resolveGlobalCodeOwners(changeNotes.getProjectName());
logger.atFine().log("global code owners = %s", globalCodeOwners);
// If the branch doesn't contain any code owner config file yet, we apply special logic
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
index 3cb377b..6d9d05e 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
@@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.server.CurrentUser;
@@ -158,6 +159,16 @@
}
/**
+ * Resolves the global code owners for the given project.
+ *
+ * @param projectName the name of the project for which the global code owners should be resolved
+ * @return the resolved global code owners of the given project
+ */
+ public CodeOwnerResolverResult resolveGlobalCodeOwners(Project.NameKey projectName) {
+ return resolve(codeOwnersPluginConfiguration.getGlobalCodeOwners(projectName));
+ }
+
+ /**
* Resolves the given {@link CodeOwnerReference}s to {@link CodeOwner}s.
*
* @param codeOwnerReferences the code owner references that should be resolved
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
index 07788ca..6829041 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
@@ -24,6 +24,7 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
@@ -126,7 +127,8 @@
.forEach(
branchNameKey ->
resultsByBranchBuilder.put(
- branchNameKey.branch(), checkBranch(input.path, branchNameKey)));
+ branchNameKey.branch(),
+ checkBranch(input.path, branchNameKey, input.verbosity)));
return Response.ok(resultsByBranchBuilder.build());
}
@@ -139,7 +141,9 @@
}
private Map<String, List<ConsistencyProblemInfo>> checkBranch(
- String pathGlob, BranchNameKey branchNameKey) {
+ String pathGlob,
+ BranchNameKey branchNameKey,
+ @Nullable ConsistencyProblemInfo.Status verbosity) {
ListMultimap<String, ConsistencyProblemInfo> problemsByPath = LinkedListMultimap.create();
CodeOwnerBackend codeOwnerBackend = codeOwnersPluginConfiguration.getBackend(branchNameKey);
codeOwnerConfigScanner.visit(
@@ -147,7 +151,7 @@
codeOwnerConfig -> {
problemsByPath.putAll(
codeOwnerBackend.getFilePath(codeOwnerConfig.key()).toString(),
- checkCodeOwnerConfig(codeOwnerBackend, codeOwnerConfig));
+ checkCodeOwnerConfig(codeOwnerBackend, codeOwnerConfig, verbosity));
return true;
},
(codeOwnerConfigFilePath, configInvalidException) -> {
@@ -162,28 +166,42 @@
}
private ImmutableList<ConsistencyProblemInfo> checkCodeOwnerConfig(
- CodeOwnerBackend codeOwnerBackend, CodeOwnerConfig codeOwnerConfig) {
+ CodeOwnerBackend codeOwnerBackend,
+ CodeOwnerConfig codeOwnerConfig,
+ @Nullable ConsistencyProblemInfo.Status verbosity) {
return codeOwnerConfigValidator
.validateCodeOwnerConfig(
currentUser.get().asIdentifiedUser(), codeOwnerBackend, codeOwnerConfig)
- .map(CheckCodeOwnerConfigFiles::createConsistencyProblemInfo)
+ .map(
+ commitValidationMessage ->
+ createConsistencyProblemInfo(commitValidationMessage, verbosity))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toImmutableList());
}
public static Optional<ConsistencyProblemInfo> createConsistencyProblemInfo(
- CommitValidationMessage commitValidationMessage) {
+ CommitValidationMessage commitValidationMessage,
+ @Nullable ConsistencyProblemInfo.Status verbosity) {
switch (commitValidationMessage.getType()) {
case FATAL:
return Optional.of(
new ConsistencyProblemInfo(
ConsistencyProblemInfo.Status.FATAL, commitValidationMessage.getMessage()));
case ERROR:
+ if (ConsistencyProblemInfo.Status.FATAL.equals(verbosity)) {
+ // errors should not be reported
+ return Optional.empty();
+ }
return Optional.of(
new ConsistencyProblemInfo(
ConsistencyProblemInfo.Status.ERROR, commitValidationMessage.getMessage()));
case WARNING:
+ if (ConsistencyProblemInfo.Status.FATAL.equals(verbosity)
+ || ConsistencyProblemInfo.Status.ERROR.equals(verbosity)) {
+ // warnings should not be reported
+ return Optional.empty();
+ }
return Optional.of(
new ConsistencyProblemInfo(
ConsistencyProblemInfo.Status.WARNING, commitValidationMessage.getMessage()));
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java
index 737e389..b3e2494 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java
@@ -122,7 +122,10 @@
changedFile,
rw,
commit)
- .map(CheckCodeOwnerConfigFiles::createConsistencyProblemInfo)
+ .map(
+ commitValidationMessage ->
+ CheckCodeOwnerConfigFiles.createConsistencyProblemInfo(
+ commitValidationMessage, input.verbosity))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toImmutableList()))));
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
index 2c7b15d..16432bd 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
@@ -25,6 +25,7 @@
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
@@ -43,8 +44,8 @@
import com.google.gerrit.plugins.codeowners.backend.findowners.FindOwnersBackend;
import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
import com.google.gerrit.plugins.codeowners.config.BackendConfig;
-import com.google.gerrit.plugins.codeowners.config.StatusConfig;
import com.google.inject.Inject;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
@@ -151,7 +152,7 @@
@Test
public void nonParseableCodeOwnerConfigFile() throws Exception {
String codeOwnerConfigPath = "/" + getCodeOwnerConfigFileName();
- createInvalidCodeOwnerConfig(codeOwnerConfigPath);
+ createNonParseableCodeOwnerConfig(codeOwnerConfigPath);
assertThat(checkCodeOwnerConfigFilesIn(project))
.containsExactly(
@@ -475,6 +476,87 @@
pathOfInvalidConfig3)))));
}
+ @Test
+ public void allIssuesAreReturnedIfNoLevelIsSpecified() throws Exception {
+ testIssuesAreFilteredByVerbosity(
+ /** verbosity */
+ null);
+ }
+
+ @Test
+ public void allIssuesAreReturnedIfLevelIsSetToWarning() throws Exception {
+ testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.WARNING);
+ }
+
+ @Test
+ public void onlyFatalAndErrorIssuesAreReturnedIfLevelIsSetToError() throws Exception {
+ testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.ERROR);
+ }
+
+ @Test
+ public void onlyFatalIssuesAreReturnedIfLevelIsSetToFatal() throws Exception {
+ testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.FATAL);
+ }
+
+ private void testIssuesAreFilteredByVerbosity(@Nullable ConsistencyProblemInfo.Status verbosity)
+ throws Exception {
+ // create a non-parseable code owner config, that will be reported as fatal
+ String pathOfNonParseableCodeOwnerConfig = "/" + getCodeOwnerConfigFileName();
+ createNonParseableCodeOwnerConfig(pathOfNonParseableCodeOwnerConfig);
+
+ // create an invalid code owner config, that will be reported as error
+ CodeOwnerConfig.Key keyOfInvalidConfig =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail("unknown@example.com")
+ .create();
+ String pathOfInvalidConfig =
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfInvalidConfig).getFilePath();
+
+ // there is currently nothing that triggers a warning
+
+ Map<String, List<ConsistencyProblemInfo>> expectedMasterIssues = new HashMap<>();
+ // the fatal issue is always expected
+ expectedMasterIssues.put(
+ pathOfNonParseableCodeOwnerConfig,
+ ImmutableList.of(
+ fatal(
+ String.format(
+ "invalid code owner config file '%s':\n %s",
+ pathOfNonParseableCodeOwnerConfig,
+ getParsingErrorMessage(
+ ImmutableMap.of(
+ FindOwnersBackend.class,
+ "invalid line: INVALID",
+ ProtoBackend.class,
+ "1:8: Expected \"{\"."))))));
+ if (verbosity == null
+ || ConsistencyProblemInfo.Status.ERROR.equals(verbosity)
+ || ConsistencyProblemInfo.Status.WARNING.equals(verbosity)) {
+ expectedMasterIssues.put(
+ pathOfInvalidConfig,
+ ImmutableList.of(
+ error(
+ String.format(
+ "code owner email 'unknown@example.com' in '%s' cannot be"
+ + " resolved for admin",
+ pathOfInvalidConfig))));
+ }
+
+ Map<String, Map<String, List<ConsistencyProblemInfo>>> result =
+ projectCodeOwnersApiFactory
+ .project(project)
+ .checkCodeOwnerConfigFiles()
+ .setVerbosity(verbosity)
+ .check();
+ assertThat(result)
+ .containsExactly(
+ "refs/heads/master", expectedMasterIssues, "refs/meta/config", ImmutableMap.of());
+ }
+
private ConsistencyProblemInfo fatal(String message) {
return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.FATAL, message);
}
@@ -498,14 +580,14 @@
throw new IllegalStateException("unknown code owner backend: " + backend.getClass().getName());
}
- private void createInvalidCodeOwnerConfig(String path) throws Exception {
+ private void createNonParseableCodeOwnerConfig(String path) throws Exception {
disableCodeOwnersForProject(project);
String changeId =
createChange("Add invalid code owners file", JgitPath.of(path).get(), "INVALID")
.getChangeId();
approve(changeId);
gApi.changes().id(changeId).current().submit();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
}
private String getParsingErrorMessage(
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
index 57b9506..98a071c 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
@@ -20,6 +20,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.plugins.codeowners.JgitPath;
@@ -32,7 +33,7 @@
import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
import com.google.gerrit.plugins.codeowners.backend.proto.ProtoCodeOwnerConfigParser;
import com.google.gerrit.plugins.codeowners.config.BackendConfig;
-import com.google.gerrit.plugins.codeowners.config.StatusConfig;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.lib.ObjectId;
@@ -120,7 +121,7 @@
String changeId =
createChange("Add code owners", JgitPath.of(codeOwnerConfigPath).get(), "INVALID")
.getChangeId();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
assertThat(checkCodeOwnerConfigFilesIn(changeId))
.containsExactly(
@@ -168,7 +169,7 @@
unknownEmail1, admin.email(), unknownEmail2))
.build()))
.getChangeId();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
Map<String, List<ConsistencyProblemInfo>> problemsByPath =
checkCodeOwnerConfigFilesIn(changeId);
@@ -209,7 +210,7 @@
CodeOwnerSet.createWithoutPathExpressions(admin.email(), user.email()))
.build()))
.getChangeId();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
// The validation request is done by 'admin' which can see 'admin' and 'user', however the
// validation is performed from the perspective of the uploader which is 'user2' and 'user2'
@@ -251,7 +252,7 @@
.getChangeId();
approve(changeId);
gApi.changes().id(changeId).current().submit();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
// Create a change that adds another code owner config file without issues.
CodeOwnerConfig.Key codeOwnerConfigKey2 = createCodeOwnerConfigKey("/foo/");
@@ -277,7 +278,7 @@
codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath();
disableCodeOwnersForProject(project);
String changeId = createChangeWithFileDeletion(codeOwnerConfigPath);
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
assertThat(checkCodeOwnerConfigFilesIn(changeId)).isEmpty();
}
@@ -312,7 +313,7 @@
unknownEmail2, admin.email()))
.build())))
.getChangeId();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
Map<String, List<ConsistencyProblemInfo>> problemsByPath =
changeCodeOwnersApiFactory
@@ -373,7 +374,7 @@
unknownEmail3, admin.email()))
.build())))
.getChangeId();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
Map<String, List<ConsistencyProblemInfo>> problemsByPath =
changeCodeOwnersApiFactory
@@ -398,6 +399,97 @@
unknownEmail3, codeOwnerConfigPath3))));
}
+ @Test
+ public void allIssuesAreReturnedIfNoLevelIsSpecified() throws Exception {
+ testIssuesAreFilteredByVerbosity(
+ /** verbosity */
+ null);
+ }
+
+ @Test
+ public void allIssuesAreReturnedIfLevelIsSetToWarning() throws Exception {
+ testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.WARNING);
+ }
+
+ @Test
+ public void onlyFatalAndErrorIssuesAreReturnedIfLevelIsSetToError() throws Exception {
+ testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.ERROR);
+ }
+
+ @Test
+ public void onlyFatalIssuesAreReturnedIfLevelIsSetToFatal() throws Exception {
+ testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.FATAL);
+ }
+
+ private void testIssuesAreFilteredByVerbosity(@Nullable ConsistencyProblemInfo.Status verbosity)
+ throws Exception {
+ CodeOwnerConfig.Key keyOfNonParseableCodeOwnerConfig = createCodeOwnerConfigKey("/");
+ String pathOfNonParseableCodeOwnerConfig =
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfNonParseableCodeOwnerConfig).getFilePath();
+
+ CodeOwnerConfig.Key keyOfInvalidCodeOwnerConfig = createCodeOwnerConfigKey("/foo/");
+ String pathOfInvalidCodeOwnerConfig =
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfInvalidCodeOwnerConfig).getFilePath();
+ String unknownEmail = "unknown@example.com";
+
+ // create a change with a) a non-parseable code owner config that will be reported as fatal and
+ // b) an invalid code owner config with an unknown email that will be reported as error
+ // (there is currently nothing that triggers a warning)
+ disableCodeOwnersForProject(project);
+ String changeId =
+ createChange(
+ "Add code owners",
+ ImmutableMap.of(
+ JgitPath.of(pathOfNonParseableCodeOwnerConfig).get(),
+ "INVALID",
+ JgitPath.of(pathOfInvalidCodeOwnerConfig).get(),
+ format(
+ CodeOwnerConfig.builder(keyOfInvalidCodeOwnerConfig, TEST_REVISION)
+ .addCodeOwnerSet(
+ CodeOwnerSet.createWithoutPathExpressions(unknownEmail))
+ .build())))
+ .getChangeId();
+ enableCodeOwnersForProject(project);
+
+ Map<String, List<ConsistencyProblemInfo>> expectedIssues = new HashMap<>();
+ // the fatal issue is always expected
+ expectedIssues.put(
+ pathOfNonParseableCodeOwnerConfig,
+ ImmutableList.of(
+ fatal(
+ String.format(
+ "invalid code owner config file '%s':\n %s",
+ pathOfNonParseableCodeOwnerConfig,
+ getParsingErrorMessage(
+ ImmutableMap.of(
+ FindOwnersBackend.class,
+ "invalid line: INVALID",
+ ProtoBackend.class,
+ "1:8: Expected \"{\"."))))));
+ if (verbosity == null
+ || ConsistencyProblemInfo.Status.ERROR.equals(verbosity)
+ || ConsistencyProblemInfo.Status.WARNING.equals(verbosity)) {
+ expectedIssues.put(
+ pathOfInvalidCodeOwnerConfig,
+ ImmutableList.of(
+ error(
+ String.format(
+ "code owner email '%s' in '%s' cannot be" + " resolved for admin",
+ unknownEmail, pathOfInvalidCodeOwnerConfig))));
+ } else {
+ expectedIssues.put(pathOfInvalidCodeOwnerConfig, ImmutableList.of());
+ }
+
+ Map<String, List<ConsistencyProblemInfo>> result =
+ changeCodeOwnersApiFactory
+ .change(changeId)
+ .current()
+ .checkCodeOwnerConfigFiles()
+ .setVerbosity(verbosity)
+ .check();
+ assertThat(result).isEqualTo(expectedIssues);
+ }
+
private ConsistencyProblemInfo fatal(String message) {
return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.FATAL, message);
}
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 0489c38..804fcbf 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
@@ -25,7 +25,6 @@
import com.google.gerrit.plugins.codeowners.backend.findowners.FindOwnersBackend;
import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
import com.google.gerrit.plugins.codeowners.config.BackendConfig;
-import com.google.gerrit.plugins.codeowners.config.StatusConfig;
import org.junit.Before;
import org.junit.Test;
@@ -325,6 +324,6 @@
.getChangeId();
approve(changeId);
gApi.changes().id(changeId).current().submit();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
}
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/BUILD b/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/BUILD
new file mode 100644
index 0000000..cef4da8
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/BUILD
@@ -0,0 +1,17 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+package(
+ default_testonly = True,
+ default_visibility = ["//plugins/code-owners:visibility"],
+)
+
+acceptance_tests(
+ srcs = glob(
+ ["*IT.java"],
+ ),
+ group = "acceptance_batch",
+ deps = [
+ "//plugins/code-owners:code-owners__plugin",
+ "//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/testing",
+ ],
+)
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/CodeOwnerSubmitRuleBatchIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/CodeOwnerSubmitRuleBatchIT.java
new file mode 100644
index 0000000..9365151
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/CodeOwnerSubmitRuleBatchIT.java
@@ -0,0 +1,74 @@
+package com.google.gerrit.plugins.codeowners.acceptance.batch;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.plugins.codeowners.testing.SubmitRequirementInfoSubject.assertThatCollection;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.plugins.codeowners.testing.SubmitRequirementInfoSubject;
+import com.google.inject.Inject;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.junit.Test;
+
+/**
+ * Test that verifies that the {@link com.google.gerrit.plugins.codeowners.BatchModule} has bound
+ * all classes that are needed to run {@code
+ * com.google.gerrit.plugins.codeowners.backend.CodeOwnerSubmitRule}.
+ */
+@TestPlugin(name = "code-owners", sysModule = "com.google.gerrit.plugins.codeowners.BatchModule")
+public class CodeOwnerSubmitRuleBatchIT extends LightweightPluginDaemonTest {
+ @Inject private ProjectOperations projectOperations;
+ @Inject private RequestScopeOperations requestScopeOperations;
+
+ @Test
+ public void invokeCodeOwnerSubmitRule() throws Exception {
+ // Upload a change as a non-code owner.
+ TestRepository<InMemoryRepository> testRepo = cloneProject(project, user);
+ PushOneCommit push =
+ pushFactory.create(user.newIdent(), testRepo, "Test Change", "foo/bar.baz", "file content");
+ String changeId = push.to("refs/for/master").getChangeId();
+
+ // Approve by a non-code-owner.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
+ requestScopeOperations.setApiUser(user.id());
+ approve(changeId);
+
+ // Verify that the change is not submittable.
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get(ListChangesOption.SUBMITTABLE);
+ assertThat(changeInfo.submittable).isFalse();
+
+ // Check the submit requirement.
+ SubmitRequirementInfoSubject submitRequirementInfoSubject =
+ assertThatCollection(changeInfo.requirements).onlyElement();
+ submitRequirementInfoSubject.hasStatusThat().isEqualTo("NOT_READY");
+ submitRequirementInfoSubject.hasFallbackTextThat().isEqualTo("Code Owners");
+ submitRequirementInfoSubject.hasTypeThat().isEqualTo("code-owners");
+
+ // Approve by a project owner who is code owner since there is no code owner config file yet,
+ // and hence we are in bootstrapping mode.
+ requestScopeOperations.setApiUser(admin.id());
+ approve(changeId);
+
+ // Verify that the change is submittable now.
+ changeInfo = gApi.changes().id(changeId).get(ListChangesOption.SUBMITTABLE);
+ assertThat(changeInfo.submittable).isTrue();
+
+ // Check the submit requirement.
+ submitRequirementInfoSubject = assertThatCollection(changeInfo.requirements).onlyElement();
+ submitRequirementInfoSubject.hasStatusThat().isEqualTo("OK");
+ submitRequirementInfoSubject.hasFallbackTextThat().isEqualTo("Code Owners");
+ submitRequirementInfoSubject.hasTypeThat().isEqualTo("code-owners");
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java
index 8788ddb..bcbb0cf 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java
@@ -24,7 +24,6 @@
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
-import com.google.gerrit.plugins.codeowners.config.StatusConfig;
import org.junit.Before;
import org.junit.Test;
@@ -74,7 +73,7 @@
createChange("Add code owners", JgitPath.of(filePath).get(), "INVALID").getChangeId();
approve(changeId);
gApi.changes().id(changeId).current().submit();
- setCodeOwnersConfig(project, null, StatusConfig.KEY_DISABLED, "false");
+ enableCodeOwnersForProject(project);
String changeId2 = createChange().getChangeId();
RestResponse r =
diff --git a/ui/owner-requirement.js b/ui/owner-requirement.js
index ce8b37a..04ac8f5 100644
--- a/ui/owner-requirement.js
+++ b/ui/owner-requirement.js
@@ -167,12 +167,12 @@
.reduce((prev, cur) => {
const oldPathStatus = cur.old_path_status;
const newPathStatus = cur.new_path_status;
- if (this._isMissing(newPathStatus.status)) {
+ if (newPathStatus && this._isMissing(newPathStatus.status)) {
prev.missing ++;
- } else if (this._isPending(newPathStatus.status)) {
+ } else if (newPathStatus && this._isPending(newPathStatus.status)) {
prev.pending ++;
} else if (oldPathStatus) {
- // check oldPath if newPath approved
+ // check oldPath if newPath approved or the file is deleted
if (this._isMissing(oldPathStatus.status)) {
prev.missing ++;
} else if (this._isPending(oldPathStatus.status)) {