Merge branch 'stable-3.4'
* stable-3.4: (48 commits)
Update git submodules
Update git submodules
Disable setting cancelLeftPadding if avatars are not set in the config
Disable setting cancelLeftPadding if avatars are not set in the config
ProjectOperations: Add method to wipe all access sections
Migrate to python 3
Pop up accounts when typing within assignee or attention on the searchbar
Pop up accounts when typing within assignee or attention on the searchbar
Add attention operator for auto-complete
Update CodeMirror to 5.62.2
Update CodeMirror to 5.62.2
Remove contenteditable="false" from within dragDropArea
Fix drag and drop
Remove float from rightControls on mobiles
Remove contenteditable="false" from within dragDropArea
Replace non-standard event.path with event.composedPath
Suppress error when conflicts predicate is disabled
Add missing "Show trailing whitespace" to edit preference
Add missing "Show trailing whitespace" to edit preference
Prevent removing e-mail associated with OpenId accounts
...
Change-Id: I313022111b61622dfbac2ce2800faa21bac45992
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index b6184d7..9d3446e 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -293,9 +293,9 @@
patch-set is uploaded that has the same list of files as the previous
patch-set.
-Renames are considered the same file when computing whether new files
-were added or old files were deleted. Hence, if there are only renames,
-scores will still be copied over.
+Renames are considered different files when computing whether new files
+were added or old files were deleted. Hence, if there are renames, scores will
+*NOT* be copied over.
Defaults to false.
diff --git a/java/com/google/gerrit/server/ApprovalInference.java b/java/com/google/gerrit/server/ApprovalInference.java
index d77427a..04d874c 100644
--- a/java/com/google/gerrit/server/ApprovalInference.java
+++ b/java/com/google/gerrit/server/ApprovalInference.java
@@ -29,11 +29,12 @@
import com.google.gerrit.entities.Patch.ChangeType;
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.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.server.change.ChangeKindCache;
import com.google.gerrit.server.change.LabelNormalizer;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
@@ -46,10 +47,14 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
+import java.util.stream.Collectors;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
/**
@@ -68,17 +73,20 @@
private final ChangeKindCache changeKindCache;
private final LabelNormalizer labelNormalizer;
private final PatchListCache patchListCache;
+ private final GitRepositoryManager repositoryManager;
@Inject
ApprovalInference(
ProjectCache projectCache,
ChangeKindCache changeKindCache,
LabelNormalizer labelNormalizer,
- PatchListCache patchListCache) {
+ PatchListCache patchListCache,
+ GitRepositoryManager repositoryManager) {
this.projectCache = projectCache;
this.changeKindCache = changeKindCache;
this.labelNormalizer = labelNormalizer;
this.patchListCache = patchListCache;
+ this.repositoryManager = repositoryManager;
}
/**
@@ -111,7 +119,8 @@
PatchSet.Id psId,
ChangeKind kind,
LabelType type,
- @Nullable PatchList patchList) {
+ @Nullable PatchList patchListCurrentPatchset,
+ @Nullable PatchList patchListPriorPatchset) {
int n = psa.key().patchSetId().get();
checkArgument(n != psId.get());
@@ -172,11 +181,7 @@
project.getName());
return true;
} else if (type.isCopyAllScoresIfListOfFilesDidNotChange()
- && patchList.getPatches().stream()
- .noneMatch(
- p ->
- p.getChangeType() == ChangeType.ADDED
- || p.getChangeType() == ChangeType.DELETED)) {
+ && didListOfFilesNotChange(patchListCurrentPatchset, patchListPriorPatchset)) {
logger.atFine().log(
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because the label has set "
@@ -308,6 +313,20 @@
}
}
+ private static boolean didListOfFilesNotChange(PatchList oldPatchList, PatchList newPatchList) {
+ Map<String, ChangeType> fileToChangeTypePs1 = getFileToChangeType(oldPatchList);
+ Map<String, ChangeType> fileToChangeTypePs2 = getFileToChangeType(newPatchList);
+ return fileToChangeTypePs1.equals(fileToChangeTypePs2);
+ }
+
+ private static Map<String, ChangeType> getFileToChangeType(PatchList ps) {
+ return ps.getPatches().stream()
+ .collect(
+ Collectors.toMap(
+ f -> f.getNewName() != null ? f.getNewName() : f.getOldName(),
+ f -> f.getChangeType()));
+ }
+
private Collection<PatchSetApproval> getForPatchSetWithoutNormalization(
ChangeNotes notes,
ProjectState project,
@@ -368,7 +387,8 @@
logger.atFine().log(
"change kind for patch set %d of change %d against prior patch set %s is %s",
ps.id().get(), ps.id().changeId().get(), priorPatchSet.getValue().id().changeId(), kind);
- PatchList patchList = null;
+ PatchList patchListCurrentPatchset = null;
+ PatchList patchListPriorPatchset = null;
LabelTypes labelTypes = project.getLabelTypes();
for (PatchSetApproval psa : priorApprovals) {
if (resultByUser.contains(psa.label(), psa.accountId())) {
@@ -376,10 +396,14 @@
}
LabelType type = labelTypes.byLabel(psa.labelId());
// Only compute patchList if there is a relevant label, since this is expensive.
- if (patchList == null && type != null && type.isCopyAllScoresIfListOfFilesDidNotChange()) {
- patchList = getPatchList(project, ps, priorPatchSet);
+ if (patchListCurrentPatchset == null
+ && type != null
+ && type.isCopyAllScoresIfListOfFilesDidNotChange()) {
+ patchListCurrentPatchset = getPatchList(project, ps);
+ patchListPriorPatchset = getPatchList(project, priorPatchSet.getValue());
}
- if (!canCopy(project, psa, ps.id(), kind, type, patchList)) {
+ if (!canCopy(
+ project, psa, ps.id(), kind, type, patchListCurrentPatchset, patchListPriorPatchset)) {
continue;
}
resultByUser.put(psa.label(), psa.accountId(), psa.copyWithPatchSet(ps.id()));
@@ -388,16 +412,18 @@
}
/**
- * Gets the {@link PatchList} between the two latest patch-sets. Can be used to compute difference
- * in files between those two patch-sets .
+ * Gets the {@link PatchList} between a patch-set and the base. Can be used to compute difference
+ * in files between two patch-sets by using both {@link PatchList}s of those 2 patch-sets.
*/
- private PatchList getPatchList(
- ProjectState project, PatchSet ps, Map.Entry<PatchSet.Id, PatchSet> priorPatchSet) {
+ private PatchList getPatchList(ProjectState project, PatchSet ps) {
+ // Compare against the base:
+ // * For merge commits the comparison is done against the 1st parent, which is the destination
+ // branch.
+ // * For non-merge commits the comparison is done against the only parent, or an empty base if
+ // no parent exists.
PatchListKey key =
- PatchListKey.againstCommit(
- priorPatchSet.getValue().commitId(),
- ps.commitId(),
- DiffPreferencesInfo.Whitespace.IGNORE_NONE);
+ PatchListKey.againstBase(
+ ps.commitId(), getParentCount(project.getNameKey(), ps.commitId()));
try {
return patchListCache.get(key, project.getNameKey());
} catch (PatchListNotAvailableException ex) {
@@ -408,4 +434,13 @@
ex);
}
}
+
+ private int getParentCount(Project.NameKey project, ObjectId objectId) {
+ try (Repository repo = repositoryManager.openRepository(project);
+ RevWalk revWalk = new RevWalk(repo)) {
+ return revWalk.parseCommit(objectId).getParentCount();
+ } catch (IOException ex) {
+ throw new StorageException(ex);
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index 5c59129..4e47bb1 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -37,6 +37,7 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -360,6 +361,46 @@
}
@Test
+ public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileAlreadyExists()
+ throws Exception {
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig()
+ .updateLabelType(
+ LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true));
+ u.save();
+ }
+
+ // create "existing file" and submit it.
+ String existingFile = "existing file";
+ Change.Id prep =
+ changeOperations
+ .newChange()
+ .project(project)
+ .file(existingFile)
+ .content("content")
+ .create();
+ vote(admin, prep.toString(), 2, 1);
+ gApi.changes().id(prep.get()).current().submit();
+
+ Change.Id changeId = changeOperations.newChange().project(project).create();
+ vote(admin, changeId.toString(), 2, 1);
+ vote(user, changeId.toString(), -2, -1);
+
+ changeOperations
+ .change(changeId)
+ .newPatchset()
+ .file(existingFile)
+ .content("new content")
+ .create();
+ ChangeInfo c = detailedChange(changeId.toString());
+
+ // no votes are copied since the list of files changed ("existing file" was added to the
+ // change).
+ assertVotes(c, admin, 0, 0);
+ assertVotes(c, user, 0, 0);
+ }
+
+ @Test
public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsDeleted()
throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
@@ -405,7 +446,32 @@
}
@Test
- public void stickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsRenamed() throws Exception {
+ @TestProjectInput(createEmptyCommit = false)
+ public void stickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsModifiedAsInitialCommit()
+ throws Exception {
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig()
+ .updateLabelType(
+ LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true));
+ u.save();
+ }
+ Change.Id changeId =
+ changeOperations.newChange().project(project).file("file").content("content").create();
+ vote(admin, changeId.toString(), 2, 1);
+ vote(user, changeId.toString(), -2, -1);
+
+ changeOperations.change(changeId).newPatchset().file("file").content("new content").create();
+ ChangeInfo c = detailedChange(changeId.toString());
+
+ // only code review votes are copied since copyAllScoresIfListOfFilesDidNotChange is
+ // configured for that label, and list of files didn't change.
+ assertVotes(c, admin, 2, 0);
+ assertVotes(c, user, -2, 0);
+ }
+
+ @Test
+ public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsRenamed()
+ throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig()
.updateLabelType(
@@ -420,10 +486,9 @@
changeOperations.change(changeId).newPatchset().file("file").renameTo("new_file").create();
ChangeInfo c = detailedChange(changeId.toString());
- // only code review votes are copied since copyAllScoresIfListOfFilesDidNotChange is
- // configured for that label, and list of files didn't change (rename is still the same file).
- assertVotes(c, admin, 2, 0);
- assertVotes(c, user, -2, 0);
+ // no votes are copied since the list of files changed (rename).
+ assertVotes(c, admin, 0, 0);
+ assertVotes(c, user, 0, 0);
}
@Test