Browse Source
The topological sort algorithm in TopoSortGenerator for RevWalk may mix multiple lines of history, producing results that differ from C git's git-log whose man page states: "Show no parents before all of its children are shown, and avoid showing commits on multiple lines of history intermixed." Lines of history are mixed because TopoSortGenerator merely delays producing a commit until all of its children have been produced; it does not immediately produce a commit after its last child has been produced. Therefore, add a new RevSort option called TOPO_KEEP_BRANCH_TOGETHER with a new topo sort algorithm in TopoNonIntermixGenerator. In the Generator, when the last child of a commit has been produced, unpop that commit so that it will be returned upon the subsequent call to next(). To avoid producing duplicates, mark commits that have not yet been produced as TOPO_QUEUED so that when a commit is popped, it is produced if and only if TOPO_QUEUED is set. To support nesting with other generators that may produce the same commit multiple times like DepthGenerator (for example, StartGenerator does this), do not increment parent inDegree for the same child commit more than once. Commitstable-5.8b5e764abd2
modified the existing TopoSortGenerator to avoid mixing lines of history, but it was reverted ine40c38ab08
because the new behavior caused problems for EGit users. This motivated adding a new Generator for the new behavior. Signed-off-by: Alex Spradlin <alexaspradlin@google.com> Change-Id: Icbb24eac98c00e45c175b01e1c8122554f617933
Alex Spradlin
5 years ago
9 changed files with 375 additions and 2 deletions
@ -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