Merge "Fix submit rule for changes for which the target branch doesn't exist"
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
index b2e2127..97ece59 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
@@ -32,7 +32,6 @@
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginProjectConfigSnapshot;
@@ -120,11 +119,9 @@
* @param start number of owned paths to skip
* @param limit the max number of owned paths that should be returned (0 = unlimited)
* @return the paths of the files in the given patch set that are owned by the specified account
- * @throws ResourceConflictException if the destination branch of the change no longer exists
*/
public ImmutableList<OwnedChangedFile> getOwnedPaths(
- ChangeNotes changeNotes, PatchSet patchSet, Account.Id accountId, int start, int limit)
- throws ResourceConflictException {
+ ChangeNotes changeNotes, PatchSet patchSet, Account.Id accountId, int start, int limit) {
try (Timer0.Context ctx = codeOwnerMetrics.computeOwnedPaths.start()) {
logger.atFine().log(
"compute owned paths for account %d (project = %s, change = %d, patch set = %d,"
@@ -190,7 +187,7 @@
* @return whether the given change has sufficient code owner approvals to be submittable
*/
public boolean isSubmittable(ChangeNotes changeNotes)
- throws ResourceConflictException, IOException, DiffNotAvailableException {
+ throws IOException, DiffNotAvailableException {
requireNonNull(changeNotes, "changeNotes");
logger.atFine().log(
"checking if change %d in project %s is submittable",
@@ -235,8 +232,7 @@
* @see #getFileStatuses(CodeOwnerConfigHierarchy, CodeOwnerResolver, ChangeNotes)
*/
public ImmutableSet<FileCodeOwnerStatus> getFileStatusesAsSet(
- ChangeNotes changeNotes, int start, int limit)
- throws ResourceConflictException, IOException, DiffNotAvailableException {
+ ChangeNotes changeNotes, int start, int limit) throws IOException, DiffNotAvailableException {
requireNonNull(changeNotes, "changeNotes");
try (Timer0.Context ctx = codeOwnerMetrics.computeFileStatuses.start()) {
logger.atFine().log(
@@ -289,7 +285,7 @@
CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
CodeOwnerResolver codeOwnerResolver,
ChangeNotes changeNotes)
- throws ResourceConflictException, IOException, DiffNotAvailableException {
+ throws IOException, DiffNotAvailableException {
requireNonNull(changeNotes, "changeNotes");
try (Timer0.Context ctx = codeOwnerMetrics.prepareFileStatusComputation.start()) {
logger.atFine().log(
@@ -358,8 +354,13 @@
overrides);
BranchNameKey branch = changeNotes.getChange().getDest();
- ObjectId revision = getDestBranchRevision(changeNotes.getChange());
- logger.atFine().log("dest branch %s has revision %s", branch.branch(), revision.name());
+ Optional<ObjectId> revision = getDestBranchRevision(changeNotes.getChange());
+ if (revision.isPresent()) {
+ logger.atFine().log(
+ "dest branch %s has revision %s", branch.branch(), revision.get().name());
+ } else {
+ logger.atFine().log("dest branch %s does not exist", branch.branch());
+ }
CodeOwnerResolverResult globalCodeOwners =
codeOwnerResolver.resolveGlobalCodeOwners(changeNotes.getProjectName());
@@ -383,7 +384,7 @@
codeOwnerConfigHierarchy,
codeOwnerResolver,
branch,
- revision,
+ revision.orElse(null),
globalCodeOwners,
enableImplicitApproval ? changeOwner : null,
reviewerAccountIds,
@@ -410,7 +411,7 @@
@VisibleForTesting
public Stream<FileCodeOwnerStatus> getFileStatusesForAccount(
ChangeNotes changeNotes, PatchSet patchSet, Account.Id accountId)
- throws ResourceConflictException, IOException, DiffNotAvailableException {
+ throws IOException, DiffNotAvailableException {
requireNonNull(changeNotes, "changeNotes");
requireNonNull(patchSet, "patchSet");
requireNonNull(accountId, "accountId");
@@ -430,8 +431,13 @@
logger.atFine().log("requiredApproval = %s", requiredApproval);
BranchNameKey branch = changeNotes.getChange().getDest();
- ObjectId revision = getDestBranchRevision(changeNotes.getChange());
- logger.atFine().log("dest branch %s has revision %s", branch.branch(), revision.name());
+ Optional<ObjectId> revision = getDestBranchRevision(changeNotes.getChange());
+ if (revision.isPresent()) {
+ logger.atFine().log(
+ "dest branch %s has revision %s", branch.branch(), revision.get().name());
+ } else {
+ logger.atFine().log("dest branch %s does not exist", branch.branch());
+ }
FallbackCodeOwners fallbackCodeOwners = codeOwnersConfig.getFallbackCodeOwners();
logger.atFine().log("fallbackCodeOwner = %s", fallbackCodeOwners);
@@ -447,7 +453,7 @@
codeOwnerConfigHierarchy,
codeOwnerResolver,
branch,
- revision,
+ revision.orElse(null),
/* globalCodeOwners= */ CodeOwnerResolverResult.createEmpty(),
// Do not check for implicit approvals since implicit approvals of other users
// should be ignored. For the given account we do not need to check for
@@ -503,7 +509,7 @@
CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
CodeOwnerResolver codeOwnerResolver,
BranchNameKey branch,
- ObjectId revision,
+ @Nullable ObjectId revision,
CodeOwnerResolverResult globalCodeOwners,
@Nullable Account.Id implicitApprover,
ImmutableSet<Account.Id> reviewerAccountIds,
@@ -568,7 +574,7 @@
CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
CodeOwnerResolver codeOwnerResolver,
BranchNameKey branch,
- ObjectId revision,
+ @Nullable ObjectId revision,
CodeOwnerResolverResult globalCodeOwners,
@Nullable Account.Id implicitApprover,
ImmutableSet<Account.Id> reviewerAccountIds,
@@ -1004,18 +1010,18 @@
* <p>This is the revision from which the code owner configs should be read when computing code
* owners for the files that are touched in the change.
*
- * @throws ResourceConflictException thrown if the destination branch is not found, e.g. when the
- * branch got deleted after the change was created
+ * @return the current revision of the destination branch of the given change, {@link
+ * Optional#empty()} if the destination branch is not found (e.g. when the initial change is
+ * uploaded to an unborn branch or when the branch got deleted after the change was created)
*/
- private ObjectId getDestBranchRevision(Change change)
- throws IOException, ResourceConflictException {
+ private Optional<ObjectId> getDestBranchRevision(Change change) throws IOException {
try (Repository repository = repoManager.openRepository(change.getProject());
RevWalk rw = new RevWalk(repository)) {
Ref ref = repository.exactRef(change.getDest().branch());
if (ref == null) {
- throw new ResourceConflictException("destination branch not found");
+ return Optional.empty();
}
- return rw.parseCommit(ref.getObjectId());
+ return Optional.of(rw.parseCommit(ref.getObjectId()));
}
}
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchy.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchy.java
index 7ba3352..8fb6d09 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchy.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchy.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
@@ -42,6 +43,10 @@
* config in the root folder of the branch. The same as any other parent it can be ignored (e.g. by
* using {@code set noparent} in the root code owner config if the {@code find-owners} backend is
* used).
+ *
+ * <p>Visiting the code owner configs also works for non-existing branches (provided branch revision
+ * is {@code null}). In this case only the default code owner config in {@code refs/meta/config} is
+ * visited (if it exists).
*/
public class CodeOwnerConfigHierarchy {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -65,7 +70,8 @@
* the path hierarchy from the given path up to the root folder.
*
* @param branchNameKey project and branch from which the code owner configs should be visited
- * @param revision the branch revision from which the code owner configs should be loaded
+ * @param revision the branch revision from which the code owner configs should be loaded, {@code
+ * null} if the branch doesn't exist
* @param absolutePath the path for which the code owner configs should be visited; the path must
* be absolute; can be the path of a file or folder; the path may or may not exist
* @param codeOwnerConfigVisitor visitor that should be invoked for the applying code owner
@@ -73,7 +79,7 @@
*/
public void visit(
BranchNameKey branchNameKey,
- ObjectId revision,
+ @Nullable ObjectId revision,
Path absolutePath,
CodeOwnerConfigVisitor codeOwnerConfigVisitor) {
visit(
@@ -89,7 +95,8 @@
* the path hierarchy from the given path up to the root folder.
*
* @param branchNameKey project and branch from which the code owner configs should be visited
- * @param revision the branch revision from which the code owner configs should be loaded
+ * @param revision the branch revision from which the code owner configs should be loaded, {@code
+ * null} if the branch doesn't exist
* @param absolutePath the path for which the code owner configs should be visited; the path must
* be absolute; can be the path of a file or folder; the path may or may not exist
* @param codeOwnerConfigVisitor visitor that should be invoked for the applying code owner
@@ -99,7 +106,7 @@
*/
public void visit(
BranchNameKey branchNameKey,
- ObjectId revision,
+ @Nullable ObjectId revision,
Path absolutePath,
CodeOwnerConfigVisitor codeOwnerConfigVisitor,
Consumer<CodeOwnerConfig.Key> parentCodeOwnersIgnoredCallback) {
@@ -119,7 +126,8 @@
* path hierarchy from the given path up to the root folder.
*
* @param branchNameKey project and branch from which the code owner configs should be visited
- * @param revision the branch revision from which the code owner configs should be loaded
+ * @param revision the branch revision from which the code owner configs should be loaded, {@code
+ * null} if the branch doesn't exist
* @param absolutePath the path for which the code owner configs should be visited; the path must
* be absolute; can be the path of a file or folder; the path may or may not exist
* @param pathCodeOwnersVisitor visitor that should be invoked for the applying path code owners
@@ -128,7 +136,7 @@
*/
public void visit(
BranchNameKey branchNameKey,
- ObjectId revision,
+ @Nullable ObjectId revision,
Path absolutePath,
PathCodeOwnersVisitor pathCodeOwnersVisitor,
Consumer<CodeOwnerConfig.Key> parentCodeOwnersIgnoredCallback) {
@@ -153,7 +161,8 @@
* (e.g. for large changes).
*
* @param branchNameKey project and branch from which the code owner configs should be visited
- * @param revision the branch revision from which the code owner configs should be loaded
+ * @param revision the branch revision from which the code owner configs should be loaded, {@code
+ * null} if the branch doesn't exist
* @param absoluteFilePath the path for which the code owner configs should be visited; the path
* must be absolute; must be the path of a file; the path may or may not exist
* @param pathCodeOwnersVisitor visitor that should be invoked for the applying path code owners
@@ -162,7 +171,7 @@
*/
public void visitForFile(
BranchNameKey branchNameKey,
- ObjectId revision,
+ @Nullable ObjectId revision,
Path absoluteFilePath,
PathCodeOwnersVisitor pathCodeOwnersVisitor,
Consumer<CodeOwnerConfig.Key> parentCodeOwnersIgnoredCallback) {
@@ -177,13 +186,12 @@
private void visit(
BranchNameKey branchNameKey,
- ObjectId revision,
+ @Nullable ObjectId revision,
Path absolutePath,
Path startFolder,
PathCodeOwnersVisitor pathCodeOwnersVisitor,
Consumer<CodeOwnerConfig.Key> parentCodeOwnersIgnoredCallback) {
requireNonNull(branchNameKey, "branch");
- requireNonNull(revision, "revision");
requireNonNull(absolutePath, "absolutePath");
requireNonNull(pathCodeOwnersVisitor, "pathCodeOwnersVisitor");
requireNonNull(parentCodeOwnersIgnoredCallback, "parentCodeOwnersIgnoredCallback");
@@ -191,47 +199,52 @@
logger.atFine().log(
"visiting code owner configs for '%s' in branch '%s' in project '%s' (revision = '%s')",
- absolutePath, branchNameKey.shortName(), branchNameKey.project(), revision.name());
+ absolutePath,
+ branchNameKey.shortName(),
+ branchNameKey.project(),
+ revision != null ? revision.name() : "n/a");
- // Next path in which we look for a code owner configuration. We start at the given folder and
- // then go up the parent hierarchy.
- Path ownerConfigFolder = startFolder;
+ if (revision != null) {
+ // Next path in which we look for a code owner configuration. We start at the given folder and
+ // then go up the parent hierarchy.
+ Path ownerConfigFolder = startFolder;
- // Iterate over the parent code owner configurations.
- while (ownerConfigFolder != null) {
- // Read code owner config and invoke the codeOwnerConfigVisitor if the code owner config
- // exists.
- logger.atFine().log("inspecting code owner config for %s", ownerConfigFolder);
- CodeOwnerConfig.Key codeOwnerConfigKey =
- CodeOwnerConfig.Key.create(branchNameKey, ownerConfigFolder);
- Optional<PathCodeOwners> pathCodeOwners =
- pathCodeOwnersFactory.create(
- transientCodeOwnerConfigCache, codeOwnerConfigKey, revision, absolutePath);
- if (pathCodeOwners.isPresent()) {
- logger.atFine().log("visit code owner config for %s", ownerConfigFolder);
- boolean visitFurtherCodeOwnerConfigs = pathCodeOwnersVisitor.visit(pathCodeOwners.get());
- boolean ignoreParentCodeOwners =
- pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners();
- if (ignoreParentCodeOwners) {
- parentCodeOwnersIgnoredCallback.accept(codeOwnerConfigKey);
+ // Iterate over the parent code owner configurations.
+ while (ownerConfigFolder != null) {
+ // Read code owner config and invoke the codeOwnerConfigVisitor if the code owner config
+ // exists.
+ logger.atFine().log("inspecting code owner config for %s", ownerConfigFolder);
+ CodeOwnerConfig.Key codeOwnerConfigKey =
+ CodeOwnerConfig.Key.create(branchNameKey, ownerConfigFolder);
+ Optional<PathCodeOwners> pathCodeOwners =
+ pathCodeOwnersFactory.create(
+ transientCodeOwnerConfigCache, codeOwnerConfigKey, revision, absolutePath);
+ if (pathCodeOwners.isPresent()) {
+ logger.atFine().log("visit code owner config for %s", ownerConfigFolder);
+ boolean visitFurtherCodeOwnerConfigs = pathCodeOwnersVisitor.visit(pathCodeOwners.get());
+ boolean ignoreParentCodeOwners =
+ pathCodeOwners.get().resolveCodeOwnerConfig().get().ignoreParentCodeOwners();
+ if (ignoreParentCodeOwners) {
+ parentCodeOwnersIgnoredCallback.accept(codeOwnerConfigKey);
+ }
+ logger.atFine().log(
+ "visitFurtherCodeOwnerConfigs = %s, ignoreParentCodeOwners = %s",
+ visitFurtherCodeOwnerConfigs, ignoreParentCodeOwners);
+ if (!visitFurtherCodeOwnerConfigs || ignoreParentCodeOwners) {
+ // If no further code owner configs should be visited or if all parent code owner
+ // configs are ignored, we are done.
+ // No need to check further parent code owner configs (including the default code owner
+ // config in refs/meta/config which is the parent of the root code owner config), hence
+ // we can return here.
+ return;
+ }
+ } else {
+ logger.atFine().log("no code owner config found in %s", ownerConfigFolder);
}
- logger.atFine().log(
- "visitFurtherCodeOwnerConfigs = %s, ignoreParentCodeOwners = %s",
- visitFurtherCodeOwnerConfigs, ignoreParentCodeOwners);
- if (!visitFurtherCodeOwnerConfigs || ignoreParentCodeOwners) {
- // If no further code owner configs should be visited or if all parent code owner configs
- // are ignored, we are done.
- // No need to check further parent code owner configs (including the default code owner
- // config in refs/meta/config which is the parent of the root code owner config), hence we
- // can return here.
- return;
- }
- } else {
- logger.atFine().log("no code owner config found in %s", ownerConfigFolder);
+
+ // Continue the loop with the next parent folder.
+ ownerConfigFolder = ownerConfigFolder.getParent();
}
-
- // Continue the loop with the next parent folder.
- ownerConfigFolder = ownerConfigFolder.getParent();
}
if (!RefNames.REFS_CONFIG.equals(branchNameKey.branch())) {
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
index ec53945..58a500e 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
@@ -21,8 +21,6 @@
import com.google.gerrit.entities.LegacySubmitRequirement;
import com.google.gerrit.entities.SubmitRecord;
import com.google.gerrit.extensions.annotations.Exports;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.backend.config.InvalidPluginConfigurationException;
@@ -95,11 +93,6 @@
return Optional.of(getSubmitRecord(changeData.notes()));
}
- } catch (RestApiException e) {
- logger.atFine().withCause(e).log(
- "Couldn't evaluate code owner statuses for patch set %d of change %d.",
- changeData.currentPatchSet().id().get(), changeData.change().getId().get());
- return Optional.of(notReady());
} catch (Exception e) {
// Whether the exception should be treated as RULE_ERROR.
// RULE_ERROR must only be returned if the exception is caused by user misconfiguration (e.g.
@@ -162,7 +155,7 @@
}
private SubmitRecord getSubmitRecord(ChangeNotes changeNotes)
- throws ResourceConflictException, IOException, DiffNotAvailableException {
+ throws IOException, DiffNotAvailableException {
requireNonNull(changeNotes, "changeNotes");
return codeOwnerApprovalCheck.isSubmittable(changeNotes) ? ok() : notReady();
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersOnAddReviewer.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersOnAddReviewer.java
index 0e0ec81..4e81f46 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersOnAddReviewer.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersOnAddReviewer.java
@@ -23,7 +23,6 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.events.ReviewerAddedListener;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginProjectConfigSnapshot;
@@ -206,23 +205,15 @@
Project.NameKey projectName, Change.Id changeId, Account.Id reviewerAccountId) {
ChangeNotes changeNotes = changeNotesFactory.create(projectName, changeId);
- ImmutableList<Path> ownedPaths;
- try {
- // limit + 1, so that we can show an indicator if there are more than <limit> files.
- ownedPaths =
- OwnedChangedFile.getOwnedPaths(
- codeOwnerApprovalCheck.getOwnedPaths(
- changeNotes,
- changeNotes.getCurrentPatchSet(),
- reviewerAccountId,
- /* start= */ 0,
- limit + 1));
- } catch (RestApiException e) {
- logger.atFine().withCause(e).log(
- "Couldn't compute owned paths of change %s for account %s",
- changeNotes.getChangeId(), reviewerAccountId.get());
- return Optional.empty();
- }
+ // limit + 1, so that we can show an indicator if there are more than <limit> files.
+ ImmutableList<Path> ownedPaths =
+ OwnedChangedFile.getOwnedPaths(
+ codeOwnerApprovalCheck.getOwnedPaths(
+ changeNotes,
+ changeNotes.getCurrentPatchSet(),
+ reviewerAccountId,
+ /* start= */ 0,
+ limit + 1));
if (ownedPaths.isEmpty()) {
// this reviewer doesn't own any of the modified paths
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/OnCodeOwnerApproval.java b/java/com/google/gerrit/plugins/codeowners/backend/OnCodeOwnerApproval.java
index 5b6f6dd..b538ebd 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/OnCodeOwnerApproval.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/OnCodeOwnerApproval.java
@@ -17,9 +17,7 @@
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.PatchSet;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginProjectConfigSnapshot;
@@ -53,8 +51,6 @@
*/
@Singleton
class OnCodeOwnerApproval implements OnPostReview {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
private final CodeOwnerApprovalCheck codeOwnerApprovalCheck;
private final CodeOwnerMetrics codeOwnerMetrics;
@@ -120,23 +116,15 @@
int limit) {
LabelVote newVote = getNewVote(requiredApproval, approvals);
- ImmutableList<Path> ownedPaths;
- try {
- // limit + 1, so that we can show an indicator if there are more than <limit> files.
- ownedPaths =
- OwnedChangedFile.getOwnedPaths(
- codeOwnerApprovalCheck.getOwnedPaths(
- changeNotes,
- changeNotes.getCurrentPatchSet(),
- user.getAccountId(),
- /* start= */ 0,
- limit + 1));
- } catch (RestApiException e) {
- logger.atFine().withCause(e).log(
- "Couldn't compute owned paths of change %s for account %s",
- changeNotes.getChangeId(), user.getAccountId().get());
- return Optional.empty();
- }
+ // limit + 1, so that we can show an indicator if there are more than <limit> files.
+ ImmutableList<Path> ownedPaths =
+ OwnedChangedFile.getOwnedPaths(
+ codeOwnerApprovalCheck.getOwnedPaths(
+ changeNotes,
+ changeNotes.getCurrentPatchSet(),
+ user.getAccountId(),
+ /* start= */ 0,
+ limit + 1));
if (ownedPaths.isEmpty()) {
// the user doesn't own any of the modified paths
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
index 22cc758..f3e7d1b 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
@@ -16,8 +16,10 @@
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.allowLabel;
import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerStatusInfoSubject.assertThat;
import static com.google.gerrit.plugins.codeowners.testing.LegacySubmitRequirementInfoSubject.assertThatCollection;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
@@ -25,8 +27,10 @@
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestMetricMaker;
+import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.RefNames;
@@ -53,6 +57,7 @@
/** Acceptance test for {@code com.google.gerrit.plugins.codeowners.backend.CodeOwnerSubmitRule}. */
public class CodeOwnerSubmitRuleIT extends AbstractCodeOwnersIT {
+ @Inject private RequestScopeOperations requestScopeOperations;
@Inject private ProjectOperations projectOperations;
@Inject private TestMetricMaker testMetricMaker;
@@ -100,7 +105,17 @@
}
@Test
- public void changeWithInsufficentReviewersIsNotSubmittable() throws Exception {
+ public void changeWithInsufficientReviewersIsNotSubmittable() throws Exception {
+ testChangeWithInsufficientReviewersIsNotSubmittable();
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void initialChangeWithInsufficientReviewersIsNotSubmittable() throws Exception {
+ testChangeWithInsufficientReviewersIsNotSubmittable();
+ }
+
+ private void testChangeWithInsufficientReviewersIsNotSubmittable() throws Exception {
String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
// Approve by a non-code-owner.
@@ -151,10 +166,23 @@
.addCodeOwnerEmail(user.email())
.create();
+ testChangeWithPendingCodeOwnerApprovalsIsNotSubmittable(user);
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void initialChangeWithPendingCodeOwnerApprovalsIsNotSubmittable() throws Exception {
+ setAsDefaultCodeOwners(user);
+
+ testChangeWithPendingCodeOwnerApprovalsIsNotSubmittable(user);
+ }
+
+ private void testChangeWithPendingCodeOwnerApprovalsIsNotSubmittable(TestAccount codeOwner)
+ throws Exception {
String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
// Add a reviewer that is a code owner.
- gApi.changes().id(changeId).addReviewer(user.email());
+ gApi.changes().id(changeId).addReviewer(codeOwner.email());
// Approve by a non-code-owner.
approve(changeId);
@@ -201,13 +229,33 @@
.project(project)
.branch("master")
.folderPath("/foo/")
- .addCodeOwnerEmail(admin.email())
+ .addCodeOwnerEmail(user.email())
.create();
+ testChangeWithCodeOwnerApprovalsIsSubmittable(user);
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void initialChangeWithCodeOwnerApprovalsIsSubmittable() throws Exception {
+ setAsDefaultCodeOwners(user);
+
+ testChangeWithCodeOwnerApprovalsIsSubmittable(user);
+ }
+
+ private void testChangeWithCodeOwnerApprovalsIsSubmittable(TestAccount codeOwner)
+ throws Exception {
String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
// Approve by a code-owner.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
+ requestScopeOperations.setApiUser(codeOwner.id());
approve(changeId);
+ requestScopeOperations.setApiUser(admin.id());
// Verify that the code owner status for the changed file is APPROVED.
CodeOwnerStatusInfo codeOwnerStatus =
@@ -239,6 +287,17 @@
@Test
@GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
public void changeWithOverrideApprovalIsSubmittable() throws Exception {
+ testChangeWithOverrideApprovalIsSubmittable();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
+ @TestProjectInput(createEmptyCommit = false)
+ public void initialChangeWithOverrideApprovalIsSubmittable() throws Exception {
+ testChangeWithOverrideApprovalIsSubmittable();
+ }
+
+ private void testChangeWithOverrideApprovalIsSubmittable() throws Exception {
createOwnersOverrideLabel();
String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
index 34642d5..88ff7d0 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
@@ -42,7 +42,6 @@
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations;
import com.google.gerrit.plugins.codeowners.common.CodeOwnerStatus;
@@ -1482,19 +1481,204 @@
}
@Test
- public void getStatus_branchDeleted() throws Exception {
+ public void getStatus_branchDeleted_defaultCodeOwner() throws Exception {
String branchName = "tempBranch";
createBranch(BranchNameKey.create(project, branchName));
- String changeId = createChange("refs/for/" + branchName).getChangeId();
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
DeleteBranchesInput input = new DeleteBranchesInput();
input.branches = ImmutableList.of(branchName);
gApi.projects().name(project.get()).deleteBranches(input);
- ResourceConflictException exception =
- assertThrows(ResourceConflictException.class, () -> getFileCodeOwnerStatuses(changeId));
- assertThat(exception).hasMessageThat().isEqualTo("destination branch not found");
+ testGetStatusBranchDoesNotExistWithDefaultCodeOwner(changeId, path);
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void getStatus_initialChange_defaultCodeOwner() throws Exception {
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
+
+ testGetStatusBranchDoesNotExistWithDefaultCodeOwner(changeId, path);
+ }
+
+ private void testGetStatusBranchDoesNotExistWithDefaultCodeOwner(String changeId, Path path)
+ throws Exception {
+ setAsDefaultCodeOwners(admin);
+
+ ImmutableSet<FileCodeOwnerStatus> fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(path, CodeOwnerStatus.INSUFFICIENT_REVIEWERS));
+
+ // Add default code owner as a reviewer.
+ gApi.changes().id(changeId).addReviewer(admin.email());
+
+ fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(
+ path,
+ CodeOwnerStatus.PENDING,
+ String.format(
+ "reviewer %s is a default code owner",
+ AccountTemplateUtil.getAccountTemplate(admin.id()))));
+
+ // Approve as default code owner.
+ approve(changeId);
+
+ fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(
+ path,
+ CodeOwnerStatus.APPROVED,
+ String.format(
+ "approved by %s who is a default code owner",
+ AccountTemplateUtil.getAccountTemplate(admin.id()))));
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
+ public void getStatus_branchDeleted_globalCodeOwner() throws Exception {
+ // Create a bot user that is a global code owner.
+ TestAccount bot =
+ accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
+
+ String branchName = "tempBranch";
+ createBranch(BranchNameKey.create(project, branchName));
+
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(admin, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
+
+ DeleteBranchesInput input = new DeleteBranchesInput();
+ input.branches = ImmutableList.of(branchName);
+ gApi.projects().name(project.get()).deleteBranches(input);
+
+ testGetStatusBranchDoesNotExistWithGlobalCodeOwner(changeId, path, bot);
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
+ public void getStatus_initialChange_globalCodeOwner() throws Exception {
+ // Create a bot user that is a global code owner.
+ TestAccount bot =
+ accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
+
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(admin, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
+
+ testGetStatusBranchDoesNotExistWithGlobalCodeOwner(changeId, path, bot);
+ }
+
+ private void testGetStatusBranchDoesNotExistWithGlobalCodeOwner(
+ String changeId, Path path, TestAccount globalCodeOwner) throws Exception {
+ ImmutableSet<FileCodeOwnerStatus> fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(path, CodeOwnerStatus.INSUFFICIENT_REVIEWERS));
+
+ // Add global code owner as a reviewer.
+ gApi.changes().id(changeId).addReviewer(globalCodeOwner.email());
+
+ fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(
+ path,
+ CodeOwnerStatus.PENDING,
+ String.format(
+ "reviewer %s is a global code owner",
+ AccountTemplateUtil.getAccountTemplate(globalCodeOwner.id()))));
+
+ // Approve as default code owner.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
+ requestScopeOperations.setApiUser(globalCodeOwner.id());
+ approve(changeId);
+
+ fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(
+ path,
+ CodeOwnerStatus.APPROVED,
+ String.format(
+ "approved by %s who is a global code owner",
+ AccountTemplateUtil.getAccountTemplate(globalCodeOwner.id()))));
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
+ public void getStatus_branchDeleted_override() throws Exception {
+ String branchName = "tempBranch";
+ createBranch(BranchNameKey.create(project, branchName));
+
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(admin, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
+
+ DeleteBranchesInput input = new DeleteBranchesInput();
+ input.branches = ImmutableList.of(branchName);
+ gApi.projects().name(project.get()).deleteBranches(input);
+
+ testGetStatusBranchDoesNotExistWithOverride(changeId, path);
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
+ public void getStatus_initialChange_override() throws Exception {
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(admin, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
+
+ testGetStatusBranchDoesNotExistWithOverride(changeId, path);
+ }
+
+ private void testGetStatusBranchDoesNotExistWithOverride(String changeId, Path path)
+ throws Exception {
+ createOwnersOverrideLabel();
+
+ ImmutableSet<FileCodeOwnerStatus> fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(path, CodeOwnerStatus.INSUFFICIENT_REVIEWERS));
+
+ // Apply an override
+ gApi.changes().id(changeId).current().review(new ReviewInput().label("Owners-Override", 1));
+
+ fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(
+ path,
+ CodeOwnerStatus.APPROVED,
+ String.format(
+ "override approval Owners-Override+1 by %s is present",
+ AccountTemplateUtil.getAccountTemplate(admin.id()))));
}
@Test
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java
index c91d897..5c31eed 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java
@@ -16,11 +16,15 @@
import static com.google.gerrit.plugins.codeowners.testing.FileCodeOwnerStatusSubject.assertThatCollection;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations;
import com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig;
@@ -459,6 +463,70 @@
FileCodeOwnerStatus.addition(path, CodeOwnerStatus.INSUFFICIENT_REVIEWERS));
}
+ @Test
+ public void getStatus_branchDeleted() throws Exception {
+ String branchName = "tempBranch";
+ createBranch(BranchNameKey.create(project, branchName));
+
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
+
+ DeleteBranchesInput input = new DeleteBranchesInput();
+ input.branches = ImmutableList.of(branchName);
+ gApi.projects().name(project.get()).deleteBranches(input);
+
+ testGetStatusBranchDoesNotExist(changeId, path);
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void getStatus_initialChange() throws Exception {
+ // Create a change as a user that is not a code owner.
+ Path path = Paths.get("/foo/bar.baz");
+ String changeId =
+ createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+ .getChangeId();
+
+ testGetStatusBranchDoesNotExist(changeId, path);
+ }
+
+ private void testGetStatusBranchDoesNotExist(String changeId, Path path) throws Exception {
+ ImmutableSet<FileCodeOwnerStatus> fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(path, CodeOwnerStatus.INSUFFICIENT_REVIEWERS));
+
+ // Add code owner as a reviewer (all users are fallback code owners).
+ gApi.changes().id(changeId).addReviewer(admin.email());
+
+ fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(
+ path,
+ CodeOwnerStatus.PENDING,
+ String.format(
+ "reviewer %s is a fallback code owner (all users are fallback code owners)",
+ AccountTemplateUtil.getAccountTemplate(admin.id()))));
+
+ // Approve as a code owner (all users are fallback code owners).
+ approve(changeId);
+
+ fileCodeOwnerStatuses = getFileCodeOwnerStatuses(changeId);
+ assertThatCollection(fileCodeOwnerStatuses)
+ .containsExactly(
+ FileCodeOwnerStatus.addition(
+ path,
+ CodeOwnerStatus.APPROVED,
+ String.format(
+ "approved by %s who is a fallback code owner"
+ + " (all users are fallback code owners)",
+ AccountTemplateUtil.getAccountTemplate(admin.id()))));
+ }
+
private ImmutableSet<FileCodeOwnerStatus> getFileCodeOwnerStatuses(String changeId)
throws Exception {
return codeOwnerApprovalCheck.getFileStatusesAsSet(
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java
index df5ad05..0422551 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigHierarchyTest.java
@@ -89,17 +89,13 @@
}
@Test
- public void cannotVisitCodeOwnerConfigsForNullRevision() throws Exception {
- NullPointerException npe =
- assertThrows(
- NullPointerException.class,
- () ->
- codeOwnerConfigHierarchy.visit(
- BranchNameKey.create(project, "master"),
- /* revision= */ null,
- Paths.get("/foo/bar/baz.md"),
- visitor));
- assertThat(npe).hasMessageThat().isEqualTo("revision");
+ public void visitorNotInvokedForNullRevision() throws Exception {
+ codeOwnerConfigHierarchy.visit(
+ BranchNameKey.create(project, "master"),
+ /* revision= */ null,
+ Paths.get("/foo/bar/baz.md"),
+ visitor);
+ verifyNoInteractions(visitor);
}
@Test
@@ -583,6 +579,27 @@
}
@Test
+ public void visitorInvokedForCodeOwnerConfigInRefsMetaConfig_nullRevision() throws Exception {
+ CodeOwnerConfig.Key metaCodeOwnerConfigKey =
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch(RefNames.REFS_CONFIG)
+ .folderPath("/")
+ .addCodeOwnerEmail(admin.email())
+ .create();
+
+ when(visitor.visit(any(CodeOwnerConfig.class))).thenReturn(true);
+ codeOwnerConfigHierarchy.visit(
+ BranchNameKey.create(project, "master"),
+ /* revision= */ null,
+ Paths.get("/foo/bar/baz.md"),
+ visitor);
+ verify(visitor).visit(codeOwnerConfigOperations.codeOwnerConfig(metaCodeOwnerConfigKey).get());
+ verifyNoMoreInteractions(visitor);
+ }
+
+ @Test
public void visitorInvokedForCodeOwnerConfigInRefsMetaConfigIfItDoesntApply() throws Exception {
CodeOwnerConfig.Key metaCodeOwnerConfigKey =
codeOwnerConfigOperations