Files
Michael Montalbo 3dc1d97529 diff-hunks: add the store format, library, and command
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>
2026-09-03 07:43:08 -07:00

147 lines
5.6 KiB
Plaintext

git-diff-hunks(1)
=================
NAME
----
git-diff-hunks - Inspect and manage the diff-hunks store
SYNOPSIS
--------
[synopsis]
git diff-hunks verify
git diff-hunks clear
DESCRIPTION
-----------
The diff-hunks store is a cache of diff hunk coordinates, the line
ranges that changed between two blobs, so that commands
which need them, such as linkgit:git-blame[1] and `git log` and `git diff`
with the `--stat`, `--numstat`, and `--shortstat` formats, can skip
running the diff algorithm, and blame can skip loading the blob
content. (The summary formats still test each pair for binariness,
which can load the blobs.)
The store is a single file, `$GIT_DIR/objects/info/diff-hunks`. Reading is
enabled by default; writing is off by default. A `git diff`, `git log`,
`git show`, or `git diff-tree` that produces one of the stat formats
fills the store as a side effect, but only when writing is enabled for
that run (see "WARMING THE STORE" below), so ordinary reads never
modify the repository. When the store does not have the pair, holds a
different object hash, the file is unreadable, or an object replacement
redirects one of the blobs, the consumer falls back to computing the
diff. A store only speeds up these commands; it never changes their
output.
`git diff-hunks` itself only inspects and manages the file. See
linkgit:gitformat-diff-hunks[5] for the file format.
WARMING THE STORE
-----------------
The store is filled by running ordinary commands with writing enabled.
Turn writing on for a single invocation with the `GIT_DIFF_HUNKS_WRITE`
environment variable, or persistently with the `diffHunks.write`
configuration; the environment variable takes precedence. A repository
owner warms the store by running the diff-producing commands they care
about with writing on, for example:
GIT_DIFF_HUNKS_WRITE=1 git log --all --stat >/dev/null
A `--stat` walk records one entry per blob pair;
linkgit:git-blame[1] replays the coordinates and the summary formats
sum the counts, so a single warming walk serves both.
A warming run seeds from the existing store and rewrites the file
with the newly computed pairs merged in, so a later run adds to what
earlier runs recorded rather than discarding it.
A walk records only the pairs it diffs. `git log --all --stat` diffs
each commit against its first parent, so a blame that follows a
merge's second parent computes those pairs itself: blame coverage is
partial on history with merges. Warming with a walk that also diffs
the other parents, for example `git log --all -m --stat`, raises
blame coverage at the cost of a larger store and a longer warming
run.
COMMANDS
--------
`verify`::
Check the integrity of the store: the trailing hash checksum, the
chunk table of contents, the sort order of the index, and the
bounds of every entry. Exits with non-zero status if the store is
corrupt. An absent store is valid.
`clear`::
Remove the store file.
CORRECTNESS
-----------
A stored result is interchangeable with a freshly computed one because an
entry is keyed by the inputs that determine the diff:
* the object IDs of the old and new blob, so a result is used only for
the exact contents it was computed from; and
* the diff algorithm and ignore flags (`xdl_opts`) the hunks were
computed under. A lookup whose `xdl_opts` differ from a stored entry
misses. This is why, for example, `blame -w` and
`--diff-algorithm=<algorithm>` (including a per-path
`diff.<driver>.algorithm`) do not reuse entries recorded under the
default settings: they change `xdl_opts`.
The context length is not part of the key because only trim-stable
pairs are recorded: pairs whose zero-context trimmed diff and untrimmed
diff are identical, so one entry answers blame (zero context) and the
summary formats (any context) alike. The rare pair where
the zero-context trimming optimization picks a different but
equally valid set of hunks is
never recorded and is always computed.
Some options shape the hunks in ways the key does not express, so a
diff that uses them is excluded from the store in both directions:
break detection (`-B`), `--ignore-matching-lines` (`-I`), and
`--anchored`. `--ignore-blank-lines` is different: it is an ignore
flag and therefore part of the key, but the summary formats exclude
it anyway, because it coalesces hunks differently between the code
path that emits text and the one that replays coordinates, so a
served answer would not match a store-less run.
linkgit:git-blame[1] additionally does not
consult the store for reverse blame, ignored revisions, or paths with a
textconv driver.
The store carries a trailing hash checksum, but readers do not
re-checksum it on every load. As with the commit-graph and
multi-pack-index, the writer fsyncs the file (honoring `core.fsync`) and
commits it atomically, so a committed store is intact; every offset and
count is still bounds-checked as it is read. The checksum is verified by
`git diff-hunks verify`, not on the read path, so structural corruption
that fails a bounds check is read as an absent entry, while a record
that stays within bounds but whose bytes were altered is served until
`verify` detects the mismatch.
CONFIGURATION
-------------
`core.diffHunks`::
Whether commands read the store. Defaults to true. See
linkgit:git-config[1].
`diffHunks.write`::
Whether diff-producing commands write to the store. Defaults to
false. The `GIT_DIFF_HUNKS_WRITE` environment variable overrides it
for a single invocation. See linkgit:git-config[1].
Writing the store honors the `core.fsync` configuration through the
`diff-hunks` component; see linkgit:git-config[1].
SEE ALSO
--------
linkgit:git-blame[1],
linkgit:git-log[1],
linkgit:gitformat-diff-hunks[5]
GIT
---
Part of the linkgit:git[1] suite