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