Skip to content

Commit ffd9238

Browse files
committed
Merge branch 'ds/omit-trailing-hash-in-index'
Introduce an optional configuration to allow the trailing hash that protects the index file from bit flipping. * ds/omit-trailing-hash-in-index: features: feature.manyFiles implies fast index writes test-lib-functions: add helper for trailing hash read-cache: add index.skipHash config option hashfile: allow skipping the hash function
2 parents ab85a7d + 17194b1 commit ffd9238

File tree

10 files changed

+89
-4
lines changed

10 files changed

+89
-4
lines changed

Documentation/config/feature.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ feature.manyFiles::
2323
working directory. With many files, commands such as `git status` and
2424
`git checkout` may be slow and these new defaults improve performance:
2525
+
26+
* `index.skipHash=true` speeds up index writes by not computing a trailing
27+
checksum. Note that this will cause Git versions earlier than 2.13.0 to
28+
refuse to parse the index and Git versions earlier than 2.40.0 will report
29+
a corrupted index during `git fsck`.
30+
+
2631
* `index.version=4` enables path-prefix compression in the index.
2732
+
2833
* `core.untrackedCache=true` enables the untracked cache. This setting assumes

Documentation/config/index.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,14 @@ index.version::
3030
Specify the version with which new index files should be
3131
initialized. This does not affect existing repositories.
3232
If `feature.manyFiles` is enabled, then the default is 4.
33+
34+
index.skipHash::
35+
When enabled, do not compute the trailing hash for the index file.
36+
This accelerates Git commands that manipulate the index, such as
37+
`git add`, `git commit`, or `git status`. Instead of storing the
38+
checksum, write a trailing set of bytes with value zero, indicating
39+
that the computation was skipped.
40+
+
41+
If you enable `index.skipHash`, then Git clients older than 2.13.0 will
42+
refuse to parse the index and Git clients older than 2.40.0 will report an
43+
error during `git fsck`.

csum-file.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ void hashflush(struct hashfile *f)
4545
unsigned offset = f->offset;
4646

4747
if (offset) {
48-
the_hash_algo->update_fn(&f->ctx, f->buffer, offset);
48+
if (!f->skip_hash)
49+
the_hash_algo->update_fn(&f->ctx, f->buffer, offset);
4950
flush(f, f->buffer, offset);
5051
f->offset = 0;
5152
}
@@ -64,7 +65,12 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result,
6465
int fd;
6566

6667
hashflush(f);
67-
the_hash_algo->final_fn(f->buffer, &f->ctx);
68+
69+
if (f->skip_hash)
70+
hashclr(f->buffer);
71+
else
72+
the_hash_algo->final_fn(f->buffer, &f->ctx);
73+
6874
if (result)
6975
hashcpy(result, f->buffer);
7076
if (flags & CSUM_HASH_IN_STREAM)
@@ -108,7 +114,8 @@ void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
108114
* the hashfile's buffer. In this block,
109115
* f->offset is necessarily zero.
110116
*/
111-
the_hash_algo->update_fn(&f->ctx, buf, nr);
117+
if (!f->skip_hash)
118+
the_hash_algo->update_fn(&f->ctx, buf, nr);
112119
flush(f, buf, nr);
113120
} else {
114121
/*
@@ -153,6 +160,7 @@ static struct hashfile *hashfd_internal(int fd, const char *name,
153160
f->tp = tp;
154161
f->name = name;
155162
f->do_crc = 0;
163+
f->skip_hash = 0;
156164
the_hash_algo->init_fn(&f->ctx);
157165

158166
f->buffer_len = buffer_len;

csum-file.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ struct hashfile {
2020
size_t buffer_len;
2121
unsigned char *buffer;
2222
unsigned char *check_buffer;
23+
24+
/**
25+
* If non-zero, skip_hash indicates that we should
26+
* not actually compute the hash for this hashfile and
27+
* instead only use it as a buffered write.
28+
*/
29+
int skip_hash;
2330
};
2431

2532
/* Checkpoint */

read-cache.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,8 @@ static int verify_hdr(const struct cache_header *hdr, unsigned long size)
18171817
git_hash_ctx c;
18181818
unsigned char hash[GIT_MAX_RAWSZ];
18191819
int hdr_version;
1820+
unsigned char *start, *end;
1821+
struct object_id oid;
18201822

18211823
if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
18221824
return error(_("bad signature 0x%08x"), hdr->hdr_signature);
@@ -1827,10 +1829,16 @@ static int verify_hdr(const struct cache_header *hdr, unsigned long size)
18271829
if (!verify_index_checksum)
18281830
return 0;
18291831

