Repository navigation
fix(vcs/cache): diff.algorithm changes never invalidate the history cache #1425
Description
Summary
The persistent VCS history cache ignores git's diff configuration.
diff.algorithm, and the diff drivers, decide how much churn is recorded
for each commit, but cache::fingerprint() hashes neither. After a change
to either, bca vcs serves stale churn_long / churn_recent values,
along with everything derived from them, including risk_score. The
incremental splice then persists the stale churn under each new head, so
it survives HEAD moving and only --clear-cache clears it.
This is the same failure class #1262 fixed for the mailmap, through a
different input that lives outside the commit graph.
Why the cache misses it
history::collect_events (src/vcs/git/history.rs:67) builds its blob
diffs from repo.diff_resource_cache_for_tree_diff(). gix configures
that platform from the repository's git config, including
diff.algorithm, so the recorded line counts depend on it.
cache::fingerprint() (src/vcs/cache.rs) hashes the window, traversal
and bot options, as_of, and (since #1262) a mailmap digest. It does not
hash the diff configuration, so a config change leaves the fingerprint
unchanged, and the old entry hits.
Myers always finds a minimal diff and histogram does not, so the two can
record different churn for the same commit. Histogram anchors on
low-occurrence lines, so moving a unique line across repeated content
separates them sharply.
Reproduction
Measured with a debug build of the fix/batch-2026-09-10 branch (#1424).
R=$(mktemp -d); C=$(mktemp -d); cd $R
git init -q; git config user.name A; git config user.email a@x
printf 'fn a() {}\nfn b() {}\nfn c() {}\nfn a() {}\nfn b() {}\nfn c() {}\nfn only_once() {}\n' > a.rs
git add a.rs; git commit -qm one
printf 'fn only_once() {}\nfn a() {}\nfn b() {}\nfn c() {}\nfn a() {}\nfn b() {}\nfn c() {}\n' > a.rs
git add a.rs; git commit -qm twogit's own view of commit two confirms the algorithms disagree:
$ git -c diff.algorithm=myers diff --numstat HEAD~1 HEAD # 1 added, 1 removed
$ git -c diff.algorithm=histogram diff --numstat HEAD~1 HEAD # 6 added, 6 removedbca vcs (churn_long) with --cache-dir $C:
| step | cached | fresh (--no-cache) |
|---|---|---|
| prime under the default (myers) | 9 | 9 |
git config diff.algorithm histogram |
9 | 19 |
| add a commit (HEAD moves; splice runs) | 10 | 20 |
| a later pure hit on that persisted entry | 10 | 20 |
The last two rows are the worse case. The splice re-persists the stale
myers-based churn for commit two under the new head, so the divergence
survives HEAD moving and every later run reproduces it.
Fix shape
Fold the effective diff configuration into cache::fingerprint(), as
#1262 did for the mailmap: the resolved algorithm, plus whichever
driver settings (diff.<driver>.*, .gitattributes diff= bindings)
can change a recorded line count. Read the effective values from the
same gix platform the walk uses rather than re-deriving git's precedence
rules locally. #1262 made the same call for the mailmap, because a
hand-rolled source list is a coverage claim that nothing checks.
Drivers are the harder half. A textconv driver changes which bytes are
diffed at all, and .gitattributes can bind one per path. Decide whether
to fingerprint the attribute-resolved driver set or document drivers as a
known gap.
Status on #1424
The max-effort review of #1424 found this. That branch corrects the docs
that claimed the cache contract was complete once the mailmap was
fingerprinted (src/vcs/cache.rs, src/vcs/git/cached.rs, and the book's
commands/vcs.md and python/vcs.md), so they now name the diff config
as an unfingerprinted input. It does not change the code. This issue
is for the fix.
Activity