package org.eclipse.jgit.dircache;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.JGitTestUtil;
public class DirCacheCGitCompatabilityTest extends LocalDiskRepositoryTestCase {
}
}
+ public void testReadWriteV3() throws Exception {
+ final File file = pathOf("gitgit.index.v3.skipWorkTree");
+ final DirCache dc = new DirCache(file, FS.DETECTED);
+ dc.read();
+
+ assertEquals(7, dc.getEntryCount());
+ assertV3TreeEntry(0, "dir1/file1.txt", false, false, dc);
+ assertV3TreeEntry(1, "dir2/file2.txt", true, false, dc);
+ assertV3TreeEntry(2, "dir3/file3.txt", false, false, dc);
+ assertV3TreeEntry(3, "dir3/file3a.txt", true, false, dc);
+ assertV3TreeEntry(4, "dir4/file4.txt", true, false, dc);
+ assertV3TreeEntry(5, "dir4/file4a.txt", false, false, dc);
+ assertV3TreeEntry(6, "file.txt", true, false, dc);
+
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ dc.writeTo(bos);
+ final byte[] indexBytes = bos.toByteArray();
+ final byte[] expectedBytes = IO.readFully(file);
+ assertTrue(Arrays.equals(expectedBytes, indexBytes));
+ }
+
+ private static void assertV3TreeEntry(int indexPosition, String path,
+ boolean skipWorkTree, boolean intentToAdd, DirCache dc) {
+ final DirCacheEntry entry = dc.getEntry(indexPosition);
+ assertEquals(path, entry.getPathString());
+ assertEquals(skipWorkTree, entry.isSkipWorkTree());
+ assertEquals(intentToAdd, entry.isIntentToAdd());
+ }
+
private File pathOf(final String name) {
return JGitTestUtil.getTestResourceFile(name);
}
DIRCExtensionIsTooLargeAt=DIRC extension {0} is too large at {1} bytes.
DIRCExtensionNotSupportedByThisVersion=DIRC extension {0} not supported by this version.
DIRCHasTooManyEntries=DIRC has too many entries.
+DIRCUnrecognizedExtendedFlags=Unrecognized extended flags: {0}
JRELacksMD5Implementation=JRE lacks MD5 implementation
URINotSupported=URI not supported: {0}
URLNotFound={0} not found
/***/ public String DIRCExtensionIsTooLargeAt;
/***/ public String DIRCExtensionNotSupportedByThisVersion;
/***/ public String DIRCHasTooManyEntries;
+ /***/ public String DIRCUnrecognizedExtendedFlags;
/***/ public String JRELacksMD5Implementation;
/***/ public String URINotSupported;
/***/ public String URLNotFound;
private static final int EXT_TREE = 0x54524545 /* 'TREE' */;
- private static final int INFO_LEN = DirCacheEntry.INFO_LEN;
-
private static final DirCacheEntry[] NO_ENTRIES = {};
static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
tree = null;
}
- private void readFrom(final FileInputStream inStream) throws IOException,
+ private void readFrom(final InputStream inStream) throws IOException,
CorruptObjectException {
final BufferedInputStream in = new BufferedInputStream(inStream);
final MessageDigest md = Constants.newMessageDigest();
if (!is_DIRC(hdr))
throw new CorruptObjectException(JGitText.get().notADIRCFile);
final int ver = NB.decodeInt32(hdr, 4);
- if (ver != 2)
+ boolean extended = false;
+ if (ver == 3)
+ extended = true;
+ else if (ver != 2)
throw new CorruptObjectException(MessageFormat.format(JGitText.get().unknownDIRCVersion, ver));
entryCnt = NB.decodeInt32(hdr, 8);
if (entryCnt < 0)
// Load the individual file entries.
//
- final byte[] infos = new byte[INFO_LEN * entryCnt];
+ final int infoLength = DirCacheEntry.getMaximumInfoLength(extended);
+ final byte[] infos = new byte[infoLength * entryCnt];
sortedEntries = new DirCacheEntry[entryCnt];
+
+ final MutableInteger infoAt = new MutableInteger();
for (int i = 0; i < entryCnt; i++)
- sortedEntries[i] = new DirCacheEntry(infos, i * INFO_LEN, in, md);
+ sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md);
lastModified = liveFile.lastModified();
// After the file entries are index extensions, and then a footer.
}
}
- private void writeTo(final OutputStream os) throws IOException {
+ void writeTo(final OutputStream os) throws IOException {
final MessageDigest foot = Constants.newMessageDigest();
final DigestOutputStream dos = new DigestOutputStream(os, foot);
+ boolean extended = false;
+ for (int i = 0; i < entryCnt; i++)
+ extended |= sortedEntries[i].isExtended();
+
// Write the header.
//
final byte[] tmp = new byte[128];
System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
- NB.encodeInt32(tmp, 4, /* version */2);
+ NB.encodeInt32(tmp, 4, extended ? 3 : 2);
NB.encodeInt32(tmp, 8, entryCnt);
dos.write(tmp, 0, 12);
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.MutableInteger;
import org.eclipse.jgit.util.NB;
/**
private static final int P_OBJECTID = 40;
private static final int P_FLAGS = 60;
+ private static final int P_FLAGS2 = 62;
/** Mask applied to data in {@link #P_FLAGS} to get the name length. */
private static final int NAME_MASK = 0xfff;
- static final int INFO_LEN = 62;
+ private static final int INTENT_TO_ADD = 0x20000000;
+ private static final int SKIP_WORKTREE = 0x40000000;
+ private static final int EXTENDED_FLAGS = (INTENT_TO_ADD | SKIP_WORKTREE);
+ private static final int INFO_LEN = 62;
+ private static final int INFO_LEN_EXTENDED = 64;
+
+ private static final int EXTENDED = 0x40;
private static final int ASSUME_VALID = 0x80;
/** In-core flag signaling that the entry should be considered as modified. */
/** Flags which are never stored to disk. */
private byte inCoreFlags;
- DirCacheEntry(final byte[] sharedInfo, final int infoAt,
+ DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
final InputStream in, final MessageDigest md) throws IOException {
info = sharedInfo;
- infoOffset = infoAt;
+ infoOffset = infoAt.value;
IO.readFully(in, info, infoOffset, INFO_LEN);
- md.update(info, infoOffset, INFO_LEN);
+
+ final int len;
+ if (isExtended()) {
+ len = INFO_LEN_EXTENDED;
+ IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN);
+
+ if ((getExtendedFlags() & ~EXTENDED_FLAGS) != 0)
+ throw new IOException(MessageFormat.format(JGitText.get()
+ .DIRCUnrecognizedExtendedFlags, String.valueOf(getExtendedFlags())));
+ } else
+ len = INFO_LEN;
+
+ infoAt.value += len;
+ md.update(info, infoOffset, len);
int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
int skipped = 0;
// Index records are padded out to the next 8 byte alignment
// for historical reasons related to how C Git read the files.
//
- final int actLen = INFO_LEN + pathLen;
+ final int actLen = len + pathLen;
final int expLen = (actLen + 8) & ~7;
final int padLen = expLen - actLen - skipped;
if (padLen > 0) {
}
void write(final OutputStream os) throws IOException {
+ final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
final int pathLen = path.length;
- os.write(info, infoOffset, INFO_LEN);
+ os.write(info, infoOffset, len);
os.write(path, 0, pathLen);
// Index records are padded out to the next 8 byte alignment
// for historical reasons related to how C Git read the files.
//
- final int actLen = INFO_LEN + pathLen;
+ final int actLen = len + pathLen;
final int expLen = (actLen + 8) & ~7;
if (actLen != expLen)
os.write(nullpad, 0, expLen - actLen);
return (info[infoOffset + P_FLAGS] >>> 4) & 0x3;
}
+ /**
+ * Returns whether this entry should be skipped from the working tree.
+ *
+ * @return true if this entry should be skipepd.
+ */
+ public boolean isSkipWorkTree() {
+ return (getExtendedFlags() & SKIP_WORKTREE) != 0;
+ }
+
+ /**
+ * Returns whether this entry is intent to be added to the Index.
+ *
+ * @return true if this entry is intent to add.
+ */
+ public boolean isIntentToAdd() {
+ return (getExtendedFlags() & INTENT_TO_ADD) != 0;
+ }
+
/**
* Obtain the raw {@link FileMode} bits for this entry.
*
| NB.decodeUInt16(info, infoOffset + P_FLAGS) & ~NAME_MASK);
}
+ /**
+ * @return true if the entry contains extended flags.
+ */
+ boolean isExtended() {
+ return (info[infoOffset + P_FLAGS] & EXTENDED) != 0;
+ }
+
private long decodeTS(final int pIdx) {
final int base = infoOffset + pIdx;
final int sec = NB.decodeInt32(info, base);
NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
}
+ private int getExtendedFlags() {
+ if (isExtended())
+ return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
+ else
+ return 0;
+ }
+
private static String toString(final byte[] path) {
return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
}
}
return componentHasChars;
}
+
+ static int getMaximumInfoLength(boolean extended) {
+ return extended ? INFO_LEN_EXTENDED : INFO_LEN;
+ }
}