Merge "RevWalk: new topo sort to not mix lines of history"
This commit is contained in:
commit
24fb3a67b5
|
@ -163,6 +163,23 @@ public void testTopoSort() throws Exception {
|
|||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTopoNonIntermixSort() throws Exception {
|
||||
RevCommit a = commit();
|
||||
RevCommit b1 = commit(a);
|
||||
RevCommit b2 = commit(a);
|
||||
RevCommit c = commit(b1, b2);
|
||||
|
||||
rw.reset();
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
rw.setFirstParent(true);
|
||||
markStart(c);
|
||||
assertCommit(c, rw.next());
|
||||
assertCommit(b1, rw.next());
|
||||
assertCommit(a, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommitTimeSort() throws Exception {
|
||||
RevCommit a = commit();
|
||||
|
@ -428,4 +445,39 @@ public void testWithTopoSortAndTreeFilter2() throws Exception {
|
|||
assertCommit(c, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithTopoNonIntermixSortAndTreeFilter() throws Exception {
|
||||
RevCommit a = commit();
|
||||
RevCommit b = commit(tree(file("0", blob("b"))), a);
|
||||
RevCommit c = commit(tree(file("0", blob("c"))), b, a);
|
||||
RevCommit d = commit(tree(file("0", blob("d"))), c);
|
||||
|
||||
rw.reset();
|
||||
rw.setFirstParent(true);
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true);
|
||||
rw.setTreeFilter(PathFilterGroup.createFromStrings("0"));
|
||||
markStart(d);
|
||||
assertCommit(d, rw.next());
|
||||
assertCommit(c, rw.next());
|
||||
assertCommit(b, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithTopoNonIntermixSortAndTreeFilter2() throws Exception {
|
||||
RevCommit a = commit();
|
||||
RevCommit b = commit(tree(file("0", blob("b"))), a);
|
||||
RevCommit c = commit(tree(file("0", blob("c"))), a, b);
|
||||
RevCommit d = commit(tree(file("0", blob("d"))), c);
|
||||
|
||||
rw.reset();
|
||||
rw.setFirstParent(true);
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true);
|
||||
rw.setTreeFilter(PathFilterGroup.createFromStrings("0"));
|
||||
markStart(d);
|
||||
assertCommit(d, rw.next());
|
||||
assertCommit(c, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,12 @@
|
|||
|
||||
package org.eclipse.jgit.revwalk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RevWalkSortTest extends RevWalkTestCase {
|
||||
|
@ -144,4 +147,171 @@ public void testSort_TOPO_REVERSE() throws Exception {
|
|||
assertCommit(d, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX() throws Exception {
|
||||
// c1 is back dated before its parent.
|
||||
//
|
||||
final RevCommit a = commit();
|
||||
final RevCommit b = commit(a);
|
||||
final RevCommit c1 = commit(-5, b);
|
||||
final RevCommit c2 = commit(10, b);
|
||||
final RevCommit d = commit(c1, c2);
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
markStart(d);
|
||||
assertCommit(d, rw.next());
|
||||
assertCommit(c2, rw.next());
|
||||
assertCommit(c1, rw.next());
|
||||
assertCommit(b, rw.next());
|
||||
assertCommit(a, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX_OutOfOrderCommitTimes()
|
||||
throws Exception {
|
||||
// b is committed before c2 in a different line of history.
|
||||
//
|
||||
final RevCommit a = commit();
|
||||
final RevCommit c1 = commit(a);
|
||||
final RevCommit b = commit(a);
|
||||
final RevCommit c2 = commit(c1);
|
||||
final RevCommit d = commit(b, c2);
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
markStart(d);
|
||||
assertCommit(d, rw.next());
|
||||
assertCommit(c2, rw.next());
|
||||
assertCommit(c1, rw.next());
|
||||
assertCommit(b, rw.next());
|
||||
assertCommit(a, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX_MultipleLinesOfHistory()
|
||||
throws Exception {
|
||||
final RevCommit a1 = commit();
|
||||
final RevCommit b1 = commit(a1);
|
||||
final RevCommit a2 = commit(a1, b1);
|
||||
final RevCommit b2 = commit(b1);
|
||||
final RevCommit b3 = commit(b1);
|
||||
final RevCommit a3 = commit(a2, b2);
|
||||
final RevCommit a4 = commit(a3, b3);
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
markStart(a4);
|
||||
assertCommit(a4, rw.next());
|
||||
assertCommit(b3, rw.next());
|
||||
assertCommit(a3, rw.next());
|
||||
assertCommit(b2, rw.next());
|
||||
assertCommit(a2, rw.next());
|
||||
assertCommit(b1, rw.next());
|
||||
assertCommit(a1, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX_REVERSE() throws Exception {
|
||||
// c1 is back dated before its parent.
|
||||
//
|
||||
final RevCommit a = commit();
|
||||
final RevCommit b = commit(a);
|
||||
final RevCommit c1 = commit(-5, b);
|
||||
final RevCommit c2 = commit(10, b);
|
||||
final RevCommit d = commit(c1, c2);
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
rw.sort(RevSort.REVERSE, true);
|
||||
markStart(d);
|
||||
assertCommit(a, rw.next());
|
||||
assertCommit(b, rw.next());
|
||||
assertCommit(c1, rw.next());
|
||||
assertCommit(c2, rw.next());
|
||||
assertCommit(d, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX_REVERSE_MultipleLinesOfHistory()
|
||||
throws Exception {
|
||||
final RevCommit a1 = commit();
|
||||
final RevCommit b1 = commit(a1);
|
||||
final RevCommit a2 = commit(a1, b1);
|
||||
final RevCommit b2 = commit(b1);
|
||||
final RevCommit b3 = commit(b1);
|
||||
final RevCommit a3 = commit(a2, b2);
|
||||
final RevCommit a4 = commit(a3, b3);
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
rw.sort(RevSort.REVERSE, true);
|
||||
markStart(a4);
|
||||
assertCommit(a1, rw.next());
|
||||
assertCommit(b1, rw.next());
|
||||
assertCommit(a2, rw.next());
|
||||
assertCommit(b2, rw.next());
|
||||
assertCommit(a3, rw.next());
|
||||
assertCommit(b3, rw.next());
|
||||
assertCommit(a4, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX_ParentOfMultipleStartChildren()
|
||||
throws Exception {
|
||||
final RevCommit a = commit();
|
||||
final RevCommit b = commit(a);
|
||||
final RevCommit c = commit(a);
|
||||
final RevCommit d1 = commit(a);
|
||||
final RevCommit d2 = commit(d1);
|
||||
final RevCommit e = commit(a);
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
markStart(b);
|
||||
markStart(c);
|
||||
markStart(d2);
|
||||
markStart(e);
|
||||
assertCommit(e, rw.next());
|
||||
assertCommit(d2, rw.next());
|
||||
assertCommit(d1, rw.next());
|
||||
assertCommit(c, rw.next());
|
||||
assertCommit(b, rw.next());
|
||||
assertCommit(a, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX_Uninteresting() throws Exception {
|
||||
final RevCommit a1 = commit();
|
||||
final RevCommit a2 = commit(a1);
|
||||
final RevCommit a3 = commit(a2);
|
||||
final RevCommit b = commit(a1);
|
||||
final RevCommit a4 = commit(a3, b);
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
markStart(a4);
|
||||
markUninteresting(a2);
|
||||
assertCommit(a4, rw.next());
|
||||
assertCommit(b, rw.next());
|
||||
assertCommit(a3, rw.next());
|
||||
assertNull(rw.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSort_TOPO_NON_INTERMIX_and_TOPO_throws() throws Exception {
|
||||
final RevCommit a = commit();
|
||||
|
||||
rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER);
|
||||
rw.sort(RevSort.TOPO, true);
|
||||
markStart(a);
|
||||
try {
|
||||
rw.next();
|
||||
fail("did not throw IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
assertEquals(
|
||||
JGitText.get().cannotCombineTopoSortWithTopoNonIntermixSort,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ cannotChangeActionOnComment=Cannot change action on comment line in git-rebase-t
|
|||
cannotCheckoutFromUnbornBranch=Cannot check out from unborn branch
|
||||
cannotCheckoutOursSwitchBranch=Checking out ours/theirs is only possible when checking out index, not when switching branches.
|
||||
cannotCombineSquashWithNoff=Cannot combine --squash with --no-ff.
|
||||
cannotCombineTopoSortWithTopoNonIntermixSort=Cannot combine sorts TOPO and TOPO_NON_INTERMIX.
|
||||
cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RevFilter {1}.
|
||||
cannotCommitOnARepoWithState=Cannot commit on a repo with state: {0}
|
||||
cannotCommitWriteTo=Cannot commit write to {0}
|
||||
|
|
|
@ -82,6 +82,7 @@ public static JGitText get() {
|
|||
/***/ public String cannotCheckoutFromUnbornBranch;
|
||||
/***/ public String cannotCheckoutOursSwitchBranch;
|
||||
/***/ public String cannotCombineSquashWithNoff;
|
||||
/***/ public String cannotCombineTopoSortWithTopoNonIntermixSort;
|
||||
/***/ public String cannotCombineTreeFilterWithRevFilter;
|
||||
/***/ public String cannotCommitOnARepoWithState;
|
||||
/***/ public String cannotCommitWriteTo;
|
||||
|
|
|
@ -152,6 +152,7 @@ public String toString() {
|
|||
*/
|
||||
protected void appendCoreFlags(StringBuilder s) {
|
||||
s.append((flags & RevWalk.TOPO_DELAY) != 0 ? 'o' : '-');
|
||||
s.append((flags & RevWalk.TOPO_QUEUED) != 0 ? 'q' : '-');
|
||||
s.append((flags & RevWalk.TEMP_MARK) != 0 ? 't' : '-');
|
||||
s.append((flags & RevWalk.REWRITE) != 0 ? 'r' : '-');
|
||||
s.append((flags & RevWalk.UNINTERESTING) != 0 ? 'u' : '-');
|
||||
|
|
|
@ -39,6 +39,18 @@ public enum RevSort {
|
|||
*/
|
||||
TOPO,
|
||||
|
||||
/**
|
||||
* Topological sorting (all children before parents) without intermixing
|
||||
* lines of history.
|
||||
* <p>
|
||||
* This behavior more closely resembles C Git's git-log --topo-order than
|
||||
* {@link #TOPO}. See C Git's topo-order <a href=
|
||||
* "https://git-scm.com/docs/git-log#Documentation/git-log.txt---topo-order">description</a>.
|
||||
*
|
||||
* @since 5.8
|
||||
*/
|
||||
TOPO_KEEP_BRANCH_TOGETHER,
|
||||
|
||||
/**
|
||||
* Flip the output into the reverse ordering.
|
||||
* <p>
|
||||
|
|
|
@ -131,8 +131,17 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
|
|||
*/
|
||||
static final int TOPO_DELAY = 1 << 5;
|
||||
|
||||
/**
|
||||
* Temporary mark for use within {@link TopoNonIntermixSortGenerator}.
|
||||
* <p>
|
||||
* This mark indicates the commit has been queued for emission in
|
||||
* {@link TopoSortGenerator} and can be produced. This mark is removed when
|
||||
* the commit has been produced.
|
||||
*/
|
||||
static final int TOPO_QUEUED = 1 << 6;
|
||||
|
||||
/** Number of flag bits we keep internal for our own use. See above flags. */
|
||||
static final int RESERVED_FLAGS = 6;
|
||||
static final int RESERVED_FLAGS = 7;
|
||||
|
||||
private static final int APP_FLAGS = -1 & ~((1 << RESERVED_FLAGS) - 1);
|
||||
|
||||
|
|
|
@ -135,8 +135,18 @@ RevCommit next() throws MissingObjectException,
|
|||
}
|
||||
|
||||
if (walker.hasRevSort(RevSort.TOPO)
|
||||
&& (g.outputType() & SORT_TOPO) == 0)
|
||||
&& walker.hasRevSort(RevSort.TOPO_KEEP_BRANCH_TOGETHER)) {
|
||||
throw new IllegalStateException(JGitText
|
||||
.get().cannotCombineTopoSortWithTopoNonIntermixSort);
|
||||
}
|
||||
|
||||
if (walker.hasRevSort(RevSort.TOPO)
|
||||
&& (g.outputType() & SORT_TOPO) == 0) {
|
||||
g = new TopoSortGenerator(g);
|
||||
} else if (walker.hasRevSort(RevSort.TOPO_KEEP_BRANCH_TOGETHER)
|
||||
&& (g.outputType() & SORT_TOPO) == 0) {
|
||||
g = new TopoNonIntermixSortGenerator(g);
|
||||
}
|
||||
if (walker.hasRevSort(RevSort.REVERSE))
|
||||
g = new LIFORevQueue(g);
|
||||
if (boundary)
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (C) 2020, Google LLC. and others
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||
* https://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
package org.eclipse.jgit.revwalk;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
|
||||
/** Sorts commits in topological order without intermixing lines of history. */
|
||||
class TopoNonIntermixSortGenerator extends Generator {
|
||||
private static final int TOPO_QUEUED = RevWalk.TOPO_QUEUED;
|
||||
|
||||
private final FIFORevQueue pending;
|
||||
|
||||
private final int outputType;
|
||||
|
||||
/**
|
||||
* Create a new sorter and completely spin the generator.
|
||||
* <p>
|
||||
* When the constructor completes the supplied generator will have no
|
||||
* commits remaining, as all of the commits will be held inside of this
|
||||
* generator's internal buffer.
|
||||
*
|
||||
* @param s
|
||||
* generator to pull all commits out of, and into this buffer.
|
||||
* @throws MissingObjectException
|
||||
* @throws IncorrectObjectTypeException
|
||||
* @throws IOException
|
||||
*/
|
||||
TopoNonIntermixSortGenerator(Generator s) throws MissingObjectException,
|
||||
IncorrectObjectTypeException, IOException {
|
||||
super(s.firstParent);
|
||||
pending = new FIFORevQueue(firstParent);
|
||||
outputType = s.outputType() | SORT_TOPO;
|
||||
s.shareFreeList(pending);
|
||||
for (;;) {
|
||||
final RevCommit c = s.next();
|
||||
if (c == null) {
|
||||
break;
|
||||
}
|
||||
if ((c.flags & TOPO_QUEUED) == 0) {
|
||||
for (RevCommit p : c.parents) {
|
||||
p.inDegree++;
|
||||
|
||||
if (firstParent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
c.flags |= TOPO_QUEUED;
|
||||
pending.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int outputType() {
|
||||
return outputType;
|
||||
}
|
||||
|
||||
@Override
|
||||
void shareFreeList(BlockRevQueue q) {
|
||||
q.shareFreeList(pending);
|
||||
}
|
||||
|
||||
@Override
|
||||
RevCommit next() throws MissingObjectException,
|
||||
IncorrectObjectTypeException, IOException {
|
||||
for (;;) {
|
||||
final RevCommit c = pending.next();
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (c.inDegree > 0) {
|
||||
// At least one of our children is missing. We delay
|
||||
// production until all of our children are output.
|
||||
//
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c.flags & TOPO_QUEUED) == 0) {
|
||||
// c is a parent that already produced or a parent that
|
||||
// was never in the priority queue and should never produce.
|
||||
//
|
||||
continue;
|
||||
}
|
||||
|
||||
for (RevCommit p : c.parents) {
|
||||
if (--p.inDegree == 0 && (p.flags & TOPO_QUEUED) != 0) {
|
||||
// The parent has no unproduced interesting children. unpop
|
||||
// the parent so it goes right behind this child. This means
|
||||
// that this parent commit may appear in "pending" more than
|
||||
// once, but this is safe since upon the second and
|
||||
// subsequent iterations with this commit, it will no longer
|
||||
// have TOPO_QUEUED set, and thus will be skipped.
|
||||
//
|
||||
pending.unpop(p);
|
||||
}
|
||||
if (firstParent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c.flags &= ~TOPO_QUEUED;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue