summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4bin0 -> 483 bytes
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheAfterCloneTest.java134
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java32
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java102
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java135
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java176
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java31
9 files changed, 552 insertions, 62 deletions
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4 b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4
new file mode 100644
index 0000000000..de49e56249
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4
Binary files differ
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheAfterCloneTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheAfterCloneTest.java
new file mode 100644
index 0000000000..f210760bf5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheAfterCloneTest.java
@@ -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());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
index c57cb263d1..6d4d0b4ab3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
@@ -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.Iterator;
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 class DirCacheCGitCompatabilityTest extends LocalDiskRepositoryTestCase {
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 @@ public class DirCacheCGitCompatabilityTest extends LocalDiskRepositoryTestCase {
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);
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
index f21c7f854f..5477f565f4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
@@ -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 @@ public class DirCacheEntryTest {
}
}
+ 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() {
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 899a799989..70883352ba 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -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.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index b2764d7804..bb725b7a35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -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.JGitText;
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 class DirCache {
/** 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 class DirCache {
return new DirCacheEditor(this, entryCnt + 16);
}
+ DirCacheVersion getVersion() {
+ return version;
+ }
+
void replace(DirCacheEntry[] e, int cnt) {
sortedEntries = e;
entryCnt = cnt;
@@ -445,13 +455,26 @@ public class DirCache {
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 @@ public class DirCache {
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 @@ public class DirCache {
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 @@ public class DirCache {
//
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 @@ public class DirCache {
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 @@ public class DirCache {
}
}
}
+
+ 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;
+ }
+
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index ced379ff1d..e7d62c7e9d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -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.text.MessageFormat;
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 class DirCacheEntry {
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);
+ }
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 2976ab65fc..0e35a9947b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -252,6 +252,8 @@ public class JGitText extends TranslationBundle {
/***/ public String dirCacheFileIsNotLocked;
/***/ public String dirCacheIsNotLocked;
/***/ public String DIRCChecksumMismatch;
+ /***/ public String DIRCCorruptLength;
+ /***/ public String DIRCCorruptLengthFirst;
/***/ public String DIRCExtensionIsTooLargeAt;
/***/ public String DIRCExtensionNotSupportedByThisVersion;
/***/ public String DIRCHasTooManyEntries;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index eef822fa4b..4fcf8e2dcd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -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";
+
}