Allow to configure validation flags per branch
Follow the example of change Ifaa955d56 to also make
rejectNonResolvableCodeOwners and rejectNonResolvableImports
configurable per branch.
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I502ddd2ca4b56149972ffb2566fff6b592fc96cf
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshot.java b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshot.java
index 4595fff..783019b 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshot.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshot.java
@@ -118,16 +118,40 @@
/**
* Checks whether newly added non-resolvable code owners should be rejected on commit received and
* submit.
+ *
+ * @param branchName the branch for which it should be checked whether non-resolvable code owners
+ * should be rejected
*/
- public boolean rejectNonResolvableCodeOwners() {
+ public boolean rejectNonResolvableCodeOwners(String branchName) {
+ requireNonNull(branchName, "branchName");
+
+ Optional<Boolean> branchSpecificFlag =
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(projectName, branchName), pluginConfig);
+ if (branchSpecificFlag.isPresent()) {
+ return branchSpecificFlag.get();
+ }
+
return generalConfig.getRejectNonResolvableCodeOwners(projectName, pluginConfig);
}
/**
* Checks whether newly added non-resolvable imports should be rejected on commit received and
* submit.
+ *
+ * @param branchName the branch for which it should be checked whether non-resolvable imports
+ * should be rejected
*/
- public boolean rejectNonResolvableImports() {
+ public boolean rejectNonResolvableImports(String branchName) {
+ requireNonNull(branchName, "branchName");
+
+ Optional<Boolean> branchSpecificFlag =
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(projectName, branchName), pluginConfig);
+ if (branchSpecificFlag.isPresent()) {
+ return branchSpecificFlag.get();
+ }
+
return generalConfig.getRejectNonResolvableImports(projectName, pluginConfig);
}
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 caf8de0..eb84049 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
@@ -234,15 +234,15 @@
}
/**
- * Gets the reject-non-resolvable-code-owners configuration from the given plugin config with
- * fallback to {@code gerrit.config}.
+ * Gets the reject-non-resolvable-code-owners configuration from the given plugin config for the
+ * specified project 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 project the project for which the freject-non-resolvable-code-owners configuration
- * should be read
+ * @param project the project for which the reject-non-resolvable-code-owners configuration should
+ * be read
* @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
@@ -254,13 +254,37 @@
}
/**
- * Gets the reject-non-resolvable-imports configuration from the given plugin config with fallback
- * to {@code gerrit.config}.
+ * Gets the reject-non-resolvable-code-owners configuration from the given plugin config for the
+ * specified branch with fallback to {@code gerrit.config}.
+ *
+ * <p>If multiple branch-specific configurations match the specified branch, it is undefined which
+ * of the matching branch configurations takes precedence.
+ *
+ * <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 branchNameKey the branch and project for which the reject-non-resolvable-code-owners
+ * configuration should be read
+ * @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
+ */
+ Optional<Boolean> getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey branchNameKey, Config pluginConfig) {
+ return getCodeOwnerConfigValidationFlagForBranch(
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS, branchNameKey, pluginConfig);
+ }
+
+ /**
+ * Gets the reject-non-resolvable-imports configuration from the given plugin config for the
+ * specified project 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 project the project for which the freject-non-resolvable-imports configuration should be
+ * @param project the project for which the reject-non-resolvable-imports configuration should be
* read
* @param pluginConfig the plugin config from which the reject-non-resolvable-imports
* configuration should be read.
@@ -272,6 +296,26 @@
project, pluginConfig, KEY_REJECT_NON_RESOLVABLE_IMPORTS, /* defaultValue= */ true);
}
+ /**
+ * Gets the reject-non-resolvable-imports configuration from the given plugin config for the
+ * specified branch 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 branchNameKey the branch and project for which the reject-non-resolvable-imports
+ * configuration should be read
+ * @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
+ */
+ Optional<Boolean> getRejectNonResolvableImportsForBranch(
+ BranchNameKey branchNameKey, Config pluginConfig) {
+ return getCodeOwnerConfigValidationFlagForBranch(
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS, branchNameKey, pluginConfig);
+ }
+
private boolean getBooleanConfig(
Project.NameKey project, Config pluginConfig, String key, boolean defaultValue) {
requireNonNull(project, "project");
@@ -493,6 +537,22 @@
validationSectionForBranch.get(), key, branchNameKey.project(), pluginConfig);
}
+ private Optional<Boolean> getCodeOwnerConfigValidationFlagForBranch(
+ String key, BranchNameKey branchNameKey, Config pluginConfig) {
+ requireNonNull(key, "key");
+ requireNonNull(branchNameKey, "branchNameKey");
+ requireNonNull(pluginConfig, "pluginConfig");
+
+ Optional<String> validationSectionForBranch =
+ getValidationSectionForBranch(branchNameKey, pluginConfig);
+ if (!validationSectionForBranch.isPresent()) {
+ return Optional.empty();
+ }
+
+ return getCodeOwnerConfigValidationFlagForBranch(
+ validationSectionForBranch.get(), key, branchNameKey.project(), pluginConfig);
+ }
+
private CodeOwnerConfigValidationPolicy getCodeOwnerConfigValidationPolicy(
String key, Project.NameKey project, Config pluginConfig) {
requireNonNull(key, "key");
@@ -601,6 +661,34 @@
return Optional.empty();
}
+ private Optional<Boolean> getCodeOwnerConfigValidationFlagForBranch(
+ String branchSubsection, String key, Project.NameKey project, Config pluginConfig) {
+ requireNonNull(branchSubsection, "branchSubsection");
+ requireNonNull(key, "key");
+ requireNonNull(project, "project");
+ requireNonNull(pluginConfig, "pluginConfig");
+
+ String codeOwnerConfigValidationFlagString =
+ pluginConfig.getString(SECTION_VALIDATION, branchSubsection, key);
+ if (codeOwnerConfigValidationFlagString != null) {
+ try {
+ return Optional.of(
+ pluginConfig.getBoolean(SECTION_VALIDATION, branchSubsection, key, true));
+ } catch (IllegalArgumentException e) {
+ logger.atWarning().withCause(e).log(
+ "Ignoring invalid value %s for %s.%s.%s in '%s.config' of project %s."
+ + " Falling back to project-level setting.",
+ codeOwnerConfigValidationFlagString,
+ SECTION_VALIDATION,
+ branchSubsection,
+ key,
+ pluginName,
+ project.get());
+ }
+ }
+ return Optional.empty();
+ }
+
/**
* Gets the merge commit strategy from the given plugin config with fallback to {@code
* gerrit.config}.
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
index 306442a..1eaaed6 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
@@ -177,11 +177,7 @@
problemsByPath.putAll(
codeOwnerBackend.getFilePath(codeOwnerConfig.key()).toString(),
checkCodeOwnerConfig(
- branchNameKey.project(),
- revWalk,
- codeOwnerBackend,
- codeOwnerConfig,
- verbosity));
+ branchNameKey, revWalk, codeOwnerBackend, codeOwnerConfig, verbosity));
return true;
},
(codeOwnerConfigFilePath, configInvalidException) -> {
@@ -196,14 +192,14 @@
}
private ImmutableList<ConsistencyProblemInfo> checkCodeOwnerConfig(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
RevWalk revWalk,
CodeOwnerBackend codeOwnerBackend,
CodeOwnerConfig codeOwnerConfig,
@Nullable ConsistencyProblemInfo.Status verbosity) {
return codeOwnerConfigValidator
.validateCodeOwnerConfig(
- project,
+ branchNameKey,
revWalk,
currentUser.get().asIdentifiedUser(),
codeOwnerBackend,
diff --git a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
index 4f03274..ce10743 100644
--- a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
+++ b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
@@ -494,7 +494,7 @@
// 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(
- branchNameKey.project(), revWalk, user, codeOwnerBackend, codeOwnerConfig)
+ branchNameKey, revWalk, user, codeOwnerBackend, codeOwnerConfig)
.map(CodeOwnerConfigValidator::downgradeErrorToWarning);
}
@@ -505,15 +505,14 @@
// Validate the parsed code owner config.
if (baseCodeOwnerConfig.isPresent()) {
return validateCodeOwnerConfig(
- branchNameKey.project(),
+ branchNameKey,
revWalk,
user,
codeOwnerBackend,
codeOwnerConfig,
baseCodeOwnerConfig.get());
}
- return validateCodeOwnerConfig(
- branchNameKey.project(), revWalk, user, codeOwnerBackend, codeOwnerConfig);
+ return validateCodeOwnerConfig(branchNameKey, revWalk, user, codeOwnerBackend, codeOwnerConfig);
}
/**
@@ -679,7 +678,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 branchNameKey the branch and the project
* @param revWalk rev walk that should be used to load the code owner configs
* @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
@@ -689,7 +688,7 @@
* empty stream if there are no issues
*/
private Stream<CommitValidationMessage> validateCodeOwnerConfig(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
RevWalk revWalk,
IdentifiedUser user,
CodeOwnerBackend codeOwnerBackend,
@@ -699,9 +698,9 @@
requireNonNull(baseCodeOwnerConfig, "baseCodeOwnerConfig");
ImmutableSet<CommitValidationMessage> issuesInBaseVersion =
- validateCodeOwnerConfig(project, revWalk, user, codeOwnerBackend, baseCodeOwnerConfig)
+ validateCodeOwnerConfig(branchNameKey, revWalk, user, codeOwnerBackend, baseCodeOwnerConfig)
.collect(toImmutableSet());
- return validateCodeOwnerConfig(project, revWalk, user, codeOwnerBackend, codeOwnerConfig)
+ return validateCodeOwnerConfig(branchNameKey, revWalk, user, codeOwnerBackend, codeOwnerConfig)
.map(
commitValidationMessage ->
issuesInBaseVersion.contains(commitValidationMessage)
@@ -712,7 +711,7 @@
/**
* Validates the given code owner config and returns validation issues as stream.
*
- * @param project the name of the project
+ * @param branchNameKey the branch and the project
* @param revWalk rev walk that should be used to load the code owner configs from {@code 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
@@ -721,7 +720,7 @@
* empty stream if there are no issues
*/
public Stream<CommitValidationMessage> validateCodeOwnerConfig(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
RevWalk revWalk,
IdentifiedUser user,
CodeOwnerBackend codeOwnerBackend,
@@ -729,9 +728,12 @@
requireNonNull(codeOwnerConfig, "codeOwnerConfig");
return Streams.concat(
validateCodeOwnerReferences(
- project, user, codeOwnerBackend.getFilePath(codeOwnerConfig.key()), codeOwnerConfig),
+ branchNameKey,
+ user,
+ codeOwnerBackend.getFilePath(codeOwnerConfig.key()),
+ codeOwnerConfig),
validateImports(
- project,
+ branchNameKey,
revWalk,
codeOwnerBackend.getFilePath(codeOwnerConfig.key()),
codeOwnerConfig));
@@ -740,7 +742,7 @@
/**
* Validates the code owner references of the given code owner config.
*
- * @param project the name of the project
+ * @param branchNameKey the branch and 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
@@ -750,7 +752,7 @@
* empty stream if there are no issues
*/
private Stream<CommitValidationMessage> validateCodeOwnerReferences(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
IdentifiedUser user,
Path codeOwnerConfigFilePath,
CodeOwnerConfig codeOwnerConfig) {
@@ -759,7 +761,7 @@
.map(
codeOwnerReference ->
validateCodeOwnerReference(
- project, user, codeOwnerConfigFilePath, codeOwnerReference))
+ branchNameKey, user, codeOwnerConfigFilePath, codeOwnerReference))
.filter(Optional::isPresent)
.map(Optional::get);
}
@@ -767,7 +769,7 @@
/**
* Validates a code owner reference.
*
- * @param project the name of the project
+ * @param branchNameKey the branch and 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
@@ -776,14 +778,14 @@
* Optional#empty()} if there is no issue
*/
private Optional<CommitValidationMessage> validateCodeOwnerReference(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
IdentifiedUser user,
Path codeOwnerConfigFilePath,
CodeOwnerReference codeOwnerReference) {
CodeOwnerResolver codeOwnerResolver = codeOwnerResolverProvider.get().forUser(user);
if (!codeOwnerResolver.isEmailDomainAllowed(codeOwnerReference.email()).get()) {
return nonResolvableCodeOwner(
- project,
+ branchNameKey,
String.format(
"the domain of the code owner email '%s' in '%s' is not allowed for code owners",
codeOwnerReference.email(), codeOwnerConfigFilePath));
@@ -801,7 +803,7 @@
// 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 nonResolvableCodeOwner(
- project,
+ branchNameKey,
String.format(
"code owner email '%s' in '%s' cannot be resolved for %s",
codeOwnerReference.email(), codeOwnerConfigFilePath, user.getLoggableName()));
@@ -810,7 +812,7 @@
/**
* Validates the imports of the given code owner config.
*
- * @param project the name of the project
+ * @param branchNameKey the branch and the project
* @param revWalk rev walk that should be used to load the code owner configs from {@code project}
* @param codeOwnerConfigFilePath the path of the code owner config file which contains the code
* owner config
@@ -819,7 +821,7 @@
* if there are no issues
*/
private Stream<CommitValidationMessage> validateImports(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
RevWalk revWalk,
Path codeOwnerConfigFilePath,
CodeOwnerConfig codeOwnerConfig) {
@@ -828,7 +830,7 @@
.map(
codeOwnerConfigReference ->
validateCodeOwnerConfigReference(
- project,
+ branchNameKey,
revWalk,
codeOwnerConfigFilePath,
codeOwnerConfig.key(),
@@ -840,7 +842,7 @@
.map(
codeOwnerConfigReference ->
validateCodeOwnerConfigReference(
- project,
+ branchNameKey,
revWalk,
codeOwnerConfigFilePath,
codeOwnerConfig.key(),
@@ -854,7 +856,7 @@
/**
* Validates a code owner config reference.
*
- * @param project the name of the project
+ * @param branchNameKey the branch and the project
* @param revWalk rev walk that should be used to load the code owner configs from {@code project}
* @param codeOwnerConfigFilePath the path of the code owner config file which contains the code
* owner config reference
@@ -867,7 +869,7 @@
* Optional#empty()} if there is no issue
*/
private Optional<CommitValidationMessage> validateCodeOwnerConfigReference(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
RevWalk revWalk,
Path codeOwnerConfigFilePath,
CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig,
@@ -892,7 +894,7 @@
// that uploaders cannot probe for the existence of projects (e.g. deduce from the error
// message whether a project exists)
return nonResolvableImport(
- project,
+ branchNameKey,
importType,
codeOwnerConfigFilePath,
String.format("project '%s' not found", keyOfImportedCodeOwnerConfig.project().get()));
@@ -900,7 +902,7 @@
if (!projectState.get().statePermitsRead()) {
return nonResolvableImport(
- project,
+ branchNameKey,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -917,7 +919,7 @@
// that uploaders cannot probe for the existence of branches (e.g. deduce from the error
// message whether a branch exists)
return nonResolvableImport(
- project,
+ branchNameKey,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -933,7 +935,7 @@
if (!codeOwnerBackend.isCodeOwnerConfigFile(
keyOfImportedCodeOwnerConfig.project(), codeOwnerConfigReference.fileName())) {
return nonResolvableImport(
- project,
+ branchNameKey,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -945,13 +947,13 @@
// walk, otherwise the revision may not be visible yet and trying to load a code owner config
// from it could fail with MissingObjectException.
Optional<CodeOwnerConfig> importedCodeOwnerConfig =
- keyOfImportedCodeOwnerConfig.project().equals(project)
+ keyOfImportedCodeOwnerConfig.project().equals(branchNameKey.project())
? codeOwnerBackend.getCodeOwnerConfig(
keyOfImportedCodeOwnerConfig, revWalk, revision.get())
: codeOwnerBackend.getCodeOwnerConfig(keyOfImportedCodeOwnerConfig, revision.get());
if (!importedCodeOwnerConfig.isPresent()) {
return nonResolvableImport(
- project,
+ branchNameKey,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -965,7 +967,7 @@
if (getInvalidConfigCause(codeOwnersInternalServerErrorException).isPresent()) {
// The imported code owner config is non-parseable.
return nonResolvableImport(
- project,
+ branchNameKey,
importType,
codeOwnerConfigFilePath,
String.format(
@@ -1047,7 +1049,7 @@
}
private Optional<CommitValidationMessage> nonResolvableImport(
- Project.NameKey project,
+ BranchNameKey branchNameKey,
CodeOwnerConfigImportType importType,
Path codeOwnerConfigFilePath,
String message) {
@@ -1055,7 +1057,9 @@
importType,
codeOwnerConfigFilePath,
message,
- codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableImports()
+ codeOwnersPluginConfiguration
+ .getProjectConfig(branchNameKey.project())
+ .rejectNonResolvableImports(branchNameKey.branch())
? ValidationMessage.Type.ERROR
: ValidationMessage.Type.WARNING);
}
@@ -1074,11 +1078,13 @@
}
private Optional<CommitValidationMessage> nonResolvableCodeOwner(
- Project.NameKey project, String message) {
+ BranchNameKey branchNameKey, String message) {
return Optional.of(
new CommitValidationMessage(
message,
- codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableCodeOwners()
+ codeOwnersPluginConfiguration
+ .getProjectConfig(branchNameKey.project())
+ .rejectNonResolvableCodeOwners(branchNameKey.branch())
? ValidationMessage.Type.ERROR
: ValidationMessage.Type.WARNING));
}
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 b501277..c35280c 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
@@ -2007,6 +2007,98 @@
assertThat(gApi.changes().id(r.getChangeId()).get().status).isEqualTo(ChangeStatus.MERGED);
}
+ @Test
+ public void disableRejectionOfNonResolvableCodeOwnersForBranch() throws Exception {
+ setAsDefaultCodeOwners(admin);
+
+ // Disable the rejection of non-resolvable code owners for the master branch.
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig ->
+ codeOwnersConfig.setBoolean(
+ GeneralConfig.SECTION_VALIDATION,
+ "refs/heads/master",
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ false));
+
+ CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
+ 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()));
+
+ approve(r.getChangeId());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ assertThat(gApi.changes().id(r.getChangeId()).get().status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ public void disableRejectionOfNonResolvableImportsForBranch() throws Exception {
+ skipTestIfImportsNotSupportedByCodeOwnersBackend();
+
+ setAsDefaultCodeOwners(admin);
+
+ // Disable the rejection of non-resolvable imports for the master branch.
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig ->
+ codeOwnersConfig.setBoolean(
+ GeneralConfig.SECTION_VALIDATION,
+ "refs/heads/master",
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ false));
+
+ // create a code owner config that imports a code owner config from a non-existing project
+ CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig = createCodeOwnerConfigKey("/");
+ 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();
+ CodeOwnerConfig codeOwnerConfig =
+ createCodeOwnerConfigWithImport(
+ keyOfImportingCodeOwnerConfig,
+ CodeOwnerConfigImportType.GLOBAL,
+ codeOwnerConfigReference);
+
+ PushOneCommit.Result r =
+ createChange(
+ "Add code owners",
+ codeOwnerConfigOperations
+ .codeOwnerConfig(keyOfImportingCodeOwnerConfig)
+ .getJGitFilePath(),
+ format(codeOwnerConfig));
+ 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()));
+
+ 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,
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
index 50114fe..5727b63 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
@@ -517,37 +517,52 @@
@Test
public void setRejectNonResolvableCodeOwners() throws Exception {
assertThat(
- codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableCodeOwners())
+ codeOwnersPluginConfiguration
+ .getProjectConfig(project)
+ .rejectNonResolvableCodeOwners("master"))
.isTrue();
CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
input.rejectNonResolvableCodeOwners = false;
projectCodeOwnersApiFactory.project(project).updateConfig(input);
assertThat(
- codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableCodeOwners())
+ codeOwnersPluginConfiguration
+ .getProjectConfig(project)
+ .rejectNonResolvableCodeOwners("master"))
.isFalse();
input.rejectNonResolvableCodeOwners = true;
projectCodeOwnersApiFactory.project(project).updateConfig(input);
assertThat(
- codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableCodeOwners())
+ codeOwnersPluginConfiguration
+ .getProjectConfig(project)
+ .rejectNonResolvableCodeOwners("master"))
.isTrue();
}
@Test
public void setRejectNonResolvableImports() throws Exception {
- assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableImports())
+ assertThat(
+ codeOwnersPluginConfiguration
+ .getProjectConfig(project)
+ .rejectNonResolvableImports("master"))
.isTrue();
CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
input.rejectNonResolvableImports = false;
projectCodeOwnersApiFactory.project(project).updateConfig(input);
- assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableImports())
+ assertThat(
+ codeOwnersPluginConfiguration
+ .getProjectConfig(project)
+ .rejectNonResolvableImports("master"))
.isFalse();
input.rejectNonResolvableImports = true;
projectCodeOwnersApiFactory.project(project).updateConfig(input);
- assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).rejectNonResolvableImports())
+ assertThat(
+ codeOwnersPluginConfiguration
+ .getProjectConfig(project)
+ .rejectNonResolvableImports("master"))
.isTrue();
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshotTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshotTest.java
index eff2303..81a9022 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshotTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigSnapshotTest.java
@@ -1101,6 +1101,143 @@
.isEqualTo(CodeOwnerConfigValidationPolicy.DRY_RUN);
}
+ @Test
+ public void cannotGetRejectNonResolvableCodeOwnersForNullBranch() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () -> cfgSnapshot().rejectNonResolvableCodeOwners(/* branchName= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("branchName");
+ }
+
+ @Test
+ public void getRejectNonResolvableCodeOwners_notConfigured() throws Exception {
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("non-existing")).isTrue();
+ }
+
+ @Test
+ public void getRejectNonResolvableCodeOwners_configuredOnProjectLevel() throws Exception {
+ configureRejectNonResolvableCodeOwners(project, false);
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("master")).isFalse();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("non-existing")).isFalse();
+ }
+
+ @Test
+ public void getRejectNonResolvableCodeOwners_configuredOnBranchLevel() throws Exception {
+ configureRejectNonResolvableCodeOwnersForBranch(project, "refs/heads/master", false);
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("master")).isFalse();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("refs/heads/master")).isFalse();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("foo")).isTrue();
+ }
+
+ @Test
+ public void getRejectNonResolvableCodeOwners_branchLevelConfigTakesPrecedence() throws Exception {
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig -> {
+ codeOwnersConfig.setBoolean(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ codeOwnersConfig.setBoolean(
+ GeneralConfig.SECTION_VALIDATION,
+ "refs/heads/master",
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ true);
+ });
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("refs/heads/master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("foo")).isFalse();
+ }
+
+ @Test
+ public void getRejectNonResolvableCodeOwners_inheritedBranchLevelConfigTakesPrecedence()
+ throws Exception {
+ configureRejectNonResolvableCodeOwnersForBranch(allProjects, "refs/heads/master", true);
+ configureRejectNonResolvableCodeOwners(project, false);
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("refs/heads/master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("foo")).isFalse();
+ }
+
+ @Test
+ public void getRejectNonResolvableCodeOwners_inheritedBranchLevelCanBeOverridden()
+ throws Exception {
+ configureRejectNonResolvableCodeOwnersForBranch(allProjects, "refs/heads/master", true);
+ configureRejectNonResolvableCodeOwnersForBranch(project, "refs/heads/master", false);
+ assertThat(cfgSnapshot().rejectNonResolvableCodeOwners("master")).isFalse();
+ }
+
+ @Test
+ public void cannotGetRejectNonResolvableImportsForNullBranch() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () -> cfgSnapshot().rejectNonResolvableImports(/* branchName= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("branchName");
+ }
+
+ @Test
+ public void getRejectNonResolvableImports_notConfigured() throws Exception {
+ assertThat(cfgSnapshot().rejectNonResolvableImports("master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("non-existing")).isTrue();
+ }
+
+ @Test
+ public void getRejectNonResolvableImports_configuredOnProjectLevel() throws Exception {
+ configureRejectNonResolvableImports(project, false);
+ assertThat(cfgSnapshot().rejectNonResolvableImports("master")).isFalse();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("non-existing")).isFalse();
+ }
+
+ @Test
+ public void getRejectNonResolvableImports_configuredOnBranchLevel() throws Exception {
+ configureRejectNonResolvableImportsForBranch(project, "refs/heads/master", false);
+ assertThat(cfgSnapshot().rejectNonResolvableImports("master")).isFalse();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("refs/heads/master")).isFalse();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("foo")).isTrue();
+ }
+
+ @Test
+ public void getRejectNonResolvableImports_branchLevelConfigTakesPrecedence() throws Exception {
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig -> {
+ codeOwnersConfig.setBoolean(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+ codeOwnersConfig.setBoolean(
+ GeneralConfig.SECTION_VALIDATION,
+ "refs/heads/master",
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ true);
+ });
+ assertThat(cfgSnapshot().rejectNonResolvableImports("master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("refs/heads/master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("foo")).isFalse();
+ }
+
+ @Test
+ public void getRejectNonResolvableImports_inheritedBranchLevelConfigTakesPrecedence()
+ throws Exception {
+ configureRejectNonResolvableImportsForBranch(allProjects, "refs/heads/master", true);
+ configureRejectNonResolvableImports(project, false);
+ assertThat(cfgSnapshot().rejectNonResolvableImports("master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("refs/heads/master")).isTrue();
+ assertThat(cfgSnapshot().rejectNonResolvableImports("foo")).isFalse();
+ }
+
+ @Test
+ public void getRejectNonResolvableImports_inheritedBranchLevelCanBeOverridden() throws Exception {
+ configureRejectNonResolvableImportsForBranch(allProjects, "refs/heads/master", true);
+ configureRejectNonResolvableImportsForBranch(project, "refs/heads/master", false);
+ assertThat(cfgSnapshot().rejectNonResolvableImports("master")).isFalse();
+ }
+
private CodeOwnersPluginConfigSnapshot cfgSnapshot() {
return codeOwnersPluginConfigSnapshotFactory.create(project);
}
@@ -1238,6 +1375,48 @@
codeOwnerConfigValidationPolicy.name()));
}
+ private void configureRejectNonResolvableCodeOwners(Project.NameKey project, boolean value)
+ throws Exception {
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ Boolean.toString(value));
+ }
+
+ private void configureRejectNonResolvableCodeOwnersForBranch(
+ Project.NameKey project, String branchSubsection, boolean value) throws Exception {
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig ->
+ codeOwnersConfig.setString(
+ GeneralConfig.SECTION_VALIDATION,
+ branchSubsection,
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ Boolean.toString(value)));
+ }
+
+ private void configureRejectNonResolvableImports(Project.NameKey project, boolean value)
+ throws Exception {
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ Boolean.toString(value));
+ }
+
+ private void configureRejectNonResolvableImportsForBranch(
+ Project.NameKey project, String branchSubsection, boolean value) throws Exception {
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig ->
+ codeOwnersConfig.setString(
+ GeneralConfig.SECTION_VALIDATION,
+ branchSubsection,
+ GeneralConfig.KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ Boolean.toString(value)));
+ }
+
private AutoCloseable registerTestBackend() {
RegistrationHandle registrationHandle =
((PrivateInternals_DynamicMapImpl<CodeOwnerBackend>) codeOwnerBackends)
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 3c9fc09..5b5b6e9 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
@@ -281,6 +281,185 @@
}
@Test
+ public void cannotGetRejectNonResolvableCodeOwnersForBranchForNullBranch() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ /* branchNameKey= */ null, new Config()));
+ assertThat(npe).hasMessageThat().isEqualTo("branchNameKey");
+ }
+
+ @Test
+ public void cannotGetRejectNonResolvableCodeOwnersForBranchForNullConfig() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), /* pluginConfig= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
+ }
+
+ @Test
+ public void noBranchSpecificRejectNonResolvableCodeOwnersConfiguration() throws Exception {
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), new Config()))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration_exact()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/foo",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration_refPattern()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/foo/*",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration_regEx()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "^refs/heads/.*foo.*",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration_invalidRegEx()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "^refs/heads/[",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void matchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration_exact()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/master",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void matchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration_refPattern()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/*",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void matchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration_regEx()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "^refs/heads/.*bar.*",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "foobarbaz"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void branchSpecificRejectNonResolvableCodeOwnersConfigurationIsIgnoredIfValueIsInvalid()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setString(
+ SECTION_VALIDATION, "refs/heads/master", KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS, "INVALID");
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void multipleMatchingBranchSpecificRejectNonResolvableCodeOwnersConfiguration()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/master",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/*",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "^refs/heads/.*",
+ KEY_REJECT_NON_RESOLVABLE_CODE_OWNERS,
+ /* value= */ false);
+
+ // it is non-deterministic which of the branch-specific configurations takes precedence, but
+ // since they all configure the same value it's not important for this assertion
+ assertThat(
+ generalConfig.getRejectNonResolvableCodeOwnersForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
public void cannotGetRejectNonResolvableImportsForNullProject() throws Exception {
NullPointerException npe =
assertThrows(
@@ -339,6 +518,176 @@
}
@Test
+ public void cannotGetRejectNonResolvableImportsForBranchForNullBranch() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ /* branchNameKey= */ null, new Config()));
+ assertThat(npe).hasMessageThat().isEqualTo("branchNameKey");
+ }
+
+ @Test
+ public void cannotGetRejectNonResolvableImportsForBranchForNullConfig() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), /* pluginConfig= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
+ }
+
+ @Test
+ public void noBranchSpecificRejectNonResolvableImportsConfiguration() throws Exception {
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), new Config()))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableImportsConfiguration_exact()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/foo",
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableImportsConfiguration_refPattern()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/foo/*",
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableImportsConfiguration_regEx()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "^refs/heads/.*foo.*",
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void noMatchingBranchSpecificRejectNonResolvableImportsConfiguration_invalidRegEx()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION, "^refs/heads/[", KEY_REJECT_NON_RESOLVABLE_IMPORTS, /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void matchingBranchSpecificRejectNonResolvableImportsConfiguration_exact()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/master",
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void matchingBranchSpecificRejectNonResolvableImportsConfiguration_refPattern()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION, "refs/heads/*", KEY_REJECT_NON_RESOLVABLE_IMPORTS, /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void matchingBranchSpecificRejectNonResolvableImportsConfiguration_regEx()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "^refs/heads/.*bar.*",
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "foobarbaz"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void branchSpecificRejectNonResolvableImportsConfigurationIsIgnoredIfValueIsInvalid()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setString(
+ SECTION_VALIDATION, "refs/heads/master", KEY_REJECT_NON_RESOLVABLE_IMPORTS, "INVALID");
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .isEmpty();
+ }
+
+ @Test
+ public void multipleMatchingBranchSpecificRejectNonResolvableImportsConfiguration()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "refs/heads/master",
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+ cfg.setBoolean(
+ SECTION_VALIDATION, "refs/heads/*", KEY_REJECT_NON_RESOLVABLE_IMPORTS, /* value= */ false);
+ cfg.setBoolean(
+ SECTION_VALIDATION,
+ "^refs/heads/.*",
+ KEY_REJECT_NON_RESOLVABLE_IMPORTS,
+ /* value= */ false);
+
+ // it is non-deterministic which of the branch-specific configurations takes precedence, but
+ // since they all configure the same value it's not important for this assertion
+ assertThat(
+ generalConfig.getRejectNonResolvableImportsForBranch(
+ BranchNameKey.create(project, "master"), cfg))
+ .value()
+ .isEqualTo(false);
+ }
+
+ @Test
public void cannotGetEnableValidationOnCommitReceivedForNullProject() throws Exception {
NullPointerException npe =
assertThrows(
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index 70e5630..9dbb6a4 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -673,10 +673,27 @@
[plugin.@PLUGIN@.rejectNonResolvableCodeOwners](#pluginCodeOwnersRejectNonResolvableCodeOwners)
in `gerrit.config` and the `codeOwners.rejectNonResolvableCodeOwners`
setting from parent projects.\
+ Can be overriden on branch-level by setting
+ [validation.\<branch\>.rejectNonResolvableCodeOwners](#validationBranchRejectNonResolvableCodeOwners).\
If not set, the global setting
[plugin.@PLUGIN@.rejectNonResolvableCodeOwners](#pluginCodeOwnersRejectNonResolvableCodeOwners)
in `gerrit.config` is used.
+<a id="validationBranchRejectNonResolvableCodeOwners">validation.\<branch\>.rejectNonResolvableCodeOwners</a>
+: Branch-level configuration to control whether modifications of code
+ owner config files that newly add non-resolvable code owners should be
+ rejected on commit received and submit.\
+ Applies to all branches that are matched by `<branch>`, which can be
+ an exact ref name (e.g. `refs/heads/master`), a ref pattern (e.g.
+ `refs/heads/*`) or a regular expression (e.g. `^refs/heads/stable-.*`).\
+ If a branches matches multiple validation subsections it is undefined
+ which of the subsections takes precedence.\
+ Overrides the project-level configuration for rejecting non-resolvable
+ code owners that is configured by
+ [codeOwners.rejectNonResolvableCodeOwners](#codeOwnersRejectNonResolvableCodeOwners).\
+ For further details see the description of
+ [codeOwners.rejectNonResolvableCodeOwners](#codeOwnersRejectNonResolvableCodeOwners).
+
<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.\
@@ -691,10 +708,27 @@
[plugin.@PLUGIN@.rejectNonResolvableImports](#pluginCodeOwnersRejectNonResolvableImports)
in `gerrit.config` and the `codeOwners.rejectNonResolvableImports`
setting from parent projects.\
+ Can be overriden on branch-level by setting
+ [validation.\<branch\>.rejectNonResolvableImports](#validationBranchRejectNonResolvableImports).\
If not set, the global setting
[plugin.@PLUGIN@.rejectNonResolvableImports](#pluginCodeOwnersRejectNonResolvableImports)
in `gerrit.config` is used.
+<a id="validationBranchRejectNonResolvableImports">validation.\<branch\>.rejectNonResolvableImports</a>
+: Branch-level configuration to control whether modifications of code
+ owner config files that newly add non-resolvable imports should be
+ rejected on commit received and submit.\
+ Applies to all branches that are matched by `<branch>`, which can be
+ an exact ref name (e.g. `refs/heads/master`), a ref pattern (e.g.
+ `refs/heads/*`) or a regular expression (e.g. `^refs/heads/stable-.*`).\
+ If a branches matches multiple validation subsections it is undefined
+ which of the subsections takes precedence.\
+ Overrides the project-level configuration for rejecting non-resolvable
+ imports that is configured by
+ [codeOwners.rejectNonResolvableImports](#codeOwnersRejectNonResolvableImports).\
+ For further details see the description of
+ [codeOwners.rejectNonResolvableImports](#codeOwnersRejectNonResolvableImports).
+
<a id="codeOwnersRequiredApproval">codeOwners.requiredApproval</a>
: Approval that is required from code owners to approve the files in a
change.\