Skip to content

fix(vcs/cache): diff.algorithm changes never invalidate the history cache #1425

Description

@dekobon

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 two

git'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 removed

bca 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingvcs

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions