Explicitly order CodeReviewCommits when merging
The commits passed in to SubmitStrategy#run were coming straight out of ChangeAccess#submitted, which should generally use primary key (numeric change ID) order; these CodeReviewCommits also had their originalOrder fields set based on this order. SubmitStrategy implementations were implicitly relying on the order of the List argument, which results in commits sharing no ancestry being merged in change ID order. However, merge-based strategies were also re-sorting the list via MergeUtil#reduceToMinimalMerge, which used the originalOrder to re-sort the results after reducing the merge. Instead of these multiple subtle layers of implicit and explicit ordering, change the SubmitStrategy interface to take Collections, and make implementations responsible for ordering the commits that are passed in. This results in a small amount of repeated code, to sort the inputs, but this is made manageable by adding an explicit Ordering to CodeReviewCommit. The advantage is that we no longer have some implementations sorting and some not, so the semantics of the SubmitStrategy class are slightly more straightforward. Change-Id: Ia6b0166cdd8b8b331989bf948de3b28b0480d249
This commit is contained in:
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git;
|
package com.google.gerrit.server.git;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
@@ -29,6 +31,24 @@ import java.util.List;
|
|||||||
|
|
||||||
/** Extended commit entity with code review specific metadata. */
|
/** Extended commit entity with code review specific metadata. */
|
||||||
public class CodeReviewCommit extends RevCommit {
|
public class CodeReviewCommit extends RevCommit {
|
||||||
|
/**
|
||||||
|
* Default ordering when merging multiple topologically-equivalent commits.
|
||||||
|
* <p>
|
||||||
|
* Operates only on these commits and does not take ancestry into account.
|
||||||
|
* <p>
|
||||||
|
* Use this in preference to the default order, which comes from {@link
|
||||||
|
* AnyObjectId} and only orders on SHA-1.
|
||||||
|
*/
|
||||||
|
public static final Ordering<CodeReviewCommit> ORDER = Ordering.natural()
|
||||||
|
.onResultOf(new Function<CodeReviewCommit, Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer apply(CodeReviewCommit in) {
|
||||||
|
return in.getPatchsetId() != null
|
||||||
|
? in.getPatchsetId().getParentKey().get()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}).nullsFirst();
|
||||||
|
|
||||||
public static RevWalk newRevWalk(Repository repo) {
|
public static RevWalk newRevWalk(Repository repo) {
|
||||||
return new RevWalk(repo) {
|
return new RevWalk(repo) {
|
||||||
@Override
|
@Override
|
||||||
@@ -76,13 +96,6 @@ public class CodeReviewCommit extends RevCommit {
|
|||||||
/** Change control for the change owner. */
|
/** Change control for the change owner. */
|
||||||
private ChangeControl control;
|
private ChangeControl control;
|
||||||
|
|
||||||
/**
|
|
||||||
* Ordinal position of this commit within the submit queue.
|
|
||||||
* <p>
|
|
||||||
* Only valid if {@link #patchsetId} is not null.
|
|
||||||
*/
|
|
||||||
int originalOrder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result status for this commit.
|
* The result status for this commit.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -120,7 +133,6 @@ public class CodeReviewCommit extends RevCommit {
|
|||||||
public void copyFrom(final CodeReviewCommit src) {
|
public void copyFrom(final CodeReviewCommit src) {
|
||||||
control = src.control;
|
control = src.control;
|
||||||
patchsetId = src.patchsetId;
|
patchsetId = src.patchsetId;
|
||||||
originalOrder = src.originalOrder;
|
|
||||||
statusCode = src.statusCode;
|
statusCode = src.statusCode;
|
||||||
missing = src.missing;
|
missing = src.missing;
|
||||||
}
|
}
|
||||||
|
@@ -496,7 +496,6 @@ public class MergeOp {
|
|||||||
tips.add(r.getObjectId());
|
tips.add(r.getObjectId());
|
||||||
}
|
}
|
||||||
|
|
||||||
int commitOrder = 0;
|
|
||||||
for (Change chg : submitted) {
|
for (Change chg : submitted) {
|
||||||
ChangeControl ctl;
|
ChangeControl ctl;
|
||||||
try {
|
try {
|
||||||
@@ -568,7 +567,6 @@ public class MergeOp {
|
|||||||
|
|
||||||
commit.setControl(ctl);
|
commit.setControl(ctl);
|
||||||
commit.setPatchsetId(ps.getId());
|
commit.setPatchsetId(ps.getId());
|
||||||
commit.originalOrder = commitOrder++;
|
|
||||||
commits.put(changeId, commit);
|
commits.put(changeId, commit);
|
||||||
|
|
||||||
MergeValidators mergeValidators = mergeValidatorsFactory.create();
|
MergeValidators mergeValidators = mergeValidatorsFactory.create();
|
||||||
|
@@ -72,7 +72,6 @@ import java.sql.Timestamp;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@@ -153,23 +152,16 @@ public class MergeUtil {
|
|||||||
return mergeTip;
|
return mergeTip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reduceToMinimalMerge(final MergeSorter mergeSorter,
|
public List<CodeReviewCommit> reduceToMinimalMerge(MergeSorter mergeSorter,
|
||||||
final List<CodeReviewCommit> toSort) throws MergeException {
|
Collection<CodeReviewCommit> toSort) throws MergeException {
|
||||||
final Collection<CodeReviewCommit> heads;
|
List<CodeReviewCommit> result = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
heads = mergeSorter.sort(toSort);
|
result.addAll(mergeSorter.sort(toSort));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new MergeException("Branch head sorting failed", e);
|
throw new MergeException("Branch head sorting failed", e);
|
||||||
}
|
}
|
||||||
|
Collections.sort(result, CodeReviewCommit.ORDER);
|
||||||
toSort.clear();
|
return result;
|
||||||
toSort.addAll(heads);
|
|
||||||
Collections.sort(toSort, new Comparator<CodeReviewCommit>() {
|
|
||||||
@Override
|
|
||||||
public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
|
|
||||||
return a.originalOrder - b.originalOrder;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PatchSetApproval getSubmitter(CodeReviewCommit c) {
|
public PatchSetApproval getSubmitter(CodeReviewCommit c) {
|
||||||
|
@@ -41,6 +41,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -63,9 +64,10 @@ public class CherryPick extends SubmitStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
||||||
List<CodeReviewCommit> toMerge) throws MergeException {
|
Collection<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
while (!toMerge.isEmpty()) {
|
List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge);
|
||||||
CodeReviewCommit n = toMerge.remove(0);
|
while (!sorted.isEmpty()) {
|
||||||
|
CodeReviewCommit n = sorted.remove(0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (mergeTip == null) {
|
if (mergeTip == null) {
|
||||||
|
@@ -19,6 +19,7 @@ import com.google.gerrit.server.git.CodeReviewCommit;
|
|||||||
import com.google.gerrit.server.git.CommitMergeStatus;
|
import com.google.gerrit.server.git.CommitMergeStatus;
|
||||||
import com.google.gerrit.server.git.MergeException;
|
import com.google.gerrit.server.git.MergeException;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FastForwardOnly extends SubmitStrategy {
|
public class FastForwardOnly extends SubmitStrategy {
|
||||||
@@ -28,13 +29,14 @@ public class FastForwardOnly extends SubmitStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
||||||
List<CodeReviewCommit> toMerge) throws MergeException {
|
Collection<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
|
List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(
|
||||||
|
args.mergeSorter, toMerge);
|
||||||
CodeReviewCommit newMergeTip =
|
CodeReviewCommit newMergeTip =
|
||||||
args.mergeUtil.getFirstFastForward(mergeTip, args.rw, toMerge);
|
args.mergeUtil.getFirstFastForward(mergeTip, args.rw, sorted);
|
||||||
|
|
||||||
while (!toMerge.isEmpty()) {
|
while (!sorted.isEmpty()) {
|
||||||
CodeReviewCommit n = toMerge.remove(0);
|
CodeReviewCommit n = sorted.remove(0);
|
||||||
n.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
|
n.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|||||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||||
import com.google.gerrit.server.git.MergeException;
|
import com.google.gerrit.server.git.MergeException;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MergeAlways extends SubmitStrategy {
|
public class MergeAlways extends SubmitStrategy {
|
||||||
@@ -27,18 +28,19 @@ public class MergeAlways extends SubmitStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
||||||
List<CodeReviewCommit> toMerge) throws MergeException {
|
Collection<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
|
List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(
|
||||||
|
args.mergeSorter, toMerge);
|
||||||
|
|
||||||
if (mergeTip == null) {
|
if (mergeTip == null) {
|
||||||
// The branch is unborn. Take a fast-forward resolution to
|
// The branch is unborn. Take a fast-forward resolution to
|
||||||
// create the branch.
|
// create the branch.
|
||||||
mergeTip = toMerge.remove(0);
|
mergeTip = sorted.remove(0);
|
||||||
}
|
}
|
||||||
while (!toMerge.isEmpty()) {
|
while (!sorted.isEmpty()) {
|
||||||
mergeTip = args.mergeUtil.mergeOneCommit(args.serverIdent.get(),
|
mergeTip = args.mergeUtil.mergeOneCommit(args.serverIdent.get(),
|
||||||
args.repo, args.rw, args.inserter, args.canMergeFlag, args.destBranch,
|
args.repo, args.rw, args.inserter, args.canMergeFlag, args.destBranch,
|
||||||
mergeTip, toMerge.remove(0));
|
mergeTip, sorted.remove(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchSetApproval submitApproval =
|
PatchSetApproval submitApproval =
|
||||||
|
@@ -18,33 +18,34 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|||||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||||
import com.google.gerrit.server.git.MergeException;
|
import com.google.gerrit.server.git.MergeException;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MergeIfNecessary extends SubmitStrategy {
|
public class MergeIfNecessary extends SubmitStrategy {
|
||||||
|
|
||||||
MergeIfNecessary(SubmitStrategy.Arguments args) {
|
MergeIfNecessary(SubmitStrategy.Arguments args) {
|
||||||
super(args);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
||||||
List<CodeReviewCommit> toMerge) throws MergeException {
|
Collection<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
|
List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(
|
||||||
|
args.mergeSorter, toMerge);
|
||||||
|
|
||||||
if (mergeTip == null) {
|
if (mergeTip == null) {
|
||||||
// The branch is unborn. Take a fast-forward resolution to
|
// The branch is unborn. Take a fast-forward resolution to
|
||||||
// create the branch.
|
// create the branch.
|
||||||
mergeTip = toMerge.remove(0);
|
mergeTip = sorted.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeTip =
|
mergeTip =
|
||||||
args.mergeUtil.getFirstFastForward(mergeTip, args.rw, toMerge);
|
args.mergeUtil.getFirstFastForward(mergeTip, args.rw, sorted);
|
||||||
|
|
||||||
// For every other commit do a pair-wise merge.
|
// For every other commit do a pair-wise merge.
|
||||||
while (!toMerge.isEmpty()) {
|
while (!sorted.isEmpty()) {
|
||||||
mergeTip = args.mergeUtil.mergeOneCommit(args.serverIdent.get(),
|
mergeTip = args.mergeUtil.mergeOneCommit(args.serverIdent.get(),
|
||||||
args.repo, args.rw, args.inserter, args.canMergeFlag, args.destBranch,
|
args.repo, args.rw, args.inserter, args.canMergeFlag, args.destBranch,
|
||||||
mergeTip, toMerge.remove(0));
|
mergeTip, sorted.remove(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchSetApproval submitApproval = args.mergeUtil.markCleanMerges(
|
PatchSetApproval submitApproval = args.mergeUtil.markCleanMerges(
|
||||||
|
@@ -34,6 +34,8 @@ import com.google.gwtorm.server.OrmException;
|
|||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -54,12 +56,12 @@ public class RebaseIfNecessary extends SubmitStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
protected CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
||||||
List<CodeReviewCommit> toMerge) throws MergeException {
|
Collection<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
CodeReviewCommit newMergeTip = mergeTip;
|
CodeReviewCommit newMergeTip = mergeTip;
|
||||||
sort(toMerge);
|
List<CodeReviewCommit> sorted = sort(toMerge);
|
||||||
|
|
||||||
while (!toMerge.isEmpty()) {
|
while (!sorted.isEmpty()) {
|
||||||
CodeReviewCommit n = toMerge.remove(0);
|
CodeReviewCommit n = sorted.remove(0);
|
||||||
|
|
||||||
if (newMergeTip == null) {
|
if (newMergeTip == null) {
|
||||||
// The branch is unborn. Take a fast-forward resolution to
|
// The branch is unborn. Take a fast-forward resolution to
|
||||||
@@ -144,12 +146,13 @@ public class RebaseIfNecessary extends SubmitStrategy {
|
|||||||
return newMergeTip;
|
return newMergeTip;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sort(List<CodeReviewCommit> toSort) throws MergeException {
|
private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
|
||||||
|
throws MergeException {
|
||||||
try {
|
try {
|
||||||
List<CodeReviewCommit> sorted = new RebaseSorter(
|
List<CodeReviewCommit> result = new RebaseSorter(
|
||||||
args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
|
args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
|
||||||
toSort.clear();
|
Collections.sort(result, CodeReviewCommit.ORDER);
|
||||||
toSort.addAll(sorted);
|
return result;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new MergeException("Commit sorting failed", e);
|
throw new MergeException("Commit sorting failed", e);
|
||||||
}
|
}
|
||||||
|
@@ -37,8 +37,8 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
|||||||
import org.eclipse.jgit.revwalk.RevFlag;
|
import org.eclipse.jgit.revwalk.RevFlag;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -106,19 +106,20 @@ public abstract class SubmitStrategy {
|
|||||||
*
|
*
|
||||||
* @param mergeTip the merge tip.
|
* @param mergeTip the merge tip.
|
||||||
* @param toMerge the list of submitted commits that should be merged using
|
* @param toMerge the list of submitted commits that should be merged using
|
||||||
* this submit strategy.
|
* this submit strategy. Implementations are responsible for ordering
|
||||||
|
* of commits, and should not modify the input in place.
|
||||||
* @return the new merge tip.
|
* @return the new merge tip.
|
||||||
* @throws MergeException
|
* @throws MergeException
|
||||||
*/
|
*/
|
||||||
public CodeReviewCommit run(CodeReviewCommit mergeTip,
|
public CodeReviewCommit run(CodeReviewCommit mergeTip,
|
||||||
List<CodeReviewCommit> toMerge) throws MergeException {
|
Collection<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
refLogIdent = null;
|
refLogIdent = null;
|
||||||
return _run(mergeTip, toMerge);
|
return _run(mergeTip, toMerge);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see #run(CodeReviewCommit, List) */
|
/** @see #run(CodeReviewCommit, Collection) */
|
||||||
protected abstract CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
protected abstract CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
||||||
List<CodeReviewCommit> toMerge) throws MergeException;
|
Collection<CodeReviewCommit> toMerge) throws MergeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the given commit can be merged.
|
* Checks whether the given commit can be merged.
|
||||||
@@ -140,7 +141,7 @@ public abstract class SubmitStrategy {
|
|||||||
* the destination branch.
|
* the destination branch.
|
||||||
* <p>
|
* <p>
|
||||||
* The reflog identity may only be set during {@link #run(CodeReviewCommit,
|
* The reflog identity may only be set during {@link #run(CodeReviewCommit,
|
||||||
* List)}, and this method is invalid to call beforehand.
|
* Collection)}, and this method is invalid to call beforehand.
|
||||||
*
|
*
|
||||||
* @return the ref log identity, which may be {@code null}.
|
* @return the ref log identity, which may be {@code null}.
|
||||||
*/
|
*/
|
||||||
@@ -155,7 +156,8 @@ public abstract class SubmitStrategy {
|
|||||||
* By default this method returns an empty map, but subclasses may override
|
* By default this method returns an empty map, but subclasses may override
|
||||||
* this method to provide any newly created commits.
|
* this method to provide any newly created commits.
|
||||||
*
|
*
|
||||||
* This method may only be called after {@link #run(CodeReviewCommit, List)}.
|
* This method may only be called after {@link #run(CodeReviewCommit,
|
||||||
|
* Collection)}.
|
||||||
*
|
*
|
||||||
* @return new commits created for changes that were merged.
|
* @return new commits created for changes that were merged.
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user