Expand description
Entry-level structural diff between two bibliography snapshots (#135 sub-feature C).
bib verify already prints a unified line-based diff when drift is
detected, but that output is mechanically useful and editorially
noisy: a one-character change in a citekey ripples through dozens of
lines of BibTeX, and the reader has to reconstruct the meaning by
eye. bib diff gives the reader the meaning directly: which entries
were added, which were removed, which changed (and which fields
changed).
§Identity rule
Two entries refer to the same paper iff one of these matches — tried strictly in order, first-match-wins:
- citekey (exact, case-sensitive — citekeys are deterministic)
- DOI (lowercased, prefix-stripped — see
crate::import::parse::normalize_doi) - arxiv_id (trimmed)
- (title, year) — last-resort fallback when all three persistent identifiers are missing, e.g. an old NeurIPS PDF with no DOI yet
Strict per-rung means we don’t try multiple at once: if citekey matches we don’t even look at DOI. This matters for ambiguous cases (e.g. the citekey was renamed but DOI still matches — we treat that as added/removed, not a rename, because the user’s manuscripts still cite by the citekey).
§Determinism
All output lists are sorted by citekey lexicographically before return so callers (CLI, MCP, tests) see stable output across runs regardless of input order.
§Format-neutral
The diff operates on Entry — a deliberately narrow shape that
both BibTeX and CSL-JSON parsers can populate. That way a BibTeX
file and a CSL-JSON file with the same papers produce zero diff
(verified by [tests::mixed_format_same_papers_produce_zero_diff]).
Structs§
- BibDiff
- Structural diff between two entry lists. Lists are sorted by citekey ascending so output is byte-stable across runs.
- Changed
Entry - One entry whose identity matched on both sides but whose fields
differ.
field_changesis sorted alphabetically by field name for deterministic output. - Entry
- Format-neutral bibliography entry. Populated by the BibTeX parser
(via
Entry::from_bib_entry) or the CSL-JSON parser (viaEntry::from_csl_value) so the diff layer doesn’t care which flavor it’s looking at. - Field
Change - One field that differs between two matched entries.
Functions§
- diff_
entries - Compute the structural diff between two entry lists. Pure: no I/O, no global state. Deterministic: same inputs ⇒ same output, lists sorted by citekey.