Fix ObjectWalk corruption when skipping over empty trees
The supplied test case comes out of the example tree identified by Robert de Wilde and Ilari on #git: $ git ls-tree -rt a54f1a85ebf6a7f53aa60a45a1be33f8b078fb7e 040000 tree bfe058ad536cdb12e127cde63b01472c960ea105 A 040000 tree4b825dc642
A/A 040000 tree4b825dc642
A/B 100644 blob abbbfafe3129f85747aba7bfac992af77134c607 B In this tree, "B" was being skipped because "A/A" as an empty tree was immediately followed by "A/B", also an empty tree, but the ObjectWalk broke out too early and never visited "B". Bug: 286653 Change-Id: I25bcb0bc99d0cbbbdd9c2bd625ad6a691a6d0335 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
parent
0d94a5ca66
commit
db54736e71
|
@ -43,6 +43,11 @@
|
||||||
|
|
||||||
package org.eclipse.jgit.revwalk;
|
package org.eclipse.jgit.revwalk;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.FileTreeEntry;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectWriter;
|
||||||
|
import org.eclipse.jgit.lib.Tree;
|
||||||
|
|
||||||
public class ObjectWalkTest extends RevWalkTestCase {
|
public class ObjectWalkTest extends RevWalkTestCase {
|
||||||
protected ObjectWalk objw;
|
protected ObjectWalk objw;
|
||||||
|
|
||||||
|
@ -194,4 +199,43 @@ public void testCull() throws Exception {
|
||||||
assertSame(f2, objw.nextObject());
|
assertSame(f2, objw.nextObject());
|
||||||
assertNull(objw.nextObject());
|
assertNull(objw.nextObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testEmptyTreeCorruption() throws Exception {
|
||||||
|
ObjectId bId = ObjectId
|
||||||
|
.fromString("abbbfafe3129f85747aba7bfac992af77134c607");
|
||||||
|
final RevTree tree_root, tree_A, tree_AB;
|
||||||
|
final RevCommit b;
|
||||||
|
{
|
||||||
|
Tree root = new Tree(db);
|
||||||
|
Tree A = root.addTree("A");
|
||||||
|
FileTreeEntry B = root.addFile("B");
|
||||||
|
B.setId(bId);
|
||||||
|
|
||||||
|
Tree A_A = A.addTree("A");
|
||||||
|
Tree A_B = A.addTree("B");
|
||||||
|
|
||||||
|
final ObjectWriter ow = new ObjectWriter(db);
|
||||||
|
A_A.setId(ow.writeTree(A_A));
|
||||||
|
A_B.setId(ow.writeTree(A_B));
|
||||||
|
A.setId(ow.writeTree(A));
|
||||||
|
root.setId(ow.writeTree(root));
|
||||||
|
|
||||||
|
tree_root = rw.parseTree(root.getId());
|
||||||
|
tree_A = rw.parseTree(A.getId());
|
||||||
|
tree_AB = rw.parseTree(A_A.getId());
|
||||||
|
assertSame(tree_AB, rw.parseTree(A_B.getId()));
|
||||||
|
b = commit(rw.parseTree(root.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
markStart(b);
|
||||||
|
|
||||||
|
assertCommit(b, objw.next());
|
||||||
|
assertNull(objw.next());
|
||||||
|
|
||||||
|
assertSame(tree_root, objw.nextObject());
|
||||||
|
assertSame(tree_A, objw.nextObject());
|
||||||
|
assertSame(tree_AB, objw.nextObject());
|
||||||
|
assertSame(rw.lookupBlob(bId), objw.nextObject());
|
||||||
|
assertNull(objw.nextObject());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,9 +86,7 @@ public class ObjectWalk extends RevWalk {
|
||||||
|
|
||||||
private RevTree currentTree;
|
private RevTree currentTree;
|
||||||
|
|
||||||
private boolean fromTreeWalk;
|
private RevObject last;
|
||||||
|
|
||||||
private RevTree nextSubtree;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new revision and object walker for a given repository.
|
* Create a new revision and object walker for a given repository.
|
||||||
|
@ -243,18 +241,12 @@ public RevCommit next() throws MissingObjectException,
|
||||||
*/
|
*/
|
||||||
public RevObject nextObject() throws MissingObjectException,
|
public RevObject nextObject() throws MissingObjectException,
|
||||||
IncorrectObjectTypeException, IOException {
|
IncorrectObjectTypeException, IOException {
|
||||||
fromTreeWalk = false;
|
if (last != null)
|
||||||
|
treeWalk = last instanceof RevTree ? enter(last) : treeWalk.next();
|
||||||
if (nextSubtree != null) {
|
|
||||||
treeWalk = treeWalk.createSubtreeIterator0(db, nextSubtree, curs);
|
|
||||||
nextSubtree = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!treeWalk.eof()) {
|
while (!treeWalk.eof()) {
|
||||||
final FileMode mode = treeWalk.getEntryFileMode();
|
final FileMode mode = treeWalk.getEntryFileMode();
|
||||||
final int sType = mode.getObjectType();
|
switch (mode.getObjectType()) {
|
||||||
|
|
||||||
switch (sType) {
|
|
||||||
case Constants.OBJ_BLOB: {
|
case Constants.OBJ_BLOB: {
|
||||||
treeWalk.getEntryObjectId(idBuffer);
|
treeWalk.getEntryObjectId(idBuffer);
|
||||||
final RevBlob o = lookupBlob(idBuffer);
|
final RevBlob o = lookupBlob(idBuffer);
|
||||||
|
@ -263,7 +255,7 @@ public RevObject nextObject() throws MissingObjectException,
|
||||||
o.flags |= SEEN;
|
o.flags |= SEEN;
|
||||||
if (shouldSkipObject(o))
|
if (shouldSkipObject(o))
|
||||||
break;
|
break;
|
||||||
fromTreeWalk = true;
|
last = o;
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
case Constants.OBJ_TREE: {
|
case Constants.OBJ_TREE: {
|
||||||
|
@ -274,8 +266,7 @@ public RevObject nextObject() throws MissingObjectException,
|
||||||
o.flags |= SEEN;
|
o.flags |= SEEN;
|
||||||
if (shouldSkipObject(o))
|
if (shouldSkipObject(o))
|
||||||
break;
|
break;
|
||||||
nextSubtree = o;
|
last = o;
|
||||||
fromTreeWalk = true;
|
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -291,6 +282,7 @@ public RevObject nextObject() throws MissingObjectException,
|
||||||
treeWalk = treeWalk.next();
|
treeWalk = treeWalk.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last = null;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
final RevObject o = pendingObjects.next();
|
final RevObject o = pendingObjects.next();
|
||||||
if (o == null)
|
if (o == null)
|
||||||
|
@ -308,6 +300,18 @@ public RevObject nextObject() throws MissingObjectException,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CanonicalTreeParser enter(RevObject tree) throws IOException {
|
||||||
|
CanonicalTreeParser p = treeWalk.createSubtreeIterator0(db, tree, curs);
|
||||||
|
if (p.eof()) {
|
||||||
|
// We can't tolerate the subtree being an empty tree, as
|
||||||
|
// that will break us out early before we visit all names.
|
||||||
|
// If it is, advance to the parent's next record.
|
||||||
|
//
|
||||||
|
return treeWalk.next();
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
private final boolean shouldSkipObject(final RevObject o) {
|
private final boolean shouldSkipObject(final RevObject o) {
|
||||||
return (o.flags & UNINTERESTING) != 0 && !hasRevSort(RevSort.BOUNDARY);
|
return (o.flags & UNINTERESTING) != 0 && !hasRevSort(RevSort.BOUNDARY);
|
||||||
}
|
}
|
||||||
|
@ -364,7 +368,7 @@ public void checkConnectivity() throws MissingObjectException,
|
||||||
* has no path, such as for annotated tags or root level trees.
|
* has no path, such as for annotated tags or root level trees.
|
||||||
*/
|
*/
|
||||||
public String getPathString() {
|
public String getPathString() {
|
||||||
return fromTreeWalk ? treeWalk.getEntryPathString() : null;
|
return last != null ? treeWalk.getEntryPathString() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -372,8 +376,8 @@ public void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
pendingObjects = new BlockObjQueue();
|
pendingObjects = new BlockObjQueue();
|
||||||
treeWalk = new CanonicalTreeParser();
|
treeWalk = new CanonicalTreeParser();
|
||||||
nextSubtree = null;
|
|
||||||
currentTree = null;
|
currentTree = null;
|
||||||
|
last = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -381,7 +385,8 @@ protected void reset(final int retainFlags) {
|
||||||
super.reset(retainFlags);
|
super.reset(retainFlags);
|
||||||
pendingObjects = new BlockObjQueue();
|
pendingObjects = new BlockObjQueue();
|
||||||
treeWalk = new CanonicalTreeParser();
|
treeWalk = new CanonicalTreeParser();
|
||||||
nextSubtree = null;
|
currentTree = null;
|
||||||
|
last = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addObject(final RevObject o) {
|
private void addObject(final RevObject o) {
|
||||||
|
|
Loading…
Reference in New Issue