diff options
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 Binary files differnew file mode 100644 index 0000000000..de49e56249 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4 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"; + } |