Merge changes If1490ca2,Ife950253

* changes:
  DirCacheBuilder: Speed up reading from trees
  Delay locating .gitattributes until requested
This commit is contained in:
Shawn Pearce 2015-11-30 19:03:10 -05:00 committed by Gerrit Code Review @ Eclipse.org
commit 8078021e8f
5 changed files with 167 additions and 62 deletions

View File

@ -43,6 +43,8 @@
package org.eclipse.jgit.treewalk;
import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
import static org.eclipse.jgit.lib.FileMode.SYMLINK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
@ -50,9 +52,11 @@
import java.io.ByteArrayOutputStream;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.TreeFormatter;
import org.eclipse.jgit.util.RawParseUtils;
import org.junit.Before;
import org.junit.Test;
@ -369,4 +373,41 @@ public void testFreakingHugePathName() throws Exception {
assertEquals(name, RawParseUtils.decode(Constants.CHARSET, ctp.path,
ctp.pathOffset, ctp.pathLen));
}
@Test
public void testFindAttributesWhenFirst() throws CorruptObjectException {
TreeFormatter tree = new TreeFormatter();
tree.append(".gitattributes", REGULAR_FILE, hash_a);
ctp.reset(tree.toByteArray());
assertTrue(ctp.findFile(".gitattributes"));
assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
assertEquals(".gitattributes", ctp.getEntryPathString());
assertEquals(hash_a, ctp.getEntryObjectId());
}
@Test
public void testFindAttributesWhenSecond() throws CorruptObjectException {
TreeFormatter tree = new TreeFormatter();
tree.append(".config", SYMLINK, hash_a);
tree.append(".gitattributes", REGULAR_FILE, hash_foo);
ctp.reset(tree.toByteArray());
assertTrue(ctp.findFile(".gitattributes"));
assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
assertEquals(".gitattributes", ctp.getEntryPathString());
assertEquals(hash_foo, ctp.getEntryObjectId());
}
@Test
public void testFindAttributesWhenMissing() throws CorruptObjectException {
TreeFormatter tree = new TreeFormatter();
tree.append("src", REGULAR_FILE, hash_a);
tree.append("zoo", REGULAR_FILE, hash_foo);
ctp.reset(tree.toByteArray());
assertFalse(ctp.findFile(".gitattributes"));
assertEquals(11, ctp.idOffset()); // Did not walk the entire tree.
assertEquals("src", ctp.getEntryPathString());
}
}

View File

@ -44,6 +44,9 @@
package org.eclipse.jgit.dircache;
import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
@ -51,9 +54,7 @@
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
/**
* Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s.
@ -163,27 +164,56 @@ public void keep(final int pos, int cnt) {
* @throws IOException
* a tree cannot be read to iterate through its entries.
*/
public void addTree(final byte[] pathPrefix, final int stage,
final ObjectReader reader, final AnyObjectId tree) throws IOException {
final TreeWalk tw = new TreeWalk(reader);
tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree
.toObjectId()));
tw.setRecursive(true);
if (tw.next()) {
final DirCacheEntry newEntry = toEntry(stage, tw);
beforeAdd(newEntry);
fastAdd(newEntry);
while (tw.next())
fastAdd(toEntry(stage, tw));
public void addTree(byte[] pathPrefix, int stage, ObjectReader reader,
AnyObjectId tree) throws IOException {
CanonicalTreeParser p = createTreeParser(pathPrefix, reader, tree);
while (!p.eof()) {
if (isTree(p)) {
p = enterTree(p, reader);
continue;
}
DirCacheEntry first = toEntry(stage, p);
beforeAdd(first);
fastAdd(first);
p = p.next();
break;
}
// Rest of tree entries are correctly sorted; use fastAdd().
while (!p.eof()) {
if (isTree(p)) {
p = enterTree(p, reader);
} else {
fastAdd(toEntry(stage, p));
p = p.next();
}
}
}
private DirCacheEntry toEntry(final int stage, final TreeWalk tw) {
final DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage);
final AbstractTreeIterator i;
private static CanonicalTreeParser createTreeParser(byte[] pathPrefix,
ObjectReader reader, AnyObjectId tree) throws IOException {
return new CanonicalTreeParser(pathPrefix, reader, tree);
}
i = tw.getTree(0, AbstractTreeIterator.class);
e.setFileMode(tw.getFileMode(0));
private static boolean isTree(CanonicalTreeParser p) {
return (p.getEntryRawMode() & TYPE_MASK) == TYPE_TREE;
}
private static CanonicalTreeParser enterTree(CanonicalTreeParser p,
ObjectReader reader) throws IOException {
p = p.createSubtreeIterator(reader);
return p.eof() ? p.next() : p;
}
private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) {
byte[] buf = i.getEntryPathBuffer();
int len = i.getEntryPathLength();
byte[] path = new byte[len];
System.arraycopy(buf, 0, path, 0, len);
DirCacheEntry e = new DirCacheEntry(path, stage);
e.setFileMode(i.getEntryRawMode());
e.setObjectIdFromRaw(i.idBuffer(), i.idOffset());
return e;
}

View File

