Skip to main content

vcs_diff/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![deny(rustdoc::broken_intra_doc_links)]
3//! `vcs-diff` — the shared git-format unified-diff model and parser for the
4//! [vcs-toolkit-rs](https://github.com/ZelAnton/vcs-toolkit-rs) workspace.
5//!
6//! `git diff` and `jj diff --git` emit byte-identical **git-format unified
7//! diffs**, so `vcs-git` and `vcs-jj` share one model and one parser here rather
8//! than each carrying a copy that could silently drift. This is the foundational
9//! crate both depend on: **std only**, no async, no subprocess — pure data types
10//! and pure functions over text the wrapper crates obtained elsewhere.
11//!
12//! # The surface
13//!
14//! - **[`parse_diff`]** — the entry point. Turns git-format diff text into one
15//!   [`FileDiff`] per file; the same call serves `git diff` and `jj diff --git`
16//!   output alike. Pure and total: arbitrary CLI bytes in, never a panic.
17//! - **[`FileDiff`]** — one file's entry: its [`ChangeKind`], the
18//!   forward-slash-normalised `path` (and `old_path` for a rename), the
19//!   [`Hunk`]s, and the verbatim `raw` section for callers that display text.
20//! - **[`Hunk`]** — a single `@@ … @@` block: the old/new line ranges, the
21//!   section heading, and a body of **[`DiffLine`]**s
22//!   ([`Context`](DiffLine::Context) / [`Added`](DiffLine::Added) /
23//!   [`Removed`](DiffLine::Removed)), each with its leading marker and line
24//!   terminator stripped.
25//! - **[`ChangeKind`]** — how the file changed: [`Added`](ChangeKind::Added) /
26//!   [`Modified`](ChangeKind::Modified) / [`Deleted`](ChangeKind::Deleted) /
27//!   [`Renamed`](ChangeKind::Renamed).
28//! - **[`DiffStat`]** — the aggregate `files_changed`/`insertions`/`deletions`
29//!   shape both `git diff --shortstat` and `jj diff --stat` parse into.
30//! - **[`Version`]** + **[`parse_dotted_version`]** — a numeric
31//!   `major.minor.patch` (it `Ord`s, so a caller can gate on a minimum) read
32//!   tolerantly from a `<tool> --version` banner.
33//!
34//! The wrapper crates re-export these (e.g. `vcs_git::FileDiff`,
35//! `vcs_git::parse_diff`, `vcs_git::GitVersion`), so consumers rarely name this
36//! crate directly.
37//!
38//! # Recipes
39//!
40//! Parse a one-file modify diff and read the structured result — pure, so this
41//! runs as written:
42//!
43//! ```rust
44//! use vcs_diff::{parse_diff, ChangeKind, DiffLine};
45//!
46//! let text = "\
47//! diff --git a/f b/f
48//! --- a/f
49//! +++ b/f
50//! @@ -1,2 +1,2 @@ fn main()
51//!  ctx
52//! -old
53//! +new
54//! ";
55//! let files = parse_diff(text);
56//! assert_eq!(files.len(), 1);
57//! assert_eq!(files[0].change, ChangeKind::Modified);
58//! assert_eq!(files[0].path, "f");
59//!
60//! let hunk = &files[0].hunks[0];
61//! assert_eq!((hunk.old_start, hunk.new_start), (1, 1));
62//! assert_eq!(hunk.section, "fn main()");
63//! assert_eq!(hunk.lines, vec![
64//!     DiffLine::Context("ctx".into()),
65//!     DiffLine::Removed("old".into()),
66//!     DiffLine::Added("new".into()),
67//! ]);
68//! ```
69//!
70//! # Features
71//!
72//! - **`serde`** — derives `serde::Serialize` on every model type
73//!   ([`FileDiff`], [`Hunk`], [`DiffLine`], [`ChangeKind`], [`DiffStat`],
74//!   [`Version`]) so a caller can emit the parsed diff as JSON.
75
76mod diff;
77mod version;
78
79pub use diff::{ChangeKind, DiffLine, DiffStat, FileDiff, Hunk, parse_diff};
80pub use version::{Version, parse_dotted_version};