1832+
end = (unsigned char *)hdr + size;
1833+
start = end - the_hash_algo->rawsz;
1834+
oidread(&oid, start);
1835+
if (oideq(&oid, null_oid()))
1836+
return 0;
1837+
18301838
the_hash_algo->init_fn(&c);
18311839
the_hash_algo->update_fn(&c, hdr, size - the_hash_algo->rawsz);
18321840
the_hash_algo->final_fn(hash, &c);
1833-
if (!hasheq(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz))
1841+
if (!hasheq(hash, start))
18341842
return error(_("bad index file sha1 signature"));
18351843
return 0;
18361844
}
@@ -2920,9 +2928,13 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
29202928
int ieot_entries = 1;
29212929
struct index_entry_offset_table *ieot = NULL;
29222930
int nr, nr_threads;
2931+
struct repository *r = istate->repo ? istate->repo : the_repository;
29232932

29242933
f = hashfd(tempfile->fd, tempfile->filename.buf);
29252934

2935+
prepare_repo_settings(r);
2936+
f->skip_hash = r->settings.index_skip_hash;
2937+
29262938
for (i = removed = extended = 0; i < entries; i++) {
29272939
if (cache[i]->ce_flags & CE_REMOVE)
29282940
removed++;

repo-settings.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ void prepare_repo_settings(struct repository *r)
4747
}
4848
if (manyfiles) {
4949
r->settings.index_version = 4;
50+
r->settings.index_skip_hash = 1;
5051
r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;
5152
}
5253

@@ -61,6 +62,7 @@ void prepare_repo_settings(struct repository *r)
6162
repo_cfg_bool(r, "pack.usesparse", &r->settings.pack_use_sparse, 1);
6263
repo_cfg_bool(r, "core.multipackindex", &r->settings.core_multi_pack_index, 1);
6364
repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 0);
65+
repo_cfg_bool(r, "index.skiphash", &r->settings.index_skip_hash, r->settings.index_skip_hash);
6466

6567
/*
6668
* The GIT_TEST_MULTI_PACK_INDEX variable is special in that

repository.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct repo_settings {
4242
struct fsmonitor_settings *fsmonitor; /* lazily loaded */
4343

4444
int index_version;
45+
int index_skip_hash;
4546
enum untracked_cache_setting core_untracked_cache;
4647

4748
int pack_use_sparse;

scalar.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ static int set_recommended_config(int reconfigure)
143143
{ "credential.validate", "false", 1 }, /* GCM4W-only */
144144
{ "gc.auto", "0", 1 },
145145
{ "gui.GCWarning", "false", 1 },
146+
{ "index.skipHash", "false", 1 },
146147
{ "index.threads", "true", 1 },
147148
{ "index.version", "4", 1 },
148149
{ "merge.stat", "false", 1 },

t/t1600-index.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,36 @@ test_expect_success 'out of bounds index.version issues warning' '
6565
)
6666
'
6767

68+
test_expect_success 'index.skipHash config option' '
69+
rm -f .git/index &&
70+
git -c index.skipHash=true add a &&
71+
test_trailing_hash .git/index >hash &&
72+
echo $(test_oid zero) >expect &&
73+
test_cmp expect hash &&
74+
git fsck &&
75+
76+
rm -f .git/index &&
77+
git -c feature.manyFiles=true add a &&
78+
test_trailing_hash .git/index >hash &&
79+
cmp expect hash &&
80+
81+
rm -f .git/index &&
82+
git -c feature.manyFiles=true \
83+
-c index.skipHash=false add a &&
84+
test_trailing_hash .git/index >hash &&
85+
! cmp expect hash &&
86+
87+
test_commit start &&
88+
git -c protocol.file.allow=always submodule add ./ sub &&
89+
git config index.skipHash false &&
90+
git -C sub config index.skipHash true &&
91+
>sub/file &&
92+
git -C sub add a &&
93+
test_trailing_hash .git/modules/sub/index >hash &&
94+
test_cmp expect hash &&
95+
git -C sub fsck
96+
'
97+
6898
test_index_version () {
6999
INDEX_VERSION_CONFIG=$1 &&
70100
FEATURE_MANY_FILES=$2 &&

t/test-lib-functions.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1816,3 +1816,11 @@ test_cmp_config_output () {
18161816
sort config-actual >sorted-actual &&
18171817
test_cmp sorted-expect sorted-actual
18181818
}
1819+
1820+
# Given a filename, extract its trailing hash as a hex string
1821+
test_trailing_hash () {
1822+
local file="$1" &&
1823+
tail -c $(test_oid rawsz) "$file" |
1824+
test-tool hexdump |
1825+
sed "s/ //g"
1826+
}

0 commit comments

Comments
 (0)