@ -505,6 +505,10 @@ public void setFileMode(final FileMode mode) {
NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
}
void setFileMode(int mode) {
NB.encodeInt32(info, infoOffset + P_MODE, mode);
}
/**
* Get the cached creation time of this file, in milliseconds.
*

View File

@ -327,6 +327,42 @@ int pathCompare(final AbstractTreeIterator p, final int pMode) {
return pathCompare(p.path, cPos, p.pathLen, pMode, cPos);
}
/**
* Seek the iterator on a file, if present.
*
* @param name
* file name to find (will not find a directory).
* @return true if the file exists in this tree; false otherwise.
* @throws CorruptObjectException
* tree is invalid.
* @since 4.2
*/
public boolean findFile(String name) throws CorruptObjectException {
return findFile(Constants.encode(name));
}
/**
* Seek the iterator on a file, if present.
*
* @param name
* file name to find (will not find a directory).
* @return true if the file exists in this tree; false otherwise.
* @throws CorruptObjectException
* tree is invalid.
* @since 4.2
*/
public boolean findFile(byte[] name) throws CorruptObjectException {
for (; !eof(); next(1)) {
int cmp = pathCompare(name, 0, name.length, 0, pathOffset);
if (cmp == 0) {
return true;
} else if (cmp > 0) {
return false;
}
}
return false;
}
/**
* Compare the path of this current entry to a raw buffer.
*

View File

@ -44,6 +44,13 @@
package org.eclipse.jgit.treewalk;
import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
import static org.eclipse.jgit.lib.Constants.TYPE_TREE;
import static org.eclipse.jgit.lib.Constants.encode;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@ -54,20 +61,15 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.util.RawParseUtils;
/** Parses raw Git trees from the canonical semi-text/semi-binary format. */
public class CanonicalTreeParser extends AbstractTreeIterator {
private static final byte[] EMPTY = {};
private static final byte[] ATTRS = Constants
.encode(Constants.DOT_GIT_ATTRIBUTES);
private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES);
private byte[] raw;
@ -133,6 +135,7 @@ public CanonicalTreeParser getParent() {
* the raw tree content.
*/
public void reset(final byte[] treeData) {
attributesNode = null;
raw = treeData;
prevPtr = -1;
currPtr = 0;
@ -208,7 +211,7 @@ public CanonicalTreeParser next() {
*/
public void reset(final ObjectReader reader, final AnyObjectId id)
throws IncorrectObjectTypeException, IOException {
reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes());
reset(reader.open(id, OBJ_TREE).getCachedBytes());
}
@Override
@ -218,7 +221,7 @@ public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
idBuffer.fromRaw(idBuffer(), idOffset());
if (!FileMode.TREE.equals(mode)) {
final ObjectId me = idBuffer.toObjectId();
throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
throw new IncorrectObjectTypeException(me, TYPE_TREE);
}
return createSubtreeIterator0(reader, idBuffer);
}
@ -263,7 +266,7 @@ public byte[] idBuffer() {
@Override
public int idOffset() {
return nextPtr - Constants.OBJECT_ID_LENGTH;
return nextPtr - OBJECT_ID_LENGTH;
}
@Override
@ -301,7 +304,7 @@ public void next(int delta) {
prevPtr = ptr;
while (raw[ptr] != 0)
ptr++;
ptr += Constants.OBJECT_ID_LENGTH + 1;
ptr += OBJECT_ID_LENGTH + 1;
}
if (delta != 0)
throw new ArrayIndexOutOfBoundsException(delta);
@ -337,7 +340,7 @@ public void back(int delta) {
trace[delta] = ptr;
while (raw[ptr] != 0)
ptr++;
ptr += Constants.OBJECT_ID_LENGTH + 1;
ptr += OBJECT_ID_LENGTH + 1;
}
if (trace[1] == -1)
throw new ArrayIndexOutOfBoundsException(delta);
@ -372,12 +375,7 @@ private void parseEntry() {
}
}
pathLen = tmp;
nextPtr = ptr + Constants.OBJECT_ID_LENGTH;
// Check if this entry is a .gitattributes file
if (path[pathOffset] == '.'
&& RawParseUtils.match(path, pathOffset, ATTRS) > 0)
attributesNode = new LazyLoadingAttributesNode(idOffset());
nextPtr = ptr + OBJECT_ID_LENGTH;
}
/**
@ -391,36 +389,32 @@ private void parseEntry() {
*/
public AttributesNode getEntryAttributesNode(ObjectReader reader)
throws IOException {
if (attributesNode instanceof LazyLoadingAttributesNode)
attributesNode = ((LazyLoadingAttributesNode) attributesNode)
.load(reader);
return attributesNode;
if (attributesNode == null) {
attributesNode = findAttributes(reader);
}
return attributesNode.getRules().isEmpty() ? null : attributesNode;
}
/**
* {@link AttributesNode} implementation that provides lazy loading
*/
private class LazyLoadingAttributesNode extends AttributesNode {
private final int idOffset;
LazyLoadingAttributesNode(int idOffset) {
super(Collections.<AttributesRule> emptyList());
this.idOffset = idOffset;
private AttributesNode findAttributes(ObjectReader reader)
throws IOException {
CanonicalTreeParser itr = new CanonicalTreeParser();
itr.reset(raw);
if (itr.findFile(ATTRS)) {
return loadAttributes(reader, itr.getEntryObjectId());
}
return noAttributes();
}
AttributesNode load(ObjectReader reader) throws IOException {
AttributesNode r = new AttributesNode();
ObjectId id = ObjectId.fromRaw(raw, idOffset);
ObjectLoader loader = reader.open(id);
if (loader != null) {
InputStream in = loader.openStream();
try {
r.parse(in);
} finally {
in.close();
}
}
return r.getRules().isEmpty() ? null : r;
private static AttributesNode loadAttributes(ObjectReader reader,
AnyObjectId id) throws IOException {
AttributesNode r = new AttributesNode();
try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
r.parse(in);
}
return r.getRules().isEmpty() ? noAttributes() : r;
}
private static AttributesNode noAttributes() {
return new AttributesNode(Collections.<AttributesRule> emptyList());
}
}