Merge changes I2a438734,I437144bd,I3414b0af,I488f0eea,I22a639cb, ...
* changes:
Read all required approvals that are configured
Test behaviour with multiple configured required approvals
Add truth subject for RequiredLabel
AbstractRequiredApprovalConfig: Merge the get methods
AbstractRequiredApprovalConfigTest: Improve readability of 2 tests
Fix comments for boolean and null arguments
Add REST endpoint to rename an email in code owner config files of one branch
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
index 2f1be86..e06e1bb 100644
--- a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
+++ b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
@@ -40,6 +41,7 @@
import com.google.inject.Inject;
import java.nio.file.Path;
import java.util.Map;
+import java.util.function.Consumer;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
@@ -145,9 +147,30 @@
protected void setCodeOwnersConfig(
Project.NameKey project, @Nullable String subsection, String key, String value)
throws Exception {
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig ->
+ codeOwnersConfig.setString(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, subsection, key, value));
+ }
+
+ protected void setCodeOwnersConfig(
+ Project.NameKey project,
+ @Nullable String subsection,
+ String key,
+ ImmutableList<String> values)
+ throws Exception {
+ updateCodeOwnersConfig(
+ project,
+ codeOwnersConfig ->
+ codeOwnersConfig.setStringList(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, subsection, key, values));
+ }
+
+ private void updateCodeOwnersConfig(Project.NameKey project, Consumer<Config> configUpdater)
+ throws Exception {
Config codeOwnersConfig = new Config();
- codeOwnersConfig.setString(
- CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, subsection, key, value);
+ configUpdater.accept(codeOwnersConfig);
try (TestRepository<Repository> testRepo =
new TestRepository<>(repoManager.openRepository(project))) {
Ref ref = testRepo.getRepository().exactRef(RefNames.REFS_CONFIG);
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
index 2b81afb..82fb351 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
@@ -66,6 +66,10 @@
public abstract List<String> paths() throws RestApiException;
}
+ /** Renames an email in the code owner config files of the branch. */
+ RenameEmailResultInfo renameEmailInCodeOwnerConfigFiles(RenameEmailInput input)
+ throws RestApiException;
+
/**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
@@ -80,5 +84,11 @@
public CodeOwnerConfigFilesRequest codeOwnerConfigFiles() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public RenameEmailResultInfo renameEmailInCodeOwnerConfigFiles(RenameEmailInput input)
+ throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
index aacc3e2..f6e4804 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerBranchConfig;
import com.google.gerrit.plugins.codeowners.restapi.GetCodeOwnerConfigFiles;
+import com.google.gerrit.plugins.codeowners.restapi.RenameEmail;
import com.google.gerrit.server.project.BranchResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -33,15 +34,18 @@
private final GetCodeOwnerBranchConfig getCodeOwnerBranchConfig;
private final Provider<GetCodeOwnerConfigFiles> getCodeOwnerConfigFilesProvider;
+ private final RenameEmail renameEmail;
private final BranchResource branchResource;
@Inject
public BranchCodeOwnersImpl(
GetCodeOwnerBranchConfig getCodeOwnerBranchConfig,
Provider<GetCodeOwnerConfigFiles> getCodeOwnerConfigFilesProvider,
+ RenameEmail renameEmail,
@Assisted BranchResource branchResource) {
this.getCodeOwnerConfigFilesProvider = getCodeOwnerConfigFilesProvider;
this.getCodeOwnerBranchConfig = getCodeOwnerBranchConfig;
+ this.renameEmail = renameEmail;
this.branchResource = branchResource;
}
@@ -66,4 +70,14 @@
}
};
}
+
+ @Override
+ public RenameEmailResultInfo renameEmailInCodeOwnerConfigFiles(RenameEmailInput input)
+ throws RestApiException {
+ try {
+ return renameEmail.apply(branchResource, input).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot rename email", e);
+ }
+ }
}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/RenameEmailInput.java b/java/com/google/gerrit/plugins/codeowners/api/RenameEmailInput.java
new file mode 100644
index 0000000..cf38e59
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/api/RenameEmailInput.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.api;
+
+/**
+ * The input for the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST endpoint.
+ */
+public class RenameEmailInput {
+ /**
+ * Optional commit message that should be used for the commit that renames the email in the code
+ * owner config files.
+ */
+ public String message;
+
+ /** The old email that should be replaced with the new email. */
+ public String oldEmail;
+
+ /** The new email that should be used to replace the old email. */
+ public String newEmail;
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/api/RenameEmailResultInfo.java b/java/com/google/gerrit/plugins/codeowners/api/RenameEmailResultInfo.java
new file mode 100644
index 0000000..6400734
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/api/RenameEmailResultInfo.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.api;
+
+import com.google.gerrit.extensions.common.CommitInfo;
+
+/**
+ * The result of the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST endpoint.
+ */
+public class RenameEmailResultInfo {
+ /** The commit that did the email rename, not set if no update was performed. */
+ public CommitInfo commit;
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerBackend.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerBackend.java
index 0f762e1..be5318d 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerBackend.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerBackend.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.server.IdentifiedUser;
import java.nio.file.Path;
import java.util.Optional;
@@ -52,11 +53,7 @@
*/
default Optional<CodeOwnerConfig> getCodeOwnerConfig(
CodeOwnerConfig.Key codeOwnerConfigKey, @Nullable ObjectId revision) {
- return getCodeOwnerConfig(
- codeOwnerConfigKey,
- /** revWalk = */
- null,
- revision);
+ return getCodeOwnerConfig(codeOwnerConfigKey, /* revWalk= */ null, revision);
}
/**
@@ -118,4 +115,19 @@
default Optional<PathExpressionMatcher> getPathExpressionMatcher() {
return Optional.empty();
}
+
+ /**
+ * Replaces the old email in the given code owner config file content with the new email.
+ *
+ * @param codeOwnerConfigFileContent the code owner config file content in which the old email
+ * should be replaced with the new email
+ * @param oldEmail the email that should be replaced by the new email
+ * @param newEmail the email that should replace the old email
+ * @return the updated content
+ * @throws NotImplementedException if the backend doesn't support replacing emails in code owner
+ * config files
+ */
+ default String replaceEmail(String codeOwnerConfigFileContent, String oldEmail, String newEmail) {
+ throw new NotImplementedException();
+ }
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfig.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfig.java
index 0e116a0..d01c4a3 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfig.java
@@ -350,11 +350,7 @@
* @return the code owner config key
*/
public static Key create(BranchNameKey branchNameKey, Path folderPath) {
- return create(
- branchNameKey,
- folderPath,
- /** fileName = */
- null);
+ return create(branchNameKey, folderPath, /* fileName= */ null);
}
/**
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImportMode.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImportMode.java
index c712b27..1e598df 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImportMode.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigImportMode.java
@@ -30,14 +30,10 @@
* <p>Imports of the referenced code owner config are resolved.
*/
ALL(
- /** importIgnoreParentCodeOwners = */
- true,
- /** importGlobalCodeOwnerSets = */
- true,
- /** importPerFileCodeOwnerSets = */
- true,
- /** resolveImportsOfImport = */
- true),
+ /* importIgnoreParentCodeOwners= */ true,
+ /* importGlobalCodeOwnerSets= */ true,
+ /* importPerFileCodeOwnerSets= */ true,
+ /* resolveImportsOfImport= */ true),
/**
* Only global code owner sets (code owner sets without path expressions) should be imported from
@@ -53,14 +49,10 @@
* <p>Imports of the referenced code owner config are resolved.
*/
GLOBAL_CODE_OWNER_SETS_ONLY(
- /** importIgnoreParentCodeOwners = */
- false,
- /** importGlobalCodeOwnerSets = */
- true,
- /** importPerFileCodeOwnerSets = */
- false,
- /** resolveImportsOfImport = */
- true);
+ /* importIgnoreParentCodeOwners= */ false,
+ /* importGlobalCodeOwnerSets= */ true,
+ /* importPerFileCodeOwnerSets= */ false,
+ /* resolveImportsOfImport= */ true);
private final boolean importIgnoreParentCodeOwners;
private final boolean importGlobalCodeOwnerSets;
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
index 9b77316..c8c96ab 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
@@ -106,8 +106,7 @@
branchNameKey,
codeOwnerConfigVisitor,
invalidCodeOwnerConfigCallback,
- /** pathGlob = */
- null);
+ /* pathGlob= */ null);
}
/**
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwners.java
index 07648ef..2165fda 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwners.java
@@ -70,10 +70,7 @@
requireNonNull(codeOwnerConfigKey, "codeOwnerConfigKey");
CodeOwnerBackend codeOwnerBackend =
codeOwnersPluginConfiguration.getBackend(codeOwnerConfigKey.branchNameKey());
- return codeOwnerBackend.getCodeOwnerConfig(
- codeOwnerConfigKey,
- /** revision = */
- null);
+ return codeOwnerBackend.getCodeOwnerConfig(codeOwnerConfigKey, /* revision= */ null);
}
/**
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersBackend.java b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersBackend.java
index 0aa9ca8..ab08201 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersBackend.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersBackend.java
@@ -63,4 +63,10 @@
public Optional<PathExpressionMatcher> getPathExpressionMatcher() {
return Optional.of(GlobMatcher.INSTANCE);
}
+
+ @Override
+ public String replaceEmail(String codeOwnerConfigFileContent, String oldEmail, String newEmail) {
+ return FindOwnersCodeOwnerConfigParser.replaceEmail(
+ codeOwnerConfigFileContent, oldEmail, newEmail);
+ }
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java
index 7e8fff8..561c3b1 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java
@@ -20,6 +20,7 @@
import static java.util.stream.Collectors.joining;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -77,6 +78,12 @@
// Artifical owner token for "set noparent" when used in per-file.
private static final String TOK_SET_NOPARENT = "set noparent";
+ /**
+ * Any Unicode linebreak sequence, is equivalent to {@code
+ * \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]}.
+ */
+ private static final String LINEBREAK_MATCHER = "\\R";
+
@Override
public CodeOwnerConfig parse(
ObjectId revision, CodeOwnerConfig.Key codeOwnerConfigKey, String codeOwnerConfigAsString)
@@ -98,6 +105,35 @@
return Formatter.formatAsString(requireNonNull(codeOwnerConfig, "codeOwnerConfig"));
}
+ public static String replaceEmail(
+ String codeOwnerConfigFileContent, String oldEmail, String newEmail) {
+ requireNonNull(codeOwnerConfigFileContent, "codeOwnerConfigFileContent");
+ requireNonNull(oldEmail, "oldEmail");
+ requireNonNull(newEmail, "newEmail");
+
+ String charsThatCanAppearBeforeOrAfterEmail = "[\\s=,#]";
+ Pattern pattern =
+ Pattern.compile(
+ "(^|.*"
+ + charsThatCanAppearBeforeOrAfterEmail
+ + "+)"
+ + "("
+ + Pattern.quote(oldEmail)
+ + ")"
+ + "($|"
+ + charsThatCanAppearBeforeOrAfterEmail
+ + "+.*)");
+
+ List<String> updatedLines = new ArrayList<>();
+ for (String line : Splitter.onPattern(LINEBREAK_MATCHER).split(codeOwnerConfigFileContent)) {
+ while (pattern.matcher(line).matches()) {
+ line = pattern.matcher(line).replaceFirst("$1" + newEmail + "$3");
+ }
+ updatedLines.add(line);
+ }
+ return Joiner.on("\n").join(updatedLines);
+ }
+
private static class Parser implements ValidationError.Sink {
private static final String COMMA = "[\\s]*,[\\s]*";
@@ -158,7 +194,7 @@
CodeOwnerSet.Builder globalCodeOwnerSetBuilder = CodeOwnerSet.builder();
List<CodeOwnerSet> perFileCodeOwnerSet = new ArrayList<>();
- for (String line : Splitter.onPattern("\\R").split(codeOwnerConfigAsString)) {
+ for (String line : Splitter.onPattern(LINEBREAK_MATCHER).split(codeOwnerConfigAsString)) {
parseLine(codeOwnerConfigBuilder, globalCodeOwnerSetBuilder, perFileCodeOwnerSet, line);
}
diff --git a/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java b/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java
index dcf50e7..260d9d9 100644
--- a/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java
@@ -17,13 +17,13 @@
import static com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS;
import static java.util.Objects.requireNonNull;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.ValidationMessage;
import com.google.gerrit.server.project.ProjectLevelConfig;
import com.google.gerrit.server.project.ProjectState;
-import java.util.Optional;
import org.eclipse.jgit.lib.Config;
/**
@@ -50,45 +50,60 @@
protected abstract String getConfigKey();
- Optional<RequiredApproval> getForProject(ProjectState projectState, Config pluginConfig) {
+ /**
+ * Reads the required approvals for the specified project from the given plugin config with
+ * fallback to {@code gerrit.config}.
+ *
+ * @param projectState state of the project for which the required approvals should be read
+ * @param pluginConfig the plugin config from which the required approvals should be read
+ * @return the required approvals, an empty list if none was configured
+ */
+ ImmutableList<RequiredApproval> get(ProjectState projectState, Config pluginConfig) {
requireNonNull(projectState, "projectState");
requireNonNull(pluginConfig, "pluginConfig");
- String requiredApproval = pluginConfig.getString(SECTION_CODE_OWNERS, null, getConfigKey());
- if (requiredApproval == null) {
- return Optional.empty();
+
+ ImmutableList.Builder<RequiredApproval> requiredApprovalList = ImmutableList.builder();
+ String[] requiredApprovals =
+ pluginConfig.getStringList(SECTION_CODE_OWNERS, /* subsection= */ null, getConfigKey());
+ if (requiredApprovals.length > 0) {
+ for (String requiredApproval : requiredApprovals) {
+ try {
+ requiredApprovalList.add(RequiredApproval.parse(projectState, requiredApproval));
+ } catch (IllegalStateException | IllegalArgumentException e) {
+ throw new InvalidPluginConfigurationException(
+ pluginName,
+ String.format(
+ "Required approval '%s' that is configured in %s.config"
+ + " (parameter %s.%s) is invalid: %s",
+ requiredApproval,
+ pluginName,
+ SECTION_CODE_OWNERS,
+ getConfigKey(),
+ e.getMessage()));
+ }
+ }
+ return requiredApprovalList.build();
}
- try {
- return Optional.of(RequiredApproval.parse(projectState, requiredApproval));
- } catch (IllegalStateException | IllegalArgumentException e) {
- throw new InvalidPluginConfigurationException(
- pluginName,
- String.format(
- "Required approval '%s' that is configured in %s.config"
- + " (parameter %s.%s) is invalid: %s",
- requiredApproval, pluginName, SECTION_CODE_OWNERS, getConfigKey(), e.getMessage()));
- }
- }
-
- Optional<RequiredApproval> getFromGlobalPluginConfig(ProjectState projectState) {
- requireNonNull(projectState, "projectState");
-
- String requiredApproval =
- pluginConfigFactory.getFromGerritConfig(pluginName).getString(getConfigKey());
- if (requiredApproval == null) {
- return Optional.empty();
+ requiredApprovals =
+ pluginConfigFactory.getFromGerritConfig(pluginName).getStringList(getConfigKey());
+ if (requiredApprovals.length > 0) {
+ for (String requiredApproval : requiredApprovals) {
+ try {
+ requiredApprovalList.add(RequiredApproval.parse(projectState, requiredApproval));
+ } catch (IllegalStateException | IllegalArgumentException e) {
+ throw new InvalidPluginConfigurationException(
+ pluginName,
+ String.format(
+ "Required approval '%s' that is configured in gerrit.config"
+ + " (parameter plugin.%s.%s) is invalid: %s",
+ requiredApproval, pluginName, getConfigKey(), e.getMessage()));
+ }
+ }
+ return requiredApprovalList.build();
}
- try {
- return Optional.of(RequiredApproval.parse(projectState, requiredApproval));
- } catch (IllegalStateException | IllegalArgumentException e) {
- throw new InvalidPluginConfigurationException(
- pluginName,
- String.format(
- "Required approval '%s' that is configured in gerrit.config"
- + " (parameter plugin.%s.%s) is invalid: %s",
- requiredApproval, pluginName, getConfigKey(), e.getMessage()));
- }
+ return ImmutableList.of();
}
/**
@@ -99,19 +114,22 @@
* @return list of validation messages for validation errors, empty list if there are no
* validation errors
*/
- Optional<CommitValidationMessage> validateProjectLevelConfig(
+ ImmutableList<CommitValidationMessage> validateProjectLevelConfig(
ProjectState projectState, String fileName, ProjectLevelConfig.Bare projectLevelConfig) {
requireNonNull(projectState, "projectState");
requireNonNull(fileName, "fileName");
requireNonNull(projectLevelConfig, "projectLevelConfig");
- String requiredApproval =
- projectLevelConfig.getConfig().getString(SECTION_CODE_OWNERS, null, getConfigKey());
- if (requiredApproval != null) {
+ String[] requiredApprovals =
+ projectLevelConfig
+ .getConfig()
+ .getStringList(SECTION_CODE_OWNERS, /* subsection= */ null, getConfigKey());
+ ImmutableList.Builder<CommitValidationMessage> validationMessages = ImmutableList.builder();
+ for (String requiredApproval : requiredApprovals) {
try {
RequiredApproval.parse(projectState, requiredApproval);
} catch (IllegalArgumentException | IllegalStateException e) {
- return Optional.of(
+ validationMessages.add(
new CommitValidationMessage(
String.format(
"Required approval '%s' that is configured in %s (parameter %s.%s) is invalid: %s",
@@ -123,6 +141,6 @@
ValidationMessage.Type.ERROR));
}
}
- return Optional.empty();
+ return validationMessages.build();
}
}
diff --git a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java
index dba7bb2..2a29766 100644
--- a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java
+++ b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java
@@ -158,12 +158,10 @@
validationMessages.addAll(backendConfig.validateProjectLevelConfig(fileName, cfg));
validationMessages.addAll(generalConfig.validateProjectLevelConfig(fileName, cfg));
validationMessages.addAll(statusConfig.validateProjectLevelConfig(fileName, cfg));
- requiredApprovalConfig
- .validateProjectLevelConfig(projectState, fileName, cfg)
- .ifPresent(validationMessages::add);
- overrideApprovalConfig
- .validateProjectLevelConfig(projectState, fileName, cfg)
- .ifPresent(validationMessages::add);
+ validationMessages.addAll(
+ requiredApprovalConfig.validateProjectLevelConfig(projectState, fileName, cfg));
+ validationMessages.addAll(
+ overrideApprovalConfig.validateProjectLevelConfig(projectState, fileName, cfg));
if (!validationMessages.isEmpty()) {
throw new CommitValidationException(
exceptionMessage(fileName, cfg.getRevision()), validationMessages);
diff --git a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java
index a881e1c..9f088a7 100644
--- a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java
+++ b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java
@@ -18,7 +18,9 @@
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
@@ -322,16 +324,22 @@
* <li>hard-coded default required approval
* </ul>
*
- * <p>The first required code owner approval that exists counts and the evaluation is stopped.
+ * <p>The first required code owner approval configuration that exists counts and the evaluation
+ * is stopped.
+ *
+ * <p>If the code owner configuration contains multiple required approvals values, the last value
+ * is used.
*
* @param project project for which the required approval should be returned
* @return the required code owner approval that should be used for the given project
*/
public RequiredApproval getRequiredApproval(Project.NameKey project) {
- Optional<RequiredApproval> configuredRequiredApprovalConfig =
+ ImmutableList<RequiredApproval> configuredRequiredApprovalConfig =
getConfiguredRequiredApproval(requiredApprovalConfig, project);
- if (configuredRequiredApprovalConfig.isPresent()) {
- return configuredRequiredApprovalConfig.get();
+ if (!configuredRequiredApprovalConfig.isEmpty()) {
+ // There can be only one required approval. If multiple ones are configured just use the last
+ // one, this is also what Config#getString(String, String, String) does.
+ return Iterables.getLast(configuredRequiredApprovalConfig);
}
// fall back to hard-coded default required approval
@@ -353,7 +361,9 @@
* <li>globally configured override approval
* </ul>
*
- * <p>The first override approval that exists counts and the evaluation is stopped.
+ * <p>The first override approval configuration that exists counts and the evaluation is stopped.
+ *
+ * <p>If the code owner configuration contains multiple override values, the last value is used.
*
* @param project project for which the override approval should be returned
* @return the override approval that should be used for the given project, {@link
@@ -362,10 +372,12 @@
*/
public Optional<RequiredApproval> getOverrideApproval(Project.NameKey project) {
try {
- Optional<RequiredApproval> configuredOverrideApprovalConfig =
+ ImmutableList<RequiredApproval> configuredOverrideApprovalConfig =
getConfiguredRequiredApproval(overrideApprovalConfig, project);
- if (configuredOverrideApprovalConfig.isPresent()) {
- return configuredOverrideApprovalConfig;
+ if (!configuredOverrideApprovalConfig.isEmpty()) {
+ // There can be only one override approval. If multiple ones are configured just use the
+ // last one, this is also what Config#getString(String, String, String) does.
+ return Optional.of(Iterables.getLast(configuredOverrideApprovalConfig));
}
} catch (InvalidPluginConfigurationException e) {
logger.atWarning().withCause(e).log(
@@ -378,33 +390,18 @@
}
/**
- * Gets the required approval that is configured for the given project.
+ * Gets the required approvals that are configured for the given project.
*
- * @param requiredApprovalConfig the config from which the required approval should be read
- * @param project the project for which the configured required approval should be returned
- * @return the required approval that is configured for the given project, {@link
- * Optional#empty()} if no required approval is configured
+ * @param requiredApprovalConfig the config from which the required approvals should be read
+ * @param project the project for which the configured required approvals should be returned
+ * @return the required approvals that is configured for the given project, an empty list if no
+ * required approvals are configured
*/
- private Optional<RequiredApproval> getConfiguredRequiredApproval(
+ private ImmutableList<RequiredApproval> getConfiguredRequiredApproval(
AbstractRequiredApprovalConfig requiredApprovalConfig, Project.NameKey project) {
Config pluginConfig = getPluginConfig(project);
-
ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
-
- // check if a project specific required approval is configured
- Optional<RequiredApproval> requiredApproval =
- requiredApprovalConfig.getForProject(projectState, pluginConfig);
- if (requiredApproval.isPresent()) {
- return requiredApproval;
- }
-
- // check if a required approval is globally configured
- requiredApproval = requiredApprovalConfig.getFromGlobalPluginConfig(projectState);
- if (requiredApproval.isPresent()) {
- return requiredApproval;
- }
-
- return Optional.empty();
+ return requiredApprovalConfig.get(projectState, pluginConfig);
}
/**
@@ -423,10 +420,7 @@
try {
return pluginConfigFactory
.getFromGerritConfig(pluginName)
- .getBoolean(
- KEY_ENABLE_EXPERIMENTAL_REST_ENDPOINTS,
- /** defaultValue = */
- false);
+ .getBoolean(KEY_ENABLE_EXPERIMENTAL_REST_ENDPOINTS, /* defaultValue= */ false);
} catch (IllegalArgumentException e) {
logger.atWarning().withCause(e).log(
"Value '%s' in gerrit.config (parameter plugin.%s.%s) is invalid.",
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/RenameEmail.java b/java/com/google/gerrit/plugins/codeowners/restapi/RenameEmail.java
new file mode 100644
index 0000000..8df9a34
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/RenameEmail.java
@@ -0,0 +1,202 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.restapi;
+
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.plugins.codeowners.api.RenameEmailInput;
+import com.google.gerrit.plugins.codeowners.api.RenameEmailResultInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwner;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigFileUpdateScanner;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerReference;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
+import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.CommitUtil;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.permissions.RefPermission;
+import com.google.gerrit.server.project.BranchResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Optional;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+@Singleton
+public class RenameEmail implements RestModifyView<BranchResource, RenameEmailInput> {
+ @VisibleForTesting
+ public static String DEFAULT_COMMIT_MESSAGE = "Rename email in code owner config files";
+
+ private final Provider<CurrentUser> currentUser;
+ private final PermissionBackend permissionBackend;
+ private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
+ private final CodeOwnerResolver codeOwnerResolver;
+ private final CodeOwnerConfigFileUpdateScanner codeOwnerConfigFileUpdateScanner;
+
+ @Inject
+ public RenameEmail(
+ Provider<CurrentUser> currentUser,
+ PermissionBackend permissionBackend,
+ CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
+ CodeOwnerResolver codeOwnerResolver,
+ CodeOwnerConfigFileUpdateScanner codeOwnerConfigFileUpdateScanner) {
+ this.currentUser = currentUser;
+ this.permissionBackend = permissionBackend;
+ this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
+ this.codeOwnerResolver = codeOwnerResolver;
+ this.codeOwnerConfigFileUpdateScanner = codeOwnerConfigFileUpdateScanner;
+ }
+
+ @Override
+ public Response<RenameEmailResultInfo> apply(
+ BranchResource branchResource, RenameEmailInput input)
+ throws AuthException, BadRequestException, ResourceConflictException,
+ MethodNotAllowedException, UnprocessableEntityException, PermissionBackendException,
+ IOException {
+ if (!currentUser.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ // caller needs to be project owner or have direct push permissions for the branch
+ if (!permissionBackend
+ .currentUser()
+ .project(branchResource.getNameKey())
+ .test(ProjectPermission.WRITE_CONFIG)) {
+ permissionBackend
+ .currentUser()
+ .ref(branchResource.getBranchKey())
+ .check(RefPermission.UPDATE);
+ }
+
+ validateInput(input);
+
+ CodeOwnerBackend codeOwnerBackend =
+ codeOwnersPluginConfiguration.getBackend(branchResource.getBranchKey());
+
+ Account.Id accountOwningOldEmail = resolveEmail(input.oldEmail);
+ Account.Id accountOwningNewEmail = resolveEmail(input.newEmail);
+ if (!accountOwningOldEmail.equals(accountOwningNewEmail)) {
+ throw new BadRequestException(
+ String.format(
+ "emails must belong to the same account"
+ + " (old email %s is owned by account %d, new email %s is owned by account %d)",
+ input.oldEmail,
+ accountOwningOldEmail.get(),
+ input.newEmail,
+ accountOwningNewEmail.get()));
+ }
+
+ String inputMessage = Strings.nullToEmpty(input.message).trim();
+ String commitMessage = !inputMessage.isEmpty() ? inputMessage : DEFAULT_COMMIT_MESSAGE;
+
+ try {
+ Optional<RevCommit> commitId =
+ codeOwnerConfigFileUpdateScanner.update(
+ branchResource.getBranchKey(),
+ commitMessage,
+ (codeOwnerConfigFilePath, codeOwnerConfigFileContent) ->
+ renameEmailInCodeOwnerConfig(
+ codeOwnerBackend,
+ codeOwnerConfigFileContent,
+ input.oldEmail,
+ input.newEmail));
+
+ RenameEmailResultInfo result = new RenameEmailResultInfo();
+ if (commitId.isPresent()) {
+ result.commit = CommitUtil.toCommitInfo(commitId.get());
+ }
+ return Response.ok(result);
+ } catch (NotImplementedException e) {
+ throw new MethodNotAllowedException(
+ String.format(
+ "rename email not supported by %s backend",
+ CodeOwnerBackendId.getBackendId(codeOwnerBackend.getClass())),
+ e);
+ }
+ }
+
+ private void validateInput(RenameEmailInput input) throws BadRequestException {
+ if (input.oldEmail == null) {
+ throw new BadRequestException("old email is required");
+ }
+ if (input.newEmail == null) {
+ throw new BadRequestException("new email is required");
+ }
+ if (input.oldEmail.equals(input.newEmail)) {
+ throw new BadRequestException("old and new email must differ");
+ }
+ }
+
+ private Account.Id resolveEmail(String email)
+ throws ResourceConflictException, UnprocessableEntityException {
+ requireNonNull(email, "email");
+
+ ImmutableSet<CodeOwner> codeOwners =
+ codeOwnerResolver.resolve(CodeOwnerReference.create(email)).collect(toImmutableSet());
+ if (codeOwners.isEmpty()) {
+ throw new UnprocessableEntityException(String.format("cannot resolve email %s", email));
+ }
+ if (codeOwners.size() > 1) {
+ throw new ResourceConflictException(String.format("email %s is ambigious", email));
+ }
+ return Iterables.getOnlyElement(codeOwners).accountId();
+ }
+
+ /**
+ * Renames an email in the given code owner config.
+ *
+ * @param codeOwnerBackend the code owner backend that is being used
+ * @param codeOwnerConfigFileContent the content of the code owner config file
+ * @param oldEmail the old email that should be replaced by the new email
+ * @param newEmail the new email that should replace the old email
+ * @return the updated code owner config file content if an update was performed, {@link
+ * Optional#empty()} if no update was done
+ */
+ private Optional<String> renameEmailInCodeOwnerConfig(
+ CodeOwnerBackend codeOwnerBackend,
+ String codeOwnerConfigFileContent,
+ String oldEmail,
+ String newEmail) {
+ requireNonNull(codeOwnerConfigFileContent, "codeOwnerConfigFileContent");
+ requireNonNull(oldEmail, "oldEmail");
+ requireNonNull(newEmail, "newEmail");
+
+ String updatedCodeOwnerConfigFileContent =
+ codeOwnerBackend.replaceEmail(codeOwnerConfigFileContent, oldEmail, newEmail);
+ if (codeOwnerConfigFileContent.equals(updatedCodeOwnerConfigFileContent)) {
+ return Optional.empty();
+ }
+ return Optional.of(updatedCodeOwnerConfigFileContent);
+ }
+}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java b/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java
index 38fe259..03d8858 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/RestApiModule.java
@@ -31,6 +31,7 @@
.to(GetCodeOwnerConfigForPathInBranch.class);
get(BRANCH_KIND, "code_owners.config_files").to(GetCodeOwnerConfigFiles.class);
get(BRANCH_KIND, "code_owners.branch_config").to(GetCodeOwnerBranchConfig.class);
+ post(BRANCH_KIND, "code_owners.rename").to(RenameEmail.class);
factory(CodeOwnerJson.Factory.class);
DynamicMap.mapOf(binder(), CodeOwnersInBranchCollection.PathResource.PATH_KIND);
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/RequiredApprovalSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/RequiredApprovalSubject.java
new file mode 100644
index 0000000..27d2b20
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/testing/RequiredApprovalSubject.java
@@ -0,0 +1,92 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.testing;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.gerrit.plugins.codeowners.config.RequiredApproval;
+import com.google.gerrit.truth.ListSubject;
+import com.google.gerrit.truth.OptionalSubject;
+import java.util.Optional;
+
+/** {@link Subject} for doing assertions on {@link RequiredApproval}s. */
+public class RequiredApprovalSubject extends Subject {
+ /**
+ * Starts a fluent chain to do assertions on a {@link RequiredApproval}.
+ *
+ * @param requiredApproval the required approval on which assertions should be done
+ * @return the created {@link RequiredApprovalSubject}
+ */
+ public static RequiredApprovalSubject assertThat(RequiredApproval requiredApproval) {
+ return assertAbout(requiredApprovals()).that(requiredApproval);
+ }
+
+ /**
+ * Starts a fluent chain to do assertions on an {@link Optional} {@link RequiredApproval}.
+ *
+ * @param requiredApproval optional required approval on which assertions should be done
+ * @return the created {@link OptionalSubject}
+ */
+ public static OptionalSubject<RequiredApprovalSubject, RequiredApproval> assertThat(
+ Optional<RequiredApproval> requiredApproval) {
+ return OptionalSubject.assertThat(requiredApproval, requiredApprovals());
+ }
+
+ /**
+ * Starts a fluent chain to do assertions on a list of {@link RequiredApproval}s.
+ *
+ * @param requiredApprovals list of required approvals on which assertions should be done
+ * @return the created {@link ListSubject}
+ */
+ public static ListSubject<RequiredApprovalSubject, RequiredApproval> assertThat(
+ ImmutableList<RequiredApproval> requiredApprovals) {
+ return ListSubject.assertThat(requiredApprovals, requiredApprovals());
+ }
+
+ /**
+ * Creates a subject factory for mapping {@link RequiredApproval}s to {@link
+ * RequiredApprovalSubject}s.
+ */
+ private static Subject.Factory<RequiredApprovalSubject, RequiredApproval> requiredApprovals() {
+ return RequiredApprovalSubject::new;
+ }
+
+ private final RequiredApproval requiredApproval;
+
+ private RequiredApprovalSubject(FailureMetadata metadata, RequiredApproval requiredApproval) {
+ super(metadata, requiredApproval);
+ this.requiredApproval = requiredApproval;
+ }
+
+ /** Returns a subject for the label name. */
+ public StringSubject hasLabelNameThat() {
+ return check("labelName()").that(requiredApproval().labelType().getName());
+ }
+
+ /** Returns a subject for the value. */
+ public IntegerSubject hasValueThat() {
+ return check("value()").that((int) requiredApproval().value());
+ }
+
+ private RequiredApproval requiredApproval() {
+ isNotNull();
+ return requiredApproval;
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
index 94bd27d..daf08e2 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
@@ -17,7 +17,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.fetch;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
@@ -75,11 +77,9 @@
Config cfg = new Config();
cfg.setBoolean(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
StatusConfig.KEY_DISABLED,
- /** value = */
- true);
+ /* value= */ true);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
@@ -94,8 +94,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
StatusConfig.KEY_DISABLED_BRANCH,
"refs/heads/master");
setCodeOwnersConfig(cfg);
@@ -113,8 +112,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
StatusConfig.KEY_DISABLED,
"INVALID");
setCodeOwnersConfig(cfg);
@@ -135,8 +133,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
StatusConfig.KEY_DISABLED_BRANCH,
"^refs/heads/[");
setCodeOwnersConfig(cfg);
@@ -157,8 +154,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
BackendConfig.KEY_BACKEND,
CodeOwnerBackendId.PROTO.getBackendId());
setCodeOwnersConfig(cfg);
@@ -194,8 +190,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
BackendConfig.KEY_BACKEND,
"INVALID");
setCodeOwnersConfig(cfg);
@@ -237,8 +232,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
"Code-Review+2");
setCodeOwnersConfig(cfg);
@@ -246,8 +240,8 @@
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(2);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(2);
}
@Test
@@ -257,8 +251,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
"INVALID");
setCodeOwnersConfig(cfg);
@@ -276,14 +269,40 @@
}
@Test
+ public void allRequiredApprovalsAreValidated() throws Exception {
+ fetchRefsMetaConfig();
+
+ ImmutableList<String> invalidValues = ImmutableList.of("INVALID", "ALSO_INVALID");
+ Config cfg = new Config();
+ cfg.setStringList(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
+ invalidValues);
+ setCodeOwnersConfig(cfg);
+
+ PushResult r = pushRefsMetaConfig();
+ assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
+ .isEqualTo(Status.REJECTED_OTHER_REASON);
+ for (String invalidValue : invalidValues) {
+ assertThat(r.getMessages())
+ .contains(
+ String.format(
+ "Required approval '%s' that is configured in code-owners.config (parameter"
+ + " codeOwners.%s) is invalid: Invalid format, expected"
+ + " '<label-name>+<label-value>'.",
+ invalidValue, RequiredApprovalConfig.KEY_REQUIRED_APPROVAL));
+ }
+ }
+
+ @Test
public void configureOverrideApproval() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
"Code-Review+2");
setCodeOwnersConfig(cfg);
@@ -292,8 +311,9 @@
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
Optional<RequiredApproval> overrideApproval =
codeOwnersPluginConfiguration.getOverrideApproval(project);
- assertThat(overrideApproval.get().labelType().getName()).isEqualTo("Code-Review");
- assertThat(overrideApproval.get().value()).isEqualTo(2);
+ assertThat(overrideApproval).isPresent();
+ assertThat(overrideApproval).value().hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(overrideApproval).value().hasValueThat().isEqualTo(2);
}
@Test
@@ -303,8 +323,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
"INVALID");
setCodeOwnersConfig(cfg);
@@ -322,14 +341,40 @@
}
@Test
+ public void allOverrideApprovalsAreValidated() throws Exception {
+ fetchRefsMetaConfig();
+
+ ImmutableList<String> invalidValues = ImmutableList.of("INVALID", "ALSO_INVALID");
+ Config cfg = new Config();
+ cfg.setStringList(
+ CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
+ invalidValues);
+ setCodeOwnersConfig(cfg);
+
+ PushResult r = pushRefsMetaConfig();
+ assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
+ .isEqualTo(Status.REJECTED_OTHER_REASON);
+ for (String invalidValue : invalidValues) {
+ assertThat(r.getMessages())
+ .contains(
+ String.format(
+ "Required approval '%s' that is configured in code-owners.config (parameter"
+ + " codeOwners.%s) is invalid: Invalid format, expected"
+ + " '<label-name>+<label-value>'.",
+ invalidValue, OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL));
+ }
+ }
+
+ @Test
public void configureMergeCommitStrategy() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
GeneralConfig.KEY_MERGE_COMMIT_STRATEGY,
MergeCommitStrategy.ALL_CHANGED_FILES);
setCodeOwnersConfig(cfg);
@@ -347,8 +392,7 @@
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
- /** subsection = */
- null,
+ /* subsection= */ null,
GeneralConfig.KEY_MERGE_COMMIT_STRATEGY,
"INVALID");
setCodeOwnersConfig(cfg);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java
index 9b05857..d5a840c 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java
@@ -62,11 +62,7 @@
public void createTestChange() throws Exception {
changeOwner =
accountCreator.create(
- "changeOwner",
- "changeOwner@example.com",
- "ChangeOwner",
- /** displayName = */
- null);
+ "changeOwner", "changeOwner@example.com", "ChangeOwner", /* displayName= */ null);
TestRepository<InMemoryRepository> testRepo = cloneProject(project, changeOwner);
// Create a change that contains files for all paths that are used in the tests. This is
// necessary since CodeOwnersInChangeCollection rejects requests for paths that are not present
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/RenameEmailIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/RenameEmailIT.java
new file mode 100644
index 0000000..ec0a368
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/RenameEmailIT.java
@@ -0,0 +1,735 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.acceptance.api;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigSubject.assertThat;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
+import com.google.gerrit.plugins.codeowners.api.RenameEmailInput;
+import com.google.gerrit.plugins.codeowners.api.RenameEmailResultInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigFileUpdateScanner;
+import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
+import com.google.gerrit.plugins.codeowners.config.BackendConfig;
+import com.google.gerrit.plugins.codeowners.restapi.RenameEmail;
+import com.google.inject.Inject;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Acceptance test for the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST
+ * endpoint.
+ *
+ * <p>Further tests for the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST
+ * endpoint that require using the REST API are implemented in {@link
+ * com.google.gerrit.plugins.codeowners.acceptance.restapi.RenameEmailRestIT}.
+ */
+public class RenameEmailIT extends AbstractCodeOwnersIT {
+ @Inject private AccountOperations accountOperations;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private RequestScopeOperations requestScopeOperations;
+
+ private BackendConfig backendConfig;
+ private CodeOwnerConfigFileUpdateScanner codeOwnerConfigFileUpdateScanner;
+
+ @Before
+ public void setup() throws Exception {
+ backendConfig = plugin.getSysInjector().getInstance(BackendConfig.class);
+ codeOwnerConfigFileUpdateScanner =
+ plugin.getSysInjector().getInstance(CodeOwnerConfigFileUpdateScanner.class);
+ }
+
+ @Test
+ public void oldEmailIsRequired() throws Exception {
+ RenameEmailInput input = new RenameEmailInput();
+ input.newEmail = "new@example.com";
+ BadRequestException exception =
+ assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input));
+ assertThat(exception).hasMessageThat().isEqualTo("old email is required");
+ }
+
+ @Test
+ public void newEmailIsRequired() throws Exception {
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = "old@example.com";
+ BadRequestException exception =
+ assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input));
+ assertThat(exception).hasMessageThat().isEqualTo("new email is required");
+ }
+
+ @Test
+ public void oldEmailNotResolvable() throws Exception {
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = "unknown@example.com";
+ input.newEmail = admin.email();
+ UnprocessableEntityException exception =
+ assertThrows(
+ UnprocessableEntityException.class, () -> renameEmail(project, "master", input));
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(String.format("cannot resolve email %s", input.oldEmail));
+ }
+
+ @Test
+ public void newEmailNotResolvable() throws Exception {
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = admin.email();
+ input.newEmail = "unknown@example.com";
+ UnprocessableEntityException exception =
+ assertThrows(
+ UnprocessableEntityException.class, () -> renameEmail(project, "master", input));
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(String.format("cannot resolve email %s", input.newEmail));
+ }
+
+ @Test
+ public void emailsMustBelongToSameAccount() throws Exception {
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = admin.email();
+ input.newEmail = user.email();
+ BadRequestException exception =
+ assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input));
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ String.format(
+ "emails must belong to the same account"
+ + " (old email %s is owned by account %d, new email %s is owned by account %d)",
+ admin.email(), admin.id().get(), user.email(), user.id().get()));
+ }
+
+ @Test
+ public void oldAndNewEmailMustDiffer() throws Exception {
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = admin.email();
+ input.newEmail = admin.email();
+ BadRequestException exception =
+ assertThrows(BadRequestException.class, () -> renameEmail(project, "master", input));
+ assertThat(exception).hasMessageThat().isEqualTo("old and new email must differ");
+ }
+
+ @Test
+ public void renameEmailRequiresDirectPushPermissionsForNonProjectOwner() throws Exception {
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ requestScopeOperations.setApiUser(user.id());
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ AuthException exception =
+ assertThrows(AuthException.class, () -> renameEmail(project, "master", input));
+ assertThat(exception).hasMessageThat().isEqualTo("not permitted: update on refs/heads/master");
+ }
+
+ @Test
+ public void renameEmail_noCodeOwnerConfig() throws Exception {
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNull();
+ }
+
+ @Test
+ public void renameEmail_noUpdateIfEmailIsNotContainedInCodeOwnerConfigs() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNull();
+ }
+
+ @Test
+ public void renameOwnEmailWithDirectPushPermission() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(admin.email())
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ // grant all users direct push permissions
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ requestScopeOperations.setApiUser(user.id());
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail, admin.email());
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail);
+ }
+
+ @Test
+ public void renameOtherEmailWithDirectPushPermission() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(admin.email())
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ // grant all users direct push permissions
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+
+ // Allow all users to see secondary emails.
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .add(allowCapability(GlobalCapability.MODIFY_ACCOUNT).group(REGISTERED_USERS))
+ .update();
+
+ String secondaryEmail = "admin-foo@example.com";
+ accountOperations.account(admin.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ requestScopeOperations.setApiUser(user.id());
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = admin.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail, user.email());
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail);
+ }
+
+ @Test
+ public void renameOwnEmailAsProjectOwner() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ String secondaryEmail = "admin-foo@example.com";
+ accountOperations.account(admin.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = admin.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail, user.email());
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail);
+ }
+
+ @Test
+ public void renameOtherEmailAsProjectOwner() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey1 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ CodeOwnerConfig.Key codeOwnerConfigKey2 =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail, admin.email());
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail);
+ }
+
+ @Test
+ public void renameEmail_callingUserBecomesCommitAuthor() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+ assertThat(result.commit.author.email).isEqualTo(admin.email());
+ }
+
+ @Test
+ public void renameEmailWithDefaultCommitMessage() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+ assertThat(result.commit.message).isEqualTo(RenameEmail.DEFAULT_COMMIT_MESSAGE);
+ }
+
+ @Test
+ public void renameEmailWithSpecifiedCommitMessage() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ input.message = "Update email with custom message";
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+ assertThat(result.commit.message).isEqualTo(input.message);
+ }
+
+ @Test
+ public void renameEmail_specifiedCommitMessageIsTrimmed() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ String message = "Update email with custom message";
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ input.message = " " + message + "\t";
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+ assertThat(result.commit.message).isEqualTo(message);
+ }
+
+ @Test
+ public void renameEmail_lineCommentsArePreserved() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ // insert some comments
+ codeOwnerConfigFileUpdateScanner.update(
+ BranchNameKey.create(project, "master"),
+ "Insert comments",
+ (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> {
+ StringBuilder b = new StringBuilder();
+ // insert comment line at the top of the file
+ b.append("# top comment\n");
+
+ String[] lines = codeOwnerConfigFileContent.split("\\n");
+ b.append(lines[0] + "\n");
+
+ // insert comment line in the middle of the file
+ b.append("# middle comment\n");
+
+ for (int n = 1; n < lines.length; n++) {
+ b.append(lines[n] + "\n");
+ }
+
+ // insert comment line at the bottom of the file
+ b.append("# bottom comment\n");
+
+ return Optional.of(b.toString());
+ });
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ renameEmail(project, "master", input);
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail, admin.email());
+
+ // verify that the comments are still present
+ String codeOwnerConfigFileContent =
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent();
+ String[] lines = codeOwnerConfigFileContent.split("\\n");
+ assertThat(lines[0]).isEqualTo("# top comment");
+ assertThat(lines[2]).isEqualTo("# middle comment");
+ assertThat(lines[lines.length - 1]).isEqualTo("# bottom comment");
+ }
+
+ @Test
+ public void renameEmail_inlineCommentsArePreserved() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ // insert some inline comments
+ codeOwnerConfigFileUpdateScanner.update(
+ BranchNameKey.create(project, "master"),
+ "Insert comments",
+ (codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> {
+ StringBuilder b = new StringBuilder();
+ for (String line : codeOwnerConfigFileContent.split("\\n")) {
+ if (line.contains(user.email())) {
+ b.append(line + "# some comment\n");
+ continue;
+ }
+ if (line.contains(admin.email())) {
+ b.append(line + "# other comment\n");
+ continue;
+ }
+ b.append(line + "\n");
+ }
+
+ return Optional.of(b.toString());
+ });
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ renameEmail(project, "master", input);
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail, admin.email());
+
+ // verify that the inline comments are still present
+ String codeOwnerConfigFileContent =
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent();
+ for (String line : codeOwnerConfigFileContent.split("\\n")) {
+ if (line.contains(secondaryEmail)) {
+ assertThat(line).endsWith("# some comment");
+ } else if (line.contains(admin.email())) {
+ assertThat(line).endsWith("# other comment");
+ }
+ }
+ }
+
+ @Test
+ public void renameEmail_emailInCommentIsReplaced() throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ // insert some comments
+ codeOwnerConfigFileUpdateScanner.update(
+ BranchNameKey.create(project, "master"),
+ "Insert comments",
+ (codeOwnerConfigFilePath, codeOwnerConfigFileContent) ->
+ Optional.of("# foo " + user.email() + " bar\n" + codeOwnerConfigFileContent));
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ renameEmail(project, "master", input);
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(secondaryEmail, admin.email());
+
+ // verify that the comments are still present
+ String codeOwnerConfigFileContent =
+ codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getContent();
+ assertThat(codeOwnerConfigFileContent.split("\\n")[0])
+ .endsWith("# foo " + secondaryEmail + " bar");
+ }
+
+ @Test
+ public void renameEmail_emailThatContainsEmailToBeReplacesAsSubstringStaysIntact()
+ throws Exception {
+ // renaming email is not supported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
+
+ TestAccount otherUser1 =
+ accountCreator.create(
+ "otherUser1", "foo" + user.email(), "Other User 1", /* displayName= */ null);
+ TestAccount otherUser2 =
+ accountCreator.create(
+ "otherUser2", user.email() + "bar", "Other User 2", /* displayName= */ null);
+ TestAccount otherUser3 =
+ accountCreator.create(
+ "otherUser3", "foo" + user.email() + "bar", "Other User 3", /* displayName= */ null);
+
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .addCodeOwnerEmail(otherUser1.email())
+ .addCodeOwnerEmail(otherUser2.email())
+ .addCodeOwnerEmail(otherUser3.email())
+ .create();
+
+ // grant all users direct push permissions
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+
+ String secondaryEmail = "user-new@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ requestScopeOperations.setApiUser(user.id());
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RenameEmailResultInfo result = renameEmail(project, "master", input);
+ assertThat(result.commit).isNotNull();
+
+ assertThat(codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get())
+ .hasCodeOwnerSetsThat()
+ .onlyElement()
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(
+ secondaryEmail, otherUser1.email(), otherUser2.email(), otherUser3.email());
+ }
+
+ private RenameEmailResultInfo renameEmail(
+ Project.NameKey projectName, String branchName, RenameEmailInput input)
+ throws RestApiException {
+ return projectCodeOwnersApiFactory
+ .project(projectName)
+ .branch(branchName)
+ .renameEmailInCodeOwnerConfigFiles(input);
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java
index aa43cc1..d70be9f 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/CodeOwnersRestApiBindingsIT.java
@@ -52,7 +52,8 @@
private static final ImmutableList<RestCall> BRANCH_ENDPOINTS =
ImmutableList.of(
RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.config_files"),
- RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.branch_config"));
+ RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.branch_config"),
+ RestCall.post("/projects/%s/branches/%s/code-owners~code_owners.rename"));
private static final ImmutableList<RestCall> BRANCH_CODE_OWNER_CONFIGS_ENDPOINTS =
ImmutableList.of(RestCall.get("/projects/%s/branches/%s/code-owners~code_owners.config/%s"));
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java
index 5583fdc..3c88f2d 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java
@@ -40,11 +40,7 @@
public void createTestChange() throws Exception {
TestAccount changeOwner =
accountCreator.create(
- "changeOwner",
- "changeOwner@example.com",
- "ChangeOwner",
- /** displayName = */
- null);
+ "changeOwner", "changeOwner@example.com", "ChangeOwner", /* displayName= */ null);
// Create a change that contains the file that is used in the tests. This is necessary since
// CodeOwnersInChangeCollection rejects requests for paths that are not present in the change.
changeId =
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/RenameEmailRestIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/RenameEmailRestIT.java
new file mode 100644
index 0000000..5978272
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/RenameEmailRestIT.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.acceptance.restapi;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
+import com.google.gerrit.plugins.codeowners.api.RenameEmailInput;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
+import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
+import com.google.gerrit.plugins.codeowners.config.BackendConfig;
+import com.google.inject.Inject;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Acceptance test for the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST
+ * endpoint. that require using via REST.
+ *
+ * <p>Acceptance test for the {@link com.google.gerrit.plugins.codeowners.restapi.RenameEmail} REST
+ * endpoint that can use the Java API are implemented in {@link
+ * com.google.gerrit.plugins.codeowners.acceptance.api.RenameEmailIT}.
+ */
+public class RenameEmailRestIT extends AbstractCodeOwnersIT {
+ @Inject private AccountOperations accountOperations;
+
+ private BackendConfig backendConfig;
+
+ @Before
+ public void setup() throws Exception {
+ backendConfig = plugin.getSysInjector().getInstance(BackendConfig.class);
+ }
+
+ @Test
+ public void cannotRenameEmailsAnonymously() throws Exception {
+ RestResponse r =
+ anonymousRestSession.post(
+ String.format(
+ "/projects/%s/branches/%s/code_owners.rename",
+ IdString.fromDecoded(project.get()), "master"));
+ r.assertForbidden();
+ assertThat(r.getEntityContent()).contains("Authentication required");
+ }
+
+ @Test
+ public void renameEmailNotSupported() throws Exception {
+ // renaming email is only unsupported for the proto backend
+ assume().that(backendConfig.getDefaultBackend()).isInstanceOf(ProtoBackend.class);
+
+ String secondaryEmail = "user-foo@example.com";
+ accountOperations.account(user.id()).forUpdate().addSecondaryEmail(secondaryEmail).update();
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/")
+ .addCodeOwnerEmail(user.email())
+ .create();
+
+ RenameEmailInput input = new RenameEmailInput();
+ input.oldEmail = user.email();
+ input.newEmail = secondaryEmail;
+ RestResponse r =
+ adminRestSession.post(
+ String.format(
+ "/projects/%s/branches/%s/code_owners.rename",
+ IdString.fromDecoded(project.get()), "master"),
+ input);
+ r.assertMethodNotAllowed();
+ assertThat(r.getEntityContent())
+ .contains(
+ String.format(
+ "rename email not supported by %s backend",
+ CodeOwnerBackendId.getBackendId(backendConfig.getDefaultBackend().getClass())));
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java
index 278dcb3..17e3ccd 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java
@@ -334,9 +334,7 @@
@Test
public void setIgnoreParentCodeOwners() throws Exception {
CodeOwnerConfig codeOwnerConfig =
- createCodeOwnerConfig(
- /** ignoreParentCodeOwners = */
- false, admin.email());
+ createCodeOwnerConfig(/* ignoreParentCodeOwners= */ false, admin.email());
codeOwnerConfigOperations
.codeOwnerConfig(codeOwnerConfig.key())
.forUpdate()
@@ -350,9 +348,7 @@
@Test
public void unsetIgnoreParentCodeOwners() throws Exception {
CodeOwnerConfig codeOwnerConfig =
- createCodeOwnerConfig(
- /** ignoreParentCodeOwners = */
- true, admin.email());
+ createCodeOwnerConfig(/* ignoreParentCodeOwners= */ true, admin.email());
codeOwnerConfigOperations
.codeOwnerConfig(codeOwnerConfig.key())
.forUpdate()
@@ -429,8 +425,8 @@
// Create a code owner config that contains only a single code owner set.
CodeOwnerConfig codeOwnerConfig =
createCodeOwnerConfig(
- /** ignoreParentCodeOwners = */
- false, CodeOwnerSetModification.set(ImmutableList.of(codeOwnerSet)));
+ /* ignoreParentCodeOwners= */ false,
+ CodeOwnerSetModification.set(ImmutableList.of(codeOwnerSet)));
// Remove all code owners so that the code owner set becomes empty.
codeOwnerConfigOperations
@@ -713,9 +709,7 @@
}
private CodeOwnerConfig createCodeOwnerConfig(String... emails) {
- return createCodeOwnerConfig(
- /** ignoreParentCodeOwners = */
- false, emails);
+ return createCodeOwnerConfig(/* ignoreParentCodeOwners= */ false, emails);
}
private CodeOwnerConfig createCodeOwnerConfig(boolean ignoreParentCodeOwners, String... emails) {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
index 563ed57..da58fca 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
@@ -70,10 +70,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwnerApprovalCheck.getFileStatuses(
- /** changeNotes = */
- null));
+ () -> codeOwnerApprovalCheck.getFileStatuses(/* changeNotes= */ null));
assertThat(npe).hasMessageThat().isEqualTo("changeNotes");
}
@@ -601,16 +598,14 @@
@Test
public void getStatusForFileAddition_noImplicitApprovalByPatchSetUploader() throws Exception {
testImplicitApprovalByPatchSetUploaderOnGetStatusForFileAddition(
- /** implicitApprovalsEnabled = */
- false);
+ /* implicitApprovalsEnabled= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void getStatusForFileAddition_withImplicitApprovalByPatchSetUploader() throws Exception {
testImplicitApprovalByPatchSetUploaderOnGetStatusForFileAddition(
- /** implicitApprovalsEnabled = */
- true);
+ /* implicitApprovalsEnabled= */ true);
}
private void testImplicitApprovalByPatchSetUploaderOnGetStatusForFileAddition(
@@ -649,8 +644,7 @@
@Test
public void getStatusForFileModification_noImplicitApprovalByPatchSetUploader() throws Exception {
testImplicitApprovalByPatchSetUploaderOnGetStatusForFileModification(
- /** implicitApprovalsEnabled = */
- false);
+ /* implicitApprovalsEnabled= */ false);
}
@Test
@@ -658,8 +652,7 @@
public void getStatusForFileModification_withImplicitApprovalByPatchSetUploader()
throws Exception {
testImplicitApprovalByPatchSetUploaderOnGetStatusForFileModification(
- /** implicitApprovalsEnabled = */
- true);
+ /* implicitApprovalsEnabled= */ true);
}
private void testImplicitApprovalByPatchSetUploaderOnGetStatusForFileModification(
@@ -700,16 +693,14 @@
@Test
public void getStatusForFileDeletion_noImplicitApprovalByPatchSetUploader() throws Exception {
testImplicitApprovalByPatchSetUploaderOnGetStatusForFileDeletion(
- /** implicitApprovalsEnabled = */
- false);
+ /* implicitApprovalsEnabled= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void getStatusForFileDeletion_withImplicitApprovalByPatchSetUploader() throws Exception {
testImplicitApprovalByPatchSetUploaderOnGetStatusForFileDeletion(
- /** implicitApprovalsEnabled = */
- true);
+ /* implicitApprovalsEnabled= */ true);
}
private void testImplicitApprovalByPatchSetUploaderOnGetStatusForFileDeletion(
@@ -748,8 +739,7 @@
public void getStatusForFileRename_noImplicitApprovalByPatchSetUploaderOnOldPath()
throws Exception {
testImplicitApprovalByPatchSetUploaderOnStatusForFileRenameOnOldPath(
- /** implicitApprovalsEnabled = */
- false);
+ /* implicitApprovalsEnabled= */ false);
}
@Test
@@ -757,8 +747,7 @@
public void getStatusForFileRename_withImplicitApprovalByPatchSetUploaderOnOldPath()
throws Exception {
testImplicitApprovalByPatchSetUploaderOnStatusForFileRenameOnOldPath(
- /** implicitApprovalsEnabled = */
- true);
+ /* implicitApprovalsEnabled= */ true);
}
private void testImplicitApprovalByPatchSetUploaderOnStatusForFileRenameOnOldPath(
@@ -803,8 +792,7 @@
public void getStatusForFileRename_noImplicitApprovalByPatchSetUploaderOnNewPath()
throws Exception {
testImplicitApprovalByPatchSetUploaderOnStatusForFileRenameOnNewPath(
- /** implicitApprovalsEnabled = */
- false);
+ /* implicitApprovalsEnabled= */ false);
}
@Test
@@ -812,8 +800,7 @@
public void getStatusForFileRename_withImplicitApprovalByPatchSetUploaderOnNewPath()
throws Exception {
testImplicitApprovalByPatchSetUploaderOnStatusForFileRenameOnNewPath(
- /** implicitApprovalsEnabled = */
- true);
+ /* implicitApprovalsEnabled= */ true);
}
private void testImplicitApprovalByPatchSetUploaderOnStatusForFileRenameOnNewPath(
@@ -967,17 +954,13 @@
@Test
public void everyoneIsCodeOwner_noImplicitApproval() throws Exception {
- testImplicitlyApprovedWhenEveryoneIsCodeOwner(
- /** implicitApprovalsEnabled = */
- false);
+ testImplicitlyApprovedWhenEveryoneIsCodeOwner(/* implicitApprovalsEnabled= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void everyoneIsCodeOwner_withImplicitApproval() throws Exception {
- testImplicitlyApprovedWhenEveryoneIsCodeOwner(
- /** implicitApprovalsEnabled = */
- true);
+ testImplicitlyApprovedWhenEveryoneIsCodeOwner(/* implicitApprovalsEnabled= */ true);
}
private void testImplicitlyApprovedWhenEveryoneIsCodeOwner(boolean implicitApprovalsEnabled)
@@ -1057,28 +1040,19 @@
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
public void approvedByGlobalCodeOwner() throws Exception {
- testApprovedByGlobalCodeOwner(
- /** bootstrappingMode = */
- false);
+ testApprovedByGlobalCodeOwner(/* bootstrappingMode= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
public void approvedByGlobalCodeOwner_bootstrappingMode() throws Exception {
- testApprovedByGlobalCodeOwner(
- /** bootstrappingMode = */
- true);
+ testApprovedByGlobalCodeOwner(/* bootstrappingMode= */ true);
}
private void testApprovedByGlobalCodeOwner(boolean bootstrappingMode) throws Exception {
// Create a bot user that is a global code owner.
TestAccount bot =
- accountCreator.create(
- "bot",
- "bot@example.com",
- "Bot",
- /** displayName = */
- null);
+ accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
if (!bootstrappingMode) {
// Create a code owner config file so that we are not in the bootstrapping mode.
@@ -1134,10 +1108,7 @@
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
public void globalCodeOwner_noImplicitApproval() throws Exception {
testImplicitlyApprovedByGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- false,
- /** bootstrappingMode = */
- false);
+ /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ false);
}
@Test
@@ -1145,20 +1116,14 @@
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void globalCodeOwner_withImplicitApproval() throws Exception {
testImplicitlyApprovedByGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- true,
- /** bootstrappingMode = */
- false);
+ /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
public void globalCodeOwner_noImplicitApproval_bootstrappingMode() throws Exception {
testImplicitlyApprovedByGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- false,
- /** bootstrappingMode = */
- true);
+ /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ true);
}
@Test
@@ -1166,21 +1131,13 @@
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void globalCodeOwner_withImplicitApproval_bootstrappingMode() throws Exception {
testImplicitlyApprovedByGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- true,
- /** bootstrappingMode = */
- true);
+ /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ true);
}
private void testImplicitlyApprovedByGlobalCodeOwner(
boolean implicitApprovalsEnabled, boolean bootstrappingMode) throws Exception {
TestAccount bot =
- accountCreator.create(
- "bot",
- "bot@example.com",
- "Bot",
- /** displayName = */
- null);
+ accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
if (!bootstrappingMode) {
codeOwnerConfigOperations
@@ -1215,28 +1172,19 @@
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
public void globalCodeOwnerAsReviewer() throws Exception {
- testGlobalCodeOwnerAsReviewer(
- /** bootstrappingMode = */
- false);
+ testGlobalCodeOwnerAsReviewer(/* bootstrappingMode= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
public void globalCodeOwnerAsReviewer_bootstrappingMode() throws Exception {
- testGlobalCodeOwnerAsReviewer(
- /** bootstrappingMode = */
- true);
+ testGlobalCodeOwnerAsReviewer(/* bootstrappingMode= */ true);
}
private void testGlobalCodeOwnerAsReviewer(boolean bootstrappingMode) throws Exception {
// Create a bot user that is a global code owner.
TestAccount bot =
- accountCreator.create(
- "bot",
- "bot@example.com",
- "Bot",
- /** displayName = */
- null);
+ accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
if (!bootstrappingMode) {
// Create a code owner config file so that we are not in the bootstrapping mode.
@@ -1305,17 +1253,13 @@
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
public void approvedByAnyoneWhenEveryoneIsGlobalCodeOwner() throws Exception {
- testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(
- /** bootstrappingMode = */
- false);
+ testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
public void approvedByAnyoneWhenEveryoneIsGlobalCodeOwner_bootstrappingMode() throws Exception {
- testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(
- /** bootstrappingMode = */
- true);
+ testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ true);
}
private void testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(boolean bootstrappingMode)
@@ -1369,10 +1313,7 @@
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
public void everyoneIsGlobalCodeOwner_noImplicitApproval() throws Exception {
testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- false,
- /** bootstrappingMode = */
- false);
+ /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ false);
}
@Test
@@ -1380,20 +1321,14 @@
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void everyoneIsGlobalCodeOwner_withImplicitApproval() throws Exception {
testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- true,
- /** bootstrappingMode = */
- false);
+ /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
public void everyoneIsGlobalCodeOwner_noImplicitApproval_bootstrappingMode() throws Exception {
testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- false,
- /** bootstrappingMode = */
- true);
+ /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ true);
}
@Test
@@ -1401,10 +1336,7 @@
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void everyoneIsGlobalCodeOwner_withImplicitApproval_bootstrappingMode() throws Exception {
testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
- /** implicitApprovalsEnabled = */
- true,
- /** bootstrappingMode = */
- true);
+ /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ true);
}
private void testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
@@ -1442,17 +1374,13 @@
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
public void anyReviewerWhenEveryoneIsGlobalCodeOwner() throws Exception {
- testAnyReviewerWhenEveryoneIsGlobalCodeOwner(
- /** bootstrappingMode = */
- false);
+ testAnyReviewerWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
public void anyReviewerWhenEveryoneIsGlobalCodeOwner_bootstrappingMode() throws Exception {
- testAnyReviewerWhenEveryoneIsGlobalCodeOwner(
- /** bootstrappingMode = */
- true);
+ testAnyReviewerWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ true);
}
private void testAnyReviewerWhenEveryoneIsGlobalCodeOwner(boolean bootstrappingMode)
@@ -1506,12 +1434,7 @@
public void parentCodeOwnerConfigsAreConsidered() throws Exception {
TestAccount user2 = accountCreator.user2();
TestAccount user3 =
- accountCreator.create(
- "user3",
- "user3@example.com",
- "User3",
- /** displayName = */
- null);
+ accountCreator.create("user3", "user3@example.com", "User3", /* displayName= */ null);
codeOwnerConfigOperations
.newCodeOwnerConfig()
@@ -1617,10 +1540,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwnerApprovalCheck.isSubmittable(
- /** changeNotes = */
- null));
+ () -> codeOwnerApprovalCheck.isSubmittable(/* changeNotes= */ null));
assertThat(npe).hasMessageThat().isEqualTo("changeNotes");
}
@@ -1710,12 +1630,7 @@
TestAccount user2 = accountCreator.user2();
TestAccount user3 =
- accountCreator.create(
- "user3",
- "user3@example.com",
- "User3",
- /** displayName = */
- null);
+ accountCreator.create("user3", "user3@example.com", "User3", /* displayName= */ null);
// Create change with a user that is not a project owner.
Path path = Paths.get("/foo/bar.baz");
@@ -1813,16 +1728,14 @@
@Test
public void bootstrappingGetStatus_noImplicitApprovalByPatchSetUploader() throws Exception {
testImplicitApprovalByPatchSetUploaderOnBootstrappingGetStatus(
- /** implicitApprovalsEnabled = */
- false);
+ /* implicitApprovalsEnabled= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void bootstrappingGetStatus_withImplicitApprovalByPatchSetUploader() throws Exception {
testImplicitApprovalByPatchSetUploaderOnBootstrappingGetStatus(
- /** implicitApprovalsEnabled = */
- true);
+ /* implicitApprovalsEnabled= */ true);
}
private void testImplicitApprovalByPatchSetUploaderOnBootstrappingGetStatus(
@@ -2206,17 +2119,13 @@
@Test
public void defaultCodeOwner_noImplicitApproval() throws Exception {
- testImplicitlyApprovedByDefaultCodeOwner(
- /** implicitApprovalsEnabled = */
- false);
+ testImplicitlyApprovedByDefaultCodeOwner(/* implicitApprovalsEnabled= */ false);
}
@Test
@GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
public void defaultCodeOwner_withImplicitApproval() throws Exception {
- testImplicitlyApprovedByDefaultCodeOwner(
- /** implicitApprovalsEnabled = */
- true);
+ testImplicitlyApprovedByDefaultCodeOwner(/* implicitApprovalsEnabled= */ true);
}
private void testImplicitlyApprovedByDefaultCodeOwner(boolean implicitApprovalsEnabled)
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScannerTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScannerTest.java
index 16ec682..839c77b 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScannerTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigFileUpdateScannerTest.java
@@ -71,8 +71,7 @@
NullPointerException.class,
() ->
codeOwnerConfigFileUpdateScanner.update(
- /** branchNameKey = */
- null,
+ /* branchNameKey= */ null,
"Update code owner configs",
(codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> Optional.empty()));
assertThat(npe).hasMessageThat().isEqualTo("branchNameKey");
@@ -87,8 +86,7 @@
() ->
codeOwnerConfigFileUpdateScanner.update(
branchNameKey,
- /** commitMessage = */
- null,
+ /* commitMessage= */ null,
(codeOwnerConfigFilePath, codeOwnerConfigFileContent) -> Optional.empty()));
assertThat(npe).hasMessageThat().isEqualTo("commitMessage");
}
@@ -103,8 +101,7 @@
codeOwnerConfigFileUpdateScanner.update(
branchNameKey,
"Update code owner configs",
- /** codeOwnerConfigFileUpdater = */
- null));
+ /* codeOwnerConfigFileUpdater= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigFileUpdater");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java
index 18ba78f..2b30264 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java
@@ -75,8 +75,7 @@
NullPointerException.class,
() ->
codeOwnerConfigHierarchy.visit(
- /** branchNameKey = */
- null,
+ /* branchNameKey= */ null,
getCurrentRevision(BranchNameKey.create(project, "master")),
Paths.get("/foo/bar/baz.md"),
visitor));
@@ -91,8 +90,7 @@
() ->
codeOwnerConfigHierarchy.visit(
BranchNameKey.create(project, "master"),
- /** revision = */
- null,
+ /* revision= */ null,
Paths.get("/foo/bar/baz.md"),
visitor));
assertThat(npe).hasMessageThat().isEqualTo("revision");
@@ -108,8 +106,7 @@
codeOwnerConfigHierarchy.visit(
branchNameKey,
getCurrentRevision(branchNameKey),
- /** absolutePath = */
- null,
+ /* absolutePath= */ null,
visitor));
assertThat(npe).hasMessageThat().isEqualTo("absolutePath");
}
@@ -143,8 +140,7 @@
branchNameKey,
getCurrentRevision(branchNameKey),
Paths.get("/foo/bar/baz.md"),
- /** codeOwnerConfigVisitor = */
- null));
+ /* codeOwnerConfigVisitor= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigVisitor");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java
index 4a9faa7..c26f000 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java
@@ -70,9 +70,7 @@
() ->
codeOwnerConfigScannerFactory
.create()
- .visit(
- /** branchNameKey = */
- null, visitor, invalidCodeOwnerConfigCallback));
+ .visit(/* branchNameKey= */ null, visitor, invalidCodeOwnerConfigCallback));
assertThat(npe).hasMessageThat().isEqualTo("branchNameKey");
}
@@ -87,8 +85,7 @@
.create()
.visit(
branchNameKey,
- /** codeOwnerConfigVisitor = */
- null,
+ /* codeOwnerConfigVisitor= */ null,
invalidCodeOwnerConfigCallback));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigVisitor");
}
@@ -102,11 +99,7 @@
() ->
codeOwnerConfigScannerFactory
.create()
- .visit(
- branchNameKey,
- visitor,
- /** invalidCodeOwnerConfigCallback = */
- null));
+ .visit(branchNameKey, visitor, /* invalidCodeOwnerConfigCallback= */ null));
assertThat(npe).hasMessageThat().isEqualTo("invalidCodeOwnerConfigCallback");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigTest.java
index 433ff34..e3e9e6a 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigTest.java
@@ -109,10 +109,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwnerConfigKey.filePath(
- /** defaultCodeOwnerConfigFileName = */
- null));
+ () -> codeOwnerConfigKey.filePath(/* defaultCodeOwnerConfigFileName= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigFileName");
}
@@ -121,11 +118,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- createCodeOwnerBuilder()
- .addCodeOwnerSet(
- /** codeOwnerSet = */
- null));
+ () -> createCodeOwnerBuilder().addCodeOwnerSet(/* codeOwnerSet= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerSet");
}
@@ -134,11 +127,7 @@
CodeOwnerConfig codeOwnerConfig = createCodeOwnerBuilder().build();
NullPointerException npe =
assertThrows(
- NullPointerException.class,
- () ->
- codeOwnerConfig.relativize(
- /** path = */
- null));
+ NullPointerException.class, () -> codeOwnerConfig.relativize(/* path= */ null));
assertThat(npe).hasMessageThat().isEqualTo("path");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
index dc4646d..6e19e28 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
@@ -67,9 +67,7 @@
() ->
codeOwnerResolver
.get()
- .resolve(
- /** codeOwnerReference = */
- (CodeOwnerReference) null));
+ .resolve(/* codeOwnerReference= */ (CodeOwnerReference) null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerReference");
npe =
@@ -78,9 +76,7 @@
() ->
codeOwnerResolver
.get()
- .resolve(
- /** codeOwnerReferences = */
- (Set<CodeOwnerReference>) null));
+ .resolve(/* codeOwnerReferences= */ (Set<CodeOwnerReference>) null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerReferences");
}
@@ -118,12 +114,7 @@
(a, u) ->
u.addExternalId(
ExternalId.create(
- "foo",
- "bar",
- user.id(),
- admin.email(),
- /** hashedPassword = */
- null)));
+ "foo", "bar", user.id(), admin.email(), /* hashedPassword= */ null)));
assertThat(codeOwnerResolver.get().resolve(CodeOwnerReference.create(admin.email()))).isEmpty();
}
@@ -252,9 +243,7 @@
() ->
codeOwnerResolver
.get()
- .resolvePathCodeOwners(
- /** codeOwnerConfig = */
- null, Paths.get("/README.md")));
+ .resolvePathCodeOwners(/* codeOwnerConfig= */ null, Paths.get("/README.md")));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfig");
}
@@ -270,10 +259,7 @@
() ->
codeOwnerResolver
.get()
- .resolvePathCodeOwners(
- codeOwnerConfig,
- /** absolutePath = */
- null));
+ .resolvePathCodeOwners(codeOwnerConfig, /* absolutePath= */ null));
assertThat(npe).hasMessageThat().isEqualTo("absolutePath");
}
@@ -354,12 +340,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwnerResolver
- .get()
- .isEmailDomainAllowed(
- /** email = */
- null));
+ () -> codeOwnerResolver.get().isEmailDomainAllowed(/* email= */ null));
assertThat(npe).hasMessageThat().isEqualTo("email");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerScoringTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerScoringTest.java
index 3deb240..0cab290 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerScoringTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerScoringTest.java
@@ -32,10 +32,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- builder.putValueForCodeOwner(
- /** codeOwner = */
- null, 50));
+ () -> builder.putValueForCodeOwner(/* codeOwner= */ null, 50));
assertThat(npe).hasMessageThat().isEqualTo("codeOwner");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSetTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSetTest.java
index fccd87c..3b6304f 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSetTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSetTest.java
@@ -45,11 +45,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- CodeOwnerSet.builder()
- .addCodeOwner(
- /** codeOwnerReference = */
- null));
+ () -> CodeOwnerSet.builder().addCodeOwner(/* codeOwnerReference= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerReference");
}
@@ -83,11 +79,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- CodeOwnerSet.builder()
- .addCodeOwnerEmail(
- /** email = */
- null));
+ () -> CodeOwnerSet.builder().addCodeOwnerEmail(/* email= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerEmail");
}
@@ -121,11 +113,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- CodeOwnerSet.builder()
- .addPathExpression(
- /** pathExpression = */
- null));
+ () -> CodeOwnerSet.builder().addPathExpression(/* pathExpression= */ null));
assertThat(npe).hasMessageThat().isEqualTo("pathExpression");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
index 25705d9..8e03935 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
@@ -115,11 +115,7 @@
@Test
public void ruleErrorWhenChangeDataIsNull() throws Exception {
SubmitRecordSubject submitRecordSubject =
- assertThatOptional(
- codeOwnerSubmitRule.evaluate(
- /** changeData = */
- null))
- .value();
+ assertThatOptional(codeOwnerSubmitRule.evaluate(/* changeData= */ null)).value();
submitRecordSubject.hasStatusThat().isRuleError();
submitRecordSubject.hasErrorMessageThat().isEqualTo("Failed to evaluate code owner statuses.");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersTest.java
index 78835dc..cb065a0 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersTest.java
@@ -53,10 +53,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwners.get(
- /** codeOwnerConfigKey = */
- null, TEST_REVISION));
+ () -> codeOwners.get(/* codeOwnerConfigKey= */ null, TEST_REVISION));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigKey");
}
@@ -67,9 +64,7 @@
NullPointerException.class,
() ->
codeOwners.get(
- CodeOwnerConfig.Key.create(project, "master", "/"),
- /** folderPath = */
- null));
+ CodeOwnerConfig.Key.create(project, "master", "/"), /* folderPath= */ null));
assertThat(npe).hasMessageThat().isEqualTo("revision");
}
@@ -79,10 +74,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwners.getFromCurrentRevision(
- /** codeOwnerConfigKey = */
- null));
+ () -> codeOwners.getFromCurrentRevision(/* codeOwnerConfigKey= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigKey");
}
@@ -95,20 +87,13 @@
.addCodeOwnerSet(CodeOwnerSet.createWithoutPathExpressions(admin.email()))
.build();
CodeOwnerBackend codeOwnerBackendMock = mock(CodeOwnerBackend.class);
- when(codeOwnerBackendMock.getCodeOwnerConfig(
- codeOwnerConfigKey,
- /** revision = */
- null))
+ when(codeOwnerBackendMock.getCodeOwnerConfig(codeOwnerConfigKey, /* revision= */ null))
.thenReturn(Optional.of(expectedCodeOwnersConfig));
try (AutoCloseable registration = registerTestBackend("test-backend", codeOwnerBackendMock)) {
Optional<CodeOwnerConfig> codeOwnerConfig =
codeOwners.getFromCurrentRevision(codeOwnerConfigKey);
assertThat(codeOwnerConfig).value().isEqualTo(expectedCodeOwnersConfig);
- verify(codeOwnerBackendMock)
- .getCodeOwnerConfig(
- codeOwnerConfigKey,
- /** revision = */
- null);
+ verify(codeOwnerBackendMock).getCodeOwnerConfig(codeOwnerConfigKey, /* revision= */ null);
}
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersUpdateTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersUpdateTest.java
index 813f44f..ec7ec15 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersUpdateTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersUpdateTest.java
@@ -66,9 +66,7 @@
@GerritConfig(name = "plugin.code-owners.backend", value = "test-backend")
public void codeOwnerUpdateIsForwardedToConfiguredBackendServerInitiated() throws Exception {
testCodeOwnerUpdateIsForwardedToConfiguredBackend(
- serverInitiatedCodeOwnersUpdate.get(),
- /** currentUser = */
- null);
+ serverInitiatedCodeOwnersUpdate.get(), /* currentUser= */ null);
}
private void testCodeOwnerUpdateIsForwardedToConfiguredBackend(
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java
index b92fc0c..9fbc92c 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java
@@ -16,10 +16,12 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigSubject.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.joining;
+import com.google.common.base.Joiner;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.plugins.codeowners.backend.AbstractCodeOwnerConfigParserTest;
@@ -34,6 +36,7 @@
import com.google.gerrit.plugins.codeowners.testing.CodeOwnerSetSubject;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.regex.Pattern;
import org.junit.Test;
/** Tests for {@link FindOwnersCodeOwnerConfigParser}. */
@@ -603,4 +606,125 @@
.isEqualTo(CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY);
});
}
+
+ @Test
+ public void replaceEmail_contentCannotBeNull() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ FindOwnersCodeOwnerConfigParser.replaceEmail(
+ /* content= */ null, admin.email(), user.email()));
+ assertThat(npe).hasMessageThat().isEqualTo("codeOwnerConfigFileContent");
+ }
+
+ @Test
+ public void replaceEmail_oldEmailCannotBeNull() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ FindOwnersCodeOwnerConfigParser.replaceEmail(
+ "content", /* oldEmail= */ null, user.email()));
+ assertThat(npe).hasMessageThat().isEqualTo("oldEmail");
+ }
+
+ @Test
+ public void replaceEmail_newEmailCannotBeNull() throws Exception {
+ NullPointerException npe =
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ FindOwnersCodeOwnerConfigParser.replaceEmail(
+ "content", admin.email(), /* newEmail= */ null));
+ assertThat(npe).hasMessageThat().isEqualTo("newEmail");
+ }
+
+ @Test
+ public void replaceEmail() throws Exception {
+ String oldEmail = "old@example.com";
+ String newEmail = "new@example.com";
+
+ // In the following test lines '${email} is used as placefolder for the email that we expect to
+ // be replaced.
+ String[] testLines = {
+ // line = email
+ "${email}",
+ // line = email with leading whitespace
+ " ${email}",
+ // line = email with trailing whitespace
+ "${email} ",
+ // line = email with leading and trailing whitespace
+ " ${email} ",
+ // line = email with comment
+ "${email}# comment",
+ // line = email with trailing whitespace and comment
+ "${email} # comment",
+ // line = email with leading whitespace and comment
+ " ${email}# comment",
+ // line = email with leading and trailing whitespace and comment
+ " ${email} # comment",
+ // line = email that ends with oldEmail
+ "foo" + oldEmail,
+ // line = email that starts with oldEmail
+ oldEmail + "bar",
+ // line = email that contains oldEmail
+ "foo" + oldEmail + "bar",
+ // line = email that contains oldEmail with leading and trailing whitespace
+ " foo" + oldEmail + "bar ",
+ // line = email that contains oldEmail with leading and trailing whitespace and comment
+ " foo" + oldEmail + "bar # comment",
+ // email in comment
+ "foo@example.com # ${email}",
+ // email in comment that contains oldEmail
+ "foo@example.com # foo" + oldEmail + "bar",
+ // per-file line with email
+ "per-file *.md=${email}",
+ // per-file line with email and whitespace
+ "per-file *.md = ${email} ",
+ // per-file line with multiple emails and old email at first position
+ "per-file *.md=${email},foo@example.com,bar@example.com",
+ // per-file line with multiple emails and old email at middle position
+ "per-file *.md=foo@example.com,${email},bar@example.com",
+ // per-file line with multiple emails and old email at last position
+ "per-file *.md=foo@example.com,bar@example.com,${email}",
+ // per-file line with multiple emails and old email at last position and comment
+ "per-file *.md=foo@example.com,bar@example.com,${email}# comment",
+ // per-file line with multiple emails and old email at last position and comment with
+ // whitespace
+ "per-file *.md = foo@example.com, bar@example.com , ${email} # comment",
+ // per-file line with multiple emails and old email appearing multiple times
+ "per-file *.md=${email},${email}",
+ "per-file *.md=${email},${email},${email}",
+ "per-file *.md=${email},foo@example.com,${email},bar@example.com,${email}",
+ // per-file line with multiple emails and old email appearing multiple times and comment
+ "per-file *.md=${email},foo@example.com,${email},bar@example.com,${email}# comment",
+ // per-file line with multiple emails and old email appearing multiple times and comment and
+ // whitespace
+ "per-file *.md= ${email} , foo@example.com , ${email} , bar@example.com , ${email} # comment",
+ // per-file line with email that contains old email
+ "per-file *.md=for" + oldEmail + "bar",
+ // per-file line with multiple emails and one email that contains the old email
+ "per-file *.md=for" + oldEmail + "bar,${email}",
+ };
+
+ for (String testLine : testLines) {
+ String content = testLine.replaceAll(Pattern.quote("${email}"), oldEmail);
+ String expectedContent = testLine.replaceAll(Pattern.quote("${email}"), newEmail);
+ assertWithMessage(testLine)
+ .that(FindOwnersCodeOwnerConfigParser.replaceEmail(content, oldEmail, newEmail))
+ .isEqualTo(expectedContent);
+ }
+
+ // join all test lines and replace email in all of them at once
+ String testContent = Joiner.on("\n").join(testLines);
+ String content = testContent.replaceAll(Pattern.quote("${email}"), oldEmail);
+ String expectedContent = testContent.replaceAll(Pattern.quote("${email}"), newEmail);
+ assertThat(FindOwnersCodeOwnerConfigParser.replaceEmail(content, oldEmail, newEmail))
+ .isEqualTo(expectedContent);
+
+ // test that trailing new line is preserved
+ assertThat(FindOwnersCodeOwnerConfigParser.replaceEmail(content + "\n", oldEmail, newEmail))
+ .isEqualTo(expectedContent + "\n");
+ }
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java
index 7ab8924..42a32dd 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java
@@ -16,16 +16,16 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS;
+import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import static com.google.gerrit.truth.OptionalSubject.assertThat;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.ValidationMessage;
import com.google.gerrit.server.project.ProjectLevelConfig;
import com.google.gerrit.server.project.ProjectState;
-import java.util.Optional;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;
@@ -34,78 +34,76 @@
/** Must return the {@link AbstractRequiredApprovalConfig} that should be tested. */
protected abstract AbstractRequiredApprovalConfig getRequiredApprovalConfig();
- protected void testGetFromGlobalPluginConfig() throws Exception {
- ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
- Optional<RequiredApproval> requiredApproval =
- getRequiredApprovalConfig().getFromGlobalPluginConfig(projectState);
- assertThat(requiredApproval).isPresent();
- assertThat(requiredApproval.get().labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.get().value()).isEqualTo(2);
- }
-
- protected void testCannotGetFromGlobalPluginConfigIfConfigIsInvalid() throws Exception {
+ protected void testCannotGetIfGlobalConfigIsInvalid(String invalidValue) throws Exception {
ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
InvalidPluginConfigurationException exception =
assertThrows(
InvalidPluginConfigurationException.class,
- () -> getRequiredApprovalConfig().getFromGlobalPluginConfig(projectState));
+ () -> getRequiredApprovalConfig().get(projectState, new Config()));
assertThat(exception)
.hasMessageThat()
.isEqualTo(
String.format(
- "Invalid configuration of the code-owners plugin. Required approval 'INVALID' that is"
+ "Invalid configuration of the code-owners plugin. Required approval '%s' that is"
+ " configured in gerrit.config (parameter plugin.code-owners.%s) is"
+ " invalid: Invalid format, expected '<label-name>+<label-value>'.",
- getRequiredApprovalConfig().getConfigKey()));
+ invalidValue, getRequiredApprovalConfig().getConfigKey()));
}
@Test
- public void cannotGetForProjectForNullProjectState() throws Exception {
+ public void cannotGetForNullProjectState() throws Exception {
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () -> getRequiredApprovalConfig().getForProject(null, new Config()));
+ () -> getRequiredApprovalConfig().get(/* projectState= */ null, new Config()));
assertThat(npe).hasMessageThat().isEqualTo("projectState");
}
@Test
- public void cannotGetForProjectForNullConfig() throws Exception {
+ public void cannotGetForNullConfig() throws Exception {
ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () -> getRequiredApprovalConfig().getForProject(projectState, null));
+ () -> getRequiredApprovalConfig().get(projectState, /* pluginConfig= */ null));
assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
}
@Test
- public void getForProjectWhenRequiredApprovalIsNotSet() throws Exception {
+ public void getWhenRequiredApprovalIsNotSet() throws Exception {
ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
- assertThat(getRequiredApprovalConfig().getForProject(projectState, new Config())).isEmpty();
+ assertThat(getRequiredApprovalConfig().get(projectState, new Config())).isEmpty();
}
@Test
- public void getForProject() throws Exception {
+ public void getFromPluginConfig() throws Exception {
ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
Config cfg = new Config();
cfg.setString(
- SECTION_CODE_OWNERS, null, getRequiredApprovalConfig().getConfigKey(), "Code-Review+2");
- Optional<RequiredApproval> requiredApproval =
- getRequiredApprovalConfig().getForProject(projectState, cfg);
- assertThat(requiredApproval).isPresent();
- assertThat(requiredApproval.get().labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.get().value()).isEqualTo(2);
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ getRequiredApprovalConfig().getConfigKey(),
+ "Code-Review+2");
+ ImmutableList<RequiredApproval> requiredApproval =
+ getRequiredApprovalConfig().get(projectState, cfg);
+ assertThat(requiredApproval).hasSize(1);
+ assertThat(requiredApproval).element(0).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).element(0).hasValueThat().isEqualTo(2);
}
@Test
- public void cannotGetForProjectIfConfigIsInvalid() throws Exception {
+ public void cannotGetFromPluginConfigIfConfigIsInvalid() throws Exception {
ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
Config cfg = new Config();
- cfg.setString(SECTION_CODE_OWNERS, null, getRequiredApprovalConfig().getConfigKey(), "INVALID");
+ cfg.setString(
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ getRequiredApprovalConfig().getConfigKey(),
+ "INVALID");
InvalidPluginConfigurationException exception =
assertThrows(
InvalidPluginConfigurationException.class,
- () -> getRequiredApprovalConfig().getForProject(projectState, cfg));
+ () -> getRequiredApprovalConfig().get(projectState, cfg));
assertThat(exception)
.hasMessageThat()
.isEqualTo(
@@ -117,21 +115,6 @@
}
@Test
- public void cannotGetFromGlobalPluginConfigForNullProjectState() throws Exception {
- NullPointerException npe =
- assertThrows(
- NullPointerException.class,
- () -> getRequiredApprovalConfig().getFromGlobalPluginConfig(null));
- assertThat(npe).hasMessageThat().isEqualTo("projectState");
- }
-
- @Test
- public void getFromGlobalPluginConfigWhenRequiredApprovalIsNotSet() throws Exception {
- ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
- assertThat(getRequiredApprovalConfig().getFromGlobalPluginConfig(projectState)).isEmpty();
- }
-
- @Test
public void cannotValidateProjectLevelConfigWithNullProjectState() throws Exception {
NullPointerException npe =
assertThrows(
@@ -139,7 +122,7 @@
() ->
getRequiredApprovalConfig()
.validateProjectLevelConfig(
- null,
+ /* projectState= */ null,
"code-owners.config",
new ProjectLevelConfig.Bare("code-owners.config")));
assertThat(npe).hasMessageThat().isEqualTo("projectState");
@@ -154,7 +137,9 @@
() ->
getRequiredApprovalConfig()
.validateProjectLevelConfig(
- projectState, null, new ProjectLevelConfig.Bare("code-owners.config")));
+ projectState,
+ /* fileName= */ null,
+ new ProjectLevelConfig.Bare("code-owners.config")));
assertThat(npe).hasMessageThat().isEqualTo("fileName");
}
@@ -166,14 +151,15 @@
NullPointerException.class,
() ->
getRequiredApprovalConfig()
- .validateProjectLevelConfig(projectState, "code-owners.config", null));
+ .validateProjectLevelConfig(
+ projectState, "code-owners.config", /* projectLevelConfig= */ null));
assertThat(npe).hasMessageThat().isEqualTo("projectLevelConfig");
}
@Test
public void validateEmptyProjectLevelConfig() throws Exception {
ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
- Optional<CommitValidationMessage> commitValidationMessage =
+ ImmutableList<CommitValidationMessage> commitValidationMessage =
getRequiredApprovalConfig()
.validateProjectLevelConfig(
projectState,
@@ -188,8 +174,11 @@
ProjectLevelConfig.Bare cfg = new ProjectLevelConfig.Bare("code-owners.config");
cfg.getConfig()
.setString(
- SECTION_CODE_OWNERS, null, getRequiredApprovalConfig().getConfigKey(), "Code-Review+2");
- Optional<CommitValidationMessage> commitValidationMessage =
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ getRequiredApprovalConfig().getConfigKey(),
+ "Code-Review+2");
+ ImmutableList<CommitValidationMessage> commitValidationMessage =
getRequiredApprovalConfig()
.validateProjectLevelConfig(projectState, "code-owners.config", cfg);
assertThat(commitValidationMessage).isEmpty();
@@ -201,13 +190,16 @@
ProjectLevelConfig.Bare cfg = new ProjectLevelConfig.Bare("code-owners.config");
cfg.getConfig()
.setString(
- SECTION_CODE_OWNERS, null, getRequiredApprovalConfig().getConfigKey(), "INVALID");
- Optional<CommitValidationMessage> commitValidationMessage =
+ SECTION_CODE_OWNERS,
+ /* subsection= */ null,
+ getRequiredApprovalConfig().getConfigKey(),
+ "INVALID");
+ ImmutableList<CommitValidationMessage> commitValidationMessage =
getRequiredApprovalConfig()
.validateProjectLevelConfig(projectState, "code-owners.config", cfg);
- assertThat(commitValidationMessage).isPresent();
- assertThat(commitValidationMessage.get().getType()).isEqualTo(ValidationMessage.Type.ERROR);
- assertThat(commitValidationMessage.get().getMessage())
+ assertThat(commitValidationMessage).hasSize(1);
+ assertThat(commitValidationMessage.get(0).getType()).isEqualTo(ValidationMessage.Type.ERROR);
+ assertThat(commitValidationMessage.get(0).getMessage())
.isEqualTo(
String.format(
"Required approval 'INVALID' that is configured in code-owners.config (parameter"
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/BUILD b/javatests/com/google/gerrit/plugins/codeowners/config/BUILD
index 9f115b5..7f1985a 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/config/BUILD
+++ b/javatests/com/google/gerrit/plugins/codeowners/config/BUILD
@@ -15,6 +15,7 @@
":testbases",
"//plugins/code-owners:code-owners__plugin",
"//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/acceptance",
+ "//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/testing",
],
)
@@ -25,9 +26,11 @@
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/com/google/gerrit/truth",
+ "//lib:guava",
"//lib:jgit",
"//lib/truth",
"//plugins/code-owners:code-owners__plugin",
"//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/acceptance",
+ "//plugins/code-owners/java/com/google/gerrit/plugins/codeowners/testing",
],
)
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java
index 723836d..dab2b0d 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigurationTest.java
@@ -15,9 +15,11 @@
package com.google.gerrit.plugins.codeowners.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.Nullable;
@@ -66,10 +68,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwnersPluginConfiguration.isDisabled(
- /** project = */
- (Project.NameKey) null));
+ () -> codeOwnersPluginConfiguration.isDisabled(/* project= */ (Project.NameKey) null));
assertThat(npe).hasMessageThat().isEqualTo("project");
}
@@ -80,8 +79,7 @@
NullPointerException.class,
() ->
codeOwnersPluginConfiguration.isDisabled(
- /** branchNameKey = */
- (BranchNameKey) null));
+ /* branchNameKey= */ (BranchNameKey) null));
assertThat(npe).hasMessageThat().isEqualTo("branchNameKey");
}
@@ -464,17 +462,16 @@
@Test
public void getDefaultRequiredApprovalWhenNoRequiredApprovalIsConfigured() throws Exception {
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName())
- .isEqualTo(RequiredApprovalConfig.DEFAULT_LABEL);
- assertThat(requiredApproval.value()).isEqualTo(RequiredApprovalConfig.DEFAULT_VALUE);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo(RequiredApprovalConfig.DEFAULT_LABEL);
+ assertThat(requiredApproval).hasValueThat().isEqualTo(RequiredApprovalConfig.DEFAULT_VALUE);
}
@Test
@GerritConfig(name = "plugin.code-owners.requiredApproval", value = "Code-Review+2")
public void getConfiguredDefaultRequireApproval() throws Exception {
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(2);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(2);
}
@Test
@@ -534,8 +531,22 @@
public void getRequiredApprovalConfiguredOnProjectLevel() throws Exception {
configureRequiredApproval(project, "Code-Review+2");
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(2);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(2);
+ }
+
+ @Test
+ public void getRequiredApprovalMultipleConfiguredOnProjectLevel() throws Exception {
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
+ ImmutableList.of("Code-Review+2", "Code-Review+1"));
+
+ // If multiple values are set for a key, the last value wins.
+ RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(1);
}
@Test
@@ -544,16 +555,16 @@
throws Exception {
configureRequiredApproval(project, "Code-Review+2");
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(2);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(2);
}
@Test
public void requiredApprovalIsInheritedFromParentProject() throws Exception {
configureRequiredApproval(allProjects, "Code-Review+2");
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(2);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(2);
}
@Test
@@ -561,8 +572,8 @@
public void inheritedRequiredApprovalOverridesDefaultRequiredApproval() throws Exception {
configureRequiredApproval(allProjects, "Code-Review+2");
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(2);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(2);
}
@Test
@@ -570,8 +581,8 @@
configureRequiredApproval(allProjects, "Code-Review+1");
configureRequiredApproval(project, "Code-Review+2");
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(2);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(2);
}
@Test
@@ -635,8 +646,8 @@
Project.NameKey otherProject = projectOperations.newProject().create();
configureRequiredApproval(otherProject, "Code-Review+2");
RequiredApproval requiredApproval = codeOwnersPluginConfiguration.getRequiredApproval(project);
- assertThat(requiredApproval.labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.value()).isEqualTo(1);
+ assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).hasValueThat().isEqualTo(1);
}
@Test
@@ -664,8 +675,8 @@
Optional<RequiredApproval> requiredApproval =
codeOwnersPluginConfiguration.getOverrideApproval(project);
assertThat(requiredApproval).isPresent();
- assertThat(requiredApproval.get().labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.get().value()).isEqualTo(2);
+ assertThat(requiredApproval).value().hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).value().hasValueThat().isEqualTo(2);
}
@Test
@@ -674,8 +685,24 @@
Optional<RequiredApproval> requiredApproval =
codeOwnersPluginConfiguration.getOverrideApproval(project);
assertThat(requiredApproval).isPresent();
- assertThat(requiredApproval.get().labelType().getName()).isEqualTo("Code-Review");
- assertThat(requiredApproval.get().value()).isEqualTo(2);
+ assertThat(requiredApproval).value().hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).value().hasValueThat().isEqualTo(2);
+ }
+
+ @Test
+ public void getOverrideApprovalMultipleConfiguredOnProjectLevel() throws Exception {
+ setCodeOwnersConfig(
+ project,
+ /* subsection= */ null,
+ OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
+ ImmutableList.of("Code-Review+2", "Code-Review+1"));
+
+ // If multiple values are set for a key, the last value wins.
+ Optional<RequiredApproval> requiredApproval =
+ codeOwnersPluginConfiguration.getOverrideApproval(project);
+ assertThat(requiredApproval).isPresent();
+ assertThat(requiredApproval).value().hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).value().hasValueThat().isEqualTo(1);
}
@Test
@@ -725,10 +752,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwnersPluginConfiguration.getFileExtension(
- /** project = */
- null));
+ () -> codeOwnersPluginConfiguration.getFileExtension(/* project= */ null));
assertThat(npe).hasMessageThat().isEqualTo("project");
}
@@ -769,10 +793,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- codeOwnersPluginConfiguration.getMergeCommitStrategy(
- /** project = */
- null));
+ () -> codeOwnersPluginConfiguration.getMergeCommitStrategy(/* project= */ null));
assertThat(npe).hasMessageThat().isEqualTo("project");
}
@@ -821,39 +842,21 @@
}
private void configureDisabled(Project.NameKey project, String disabled) throws Exception {
- setCodeOwnersConfig(
- project,
- /** subsection = */
- null,
- StatusConfig.KEY_DISABLED,
- disabled);
+ setCodeOwnersConfig(project, /* subsection= */ null, StatusConfig.KEY_DISABLED, disabled);
}
private void configureDisabledBranch(Project.NameKey project, String disabledBranch)
throws Exception {
setCodeOwnersConfig(
- project,
- /** subsection = */
- null,
- StatusConfig.KEY_DISABLED_BRANCH,
- disabledBranch);
+ project, /* subsection= */ null, StatusConfig.KEY_DISABLED_BRANCH, disabledBranch);
}
private void enableCodeOwnersForAllBranches(Project.NameKey project) throws Exception {
- setCodeOwnersConfig(
- project,
- /** subsection = */
- null,
- StatusConfig.KEY_DISABLED_BRANCH,
- "");
+ setCodeOwnersConfig(project, /* subsection= */ null, StatusConfig.KEY_DISABLED_BRANCH, "");
}
private void configureBackend(Project.NameKey project, String backendName) throws Exception {
- configureBackend(
- project,
- /** branch = */
- null,
- backendName);
+ configureBackend(project, /* branch= */ null, backendName);
}
private void configureBackend(
@@ -865,8 +868,7 @@
throws Exception {
setCodeOwnersConfig(
project,
- /** subsection = */
- null,
+ /* subsection= */ null,
RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
requiredApproval);
}
@@ -875,8 +877,7 @@
throws Exception {
setCodeOwnersConfig(
project,
- /** subsection = */
- null,
+ /* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
requiredApproval);
}
@@ -884,19 +885,14 @@
private void configureFileExtension(Project.NameKey project, String fileExtension)
throws Exception {
setCodeOwnersConfig(
- project,
- /** subsection = */
- null,
- GeneralConfig.KEY_FILE_EXTENSION,
- fileExtension);
+ project, /* subsection= */ null, GeneralConfig.KEY_FILE_EXTENSION, fileExtension);
}
private void configureMergeCommitStrategy(
Project.NameKey project, MergeCommitStrategy mergeCommitStrategy) throws Exception {
setCodeOwnersConfig(
project,
- /** subsection = */
- null,
+ /* subsection= */ null,
GeneralConfig.KEY_MERGE_COMMIT_STRATEGY,
mergeCommitStrategy.name());
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java
index 343ee30..3ea4e18 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java
@@ -14,7 +14,13 @@
package com.google.gerrit.plugins.codeowners.config;
+import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
+import static com.google.gerrit.server.project.ProjectCache.illegalState;
+
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.server.project.ProjectState;
+import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
@@ -33,14 +39,21 @@
}
@Test
- @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Code-Review+2")
+ @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
public void getFromGlobalPluginConfig() throws Exception {
- testGetFromGlobalPluginConfig();
+ createOwnersOverrideLabel();
+
+ ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
+ ImmutableList<RequiredApproval> requiredApproval =
+ getRequiredApprovalConfig().get(projectState, new Config());
+ assertThat(requiredApproval).hasSize(1);
+ assertThat(requiredApproval).element(0).hasLabelNameThat().isEqualTo("Owners-Override");
+ assertThat(requiredApproval).element(0).hasValueThat().isEqualTo(1);
}
@Test
@GerritConfig(name = "plugin.code-owners.overrideApproval", value = "INVALID")
- public void cannotGetFromGlobalPluginConfigIfConfigIsInvalid() throws Exception {
- testCannotGetFromGlobalPluginConfigIfConfigIsInvalid();
+ public void cannotGetIfGlobalConfigIsInvalid() throws Exception {
+ testCannotGetIfGlobalConfigIsInvalid("INVALID");
}
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java
index 53c0fa2..37da12d 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java
@@ -15,11 +15,14 @@
package com.google.gerrit.plugins.codeowners.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.server.project.ProjectState;
+import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
@@ -40,13 +43,18 @@
@Test
@GerritConfig(name = "plugin.code-owners.requiredApproval", value = "Code-Review+2")
public void getFromGlobalPluginConfig() throws Exception {
- testGetFromGlobalPluginConfig();
+ ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
+ ImmutableList<RequiredApproval> requiredApproval =
+ getRequiredApprovalConfig().get(projectState, new Config());
+ assertThat(requiredApproval).hasSize(1);
+ assertThat(requiredApproval).element(0).hasLabelNameThat().isEqualTo("Code-Review");
+ assertThat(requiredApproval).element(0).hasValueThat().isEqualTo(2);
}
@Test
@GerritConfig(name = "plugin.code-owners.requiredApproval", value = "INVALID")
- public void cannotGetFromGlobalPluginConfigIfConfigIsInvalid() throws Exception {
- testCannotGetFromGlobalPluginConfigIfConfigIsInvalid();
+ public void cannotIfGlobalConfigIsInvalid() throws Exception {
+ testCannotGetIfGlobalConfigIsInvalid("INVALID");
}
@Test
diff --git a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerJsonTest.java b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerJsonTest.java
index cb7ede8..2aa5b6f 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerJsonTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerJsonTest.java
@@ -89,9 +89,7 @@
() ->
codeOwnerJsonFactory
.create(EnumSet.of(FillOptions.ID))
- .format(
- /** codeOwners = */
- null));
+ .format(/* codeOwners= */ null));
assertThat(npe).hasMessageThat().isEqualTo("codeOwners");
}
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
index 28d05fe..30f8454 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
@@ -97,10 +97,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- CodeOwnerProjectConfigJson.formatRequiredApproval(
- /** requiredApproval = */
- null));
+ () -> CodeOwnerProjectConfigJson.formatRequiredApproval(/* requiredApproval= */ null));
assertThat(npe).hasMessageThat().isEqualTo("requiredApproval");
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerStatusInfoJsonTest.java b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerStatusInfoJsonTest.java
index 93aeeca..75aaeef 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerStatusInfoJsonTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerStatusInfoJsonTest.java
@@ -50,8 +50,7 @@
NullPointerException.class,
() ->
CodeOwnerStatusInfoJson.format(
- /** pathCodeOwnerStatus = */
- (PathCodeOwnerStatus) null));
+ /* pathCodeOwnerStatus= */ (PathCodeOwnerStatus) null));
assertThat(npe).hasMessageThat().isEqualTo("pathCodeOwnerStatus");
}
@@ -72,8 +71,7 @@
NullPointerException.class,
() ->
CodeOwnerStatusInfoJson.format(
- /** fileCodeOwnerStatus = */
- (FileCodeOwnerStatus) null));
+ /* fileCodeOwnerStatus= */ (FileCodeOwnerStatus) null));
assertThat(npe).hasMessageThat().isEqualTo("fileCodeOwnerStatus");
}
@@ -192,9 +190,7 @@
NullPointerException.class,
() ->
CodeOwnerStatusInfoJson.format(
- PatchSet.id(Change.id(1), 1),
- /** fileCodeOwnerStatuses = */
- null));
+ PatchSet.id(Change.id(1), 1), /* fileCodeOwnerStatuses= */ null));
assertThat(npe).hasMessageThat().isEqualTo("fileCodeOwnerStatuses");
}
@@ -203,10 +199,7 @@
NullPointerException npe =
assertThrows(
NullPointerException.class,
- () ->
- CodeOwnerStatusInfoJson.format(
- /** patchSetId = */
- null, ImmutableSet.of()));
+ () -> CodeOwnerStatusInfoJson.format(/* patchSetId= */ null, ImmutableSet.of()));
assertThat(npe).hasMessageThat().isEqualTo("patchSetId");
}
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 1978bd0..3fd9f84 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -214,6 +214,70 @@
]
```
+### <a id="rename-email-in-code-owner-config-files">Rename Email In Code Owner Config Files
+_'POST /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/branches/[\{branch-id\}](../../../Documentation/rest-api-projects.html#branch-id)/code_owners.rename/'_
+
+Renames an email in all code owner config files in the branch.
+
+The old and new email must be specified in the request body as
+[RenameEmailInput](#rename-email-input).
+
+The old and new email must both belong to the same Gerrit account.
+
+All updates are done atomically within one commit. The calling user will be the
+author of this commit.
+
+Requires that the calling user is a project owner
+([Owner](../../../Documentation/access-control.html#category_owner) permission
+on ‘refs/*’) or has
+[direct push](../../../Documentation/access-control.html#category_push)
+permissions for the branch.
+
+#### Request
+
+```
+ POST /projects/foo%2Fbar/branches/master/code_owners.rename HTTP/1.0
+```
+
+#### Response
+
+As response a [RenameEmailResultInfo](#rename-email-result-info) entity is
+returned.
+
+```
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "commit": {
+ "commit": "",
+ "parents": [
+ {
+ "commit": "1efe2c9d8f352483781e772f35dc586a69ff5646",
+ "subject": "Fix Foo Bar"
+ }
+ ],
+ "author": {
+ "name": "John Doe",
+ "email": "john.doe@example.com",
+ "date": "2020-03-30 18:08:08.000000000",
+ "tz": -420
+ },
+ "committer": {
+ "name": "Gerrit Code Review",
+ "email": "no-reply@gerritcodereview.com",
+ "date": "2020-03-30 18:08:08.000000000",
+ "tz": -420
+ },
+ "subject": "Rename email in code owner config files",
+ "message": "Rename email in code owner config files"
+ }
+ }
+```
+
+
### <a id="get-code-owner-config">[EXPERIMENTAL] Get Code Owner Config
_'GET /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/branches/[\{branch-id\}](../../../Documentation/rest-api-projects.html#branch-id)/code_owners.config/[\{path\}](#path)'_
@@ -633,6 +697,27 @@
---
+### <a id="rename-email-input"> RenameEmailInput
+The `RenameEmailInput` entity specifies how an email should be renamed.
+
+| Field Name | | Description |
+| ----------- | -------- | ----------- |
+| `message` | optional | Commit message that should be used for the commit that renames the email in the code owner config files. If not set the following default commit message is used: "Rename email in code owner config files"
+| `old_email` || The old email that should be replaced with the new email.
+| `new_email` || The new email that should be used to replace the old email.
+
+---
+
+### <a id="rename-email-result-info"> RenameEmailResultInfo
+The `RenameEmailResultInfo` entity describes the result of the rename email REST
+endpoint.
+
+| Field Name | | Description |
+| ----------- | -------- | ----------- |
+| `commit` | optional | The commit that did the email rename. Not set, if no update was necessary.
+
+---
+
### <a id="required-approval-info"> RequiredApprovalInfo
The `RequiredApprovalInfo` entity describes an approval that is required for an
action.