Do not match NO_CHANGE for non-merge if changekind:MERGE_FIRST_PARENT_UPDATE
It's unexpected that setting changekind:MERGE_FIRST_PARENT_UPDATE as
copy condition or copyAllScoresOnMergeFirstParentUpdate=true has any
effect on changes for non merge commits (e.g. the documentation for
copyAllScoresOnMergeFirstParentUpdate says "It only applies if the patch
set is a merge commit."). Since changekind:MERGE_FIRST_PARENT_UPDATE and
copyAllScoresOnMergeFirstParentUpdate=true implicitly match approvals if
the change kind was NO_CHANGE this setting unintentionally caused
copying of approvals on non-merge changes if the change kind was
NO_CHANGE.
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: Ida02e8870a77205c91a615d8ece7fe484949fbaf
Release-Notes: skip
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 037e1ba..db0308f 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -303,7 +303,8 @@
`NO_CHANGE` is more trivial than a trivial rebase, no code change and
a first parent update, hence this change kind is also matched by
`changekind:TRIVIAL_REBASE`, `changekind:NO_CODE_CHANGE` and
-`changekind:MERGE_FIRST_PARENT_UPDATE`.
+`changekind:MERGE_FIRST_PARENT_UPDATE` (only if the change is for a
+merge commit).
==== is:{MIN,MAX,ANY}
diff --git a/java/com/google/gerrit/server/approval/ApprovalInference.java b/java/com/google/gerrit/server/approval/ApprovalInference.java
index fba2fcb..c3c066f 100644
--- a/java/com/google/gerrit/server/approval/ApprovalInference.java
+++ b/java/com/google/gerrit/server/approval/ApprovalInference.java
@@ -28,6 +28,7 @@
import com.google.gerrit.entities.LabelTypes;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.index.query.QueryParseException;
@@ -50,6 +51,7 @@
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
@@ -125,6 +127,7 @@
PatchSetApproval psa,
PatchSet.Id psId,
ChangeKind kind,
+ boolean isMerge,
LabelType type,
@Nullable Map<String, ModifiedFile> baseVsCurrentDiff,
@Nullable Map<String, ModifiedFile> baseVsPriorDiff,
@@ -272,7 +275,7 @@
project.getName());
return true;
}
- if (type.isCopyAllScoresOnMergeFirstParentUpdate()) {
+ if (isMerge && type.isCopyAllScoresOnMergeFirstParentUpdate()) {
logger.atFine().log(
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
@@ -317,13 +320,15 @@
PatchSet patchSet,
LabelType type,
ChangeKind changeKind,
+ boolean isMerge,
RevWalk revWalk,
Config repoConfig) {
if (!type.getCopyCondition().isPresent()) {
return false;
}
ApprovalContext ctx =
- ApprovalContext.create(changeNotes, psa, patchSet, changeKind, revWalk, repoConfig);
+ ApprovalContext.create(
+ changeNotes, psa, patchSet, changeKind, isMerge, revWalk, repoConfig);
try {
// Use a request context to run checks as an internal user with expanded visibility. This is
// so that the output of the copy condition does not depend on who is running the current
@@ -377,6 +382,7 @@
repoConfig,
priorPatchSet.getValue().commitId(),
patchSet.commitId());
+ boolean isMerge = isMerge(project.getNameKey(), rw, patchSet);
logger.atFine().log(
"change kind for patch set %d of change %d against prior patch set %s is %s",
patchSet.id().get(),
@@ -420,12 +426,13 @@
psa,
patchSet.id(),
changeKind,
+ isMerge,
type.get(),
baseVsCurrent,
baseVsPrior,
priorVsCurrent)
&& !canCopyBasedOnCopyCondition(
- notes, psa, patchSet, type.get(), changeKind, rw, repoConfig)) {
+ notes, psa, patchSet, type.get(), changeKind, isMerge, rw, repoConfig)) {
continue;
}
resultByUser.put(psa.label(), psa.accountId(), psa.copyWithPatchSet(patchSet.id()));
@@ -433,6 +440,18 @@
return resultByUser.values();
}
+ private boolean isMerge(Project.NameKey project, RevWalk rw, PatchSet patchSet) {
+ try {
+ return rw.parseCommit(patchSet.commitId()).getParentCount() > 1;
+ } catch (IOException e) {
+ throw new StorageException(
+ String.format(
+ "failed to check if patch set %d of change %s in project %s is a merge commit",
+ patchSet.id().get(), patchSet.id().changeId(), project),
+ e);
+ }
+ }
+
/**
* Gets the modified files between the two latest patch-sets. Can be used to compute difference in
* files between those two patch-sets .
diff --git a/java/com/google/gerrit/server/query/approval/ApprovalContext.java b/java/com/google/gerrit/server/query/approval/ApprovalContext.java
index 2839eb7..6e433c5 100644
--- a/java/com/google/gerrit/server/query/approval/ApprovalContext.java
+++ b/java/com/google/gerrit/server/query/approval/ApprovalContext.java
@@ -43,6 +43,9 @@
/** {@link ChangeKind} of the delta between the origin and target patch set. */
public abstract ChangeKind changeKind();
+ /** Whether the new patch set is a merge commit. */
+ public abstract boolean isMerge();
+
/** {@link RevWalk} of the repository for the current commit. */
public abstract RevWalk revWalk();
@@ -54,6 +57,7 @@
PatchSetApproval psa,
PatchSet patchSet,
ChangeKind changeKind,
+ boolean isMerge,
RevWalk revWalk,
Config repoConfig) {
checkState(
@@ -68,6 +72,6 @@
// are no changes with gaps in patch set numbers. Since it's planned to fix-up old changes with
// gaps in patch set numbers, this todo is a reminder to add back the check once this is done.
return new AutoValue_ApprovalContext(
- psa, patchSet, changeNotes, changeKind, revWalk, repoConfig);
+ psa, patchSet, changeNotes, changeKind, isMerge, revWalk, repoConfig);
}
}
diff --git a/java/com/google/gerrit/server/query/approval/ChangeKindPredicate.java b/java/com/google/gerrit/server/query/approval/ChangeKindPredicate.java
index 370f46c..6866983 100644
--- a/java/com/google/gerrit/server/query/approval/ChangeKindPredicate.java
+++ b/java/com/google/gerrit/server/query/approval/ChangeKindPredicate.java
@@ -42,8 +42,8 @@
// configured change kind (changeKind) is:
// * TRIVIAL_REBASE: since NO_CHANGE is a special kind of a trivial rebase
// * NO_CODE_CHANGE: if there is no change, there is also no code change
- // * MERGE_FIRST_PARENT_UPDATE: if votes should be copied on first parent update, they should
- // also be copied if there was no change
+ // * MERGE_FIRST_PARENT_UPDATE (only if the new patch set is a merge commit): if votes should be
+ // copied on first parent update, they should also be copied if there was no change
//
// Motivation:
// * https://gerrit-review.googlesource.com/c/gerrit/+/74690
@@ -58,7 +58,7 @@
return ctx.changeKind() == ChangeKind.NO_CHANGE
&& (changeKind == ChangeKind.TRIVIAL_REBASE
|| changeKind == ChangeKind.NO_CODE_CHANGE
- || changeKind == ChangeKind.MERGE_FIRST_PARENT_UPDATE);
+ || (ctx.isMerge() && changeKind == ChangeKind.MERGE_FIRST_PARENT_UPDATE));
}
@Override
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index 6a9dd37..0bf5518 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -359,6 +359,37 @@
}
@Test
+ public void
+ notStickyOnNoChangeForNonMergeIfCopyingIsConfiguredForMergeFirstParentUpdate_withoutCopyCondition()
+ throws Exception {
+ updateCodeReviewLabel(b -> b.setCopyAllScoresOnMergeFirstParentUpdate(true));
+ testNotStickyOnNoChangeForNonMergeIfCopyingIsConfiguredForMergeFirstParentUpdate();
+ }
+
+ @Test
+ public void
+ notStickyOnNoChangeForNonMergeIfCopyingIsConfiguredForMergeFirstParentUpdate_withCopyCondition()
+ throws Exception {
+ updateCodeReviewLabel(
+ b -> b.setCopyCondition("changekind:" + MERGE_FIRST_PARENT_UPDATE.name()));
+ testNotStickyOnNoChangeForNonMergeIfCopyingIsConfiguredForMergeFirstParentUpdate();
+ }
+
+ private void testNotStickyOnNoChangeForNonMergeIfCopyingIsConfiguredForMergeFirstParentUpdate()
+ throws Exception {
+ // Create a change with a non-merge commit
+ String changeId = changeKindCreator.createChange(REWORK, testRepo, admin);
+ vote(admin, changeId, 2, 1);
+ vote(user, changeId, -2, -1);
+
+ // Make a NO_CHANGE update (expect that votes are not copied since it's not a merge change).
+ changeKindCreator.updateChange(changeId, NO_CHANGE, testRepo, admin, project);
+ ChangeInfo c = detailedChange(changeId);
+ assertVotes(c, admin, 0, 0, NO_CHANGE);
+ assertVotes(c, user, -0, 0, NO_CHANGE);
+ }
+
+ @Test
public void notStickyWithCopyOnNoChangeWhenSecondParentIsUpdated() throws Exception {
updateCodeReviewLabel(b -> b.setCopyAllScoresIfNoChange(true));
diff --git a/javatests/com/google/gerrit/acceptance/server/query/ApprovalQueryIT.java b/javatests/com/google/gerrit/acceptance/server/query/ApprovalQueryIT.java
index 44b59bf..b019354 100644
--- a/javatests/com/google/gerrit/acceptance/server/query/ApprovalQueryIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/query/ApprovalQueryIT.java
@@ -314,6 +314,7 @@
approval,
changeNotes.getPatchSets().get(newPsId),
changeKind,
+ /* isMerge= */ false,
rw,
repo.getConfig());
}