mirror of
https://github.com/git/git.git
synced 2026-08-09 17:40:16 +00:00
Blame and "git log --stat" recover hunk coordinates by diffing blob pairs, and recompute them on every run. Add a cache of those coordinates at $GIT_DIR/objects/info/diff-hunks, beside the commit-graph, so a later run can look them up instead of decompressing the blobs and running xdiff again. The store is a single chunk-format file (see gitformat-chunk(5)): an 8-byte header, a DHIX index of fixed-size entries sorted by key, a DHDT segment of hunk records, and a trailing hash checksum. An entry is keyed by the two blob object ids and the xdl_opts the pair was diffed under, so a stored result is served only where that exact key recurs, independent of path. A zero-context diff trims unchanged lines from hunk edges and can pick a different but equally valid set of hunks than an untrimmed diff, so a recording caller stores a pair only when its trimmed and untrimmed diffs are identical; such an entry answers any consumer at any context, and the rare divergent pair is always computed. Identical hunk blocks are interned once and shared across keys. The library provides a reader (repo_diff_hunks_store and _replay, gated by core.diffHunks), loaded once and cached on the object database as the commit-graph is, and a writer that accumulates entries and flushes them in one atomic pass. An absent, corrupt, or disabled store reads as all misses. A record with no hunks is invalid too: replaying it would claim the pair equivalent, which the store never asserts, so it reads as a miss. Ordinary reads are diagnostic-free. Loading parses the chunk table through read_table_of_contents_quiet(), new in chunk-format, which prints nothing on a malformed table and takes the repository's hash algorithm rather than the_hash_algo, so the file is bounds-checked under the algorithm it is keyed by. The flush closes the repository's mmapped store and forgets that loading was attempted before committing the lockfile. A warming run that also reads may hold the file it is replacing mapped, and the rename must not land on a live mapping, which Windows refuses; a read after the flush then observes the committed file. commit-graph closes its graph before committing for the same reason. Writing is off by default, enabled per run by GIT_DIFF_HUNKS_WRITE or persistently by diffHunks.write, the environment winning. A writer seeds from the existing store, so a flush merges rather than replaces. The seed's checksum is verified first: a corrupt store is discarded, not rewritten with a fresh checksum verify could no longer catch. An entry that fails the shared diff_provider_check_hunk() or names no blob is dropped with a warning, since it would only ever read as a miss. A seed that discarded or dropped anything forces the flush even when the warming run computed nothing new. The writer fsyncs through a new diff-hunks core.fsync component. "git diff-hunks" inspects and manages the file: "verify" checks the checksum, chunk table, sort order, entry bounds, and every entry's hunk sequence against that shared check, so a store whose entries could only read as misses fails verify; "clear" removes the file. Later patches wire the readers and the writer into the diff and blame paths. Signed-off-by: Michael Montalbo <mmontalbo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
248 lines
5.6 KiB
C
248 lines
5.6 KiB
C
#define USE_THE_REPOSITORY_VARIABLE
|
|
#define DISABLE_SIGN_COMPARE_WARNINGS
|
|
|
|
#include "git-compat-util.h"
|
|
#include "chunk-format.h"
|
|
#include "csum-file.h"
|
|
#include "gettext.h"
|
|
#include "hash.h"
|
|
#include "trace2.h"
|
|
|
|
/*
|
|
* When writing a chunk-based file format, collect the chunks in
|
|
* an array of chunk_info structs. The size stores the _expected_
|
|
* amount of data that will be written by write_fn.
|
|
*/
|
|
struct chunk_info {
|
|
uint32_t id;
|
|
uint64_t size;
|
|
chunk_write_fn write_fn;
|
|
|
|
const void *start;
|
|
};
|
|
|
|
struct chunkfile {
|
|
struct hashfile *f;
|
|
|
|
struct chunk_info *chunks;
|
|
size_t chunks_nr;
|
|
size_t chunks_alloc;
|
|
};
|
|
|
|
struct chunkfile *init_chunkfile(struct hashfile *f)
|
|
{
|
|
struct chunkfile *cf = xcalloc(1, sizeof(*cf));
|
|
cf->f = f;
|
|
return cf;
|
|
}
|
|
|
|
void free_chunkfile(struct chunkfile *cf)
|
|
{
|
|
if (!cf)
|
|
return;
|
|
free(cf->chunks);
|
|
free(cf);
|
|
}
|
|
|
|
int get_num_chunks(struct chunkfile *cf)
|
|
{
|
|
return cf->chunks_nr;
|
|
}
|
|
|
|
void add_chunk(struct chunkfile *cf,
|
|
uint32_t id,
|
|
size_t size,
|
|
chunk_write_fn fn)
|
|
{
|
|
ALLOC_GROW(cf->chunks, cf->chunks_nr + 1, cf->chunks_alloc);
|
|
|
|
cf->chunks[cf->chunks_nr].id = id;
|
|
cf->chunks[cf->chunks_nr].write_fn = fn;
|
|
cf->chunks[cf->chunks_nr].size = size;
|
|
cf->chunks_nr++;
|
|
}
|
|
|
|
int write_chunkfile(struct chunkfile *cf, void *data)
|
|
{
|
|
int i, result = 0;
|
|
uint64_t cur_offset = hashfile_total(cf->f);
|
|
|
|
trace2_region_enter("chunkfile", "write", the_repository);
|
|
|
|
/* Add the table of contents to the current offset */
|
|
cur_offset += (cf->chunks_nr + 1) * CHUNK_TOC_ENTRY_SIZE;
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
hashwrite_be32(cf->f, cf->chunks[i].id);
|
|
hashwrite_be64(cf->f, cur_offset);
|
|
|
|
cur_offset += cf->chunks[i].size;
|
|
}
|
|
|
|
/* Trailing entry marks the end of the chunks */
|
|
hashwrite_be32(cf->f, 0);
|
|
hashwrite_be64(cf->f, cur_offset);
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
off_t start_offset = hashfile_total(cf->f);
|
|
result = cf->chunks[i].write_fn(cf->f, data);
|
|
|
|
if (result)
|
|
goto cleanup;
|
|
|
|
if (hashfile_total(cf->f) - start_offset != cf->chunks[i].size)
|
|
BUG("expected to write %"PRId64" bytes to chunk %"PRIx32", but wrote %"PRId64" instead",
|
|
cf->chunks[i].size, cf->chunks[i].id,
|
|
hashfile_total(cf->f) - start_offset);
|
|
}
|
|
|
|
cleanup:
|
|
trace2_region_leave("chunkfile", "write", the_repository);
|
|
return result;
|
|
}
|
|
|
|
static int read_table_of_contents_1(struct chunkfile *cf,
|
|
const unsigned char *mfile,
|
|
size_t mfile_size,
|
|
uint64_t toc_offset,
|
|
int toc_length,
|
|
unsigned expected_alignment,
|
|
const struct git_hash_algo *algo,
|
|
int quiet)
|
|
{
|
|
int i;
|
|
uint32_t chunk_id;
|
|
const unsigned char *table_of_contents = mfile + toc_offset;
|
|
|
|
ALLOC_GROW(cf->chunks, toc_length, cf->chunks_alloc);
|
|
|
|
while (toc_length--) {
|
|
uint64_t chunk_offset, next_chunk_offset;
|
|
|
|
chunk_id = get_be32(table_of_contents);
|
|
chunk_offset = get_be64(table_of_contents + 4);
|
|
|
|
if (!chunk_id) {
|
|
if (!quiet)
|
|
error(_("terminating chunk id appears earlier than expected"));
|
|
return 1;
|
|
}
|
|
if (chunk_offset % expected_alignment != 0) {
|
|
if (!quiet)
|
|
error(_("chunk id %"PRIx32" not %d-byte aligned"),
|
|
chunk_id, expected_alignment);
|
|
return 1;
|
|
}
|
|
|
|
table_of_contents += CHUNK_TOC_ENTRY_SIZE;
|
|
next_chunk_offset = get_be64(table_of_contents + 4);
|
|
|
|
if (next_chunk_offset < chunk_offset ||
|
|
next_chunk_offset > mfile_size - algo->rawsz) {
|
|
if (!quiet)
|
|
error(_("improper chunk offset(s) %"PRIx64" and %"PRIx64""),
|
|
chunk_offset, next_chunk_offset);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
if (cf->chunks[i].id == chunk_id) {
|
|
if (!quiet)
|
|
error(_("duplicate chunk ID %"PRIx32" found"),
|
|
chunk_id);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
cf->chunks[cf->chunks_nr].id = chunk_id;
|
|
cf->chunks[cf->chunks_nr].start = mfile + chunk_offset;
|
|
cf->chunks[cf->chunks_nr].size = next_chunk_offset - chunk_offset;
|
|
cf->chunks_nr++;
|
|
}
|
|
|
|
chunk_id = get_be32(table_of_contents);
|
|
if (chunk_id) {
|
|
if (!quiet)
|
|
error(_("final chunk has non-zero id %"PRIx32""), chunk_id);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int read_table_of_contents(struct chunkfile *cf,
|
|
const unsigned char *mfile,
|
|
size_t mfile_size,
|
|
uint64_t toc_offset,
|
|
int toc_length,
|
|
unsigned expected_alignment)
|
|
{
|
|
return read_table_of_contents_1(cf, mfile, mfile_size, toc_offset,
|
|
toc_length, expected_alignment,
|
|
the_hash_algo, 0);
|
|
}
|
|
|
|
int read_table_of_contents_quiet(struct chunkfile *cf,
|
|
const unsigned char *mfile,
|
|
size_t mfile_size,
|
|
uint64_t toc_offset,
|
|
int toc_length,
|
|
unsigned expected_alignment,
|
|
const struct git_hash_algo *algo)
|
|
{
|
|
return read_table_of_contents_1(cf, mfile, mfile_size, toc_offset,
|
|
toc_length, expected_alignment,
|
|
algo, 1);
|
|
}
|
|
|
|
struct pair_chunk_data {
|
|
const unsigned char **p;
|
|
size_t *size;
|
|
};
|
|
|
|
static int pair_chunk_fn(const unsigned char *chunk_start,
|
|
size_t chunk_size,
|
|
void *data)
|
|
{
|
|
struct pair_chunk_data *pcd = data;
|
|
*pcd->p = chunk_start;
|
|
*pcd->size = chunk_size;
|
|
return 0;
|
|
}
|
|
|
|
int pair_chunk(struct chunkfile *cf,
|
|
uint32_t chunk_id,
|
|
const unsigned char **p,
|
|
size_t *size)
|
|
{
|
|
struct pair_chunk_data pcd = { .p = p, .size = size };
|
|
return read_chunk(cf, chunk_id, pair_chunk_fn, &pcd);
|
|
}
|
|
|
|
int read_chunk(struct chunkfile *cf,
|
|
uint32_t chunk_id,
|
|
chunk_read_fn fn,
|
|
void *data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
if (cf->chunks[i].id == chunk_id)
|
|
return fn(cf->chunks[i].start, cf->chunks[i].size, data);
|
|
}
|
|
|
|
return CHUNK_NOT_FOUND;
|
|
}
|
|
|
|
uint8_t oid_version(const struct git_hash_algo *algop)
|
|
{
|
|
switch (hash_algo_by_ptr(algop)) {
|
|
case GIT_HASH_SHA1:
|
|
return 1;
|
|
case GIT_HASH_SHA256:
|
|
return 2;
|
|
default:
|
|
die(_("invalid hash version"));
|
|
}
|
|
}
|