DirCache: support index V4

Index format version 4 was introduced in C git in 2012. It's about
time that JGit can deal with it.

Version 4 added prefix path compression. Instead of writing the full
path for each index entry to disk, only the difference to the previous
entry's path is written: a variable-encoded int telling how many bytes
to remove from the previous entry's path to get the common prefix,
followed by the new suffix.

Also, cache entries in a version 4 index are not padded anymore.

Internally, version 3 and version 4 index entries are identical; it's
only the stored format that changes.

Implement this path compression, and make sure we write an index file
that we read previously in the same format. (Only changing from version
2 to version 3 if there are extended flags.)

Add support for the "feature.manyFiles" and the "index.version" git
configs, and honor them when writing a new index file.

Add tests, including a compatibility test that verifies that JGit can
read a version 4 index generated by C git and write an identical
version 4 index.

Bug: 565774
Change-Id: Id83241cf009e50f950eb42f8d56b834fb47da1ed
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2020-08-10 21:38:50 +02:00
parent 72b111ecd7
commit e9cb0a8e47
9 changed files with 552 additions and 62 deletions

View File

@ -0,0 +1,134 @@
/*
* Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> 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.dircache;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.EnumSet;
import java.util.Set;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Test;
/**
* Tests for initial DirCache version after a clone or after a mixed or hard
* reset.
*/
public class DirCacheAfterCloneTest extends RepositoryTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
try (Git git = new Git(db)) {
writeTrashFile("Test.txt", "Hello world");
git.add().addFilepattern("Test.txt").call();
git.commit().setMessage("Initial commit").call();
}
}
private DirCacheVersion cloneAndCheck(Set<DirCacheVersion> expected)
throws Exception {
File directory = createTempDirectory("testCloneRepository");
CloneCommand command = Git.cloneRepository();
command.setDirectory(directory);
command.setURI("file://" + db.getWorkTree().getAbsolutePath());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
DirCache dc = DirCache.read(git2.getRepository());
DirCacheVersion version = dc.getVersion();
assertTrue(expected.contains(version));
return version;
}
@Test
public void testCloneV3OrV2() throws Exception {
cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
DirCacheVersion.DIRC_VERSION_EXTENDED));
}
@Test
public void testCloneV4() throws Exception {
StoredConfig cfg = SystemReader.getInstance().getUserConfig();
cfg.load();
cfg.setInt("index", null, "version", 4);
cfg.save();
cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS));
}
@Test
public void testCloneV4manyFiles() throws Exception {
StoredConfig cfg = SystemReader.getInstance().getUserConfig();
cfg.load();
cfg.setBoolean("feature", null, "manyFiles", true);
cfg.save();
cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS));
}
@Test
public void testCloneV3CommitNoVersionChange() throws Exception {
DirCacheVersion initial = cloneAndCheck(
EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
DirCacheVersion.DIRC_VERSION_EXTENDED));
StoredConfig cfg = db.getConfig();
cfg.setInt("index", null, "version", 4);
cfg.save();
try (Git git = new Git(db)) {
writeTrashFile("Test.txt2", "Hello again");
git.add().addFilepattern("Test.txt2").call();
git.commit().setMessage("Second commit").call();
}
assertEquals("DirCache version should be unchanged", initial,
DirCache.read(db).getVersion());
}
@Test
public void testCloneV3ResetHardVersionChange() throws Exception {
cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
DirCacheVersion.DIRC_VERSION_EXTENDED));
StoredConfig cfg = db.getConfig();
cfg.setInt("index", null, "version", 4);
cfg.save();
FileUtils.delete(new File(db.getDirectory(), "index"));
try (Git git = new Git(db)) {
git.reset().setMode(ResetType.HARD).call();
}
assertEquals("DirCache version should have changed",
DirCacheVersion.DIRC_VERSION_PATHCOMPRESS,
DirCache.read(db).getVersion());
}
@Test
public void testCloneV3ResetMixedVersionChange() throws Exception {
cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
DirCacheVersion.DIRC_VERSION_EXTENDED));
StoredConfig cfg = db.getConfig();
cfg.setInt("index", null, "version", 4);
cfg.save();
FileUtils.delete(new File(db.getDirectory(), "index"));
try (Git git = new Git(db)) {
git.reset().setMode(ResetType.MIXED).call();
}
assertEquals("DirCache version should have changed",
DirCacheVersion.DIRC_VERSION_PATHCOMPRESS,
DirCache.read(db).getVersion());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2010, Google Inc. and others
* Copyright (C) 2008, 2020, Google Inc. 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
@ -28,6 +28,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
@ -188,6 +189,28 @@ public void testReadWriteV3() throws Exception {
assertArrayEquals(expectedBytes, indexBytes);
}
@Test
public void testReadWriteV4() throws Exception {
final File file = pathOf("gitgit.index.v4");
final DirCache dc = new DirCache(file, FS.DETECTED);
dc.read();
assertEquals(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS,
dc.getVersion());
assertEquals(5, dc.getEntryCount());
assertV4TreeEntry(0, "src/org/eclipse/jgit/atest/foo.txt", false, dc);
assertV4TreeEntry(1, "src/org/eclipse/jgit/atest/foobar.txt", false,
dc);
assertV4TreeEntry(2, "src/org/eclipse/jgit/other/bar.txt", true, dc);
assertV4TreeEntry(3, "test.txt", false, dc);
assertV4TreeEntry(4, "test.txt2", false, dc);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
dc.writeTo(null, bos);
final byte[] indexBytes = bos.toByteArray();
final byte[] expectedBytes = IO.readFully(file);
assertArrayEquals(expectedBytes, indexBytes);
}
private static void assertV3TreeEntry(int indexPosition, String path,
boolean skipWorkTree, boolean intentToAdd, DirCache dc) {
final DirCacheEntry entry = dc.getEntry(indexPosition);
@ -196,6 +219,13 @@ private static void assertV3TreeEntry(int indexPosition, String path,
assertEquals(intentToAdd, entry.isIntentToAdd());
}
private static void assertV4TreeEntry(int indexPosition, String path,
boolean skipWorkTree, DirCache dc) {
final DirCacheEntry entry = dc.getEntry(indexPosition);
assertEquals(path, entry.getPathString());
assertEquals(skipWorkTree, entry.isSkipWorkTree());
}
private static File pathOf(String name) {
return JGitTestUtil.getTestResourceFile(name);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2009, Google Inc. and others
* Copyright (C) 2009, 2020 Google Inc. 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
@ -11,14 +11,25 @@
package org.eclipse.jgit.dircache;
import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.MutableInteger;
import org.junit.Test;
public class DirCacheEntryTest {
@ -47,6 +58,95 @@ private static boolean isValidPath(String path) {
}
}
private static void checkPath(DirCacheVersion indexVersion,
DirCacheEntry previous, String name) throws IOException {
DirCacheEntry dce = new DirCacheEntry(name);
long now = System.currentTimeMillis();
long anHourAgo = now - TimeUnit.HOURS.toMillis(1);
dce.setLastModified(Instant.ofEpochMilli(anHourAgo));
ByteArrayOutputStream out = new ByteArrayOutputStream();
dce.write(out, indexVersion, previous);
byte[] raw = out.toByteArray();
MessageDigest md0 = Constants.newMessageDigest();
md0.update(raw);
ByteArrayInputStream in = new ByteArrayInputStream(raw);
MutableInteger infoAt = new MutableInteger();
byte[] sharedInfo = new byte[raw.length];
MessageDigest md = Constants.newMessageDigest();
DirCacheEntry read = new DirCacheEntry(sharedInfo, infoAt, in, md,
Instant.ofEpochMilli(now), indexVersion, previous);
assertEquals("Paths of length " + name.length() + " should match", name,
read.getPathString());
assertEquals("Should have been fully read", -1, in.read());
assertArrayEquals("Digests should match", md0.digest(),
md.digest());
}
@Test
public void testLongPath() throws Exception {
StringBuilder name = new StringBuilder(4094 + 16);
for (int i = 0; i < 4094; i++) {
name.append('a');
}
for (int j = 0; j < 16; j++) {
checkPath(DirCacheVersion.DIRC_VERSION_EXTENDED, null,
name.toString());
name.append('b');
}
}
@Test
public void testLongPathV4() throws Exception {
StringBuilder name = new StringBuilder(4094 + 16);
for (int i = 0; i < 4094; i++) {
name.append('a');
}
DirCacheEntry previous = new DirCacheEntry(name.toString());
for (int j = 0; j < 16; j++) {
checkPath(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS, previous,
name.toString());
name.append('b');
}
}
@Test
public void testShortPath() throws Exception {
StringBuilder name = new StringBuilder(1 + 16);
name.append('a');
for (int j = 0; j < 16; j++) {
checkPath(DirCacheVersion.DIRC_VERSION_EXTENDED, null,
name.toString());
name.append('b');
}
}
@Test
public void testShortPathV4() throws Exception {
StringBuilder name = new StringBuilder(1 + 16);
name.append('a');
DirCacheEntry previous = new DirCacheEntry(name.toString());
for (int j = 0; j < 16; j++) {
checkPath(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS, previous,
name.toString());
name.append('b');
}
}
@Test
public void testPathV4() throws Exception {
StringBuilder name = new StringBuilder();
for (int i = 0; i < 20; i++) {
name.append('a');
}
DirCacheEntry previous = new DirCacheEntry(name.toString());
for (int j = 0; j < 20; j++) {
name.setLength(name.length() - 1);
String newName = name.toString() + "bbb";
checkPath(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS, previous,
newName);
}
}
@SuppressWarnings("unused")
@Test
public void testCreate_ByStringPath() {

View File

@ -224,6 +224,8 @@ dirCacheDoesNotHaveABackingFile=DirCache does not have a backing file
dirCacheFileIsNotLocked=DirCache {0} not locked
dirCacheIsNotLocked=DirCache is not locked
DIRCChecksumMismatch=DIRC checksum mismatch
DIRCCorruptLength=DIRC variable int {0} invalid after entry for {1}
DIRCCorruptLengthFirst=DIRC variable int {0} invalid in first entry
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.

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008, 2010, Google Inc.
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com> and others
* Copyright (C) 2011, 2020, Matthias Sohn <matthias.sohn@sap.com> 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
@ -41,6 +41,9 @@
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
import org.eclipse.jgit.internal.storage.file.LockFile;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.ConfigEnum;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@ -321,6 +324,9 @@ public static DirCache lock(final File indexLocation, final FS fs,
/** Repository containing this index */
private Repository repository;
/** If we read this index from disk, the original format. */
private DirCacheVersion version;
/**
* Create a new in-core index representation.
* <p>
@ -364,6 +370,10 @@ public DirCacheEditor editor() {
return new DirCacheEditor(this, entryCnt + 16);
}
DirCacheVersion getVersion() {
return version;
}
void replace(DirCacheEntry[] e, int cnt) {
sortedEntries = e;
entryCnt = cnt;
@ -445,13 +455,26 @@ private void readFrom(InputStream inStream) throws IOException,
md.update(hdr, 0, 12);
if (!is_DIRC(hdr))
throw new CorruptObjectException(JGitText.get().notADIRCFile);
final int ver = NB.decodeInt32(hdr, 4);
int versionCode = NB.decodeInt32(hdr, 4);
DirCacheVersion ver = DirCacheVersion.fromInt(versionCode);
if (ver == null) {
throw new CorruptObjectException(
MessageFormat.format(JGitText.get().unknownDIRCVersion,
Integer.valueOf(versionCode)));
}
boolean extended = false;
if (ver == 3)
switch (ver) {
case DIRC_VERSION_MINIMUM:
break;
case DIRC_VERSION_EXTENDED:
case DIRC_VERSION_PATHCOMPRESS:
extended = true;
else if (ver != 2)
throw new CorruptObjectException(MessageFormat.format(
JGitText.get().unknownDIRCVersion, Integer.valueOf(ver)));
break;
default:
throw new CorruptObjectException(MessageFormat
.format(JGitText.get().unknownDIRCVersion, ver));
}
version = ver;
entryCnt = NB.decodeInt32(hdr, 8);
if (entryCnt < 0)
throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
@ -467,7 +490,8 @@ else if (ver != 2)
final MutableInteger infoAt = new MutableInteger();
for (int i = 0; i < entryCnt; i++) {
sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge);
sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge,
version, i == 0 ? null : sortedEntries[i - 1]);
}
// After the file entries are index extensions, and then a footer.
@ -606,11 +630,20 @@ void writeTo(File dir, 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++) {
if (sortedEntries[i].isExtended()) {
extended = true;
break;
if (version == null && this.repository != null) {
// A new DirCache is being written.
DirCacheConfig config = repository.getConfig()
.get(DirCacheConfig::new);
version = config.getIndexVersion();
}
if (version == null
|| version == DirCacheVersion.DIRC_VERSION_MINIMUM) {
version = DirCacheVersion.DIRC_VERSION_MINIMUM;
for (int i = 0; i < entryCnt; i++) {
if (sortedEntries[i].isExtended()) {
version = DirCacheVersion.DIRC_VERSION_EXTENDED;
break;
}
}
}
@ -618,7 +651,7 @@ void writeTo(File dir, OutputStream os) throws IOException {
//
final byte[] tmp = new byte[128];
System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
NB.encodeInt32(tmp, 4, extended ? 3 : 2);
NB.encodeInt32(tmp, 4, version.getVersionCode());
NB.encodeInt32(tmp, 8, entryCnt);
dos.write(tmp, 0, 12);
@ -650,7 +683,7 @@ void writeTo(File dir, OutputStream os) throws IOException {
if (e.mightBeRacilyClean(smudge)) {
e.smudgeRacilyClean();
}
e.write(dos);
e.write(dos, version, i == 0 ? null : sortedEntries[i - 1]);
}
if (writeTree) {
@ -982,4 +1015,76 @@ private void updateSmudgedEntries() throws IOException {
}
}
}
enum DirCacheVersion implements ConfigEnum {
/** Minimum index version on-disk format that we support. */
DIRC_VERSION_MINIMUM(2),
/** Version 3 supports extended flags. */
DIRC_VERSION_EXTENDED(3),
/**
* Version 4 adds very simple "path compression": it strips out the
* common prefix between the last entry written and the current entry.
* Instead of writing two entries with paths "foo/bar/baz/a.txt" and
* "foo/bar/baz/b.txt" it only writes "b.txt" for the second entry.
* <p>
* It is also <em>not</em> padded.
* </p>
*/
DIRC_VERSION_PATHCOMPRESS(4);
private int version;
private DirCacheVersion(int versionCode) {
this.version = versionCode;
}
public int getVersionCode() {
return version;
}
@Override
public String toConfigValue() {
return Integer.toString(version);
}
@Override
public boolean matchConfigValue(String in) {
try {
return version == Integer.parseInt(in);
} catch (NumberFormatException e) {
return false;
}
}
public static DirCacheVersion fromInt(int val) {
for (DirCacheVersion v : DirCacheVersion.values()) {
if (val == v.getVersionCode()) {
return v;
}
}
return null;
}
}
private static class DirCacheConfig {
private final DirCacheVersion indexVersion;
public DirCacheConfig(Config cfg) {
boolean manyFiles = cfg.getBoolean(
ConfigConstants.CONFIG_FEATURE_SECTION,
ConfigConstants.CONFIG_KEY_MANYFILES, false);
indexVersion = cfg.getEnum(DirCacheVersion.values(),
ConfigConstants.CONFIG_INDEX_SECTION, null,
ConfigConstants.CONFIG_KEY_VERSION,
manyFiles ? DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
: DirCacheVersion.DIRC_VERSION_EXTENDED);
}
public DirCacheVersion getIndexVersion() {
return indexVersion;
}
}
}

View File

@ -1,8 +1,8 @@
/*
* Copyright (C) 2008-2009, Google Inc.
* Copyright (C) 2008, 2009, Google Inc.
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
* Copyright (C) 2010, 2020, Christian Halstrick <christian.halstrick@sap.com> 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
@ -26,6 +26,7 @@
import java.time.Instant;
import java.util.Arrays;
import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
@ -112,15 +113,16 @@ public class DirCacheEntry {
/** Flags which are never stored to disk. */
private byte inCoreFlags;
DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
final InputStream in, final MessageDigest md, final Instant smudge)
DirCacheEntry(byte[] sharedInfo, MutableInteger infoAt, InputStream in,
MessageDigest md, Instant smudge, DirCacheVersion version,
DirCacheEntry previous)
throws IOException {
info = sharedInfo;
infoOffset = infoAt.value;
IO.readFully(in, info, infoOffset, INFO_LEN);
final int len;
int len;
if (isExtended()) {
len = INFO_LEN_EXTENDED;
IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN);
@ -134,31 +136,66 @@ public class DirCacheEntry {
infoAt.value += len;
md.update(info, infoOffset, len);
int toRemove = 0;
if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
// Read variable int and update digest
int b = in.read();
md.update((byte) b);
toRemove = b & 0x7F;
while ((b & 0x80) != 0) {
toRemove++;
b = in.read();
md.update((byte) b);
toRemove = (toRemove << 7) | (b & 0x7F);
}
if (toRemove < 0
|| previous != null && toRemove > previous.path.length) {
if (previous == null) {
throw new IOException(MessageFormat.format(
JGitText.get().DIRCCorruptLengthFirst,
Integer.valueOf(toRemove)));
}
throw new IOException(MessageFormat.format(
JGitText.get().DIRCCorruptLength,
Integer.valueOf(toRemove), previous.getPathString()));
}
}
int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
int skipped = 0;
if (pathLen < NAME_MASK) {
path = new byte[pathLen];
IO.readFully(in, path, 0, pathLen);
md.update(path, 0, pathLen);
} else {
final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
{
final byte[] buf = new byte[NAME_MASK];
IO.readFully(in, buf, 0, NAME_MASK);
tmp.write(buf);
}
for (;;) {
final int c = in.read();
if (c < 0)
throw new EOFException(JGitText.get().shortReadOfBlock);
if (c == 0)
break;
tmp.write(c);
if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
&& previous != null) {
System.arraycopy(previous.path, 0, path, 0,
previous.path.length - toRemove);
IO.readFully(in, path, previous.path.length - toRemove,
pathLen - (previous.path.length - toRemove));
md.update(path, previous.path.length - toRemove,
pathLen - (previous.path.length - toRemove));
pathLen = pathLen - (previous.path.length - toRemove);
} else {
IO.readFully(in, path, 0, pathLen);
md.update(path, 0, pathLen);
}
} else if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
|| previous == null || toRemove == previous.path.length) {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
byte[] buf = new byte[NAME_MASK];
IO.readFully(in, buf, 0, NAME_MASK);
tmp.write(buf);
readNulTerminatedString(in, tmp);
path = tmp.toByteArray();
pathLen = path.length;
skipped = 1; // we already skipped 1 '\0' above to break the loop.
md.update(path, 0, pathLen);
skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString
md.update((byte) 0);
} else {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
tmp.write(previous.path, 0, previous.path.length - toRemove);
pathLen = readNulTerminatedString(in, tmp);
path = tmp.toByteArray();
md.update(path, previous.path.length - toRemove, pathLen);
skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString
md.update((byte) 0);
}
@ -172,17 +209,26 @@ public class DirCacheEntry {
throw p;
}
// 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 = len + pathLen;
final int expLen = (actLen + 8) & ~7;
final int padLen = expLen - actLen - skipped;
if (padLen > 0) {
IO.skipFully(in, padLen);
md.update(nullpad, 0, padLen);
if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
if (skipped == 0) {
int b = in.read();
if (b < 0) {
throw new EOFException(JGitText.get().shortReadOfBlock);
}
md.update((byte) b);
}
} else {
// 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 = len + pathLen;
final int expLen = (actLen + 8) & ~7;
final int padLen = expLen - actLen - skipped;
if (padLen > 0) {
IO.skipFully(in, padLen);
md.update(nullpad, 0, padLen);
}
}
if (mightBeRacilyClean(smudge)) {
smudgeRacilyClean();
}
@ -283,19 +329,61 @@ public DirCacheEntry(DirCacheEntry src) {
System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN);
}
void write(OutputStream os) throws IOException {
final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
final int pathLen = path.length;
os.write(info, infoOffset, len);
os.write(path, 0, pathLen);
private int readNulTerminatedString(InputStream in, OutputStream out)
throws IOException {
int n = 0;
for (;;) {
int c = in.read();
if (c < 0) {
throw new EOFException(JGitText.get().shortReadOfBlock);
}
if (c == 0) {
break;
}
out.write(c);
n++;
}
return n;
}
// 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 = len + pathLen;
final int expLen = (actLen + 8) & ~7;
if (actLen != expLen)
os.write(nullpad, 0, expLen - actLen);
void write(OutputStream os, DirCacheVersion version, DirCacheEntry previous)
throws IOException {
final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
os.write(info, infoOffset, len);
os.write(path, 0, path.length);
// Index records are padded out to the next 8 byte alignment
// for historical reasons related to how C Git read the files.
//
int entryLen = len + path.length;
int expLen = (entryLen + 8) & ~7;
if (entryLen != expLen)
os.write(nullpad, 0, expLen - entryLen);
} else {
int pathCommon = 0;
int toRemove;
if (previous != null) {
// Figure out common prefix
int pathLen = Math.min(path.length, previous.path.length);
while (pathCommon < pathLen
&& path[pathCommon] == previous.path[pathCommon]) {
pathCommon++;
}
toRemove = previous.path.length - pathCommon;
} else {
toRemove = 0;
}
byte[] tmp = new byte[16];
int n = tmp.length;
tmp[--n] = (byte) (toRemove & 0x7F);
while ((toRemove >>>= 7) != 0) {
tmp[--n] = (byte) (0x80 | (--toRemove & 0x7F));
}
os.write(info, infoOffset, len);
os.write(tmp, n, tmp.length - n);
os.write(path, pathCommon, path.length - pathCommon);
os.write(0);
}
}
/**

View File

@ -252,6 +252,8 @@ public static JGitText get() {
/***/ public String dirCacheFileIsNotLocked;
/***/ public String dirCacheIsNotLocked;
/***/ public String DIRCChecksumMismatch;
/***/ public String DIRCCorruptLength;
/***/ public String DIRCCorruptLengthFirst;
/***/ public String DIRCExtensionIsTooLargeAt;
/***/ public String DIRCExtensionNotSupportedByThisVersion;
/***/ public String DIRCHasTooManyEntries;

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
* Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
* Copyright (C) 2012-2013, Robin Rosenberg and others
* Copyright (C) 2012, 2020, Robin Rosenberg 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
@ -662,4 +662,33 @@ public final class ConfigConstants {
* @since 5.8
*/
public static final String CONFIG_KEY_WINDOW_MEMORY = "windowmemory";
/**
* The "feature" section
*
* @since 5.9
*/
public static final String CONFIG_FEATURE_SECTION = "feature";
/**
* The "feature.manyFiles" key
*
* @since 5.9
*/
public static final String CONFIG_KEY_MANYFILES = "manyFiles";
/**
* The "index" section
*
* @since 5.9
*/
public static final String CONFIG_INDEX_SECTION = "index";
/**
* The "index.version" key
*
* @since 5.9
*/
public static final String CONFIG_KEY_VERSION = "version";
}