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:
parent
72b111ecd7
commit
e9cb0a8e47
Binary file not shown.
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue