DirCache: support option index.skipHash

Support the new option index.skipHash which was introduced in git 2.40
[1]. If it is set to true skip computing the git index checksum. This
accelerates Git commands that manipulate the index, such as git add, git
commit, or git status. Instead of storing the checksum, write a trailing
set of bytes with value zero, indicating that the computation was
skipped.

Accept a skipped checksum consisting of 20 null bytes when reading the
index since the option could have been set to true at the time when the
index was written.

[1] https://git-scm.com/docs/git-config#Documentation/git-config.txt-indexskipHash

Bug: 581723
Change-Id: I28ebe44c5ca1cbcb882438665d686452a0c111b2
This commit is contained in:
Matthias Sohn 2023-03-27 22:23:11 +02:00
parent 3212c8fa38
commit 23b9693a75
5 changed files with 137 additions and 12 deletions

View File

@ -17,19 +17,48 @@
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.junit.MockSystemReader;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class DirCacheBasicTest extends RepositoryTestCase {
@Parameter(0)
public boolean skipHash;
@Parameters(name = "skipHash: {0}")
public static Collection<Boolean[]> getSkipHashValues() {
return Arrays
.asList(new Boolean[][] { { Boolean.TRUE },
{ Boolean.FALSE } });
}
@Before
public void setup() throws IOException {
FileBasedConfig cfg = db.getConfig();
cfg.setBoolean(ConfigConstants.CONFIG_INDEX_SECTION, null,
ConfigConstants.CONFIG_KEY_SKIPHASH, skipHash);
cfg.save();
}
@Test
public void testReadMissing_RealIndex() throws Exception {
final File idx = new File(db.getDirectory(), "index");

View File

@ -19,6 +19,12 @@
<message_argument value="CONFIG_KEY_PRUNE_PRESERVED"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.13.2"/>
<message_argument value="CONFIG_KEY_SKIPHASH"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.13.2"/>

View File

@ -40,6 +40,7 @@
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.internal.storage.io.NullMessageDigest;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.ConfigEnum;
@ -327,6 +328,9 @@ public static DirCache lock(final File indexLocation, final FS fs,
/** If we read this index from disk, the original format. */
private DirCacheVersion version;
/** Whether to skip computing and checking the index checksum */
private boolean skipHash;
/**
* Create a new in-core index representation.
* <p>
@ -446,7 +450,8 @@ public void clear() {
private void readFrom(InputStream inStream) throws IOException,
CorruptObjectException {
final BufferedInputStream in = new BufferedInputStream(inStream);
final MessageDigest md = Constants.newMessageDigest();
readConfig();
MessageDigest md = newMessageDigest();
// Read the index header and verify we understand it.
//
@ -543,8 +548,12 @@ private void readFrom(InputStream inStream) throws IOException,
}
readIndexChecksum = md.digest();
if (!Arrays.equals(readIndexChecksum, hdr)) {
throw new CorruptObjectException(JGitText.get().DIRCChecksumMismatch);
if (!(skipHash
|| Arrays.equals(readIndexChecksum, hdr)
|| Arrays.equals(NullMessageDigest.getInstance().digest(),
hdr))) {
throw new CorruptObjectException(
JGitText.get().DIRCChecksumMismatch);
}
}
@ -627,15 +636,9 @@ public void write() throws IOException {
}
void writeTo(File dir, OutputStream os) throws IOException {
final MessageDigest foot = Constants.newMessageDigest();
final DigestOutputStream dos = new DigestOutputStream(os, foot);
if (version == null && this.repository != null) {
// A new DirCache is being written.
DirCacheConfig config = repository.getConfig()
.get(DirCacheConfig::new);
version = config.getIndexVersion();
}
readConfig();
MessageDigest foot = newMessageDigest();
DigestOutputStream dos = new DigestOutputStream(os, foot);
if (version == null
|| version == DirCacheVersion.DIRC_VERSION_MINIMUM) {
version = DirCacheVersion.DIRC_VERSION_MINIMUM;
@ -707,6 +710,22 @@ void writeTo(File dir, OutputStream os) throws IOException {
os.close();
}
private void readConfig() {
if (version == null && this.repository != null) {
DirCacheConfig config = repository.getConfig()
.get(DirCacheConfig::new);
version = config.getIndexVersion();
skipHash = config.isSkipHash();
}
}
private MessageDigest newMessageDigest() {
if (skipHash) {
return NullMessageDigest.getInstance();
}
return Constants.newMessageDigest();
}
/**
* Commit this change and release the lock.
* <p>
@ -1071,6 +1090,8 @@ private static class DirCacheConfig {
private final DirCacheVersion indexVersion;
private final boolean skipHash;
public DirCacheConfig(Config cfg) {
boolean manyFiles = cfg.getBoolean(
ConfigConstants.CONFIG_FEATURE_SECTION,
@ -1080,11 +1101,16 @@ public DirCacheConfig(Config cfg) {
ConfigConstants.CONFIG_KEY_VERSION,
manyFiles ? DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
: DirCacheVersion.DIRC_VERSION_EXTENDED);
skipHash = cfg.getBoolean(ConfigConstants.CONFIG_INDEX_SECTION,
ConfigConstants.CONFIG_KEY_SKIPHASH, false);
}
public DirCacheVersion getIndexVersion() {
return indexVersion;
}
public boolean isSkipHash() {
return skipHash;
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2023, SAP SE 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.internal.storage.io;
import java.security.MessageDigest;
import org.eclipse.jgit.lib.Constants;
/**
* Dummy message digest consisting of only null bytes with the length of an
* ObjectId. This class can be used to skip computing a real digest.
*/
public final class NullMessageDigest extends MessageDigest {
private static final byte[] digest = new byte[Constants.OBJECT_ID_LENGTH];
private static final NullMessageDigest INSTANCE = new NullMessageDigest();
/**
* Get the only instance of NullMessageDigest
*
* @return the only instance of NullMessageDigest
*/
public static MessageDigest getInstance() {
return INSTANCE;
}
private NullMessageDigest() {
super("null"); //$NON-NLS-1$
}
@Override
protected void engineUpdate(byte input) {
// empty
}
@Override
protected void engineUpdate(byte[] input, int offset, int len) {
// empty
}
@Override
protected byte[] engineDigest() {
return digest;
}
@Override
protected void engineReset() {
// empty
}
}

View File

@ -348,6 +348,12 @@ public final class ConfigConstants {
/** The "indexversion" key */
public static final String CONFIG_KEY_INDEXVERSION = "indexversion";
/**
* The "skiphash" key
* @since 5.13.2
*/
public static final String CONFIG_KEY_SKIPHASH = "skiphash";
/**
* The "hidedotfiles" key
* @since 3.5