Extended flags are processed and available via DirCacheEntry's new isSkipWorkTree() and isIntentToAdd() methods. "resolve-undo" information is completely ignored since its an optional extension. Change-Id: Ie6e9c6784c9f265ca3c013c6dc0e6bd29d3b7233tags/v0.9.1
@@ -44,10 +44,12 @@ | |||
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; | |||
@@ -59,6 +61,7 @@ import org.eclipse.jgit.lib.ObjectId; | |||
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 { | |||
@@ -179,6 +182,35 @@ 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); | |||
} |
@@ -2,6 +2,7 @@ DIRCChecksumMismatch=DIRC checksum mismatch | |||
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 |
@@ -62,6 +62,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String DIRCExtensionIsTooLargeAt; | |||
/***/ public String DIRCExtensionNotSupportedByThisVersion; | |||
/***/ public String DIRCHasTooManyEntries; | |||
/***/ public String DIRCUnrecognizedExtendedFlags; | |||
/***/ public String JRELacksMD5Implementation; | |||
/***/ public String URINotSupported; | |||
/***/ public String URLNotFound; |
@@ -91,8 +91,6 @@ public class DirCache { | |||
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>() { | |||
@@ -322,7 +320,7 @@ public class DirCache { | |||
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(); | |||
@@ -335,7 +333,10 @@ public class DirCache { | |||
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) | |||
@@ -343,10 +344,13 @@ public class DirCache { | |||
// 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. | |||
@@ -484,15 +488,19 @@ public class DirCache { | |||
} | |||
} | |||
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); | |||
@@ -62,6 +62,7 @@ import org.eclipse.jgit.lib.Constants; | |||
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; | |||
/** | |||
@@ -108,12 +109,19 @@ public class DirCacheEntry { | |||
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. */ | |||
@@ -131,13 +139,26 @@ public class DirCacheEntry { | |||
/** 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; | |||
@@ -170,7 +191,7 @@ public class DirCacheEntry { | |||
// 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) { | |||
@@ -258,14 +279,15 @@ public class DirCacheEntry { | |||
} | |||
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); | |||
@@ -400,6 +422,24 @@ public class DirCacheEntry { | |||
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. | |||
* | |||
@@ -575,6 +615,13 @@ public class DirCacheEntry { | |||
| 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); | |||
@@ -588,6 +635,13 @@ public class DirCacheEntry { | |||
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(); | |||
} | |||
@@ -615,4 +669,8 @@ public class DirCacheEntry { | |||
} | |||
return componentHasChars; | |||
} | |||
static int getMaximumInfoLength(boolean extended) { | |||
return extended ? INFO_LEN_EXTENDED : INFO_LEN; | |||
} | |||
} |