Merge changes I74098f90,I3216fe91,I0bf9974b,I7311efb4,If53eb1d1
* changes:
GeneralConfig: Include cause when logging warning for IllegalArgumentException
Make toString methods in AutoValue classes final
Fix typos
Include plugin name into validation messages
Add config options to not reject non-resolvable code owners / imports
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
index 4a9e372..0744e5b 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
@@ -66,7 +66,7 @@
}
@Override
- public String toString() {
+ public final String toString() {
return MoreObjects.toStringHelper(this)
.add("codeOwners", codeOwners())
.add("ownedByAllUsers", ownedByAllUsers())
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
index 934d730..bc45ded 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResult.java
@@ -71,7 +71,7 @@
}
@Override
- public String toString() {
+ public final String toString() {
return MoreObjects.toStringHelper(this)
.add("path", path())
.add("codeOwnerConfig", codeOwnerConfig())
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImport.java b/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImport.java
index 4d1b975..865c469 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImport.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/UnresolvedImport.java
@@ -51,7 +51,7 @@
}
@Override
- public String toString() {
+ public final String toString() {
return MoreObjects.toStringHelper(this)
.add("keyOfImportingCodeOwnerConfig", keyOfImportingCodeOwnerConfig())
.add("keyOfImportedCodeOwnerConfig", keyOfImportedCodeOwnerConfig())
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java
index a440235..4b70846 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java
@@ -107,7 +107,8 @@
/**
* Checks whether code owner configs in the given project are read-only.
*
- * @param project the project for it should be checked whether code owner configs are read-only
+ * @param project the project for which it should be checked whether code owner configs are
+ * read-only
* @return whether code owner configs in the given project are read-only
*/
public boolean areCodeOwnerConfigsReadOnly(Project.NameKey project) {
@@ -116,6 +117,34 @@
}
/**
+ * Checks whether newly added non-resolvable code owners should be rejected on commit received and
+ * submit.
+ *
+ * @param project the project for which it should be checked whether non-resolvable code owners
+ * should be rejected
+ * @return whether newly added non-resolvable code owners should be rejected on commit received
+ * and submit
+ */
+ public boolean rejectNonResolvableCodeOwners(Project.NameKey project) {
+ requireNonNull(project, "project");
+ return generalConfig.getRejectNonResolvableCodeOwners(getPluginConfig(project));
+ }
+
+ /**
+ * Checks whether newly added non-resolvable imports should be rejected on commit received and
+ * submit.
+ *
+ * @param project the project for which it should be checked whether non-resolvable imports should
+ * be rejected
+ * @return whether newly added non-resolvable imports should be rejected on commit received and
+ * submit
+ */
+ public boolean rejectNonResolvableImports(Project.NameKey project) {
+ requireNonNull(project, "project");
+ return generalConfig.getRejectNonResolvableImports(getPluginConfig(project));
+ }
+
+ /**
* Whether code owner configs should be validated when a commit is received.
*
* @param project the project for it should be checked whether code owner configs should be
@@ -179,7 +208,7 @@
/**
* Checks whether an implicit code owner approval from the last uploader is assumed.
*
- * @param project the project for it should be checked whether implict approvals are enabled
+ * @param project the project for it should be checked whether implicit approvals are enabled
* @return whether an implicit code owner approval from the last uploader is assumed
*/
public boolean areImplicitApprovalsEnabled(Project.NameKey project) {
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java b/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
index 5d7062b..e14ece9 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
@@ -82,6 +82,13 @@
@VisibleForTesting static final int DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES = 100;
+ @VisibleForTesting
+ public static final String KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS =
+ "rejectNonResolvableCodeOwners";
+
+ @VisibleForTesting
+ public static final String KEY_REJECT_NON_RESOLVABLE_IMPORTS = "rejectNonResolvableImports";
+
private final String pluginName;
private final PluginConfig pluginConfigFromGerritConfig;
@@ -208,20 +215,58 @@
* Gets the read-only configuration from the given plugin config with fallback to {@code
* gerrit.config}.
*
- * <p>The read-only controls whether code owner config files are read-only and all modifications
- * of code owner config files should be rejected.
+ * <p>The read-only configuration controls whether code owner config files are read-only and all
+ * modifications of code owner config files should be rejected.
*
* @param pluginConfig the plugin config from which the read-only configuration should be read.
* @return whether code owner config files are read-only
*/
boolean getReadOnly(Config pluginConfig) {
- requireNonNull(pluginConfig, "pluginConfig");
+ return getBooleanConfig(pluginConfig, KEY_READ_ONLY, false);
+ }
- if (pluginConfig.getString(SECTION_CODE_OWNERS, null, KEY_READ_ONLY) != null) {
- return pluginConfig.getBoolean(SECTION_CODE_OWNERS, null, KEY_READ_ONLY, false);
+ /**
+ * Gets the reject-non-resolvable-code-owners configuration from the given plugin config with
+ * fallback to {@code gerrit.config}.
+ *
+ * <p>The reject-non-resolvable-code-owners configuration controls whether code owner config files
+ * with newly added non-resolvable code owners should be rejected on commit received and on
+ * submit.
+ *
+ * @param pluginConfig the plugin config from which the reject-non-resolvable-code-owners
+ * configuration should be read.
+ * @return whether code owner config files with newly added non-resolvable code owners should be
+ * rejected on commit received and on submit
+ */
+ boolean getRejectNonResolvableCodeOwners(Config pluginConfig) {
+ return getBooleanConfig(pluginConfig, KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS, true);
+ }
+
+ /**
+ * Gets the reject-non-resolvable-imports configuration from the given plugin config with fallback
+ * to {@code gerrit.config}.
+ *
+ * <p>The reject-non-resolvable-imports configuration controls whether code owner config files
+ * with newly added non-resolvable imports should be rejected on commit received and on submit.
+ *
+ * @param pluginConfig the plugin config from which the reject-non-resolvable-imports
+ * configuration should be read.
+ * @return whether code owner config files with newly added non-resolvable imports should be
+ * rejected on commit received and on submit
+ */
+ boolean getRejectNonResolvableImports(Config pluginConfig) {
+ return getBooleanConfig(pluginConfig, KEY_REJECT_NON_RESOLVABLE_IMPORTS, true);
+ }
+
+ private boolean getBooleanConfig(Config pluginConfig, String key, boolean defaultValue) {
+ requireNonNull(pluginConfig, "pluginConfig");
+ requireNonNull(key, "key");
+
+ if (pluginConfig.getString(SECTION_CODE_OWNERS, null, key) != null) {
+ return pluginConfig.getBoolean(SECTION_CODE_OWNERS, null, key, defaultValue);
}
- return pluginConfigFromGerritConfig.getBoolean(KEY_READ_ONLY, false);
+ return pluginConfigFromGerritConfig.getBoolean(key, defaultValue);
}
/**
@@ -242,7 +287,7 @@
return pluginConfig.getEnum(
SECTION_CODE_OWNERS, null, KEY_FALLBACK_CODE_OWNERS, FallbackCodeOwners.NONE);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for fallback code owners in '%s.config' of project %s."
+ " Falling back to global config.",
fallbackCodeOwnersString, pluginName, project.get());
@@ -253,7 +298,7 @@
return pluginConfigFromGerritConfig.getEnum(
KEY_FALLBACK_CODE_OWNERS, FallbackCodeOwners.NONE);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for fallback code owners in gerrit.config (parameter"
+ " plugin.%s.%s). Falling back to default value %s.",
pluginConfigFromGerritConfig.getString(KEY_FALLBACK_CODE_OWNERS),
@@ -287,7 +332,7 @@
KEY_MAX_PATHS_IN_CHANGE_MESSAGES,
DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for max paths in change messages in '%s.config' of"
+ " project %s. Falling back to global config.",
maxPathInChangeMessagesString, pluginName, project.get());
@@ -298,7 +343,7 @@
return pluginConfigFromGerritConfig.getInt(
KEY_MAX_PATHS_IN_CHANGE_MESSAGES, DEFAULT_MAX_PATHS_IN_CHANGE_MESSAGES);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for max paths in change messages in gerrit.config (parameter"
+ " plugin.%s.%s). Falling back to default value %s.",
pluginConfigFromGerritConfig.getString(KEY_MAX_PATHS_IN_CHANGE_MESSAGES),
@@ -356,7 +401,7 @@
return pluginConfig.getEnum(
SECTION_CODE_OWNERS, null, key, CodeOwnerConfigValidationPolicy.TRUE);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for the code owner config validation policy in '%s.config'"
+ " of project %s. Falling back to global config.",
codeOwnerConfigValidationPolicyString, pluginName, project.get());
@@ -366,7 +411,7 @@
try {
return pluginConfigFromGerritConfig.getEnum(key, CodeOwnerConfigValidationPolicy.TRUE);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for the code owner config validation policy in gerrit.config"
+ " (parameter plugin.%s.%s). Falling back to default value %s.",
pluginConfigFromGerritConfig.getString(key),
@@ -402,7 +447,7 @@
KEY_MERGE_COMMIT_STRATEGY,
MergeCommitStrategy.ALL_CHANGED_FILES);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for merge commit stategy in '%s.config' of project %s."
+ " Falling back to global config or default value.",
mergeCommitStrategyString, pluginName, project.get());
@@ -413,7 +458,7 @@
return pluginConfigFromGerritConfig.getEnum(
KEY_MERGE_COMMIT_STRATEGY, MergeCommitStrategy.ALL_CHANGED_FILES);
} catch (IllegalArgumentException e) {
- logger.atWarning().log(
+ logger.atWarning().withCause(e).log(
"Ignoring invalid value %s for merge commit stategy in gerrit.config (parameter plugin.%s.%s)."
+ " Falling back to default value %s.",
pluginConfigFromGerritConfig.getString(KEY_MERGE_COMMIT_STRATEGY),
diff --git a/java/com/google/gerrit/plugins/codeowners/common/MergeCommitStrategy.java b/java/com/google/gerrit/plugins/codeowners/common/MergeCommitStrategy.java
index da9c76d..ca08938 100644
--- a/java/com/google/gerrit/plugins/codeowners/common/MergeCommitStrategy.java
+++ b/java/com/google/gerrit/plugins/codeowners/common/MergeCommitStrategy.java
@@ -17,7 +17,7 @@
/** Strategy that defines for merge commits which files require code owner approvals. */
public enum MergeCommitStrategy {
/**
- * All files which differ between the merge commmit that is being reviewed and its first parent
+ * All files which differ between the merge commit that is being reviewed and its first parent
* commit (which is the HEAD of the destination branch) require code owner approvals.
*
* <p>Using this strategy is the safest option, but requires code owners to also approve files
@@ -30,7 +30,7 @@
ALL_CHANGED_FILES,
/**
- * Only files which differ between the merge commmit that is being reviewed and the auto merge
+ * Only files which differ between the merge commit that is being reviewed and the auto merge
* commit (the result of automatically merging the 2 parent commits, may contain Git conflict
* markers) require code owner approvals.
*
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
index 73ed96d..b0da9f7 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
@@ -161,7 +161,8 @@
codeOwnerConfig -> {
problemsByPath.putAll(
codeOwnerBackend.getFilePath(codeOwnerConfig.key()).toString(),
- checkCodeOwnerConfig(codeOwnerBackend, codeOwnerConfig, verbosity));
+ checkCodeOwnerConfig(
+ branchNameKey.project(), codeOwnerBackend, codeOwnerConfig, verbosity));
return true;
},
(codeOwnerConfigFilePath, configInvalidException) -> {
@@ -176,12 +177,13 @@
}
private ImmutableList<ConsistencyProblemInfo> checkCodeOwnerConfig(
+ Project.NameKey project,
CodeOwnerBackend codeOwnerBackend,
CodeOwnerConfig codeOwnerConfig,
@Nullable ConsistencyProblemInfo.Status verbosity) {
return codeOwnerConfigValidator
.validateCodeOwnerConfig(
- currentUser.get().asIdentifiedUser(), codeOwnerBackend, codeOwnerConfig)
+ project, currentUser.get().asIdentifiedUser(), codeOwnerBackend, codeOwnerConfig)
.map(
commitValidationMessage ->
createConsistencyProblemInfo(commitValidationMessage, verbosity))
diff --git a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
index 9458289..e4714bd 100644
--- a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
+++ b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
@@ -29,6 +29,7 @@
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.plugins.codeowners.backend.ChangedFiles;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
@@ -125,6 +126,7 @@
public class CodeOwnerConfigValidator implements CommitValidationListener, MergeValidationListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private final String pluginName;
private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
private final GitRepositoryManager repoManager;
private final ChangedFiles changedFiles;
@@ -137,6 +139,7 @@
@Inject
CodeOwnerConfigValidator(
+ @PluginName String pluginName,
CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
GitRepositoryManager repoManager,
ChangedFiles changedFiles,
@@ -146,6 +149,7 @@
ChangeNotes.Factory changeNotesFactory,
PatchSetUtil patchSetUtil,
IdentifiedUser.GenericFactory userFactory) {
+ this.pluginName = pluginName;
this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
this.repoManager = repoManager;
this.changedFiles = changedFiles;
@@ -178,6 +182,7 @@
validationResult =
Optional.of(
ValidationResult.create(
+ pluginName,
"skipping validation of code owner config files",
new CommitValidationMessage(
"code owners config validation is disabled", ValidationMessage.Type.HINT)));
@@ -246,6 +251,7 @@
validationResult =
Optional.of(
ValidationResult.create(
+ pluginName,
"skipping validation of code owner config files",
new CommitValidationMessage(
"code owners config validation is disabled", ValidationMessage.Type.HINT)));
@@ -300,6 +306,7 @@
if (codeOwnersPluginConfiguration.isDisabled(branchNameKey)) {
return Optional.of(
ValidationResult.create(
+ pluginName,
"skipping validation of code owner config files",
new CommitValidationMessage(
"code-owners functionality is disabled", ValidationMessage.Type.HINT)));
@@ -307,6 +314,7 @@
if (codeOwnersPluginConfiguration.areCodeOwnerConfigsReadOnly(branchNameKey.project())) {
return Optional.of(
ValidationResult.create(
+ pluginName,
"modifying code owner config files not allowed",
new CommitValidationMessage(
"code owner config files are configured to be read-only",
@@ -352,6 +360,7 @@
// validate the code owner config files
return Optional.of(
ValidationResult.create(
+ pluginName,
modifiedCodeOwnerConfigFiles.stream()
.flatMap(
changedFile ->
@@ -375,6 +384,7 @@
e.getMessage()));
return Optional.of(
ValidationResult.create(
+ pluginName,
"skipping validation of code owner config files",
new CommitValidationMessage(
"code-owners plugin configuration is invalid,"
@@ -477,7 +487,8 @@
// config parseable, it is a good update even if the code owner config still contains
// issues. Hence in this case we downgrade all validation errors in the new version to
// warnings so that the update is not blocked.
- return validateCodeOwnerConfig(user, codeOwnerBackend, codeOwnerConfig)
+ return validateCodeOwnerConfig(
+ branchNameKey.project(), user, codeOwnerBackend, codeOwnerConfig)
.map(CodeOwnerConfigValidator::downgradeErrorToWarning);
}
@@ -488,9 +499,14 @@
// Validate the parsed code owner config.
if (baseCodeOwnerConfig.isPresent()) {
return validateCodeOwnerConfig(
- user, codeOwnerBackend, codeOwnerConfig, baseCodeOwnerConfig.get());
+ branchNameKey.project(),
+ user,
+ codeOwnerBackend,
+ codeOwnerConfig,
+ baseCodeOwnerConfig.get());
}
- return validateCodeOwnerConfig(user, codeOwnerBackend, codeOwnerConfig);
+ return validateCodeOwnerConfig(
+ branchNameKey.project(), user, codeOwnerBackend, codeOwnerConfig);
}
/**
@@ -655,6 +671,7 @@
* <p>Validation errors that exist in both code owner configs are returned as warning (because
* they are not newly introduced by the given code owner config).
*
+ * @param project the name of the project
* @param user user for which the code owner visibility checks should be performed
* @param codeOwnerBackend the code owner backend from which the code owner configs were loaded
* @param codeOwnerConfig the code owner config that should be validated
@@ -663,6 +680,7 @@
* empty stream if there are no issues
*/
private Stream<CommitValidationMessage> validateCodeOwnerConfig(
+ Project.NameKey project,
IdentifiedUser user,
CodeOwnerBackend codeOwnerBackend,
CodeOwnerConfig codeOwnerConfig,
@@ -671,9 +689,9 @@
requireNonNull(baseCodeOwnerConfig, "baseCodeOwnerConfig");
ImmutableSet<CommitValidationMessage> issuesInBaseVersion =
- validateCodeOwnerConfig(user, codeOwnerBackend, baseCodeOwnerConfig)
+ validateCodeOwnerConfig(project, user, codeOwnerBackend, baseCodeOwnerConfig)
.collect(toImmutableSet());
- return validateCodeOwnerConfig(user, codeOwnerBackend, codeOwnerConfig)
+ return validateCodeOwnerConfig(project, user, codeOwnerBackend, codeOwnerConfig)
.map(
commitValidationMessage ->
issuesInBaseVersion.contains(commitValidationMessage)
@@ -684,6 +702,7 @@
/**
* Validates the given code owner config and returns validation issues as stream.
*
+ * @param project the name of the project
* @param user user for which the code owner visibility checks should be performed
* @param codeOwnerBackend the code owner backend from which the code owner config was loaded
* @param codeOwnerConfig the code owner config that should be validated
@@ -691,17 +710,22 @@
* empty stream if there are no issues
*/
public Stream<CommitValidationMessage> validateCodeOwnerConfig(
- IdentifiedUser user, CodeOwnerBackend codeOwnerBackend, CodeOwnerConfig codeOwnerConfig) {
+ Project.NameKey project,
+ IdentifiedUser user,
+ CodeOwnerBackend codeOwnerBackend,
+ CodeOwnerConfig codeOwnerConfig) {
requireNonNull(codeOwnerConfig, "codeOwnerConfig");
return Streams.concat(
validateCodeOwnerReferences(
- user, codeOwnerBackend.getFilePath(codeOwnerConfig.key()), codeOwnerConfig),
- validateImports(codeOwnerBackend.getFilePath(codeOwnerConfig.key()), codeOwnerConfig));
+ project, user, codeOwnerBackend.getFilePath(codeOwnerConfig.key()), codeOwnerConfig),
+ validateImports(
+ project, codeOwnerBackend.getFilePath(codeOwnerConfig.key()), codeOwnerConfig));
}
/**
* Validates the code owner references of the given code owner config.
*
+ * @param project the name of the project
* @param user user for which the code owner visibility checks should be performed
* @param codeOwnerConfigFilePath the path of the code owner config file which contains the code
* owner references
@@ -711,12 +735,16 @@
* empty stream if there are no issues
*/
private Stream<CommitValidationMessage> validateCodeOwnerReferences(
- IdentifiedUser user, Path codeOwnerConfigFilePath, CodeOwnerConfig codeOwnerConfig) {
+ Project.NameKey project,
+ IdentifiedUser user,
+ Path codeOwnerConfigFilePath,
+ CodeOwnerConfig codeOwnerConfig) {
return codeOwnerConfig.codeOwnerSets().stream()
.flatMap(codeOwnerSet -> codeOwnerSet.codeOwners().stream())
.map(
codeOwnerReference ->
- validateCodeOwnerReference(user, codeOwnerConfigFilePath, codeOwnerReference))
+ validateCodeOwnerReference(
+ project, user, codeOwnerConfigFilePath, codeOwnerReference))
.filter(Optional::isPresent)
.map(Optional::get);
}
@@ -724,6 +752,7 @@
/**
* Validates a code owner reference.
*
+ * @param project the name of the project
* @param user user for which the code owner visibility checks should be performed
* @param codeOwnerConfigFilePath the path of the code owner config file which contains the code
* owner reference
@@ -732,10 +761,14 @@
* Optional#empty()} if there is no issue
*/
private Optional<CommitValidationMessage> validateCodeOwnerReference(
- IdentifiedUser user, Path codeOwnerConfigFilePath, CodeOwnerReference codeOwnerReference) {
+ Project.NameKey project,
+ IdentifiedUser user,
+ Path codeOwnerConfigFilePath,
+ CodeOwnerReference codeOwnerReference) {
CodeOwnerResolver codeOwnerResolver = codeOwnerResolverProvider.get().forUser(user);
if (!codeOwnerResolver.isEmailDomainAllowed(codeOwnerReference.email()).get()) {
- return error(
+ return nonResolvableCodeOwner(
+ project,
String.format(
"the domain of the code owner email '%s' in '%s' is not allowed for code owners",
codeOwnerReference.email(), codeOwnerConfigFilePath));
@@ -752,7 +785,8 @@
// CodeOwerResolver for details). We intentionally return the same generic message in all these
// cases so that uploaders cannot probe emails for existence (e.g. they cannot add an email and
// conclude from the error message whether the email exists).
- return error(
+ return nonResolvableCodeOwner(
+ project,
String.format(
"code owner email '%s' in '%s' cannot be resolved for %s",
codeOwnerReference.email(), codeOwnerConfigFilePath, user.getLoggableName()));
@@ -761,6 +795,7 @@
/**
* Validates the imports of the given code owner config.
*
+ * @param project the name of the project
* @param codeOwnerConfigFilePath the path of the code owner config file which contains the code
* owner config
* @param codeOwnerConfig the code owner config for which the imports should be validated
@@ -768,12 +803,13 @@
* if there are no issues
*/
private Stream<CommitValidationMessage> validateImports(
- Path codeOwnerConfigFilePath, CodeOwnerConfig codeOwnerConfig) {
+ Project.NameKey project, Path codeOwnerConfigFilePath, CodeOwnerConfig codeOwnerConfig) {
return Streams.concat(
codeOwnerConfig.imports().stream()
.map(
codeOwnerConfigReference ->
validateCodeOwnerConfigReference(
+ project,
codeOwnerConfigFilePath,
codeOwnerConfig.key(),
codeOwnerConfig.revision(),
@@ -784,6 +820,7 @@
.map(
codeOwnerConfigReference ->
validateCodeOwnerConfigReference(
+ project,
codeOwnerConfigFilePath,
codeOwnerConfig.key(),
codeOwnerConfig.revision(),
@@ -796,6 +833,7 @@
/**
* Validates a code owner config reference.
*
+ * @param project the name of the project
* @param codeOwnerConfigFilePath the path of the code owner config file which contains the code
* owner config reference
* @param keyOfImportingCodeOwnerConfig key of the importing code owner config
@@ -807,6 +845,7 @@
* Optional#empty()} if there is no issue
*/
private Optional<CommitValidationMessage> validateCodeOwnerConfigReference(
+ Project.NameKey project,
Path codeOwnerConfigFilePath,
CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig,
ObjectId codeOwnerConfigRevision,
@@ -821,14 +860,16 @@
// we intentionally use the same error message for non-existing and non-readable projects so
// that uploaders cannot probe for the existence of projects (e.g. deduce from the error
// message whether a project exists)
- return invalidImport(
+ return nonResolvableImport(
+ project,
importType,
codeOwnerConfigFilePath,
String.format("project '%s' not found", keyOfImportedCodeOwnerConfig.project().get()));
}
if (!projectState.get().statePermitsRead()) {
- return invalidImport(
+ return nonResolvableImport(
+ project,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -844,7 +885,8 @@
// we intentionally use the same error message for non-existing and non-readable branches so
// that uploaders cannot probe for the existence of branches (e.g. deduce from the error
// message whether a branch exists)
- return invalidImport(
+ return nonResolvableImport(
+ project,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -857,7 +899,8 @@
codeOwnersPluginConfiguration.getBackend(keyOfImportedCodeOwnerConfig.branchNameKey());
if (!codeOwnerBackend.isCodeOwnerConfigFile(
keyOfImportedCodeOwnerConfig.project(), codeOwnerConfigReference.fileName())) {
- return invalidImport(
+ return nonResolvableImport(
+ project,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -868,7 +911,8 @@
if (!codeOwnerBackend
.getCodeOwnerConfig(keyOfImportedCodeOwnerConfig, revision.get())
.isPresent()) {
- return invalidImport(
+ return nonResolvableImport(
+ project,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -881,7 +925,8 @@
} catch (StorageException storageException) {
if (getInvalidConfigCause(storageException).isPresent()) {
// The imported code owner config is non-parseable.
- return invalidImport(
+ return nonResolvableImport(
+ project,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -944,16 +989,29 @@
}
}
- private Optional<CommitValidationMessage> invalidImport(
- CodeOwnerConfigImportType importType, Path codeOwnerConfigFilePath, String message) {
- return error(
- String.format(
- "invalid %s import in '%s': %s",
- importType.getType(), codeOwnerConfigFilePath, message));
+ private Optional<CommitValidationMessage> nonResolvableImport(
+ Project.NameKey project,
+ CodeOwnerConfigImportType importType,
+ Path codeOwnerConfigFilePath,
+ String message) {
+ return Optional.of(
+ new CommitValidationMessage(
+ String.format(
+ "invalid %s import in '%s': %s",
+ importType.getType(), codeOwnerConfigFilePath, message),
+ codeOwnersPluginConfiguration.rejectNonResolvableImports(project)
+ ? ValidationMessage.Type.ERROR
+ : ValidationMessage.Type.WARNING));
}
- private Optional<CommitValidationMessage> error(String message) {
- return Optional.of(new CommitValidationMessage(message, ValidationMessage.Type.ERROR));
+ private Optional<CommitValidationMessage> nonResolvableCodeOwner(
+ Project.NameKey project, String message) {
+ return Optional.of(
+ new CommitValidationMessage(
+ message,
+ codeOwnersPluginConfiguration.rejectNonResolvableCodeOwners(project)
+ ? ValidationMessage.Type.ERROR
+ : ValidationMessage.Type.WARNING));
}
/** The result of validating code owner config files. */
@@ -963,21 +1021,26 @@
"code owner config files validated, no issues found";
private static final String INVALID_MSG = "invalid code owner config files";
+ abstract String pluginName();
+
abstract String summaryMessage();
abstract ImmutableList<CommitValidationMessage> validationMessages();
static ValidationResult create(
- String summaryMessage, CommitValidationMessage commitValidationMessage) {
+ String pluginName, String summaryMessage, CommitValidationMessage commitValidationMessage) {
return new AutoValue_CodeOwnerConfigValidator_ValidationResult(
- summaryMessage, ImmutableList.of(commitValidationMessage));
+ pluginName, summaryMessage, ImmutableList.of(commitValidationMessage));
}
- static ValidationResult create(Stream<CommitValidationMessage> validationMessagesStream) {
+ static ValidationResult create(
+ String pluginName, Stream<CommitValidationMessage> validationMessagesStream) {
ImmutableList<CommitValidationMessage> validationMessages =
validationMessagesStream.collect(toImmutableList());
return new AutoValue_CodeOwnerConfigValidator_ValidationResult(
- validationMessages.isEmpty() ? NO_ISSUES_MSG : INVALID_MSG, validationMessages);
+ pluginName,
+ validationMessages.isEmpty() ? NO_ISSUES_MSG : INVALID_MSG,
+ validationMessages);
}
/**
@@ -992,7 +1055,8 @@
List<CommitValidationMessage> processForOnCommitReceived(boolean dryRun)
throws CommitValidationException {
if (!dryRun && hasError()) {
- throw new CommitValidationException(summaryMessage(), validationMessages());
+ throw new CommitValidationException(
+ withPluginName(summaryMessage()), withPluginName(validationMessages()));
}
return validationMessagesWithIncludedSummaryMessage();
@@ -1033,8 +1097,8 @@
return ImmutableList.<CommitValidationMessage>builder()
.add(
new CommitValidationMessage(
- summaryMessage(), getValidationMessageTypeForSummaryMessage()))
- .addAll(validationMessages())
+ withPluginName(summaryMessage()), getValidationMessageTypeForSummaryMessage()))
+ .addAll(withPluginName(validationMessages()))
.build();
}
@@ -1082,7 +1146,7 @@
*/
private String getMessage(List<CommitValidationMessage> validationMessages) {
checkState(!validationMessages.isEmpty(), "expected at least 1 validation message");
- StringBuilder msgBuilder = new StringBuilder(summaryMessage()).append(":");
+ StringBuilder msgBuilder = new StringBuilder(withPluginName(summaryMessage())).append(":");
for (CommitValidationMessage msg : validationMessages) {
msgBuilder
.append("\n ")
@@ -1092,5 +1156,16 @@
}
return msgBuilder.toString();
}
+
+ private String withPluginName(String message) {
+ return "[" + pluginName() + "] " + message;
+ }
+
+ private ImmutableList<CommitValidationMessage> withPluginName(
+ ImmutableList<CommitValidationMessage> validationMessages) {
+ return validationMessages.stream()
+ .map(msg -> new CommitValidationMessage(withPluginName(msg.getMessage()), msg.getType()))
+ .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 55dee9b..758d0ef 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
@@ -184,6 +184,18 @@
@Test
public void issuesInCodeOwnerConfigFile() throws Exception {
+ testIssuesInCodeOwnerConfigFile(ConsistencyProblemInfo.Status.ERROR);
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void issuesInCodeOwnerConfigFileReportedAsWarnings() throws Exception {
+ testIssuesInCodeOwnerConfigFile(ConsistencyProblemInfo.Status.WARNING);
+ }
+
+ private void testIssuesInCodeOwnerConfigFile(ConsistencyProblemInfo.Status expectedStatus)
+ throws Exception {
// imports are not supported for the proto backend
assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
@@ -228,19 +240,22 @@
ImmutableMap.of(
pathOfInvalidConfig1,
ImmutableList.of(
- error(
+ problem(
+ expectedStatus,
String.format(
"invalid global import in '%s': '/not-a-code-owner-config' is"
+ " not a code owner config file",
pathOfInvalidConfig1))),
pathOfInvalidConfig2,
ImmutableList.of(
- error(
+ problem(
+ expectedStatus,
String.format(
"code owner email 'unknown1@example.com' in '%s' cannot be"
+ " resolved for admin",
pathOfInvalidConfig2)),
- error(
+ problem(
+ expectedStatus,
String.format(
"code owner email 'unknown2@example.com' in '%s' cannot be"
+ " resolved for admin",
@@ -515,27 +530,69 @@
@Test
public void allIssuesAreReturnedIfNoLevelIsSpecified() throws Exception {
- testIssuesAreFilteredByVerbosity(
- /** verbosity */
- null);
+ testIssuesAreFilteredByVerbosity(/* verbosity= */ null, ConsistencyProblemInfo.Status.ERROR);
}
@Test
public void allIssuesAreReturnedIfLevelIsSetToWarning() throws Exception {
- testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.WARNING);
+ testIssuesAreFilteredByVerbosity(
+ ConsistencyProblemInfo.Status.WARNING, ConsistencyProblemInfo.Status.ERROR);
}
@Test
public void onlyFatalAndErrorIssuesAreReturnedIfLevelIsSetToError() throws Exception {
- testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.ERROR);
+ testIssuesAreFilteredByVerbosity(
+ ConsistencyProblemInfo.Status.ERROR, ConsistencyProblemInfo.Status.ERROR);
}
@Test
public void onlyFatalIssuesAreReturnedIfLevelIsSetToFatal() throws Exception {
- testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.FATAL);
+ testIssuesAreFilteredByVerbosity(
+ ConsistencyProblemInfo.Status.FATAL, ConsistencyProblemInfo.Status.ERROR);
}
- private void testIssuesAreFilteredByVerbosity(@Nullable ConsistencyProblemInfo.Status verbosity)
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void
+ allIssuesAreReturnedIfNoLevelIsSpecified_issuesInCodeOwnerConfigFileReportedAsWarnings()
+ throws Exception {
+ testIssuesAreFilteredByVerbosity(/* verbosity= */ null, ConsistencyProblemInfo.Status.WARNING);
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void
+ allIssuesAreReturnedIfLevelIsSetToWarning_issuesInCodeOwnerConfigFileReportedAsWarnings()
+ throws Exception {
+ testIssuesAreFilteredByVerbosity(
+ ConsistencyProblemInfo.Status.WARNING, ConsistencyProblemInfo.Status.WARNING);
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void
+ onlyFatalAndErrorIssuesAreReturnedIfLevelIsSetToError_issuesInCodeOwnerConfigFileReportedAsWarnings()
+ throws Exception {
+ testIssuesAreFilteredByVerbosity(
+ ConsistencyProblemInfo.Status.ERROR, ConsistencyProblemInfo.Status.WARNING);
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void
+ onlyFatalIssuesAreReturnedIfLevelIsSetToFatal_issuesInCodeOwnerConfigFileReportedAsWarnings()
+ throws Exception {
+ testIssuesAreFilteredByVerbosity(
+ ConsistencyProblemInfo.Status.FATAL, ConsistencyProblemInfo.Status.WARNING);
+ }
+
+ private void testIssuesAreFilteredByVerbosity(
+ @Nullable ConsistencyProblemInfo.Status verbosity,
+ ConsistencyProblemInfo.Status expectedStatus)
throws Exception {
// create a non-parseable code owner config, that will be reported as fatal
String pathOfNonParseableCodeOwnerConfig = "/" + getCodeOwnerConfigFileName();
@@ -572,12 +629,15 @@
ProtoBackend.class,
"1:8: Expected \"{\"."))))));
if (verbosity == null
- || ConsistencyProblemInfo.Status.ERROR.equals(verbosity)
- || ConsistencyProblemInfo.Status.WARNING.equals(verbosity)) {
+ || (ConsistencyProblemInfo.Status.ERROR.equals(verbosity)
+ && (expectedStatus.equals(ConsistencyProblemInfo.Status.FATAL)
+ || expectedStatus.equals(ConsistencyProblemInfo.Status.ERROR)))
+ || (ConsistencyProblemInfo.Status.WARNING.equals(verbosity))) {
expectedMasterIssues.put(
pathOfInvalidConfig,
ImmutableList.of(
- error(
+ problem(
+ expectedStatus,
String.format(
"code owner email 'unknown@example.com' in '%s' cannot be"
+ " resolved for admin",
@@ -603,6 +663,10 @@
return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.ERROR, message);
}
+ private ConsistencyProblemInfo problem(ConsistencyProblemInfo.Status status, String message) {
+ return new ConsistencyProblemInfo(status, message);
+ }
+
private Map<String, Map<String, List<ConsistencyProblemInfo>>> checkCodeOwnerConfigFilesIn(
Project.NameKey projectName) throws RestApiException {
return projectCodeOwnersApiFactory.project(projectName).checkCodeOwnerConfigFiles().check();
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
index 82c6127..e65efbd 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
@@ -469,7 +469,7 @@
.isEqualTo(
String.format(
"Failed to submit 1 change due to the following problems:\n"
- + "Change %d: invalid code owner config files:\n"
+ + "Change %d: [code-owners] invalid code owner config files:\n"
+ " ERROR: code owner email '%s' in '%s' cannot be resolved for %s",
r.getChange().getId().get(),
unknownEmail,
@@ -807,10 +807,11 @@
String abbreviatedCommit = abbreviateName(r.getCommit());
r.assertErrorStatus(
- String.format("commit %s: %s", abbreviatedCommit, "invalid code owner config files"));
+ String.format(
+ "commit %s: [code-owners] %s", abbreviatedCommit, "invalid code owner config files"));
r.assertMessage(
String.format(
- "error: commit %s: %s",
+ "error: commit %s: [code-owners] %s",
abbreviatedCommit,
String.format(
"code owner email '%s' in '%s' cannot be resolved for %s",
@@ -821,7 +822,7 @@
// the pre-existing issue is returned as warning
r.assertMessage(
String.format(
- "warning: commit %s: code owner email '%s' in '%s' cannot be resolved for %s",
+ "warning: commit %s: [code-owners] code owner email '%s' in '%s' cannot be resolved for %s",
abbreviatedCommit,
unknownEmail1,
codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
@@ -866,7 +867,7 @@
.isEqualTo(
String.format(
"Failed to submit 1 change due to the following problems:\n"
- + "Change %d: invalid code owner config files:\n"
+ + "Change %d: [code-owners] invalid code owner config files:\n"
+ " ERROR: code owner email '%s' in '%s' cannot be resolved for %s",
r.getChange().getId().get(),
unknownEmail,
@@ -915,7 +916,7 @@
r.assertOkStatus();
r.assertMessage(
String.format(
- "error: commit %s: %s",
+ "error: commit %s: [code-owners] %s",
abbreviatedCommit,
String.format(
"code owner email '%s' in '%s' cannot be resolved for %s",
@@ -926,7 +927,8 @@
// the pre-existing issue is returned as warning
r.assertMessage(
String.format(
- "warning: commit %s: code owner email '%s' in '%s' cannot be resolved for %s",
+ "warning: commit %s: [code-owners] code owner email '%s' in '%s' cannot be resolved"
+ + " for %s",
abbreviatedCommit,
unknownEmail1,
codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
@@ -979,7 +981,7 @@
.isEqualTo(
String.format(
"Failed to submit 1 change due to the following problems:\n"
- + "Change %d: invalid code owner config files:\n"
+ + "Change %d: [code-owners] invalid code owner config files:\n"
+ " ERROR: code owner email '%s' in '%s' cannot be resolved for %s",
r.getChange().getId().get(),
admin.email(),
@@ -1594,6 +1596,139 @@
assertThat(gApi.changes().id(r.getChangeId()).get().status).isEqualTo(ChangeStatus.MERGED);
}
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ public void canUploadAndSubmitConfigWithUnresolvableCodeOwners() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
+
+ // upload a code owner config that has issues (non-resolvable code owners)
+ String unknownEmail = "non-existing-email@example.com";
+ PushOneCommit.Result r =
+ createChange(
+ "Add code owners",
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getJGitFilePath(),
+ format(
+ CodeOwnerConfig.builder(codeOwnerConfigKey, TEST_REVISION)
+ .addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(unknownEmail))
+ .build()));
+ assertOkWithWarnings(
+ r,
+ "invalid code owner config files",
+ String.format(
+ "code owner email '%s' in '%s' cannot be resolved for %s",
+ unknownEmail,
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
+ identifiedUserFactory.create(admin.id()).getLoggableName()));
+
+ // submit the change
+ approve(r.getChangeId());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ assertThat(gApi.changes().id(r.getChangeId()).get().status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void canUploadAndSubmitConfigWithUnresolvableImports() throws Exception {
+ // imports are not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig = createCodeOwnerConfigKey("/");
+
+ // upload a code owner config that has issues (non-resolvable imports)
+ Project.NameKey nonExistingProject = Project.nameKey("non-existing");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ CodeOwnerConfigReference.builder(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+ codeOwnerConfigOperations
+ .codeOwnerConfig(CodeOwnerConfig.Key.create(nonExistingProject, "master", "/"))
+ .getFilePath())
+ .setProject(nonExistingProject)
+ .build();
+ PushOneCommit.Result r =
+ createChange(
+ "Add code owners",
+ codeOwnerConfigOperations
+ .codeOwnerConfig(keyOfImportingCodeOwnerConfig)
+ .getJGitFilePath(),
+ format(
+ CodeOwnerConfig.builder(keyOfImportingCodeOwnerConfig, TEST_REVISION)
+ .addImport(codeOwnerConfigReference)
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("foo")
+ .addImport(codeOwnerConfigReference)
+ .build())
+ .build()));
+ assertOkWithWarnings(
+ r,
+ "invalid code owner config files",
+ String.format(
+ "invalid %s import in '%s': project '%s' not found",
+ CodeOwnerConfigImportType.GLOBAL.getType(),
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfImportingCodeOwnerConfig).getFilePath(),
+ nonExistingProject.get()),
+ String.format(
+ "invalid %s import in '%s': project '%s' not found",
+ CodeOwnerConfigImportType.PER_FILE.getType(),
+ codeOwnerConfigOperations.codeOwnerConfig(keyOfImportingCodeOwnerConfig).getFilePath(),
+ nonExistingProject.get()));
+
+ // submit the change
+ approve(r.getChangeId());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ assertThat(gApi.changes().id(r.getChangeId()).get().status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "true")
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "true")
+ @GerritConfig(name = "plugin.code-owners.enableValidationOnCommitReceived", value = "false")
+ @GerritConfig(name = "plugin.code-owners.enableValidationOnSubmit", value = "false")
+ public void rejectConfigOptionsAreIgnoredIfValidationIsDisabled() throws Exception {
+ // imports are not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig = createCodeOwnerConfigKey("/");
+
+ // upload a code owner config that has issues (non-resolvable code owners and non-resolvable
+ // imports)
+ String unknownEmail = "non-existing-email@example.com";
+ Project.NameKey nonExistingProject = Project.nameKey("non-existing");
+ CodeOwnerConfigReference codeOwnerConfigReference =
+ CodeOwnerConfigReference.builder(
+ CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY,
+ codeOwnerConfigOperations
+ .codeOwnerConfig(CodeOwnerConfig.Key.create(nonExistingProject, "master", "/"))
+ .getFilePath())
+ .setProject(nonExistingProject)
+ .build();
+ PushOneCommit.Result r =
+ createChange(
+ "Add code owners",
+ codeOwnerConfigOperations
+ .codeOwnerConfig(keyOfImportingCodeOwnerConfig)
+ .getJGitFilePath(),
+ format(
+ CodeOwnerConfig.builder(keyOfImportingCodeOwnerConfig, TEST_REVISION)
+ .addImport(codeOwnerConfigReference)
+ .addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(unknownEmail))
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression("foo")
+ .addImport(codeOwnerConfigReference)
+ .build())
+ .build()));
+ assertOkWithHints(
+ r,
+ "skipping validation of code owner config files",
+ "code owners config validation is disabled");
+
+ // submit the change
+ approve(r.getChangeId());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ assertThat(gApi.changes().id(r.getChangeId()).get().status).isEqualTo(ChangeStatus.MERGED);
+ }
+
private CodeOwnerConfig createCodeOwnerConfigWithImport(
CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig,
CodeOwnerConfigImportType importType,
@@ -1658,7 +1793,8 @@
pushResult.assertOkStatus();
for (String hint : hints) {
pushResult.assertMessage(
- String.format("hint: commit %s: %s", abbreviateName(pushResult.getCommit()), hint));
+ String.format(
+ "hint: commit %s: [code-owners] %s", abbreviateName(pushResult.getCommit()), hint));
}
pushResult.assertNotMessage("fatal");
pushResult.assertNotMessage("error");
@@ -1670,7 +1806,8 @@
pushResult.assertOkStatus();
for (String error : errors) {
pushResult.assertMessage(
- String.format("fatal: commit %s: %s", abbreviateName(pushResult.getCommit()), error));
+ String.format(
+ "fatal: commit %s: [code-owners] %s", abbreviateName(pushResult.getCommit()), error));
}
pushResult.assertNotMessage("error");
pushResult.assertNotMessage("warning");
@@ -1682,7 +1819,8 @@
pushResult.assertOkStatus();
for (String error : errors) {
pushResult.assertMessage(
- String.format("error: commit %s: %s", abbreviateName(pushResult.getCommit()), error));
+ String.format(
+ "error: commit %s: [code-owners] %s", abbreviateName(pushResult.getCommit()), error));
}
pushResult.assertNotMessage("fatal");
pushResult.assertNotMessage("warning");
@@ -1694,7 +1832,9 @@
pushResult.assertOkStatus();
for (String warning : warnings) {
pushResult.assertMessage(
- String.format("warning: commit %s: %s", abbreviateName(pushResult.getCommit()), warning));
+ String.format(
+ "warning: commit %s: [code-owners] %s",
+ abbreviateName(pushResult.getCommit()), warning));
}
pushResult.assertNotMessage("fatal");
pushResult.assertNotMessage("error");
@@ -1704,9 +1844,11 @@
private void assertErrorWithMessages(
PushOneCommit.Result pushResult, String summaryMessage, String... errors) throws Exception {
String abbreviatedCommit = abbreviateName(pushResult.getCommit());
- pushResult.assertErrorStatus(String.format("commit %s: %s", abbreviatedCommit, summaryMessage));
+ pushResult.assertErrorStatus(
+ String.format("commit %s: [code-owners] %s", abbreviatedCommit, summaryMessage));
for (String error : errors) {
- pushResult.assertMessage(String.format("error: commit %s: %s", abbreviatedCommit, error));
+ pushResult.assertMessage(
+ String.format("error: commit %s: [code-owners] %s", abbreviatedCommit, error));
}
pushResult.assertNotMessage("fatal");
pushResult.assertNotMessage("warning");
@@ -1716,9 +1858,11 @@
private void assertFatalWithMessages(
PushOneCommit.Result pushResult, String summaryMessage, String... errors) throws Exception {
String abbreviatedCommit = abbreviateName(pushResult.getCommit());
- pushResult.assertErrorStatus(String.format("commit %s: %s", abbreviatedCommit, summaryMessage));
+ pushResult.assertErrorStatus(
+ String.format("commit %s: [code-owners] %s", abbreviatedCommit, summaryMessage));
for (String error : errors) {
- pushResult.assertMessage(String.format("fatal: commit %s: %s", abbreviatedCommit, error));
+ pushResult.assertMessage(
+ String.format("fatal: commit %s: [code-owners] %s", abbreviatedCommit, error));
}
pushResult.assertNotMessage("error");
pushResult.assertNotMessage("warning");
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
index 43771de..ff2785c 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
@@ -27,6 +27,8 @@
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_MERGE_COMMIT_STRATEGY;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_OVERRIDE_INFO_URL;
import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_READ_ONLY;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
@@ -104,7 +106,8 @@
@Test
public void cannotGetReadOnlyForNullPluginConfig() throws Exception {
NullPointerException npe =
- assertThrows(NullPointerException.class, () -> generalConfig.getReadOnly(null));
+ assertThrows(
+ NullPointerException.class, () -> generalConfig.getReadOnly(/* pluginConfig= */ null));
assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
}
@@ -130,6 +133,70 @@
}
@Test
+ public void cannotGetRejectNonResolvableCodeOwnersForNullPluginConfig() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () -> generalConfig.getRejectNonResolvableCodeOwners(/* pluginConfig= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
+ }
+
+ @Test
+ public void noRejectNonResolvableCodeOwnersConfiguration() throws Exception {
+ assertThat(generalConfig.getRejectNonResolvableCodeOwners(new Config())).isTrue();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ public void
+ rejectNonResolvableCodeOwnersConfigurationIsRetrievedFromGerritConfigIfNotSpecifiedOnProjectLevel()
+ throws Exception {
+ assertThat(generalConfig.getRejectNonResolvableCodeOwners(new Config())).isFalse();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
+ public void
+ rejectNonResolvableCodeOwnersConfigurationInPluginConfigOverridesRejectNonResolvableCodeOwnersConfigurationInGerritConfig()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setString(SECTION_CODE_OWNERS, null, KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS, "true");
+ assertThat(generalConfig.getRejectNonResolvableCodeOwners(cfg)).isTrue();
+ }
+
+ @Test
+ public void cannotGetRejectNonResolvableImportsForNullPluginConfig() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () -> generalConfig.getRejectNonResolvableImports(/* pluginConfig= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
+ }
+
+ @Test
+ public void noRejectNonResolvableImportsConfiguration() throws Exception {
+ assertThat(generalConfig.getRejectNonResolvableImports(new Config())).isTrue();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void
+ rejectNonResolvableImportsConfigurationIsRetrievedFromGerritConfigIfNotSpecifiedOnProjectLevel()
+ throws Exception {
+ assertThat(generalConfig.getRejectNonResolvableImports(new Config())).isFalse();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
+ public void
+ rejectNonResolvableImportsConfigurationInPluginConfigOverridesRejectNonResolvableImportsConfigurationInGerritConfig()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setString(SECTION_CODE_OWNERS, null, KEY_REJECT_NON_RESOLVABLE_IMPORTS, "true");
+ assertThat(generalConfig.getRejectNonResolvableImports(cfg)).isTrue();
+ }
+
+ @Test
public void cannotGetEnableValidationOnCommitReceivedForNullProject() throws Exception {
NullPointerException npe =
assertThrows(
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index d546374..90c7b3c 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -147,6 +147,37 @@
in `@PLUGIN@.config`.\
By default `true`.
+<a id="pluginCodeOwnersRejectNonResolvableCodeOwners">plugin.@PLUGIN@.rejectNonResolvableCodeOwners</a>
+: Whether modifications of code owner config files that newly add
+ non-resolvable code owners should be rejected on commit received and
+ submit.\
+ if `true` newly added non-resolveable code owners are reported as errors
+ and the commit is rejected.\
+ If `false` newly added non-resolvable code owners are only reported as
+ warnings and the commit is not rejected.\
+ This setting has no effect if the validation is disabled via
+ [enableValidationOnCommitReceived](#pluginCodeOwnersEnableValidationOnCommitReceived)
+ or [enableValidationOnSubmit](#pluginCodeOwnersEnableValidationOnSubmit).
+ Can be overridden per project by setting
+ [codeOwners.rejectNonResolvableCodeOwners](#codeOwnersRejectNonResolvableCodeOwners)
+ in `@PLUGIN@.config`.\
+ By default `true`.
+
+<a id="pluginCodeOwnersRejectNonResolvableImports">plugin.@PLUGIN@.rejectNonResolvableImports</a>
+: Whether modifications of code owner config files that newly add
+ non-resolvable imports should be rejected on commit received an submit.\
+ if `true` newly added non-resolveable imports are reported as errors and
+ the commit is rejected.\
+ If `false` newly added non-resolvable imports are only reported as
+ warnings and the commit is not rejected.\
+ This setting has no effect if the validation is disabled via
+ [enableValidationOnCommitReceived](#pluginCodeOwnersEnableValidationOnCommitReceived)
+ or [enableValidationOnSubmit](#pluginCodeOwnersEnableValidationOnSubmit).
+ Can be overridden per project by setting
+ [codeOwners.rejectNonResolvableImports](#codeOwnersRejectNonResolvableImports)
+ in `@PLUGIN@.config`.\
+ By default `true`.
+
<a id="pluginCodeOwnersAllowedEmailDomain">plugin.@PLUGIN@.allowedEmailDomain</a>
: Email domain that allows to assign code ownerships to emails with this
domain.\
@@ -465,6 +496,41 @@
[plugin.@PLUGIN@.enableValidationOnSubmit](#pluginCodeOwnersEnableValidationOnSubmit)
in `gerrit.config` is used.
+<a id="codeOwnersRejectNonResolvableCodeOwners">codeOwners.rejectNonResolvableCodeOwners</a>
+: Whether modifications of code owner config files that newly add
+ non-resolvable code owners should be rejected on commit received and
+ submit.\
+ if `true` newly added non-resolveable code owners are reported as errors
+ and the commit is rejected.\
+ If `false` newly added non-resolvable code owners are only reported as
+ warnings and the commit is not rejected.\
+ This setting has no effect if the validation is disabled via
+ [enableValidationOnCommitReceived](#codeOwnersEnableValidationOnCommitReceived)
+ or [enableValidationOnSubmit](#codeOwnersEnableValidationOnSubmit).
+ Overrides the global setting
+ [plugin.@PLUGIN@.rejectNonResolvableCodeOwners](#pluginCodeOwnersRejectNonResolvableCodeOwners)
+ in `gerrit.config`.\
+ If not set, the global setting
+ [plugin.@PLUGIN@.rejectNonResolvableCodeOwners](#pluginCodeOwnersRejectNonResolvableCodeOwners)
+ in `gerrit.config` is used.
+
+<a id="codeOwnersRejectNonResolvableImports">codeOwners.rejectNonResolvableImports</a>
+: Whether modifications of code owner config files that newly add
+ non-resolvable imports should be rejected on commit received an submit.\
+ if `true` newly added non-resolveable imports are reported as errors and
+ the commit is rejected.\
+ If `false` newly added non-resolvable imports are only reported as
+ warnings and the commit is not rejected.\
+ This setting has no effect if the validation is disabled via
+ [enableValidationOnCommitReceived](#codeOwnersEnableValidationOnCommitReceived)
+ or [enableValidationOnSubmit](#codeOwnersEnableValidationOnSubmit).
+ Overrides the global setting
+ [plugin.@PLUGIN@.rejectNonResolvableImports](#pluginCodeOwnersRejectNonResolvableImports)
+ in `gerrit.config`.\
+ If not set, the global setting
+ [plugin.@PLUGIN@.rejectNonResolvableImports](#pluginCodeOwnersRejectNonResolvableImports)
+ in `gerrit.config` is used.
+
<a id="codeOwnersRequiredApproval">codeOwners.requiredApproval</a>
: Approval that is required from code owners to approve the files in a
change.\
diff --git a/resources/Documentation/validation.md b/resources/Documentation/validation.md
index 746221e..47c3ef5 100644
--- a/resources/Documentation/validation.md
+++ b/resources/Documentation/validation.md
@@ -116,6 +116,13 @@
* the project from which the file should be imported doesn't permit reads
(e.g. has the state `HIDDEN`)
+**NOTE:** Whether commits that newly add non-resolvable code owners and
+non-resolvable imports are rejected on commit received and on submit is
+controlled by the
+[rejectNonResolvableCodeOwners](config.html#pluginCodeOwnersRejectNonResolvableCodeOwners)
+and [rejectNonResolvableImports](config.html#pluginCodeOwnersRejectNonResolvableImports)
+config settings.
+
The following things are **not** checked (not an exhaustive list):
* Cycles in imports of owner config files:\