Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ These correctness fixes landed after the 2026-10-03 checkpoint below:
- The footer keeps its status readable at 100–160 columns by dropping optional view hints first. The header label no longer runs into the file count, condensed help keeps its indentation, and the small-terminal notice wraps.
- The footer source status describes the cursor's file instead of any file visible on screen.
- Host calls into the syntax worker recover a poisoned mailbox lock instead of panicking; a dead worker surfaces through the existing `Engine::poll` error.
- The vendored, patched `libgit2-sys` is gone; the build uses the published crate with its bundled libgit2. Stock libgit2 cuts unquoted `rename`/`copy` names at whitespace, so the parser now receives those names in Git's quoted form and line offsets map back to the supplied bytes. `LIBGIT2_NO_VENDOR` is still rejected at build time.
- Unnamed temporary files (patch spool, Git helper output, clipboard input) are created owner-only. On Linux they previously took the umask-derived mode, usually 0644. Linux CI found this.

Canonical source output is byte-identical to the 2026-10-03 build for the five DiffsHub corpus patches and the repository fixtures.
Expand Down
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ keywords = ["diff", "patch", "terminal", "tui", "ratatui"]
categories = ["command-line-utilities", "development-tools", "text-processing"]
# Publishing stays off until the crate no longer needs the [patch.crates-io]
# overrides below. crates.io ignores [patch], so a published build would
# silently use unpatched upstream syntect, fancy-regex and libgit2-sys.
# silently use unpatched upstream syntect and fancy-regex.
publish = false
# Ship only what the library and binary need to build, plus the docs a reader
# of the crate needs. Tests, fixtures, tools, reports and vendor/ stay in Git.
include = [
"/src/**/*.rs",
"/build.rs",
"/assets/pierre/**",
"/examples/embedded_viewer.rs",
"/Cargo.toml",
Expand All @@ -41,7 +42,6 @@ owner-census = ["regex-census", "syntect/owner-census"]
[patch.crates-io]
syntect = { path = "vendor/syntect" }
fancy-regex = { path = "vendor/fancy-regex" }
libgit2-sys = { path = "vendor/libgit2-sys" }

[dependencies]
anyhow = "1.0"
Expand Down
6 changes: 3 additions & 3 deletions EMBEDDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Native clipboard, physical-terminal acceptance, and the final package remain pen

Use a path dependency on the source package. The host must use Ratatui 0.30.
Cargo does not inherit dependency patches from a dependency's manifest.
The consuming workspace root therefore needs all three patches:
The consuming workspace root therefore needs both patches:

```toml
[dependencies]
Expand All @@ -21,11 +21,11 @@ ratatui = { version = "0.30.2", default-features = false, features = ["crossterm
[patch.crates-io]
syntect = { path = "../native-diff/vendor/syntect" }
fancy-regex = { path = "../native-diff/vendor/fancy-regex" }
libgit2-sys = { path = "../native-diff/vendor/libgit2-sys" }
```

Adjust these paths for the host's workspace. Retain the vendor directories and their notices.
The patches provide required syntax-engine behavior and the corrected local Git parser.
The patches provide required syntax-engine behavior.
The Git patch parser is the stock `libgit2-sys` crate. The build fails if `LIBGIT2_NO_VENDOR` asks it to link a system libgit2.
Do not substitute registry or system versions to make dependency resolution succeed.
Keep the host's generated lockfile and use `--locked` for subsequent builds.
Diagnostic crate features are not required for embedding.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ The installation record (external record `native-diff-production-hardening/INSTA

The qualified build environment is macOS arm64 with Rust 1.96.0, the separately checked Rust 1.88.0 minimum, and Xcode command-line build tools. Other platforms remain unqualified. For an isolated source installation, use `cargo install --path . --locked --root /absolute/path/to/an/isolated/prefix`; add `--offline` only after locked dependencies have been acquired. This does not add the prefix to PATH.

The build uses the [locally corrected parser](vendor/libgit2-sys/NATIVE_DIFF_PATCH.md).
The build compiles the libgit2 source bundled with the published `libgit2-sys` crate.
If `LIBGIT2_NO_VENDOR` is set to a nonzero value, unset it before building.
The build rejects an override that would select an unpatched system library.
The build rejects an override that would select a system library.

In an existing Ghostty window, open the sample patch:

Expand Down Expand Up @@ -291,6 +291,7 @@ One Cargo package contains one library and one executable. Ratatui draws the ter
| --- | --- |
| `ingest.rs` | Read file-patch units and check their hunk boundaries |
| `model.rs` | Parse file-patch units through libgit2 and apply the existing line-pairing rule |
| `model/rename_names.rs` | Quote rename and copy names that libgit2 would cut at whitespace, and map parser offsets back |
| `storage.rs` | Publish compact patch indexes and stable source identities |
| `source.rs` | Capture immutable source bodies, disk line indexes, and exact correspondence |
| `input` | Bind local comparisons, supervise Git, and prepare visible source pairs |
Expand Down
2 changes: 1 addition & 1 deletion RELEASE_READINESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ The inventory records each changed and added file separately.
| --- | --- | --- |
| Syntect 5.3.0 | [Patch note](vendor/syntect/NATIVE_DIFF_PATCH.md), source provenance, standalone checks | Summary updated 2026-09-22 for required-byte rejection, retained-heap APIs, and affected files. Keep the documented checks during updates. |
| Fancy Regex 0.16.2 | [Patch note](vendor/fancy-regex/NATIVE_DIFF_PATCH.md), source provenance, standalone checks | Summary updated 2026-09-22 for required-byte metadata, public error behavior, and affected files. Keep the documented checks during updates. |
| libgit2-sys 0.18.8+1.9.7 | [Parser correction](vendor/libgit2-sys/NATIVE_DIFF_PATCH.md), unchanged notices | Retain the parser regression checks during updates |
| libgit2-sys 0.18.8+1.9.7 | Parser correction, unchanged notices | Removed 2026-10-27. The stock crate is used; `src/model/rename_names.rs` quotes rename and copy names before parsing. Retain the parser regression checks during updates. |

The bundled grammar package supplies notices in addition to its crate license.
The local export contains 66 syntax notice entries from the pinned package's public API.
Expand Down
13 changes: 13 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Keeps the Git patch parser on the libgit2 source that `git2`'s `vendored-libgit2` feature
//! builds. libgit2-sys links a system library instead when `LIBGIT2_NO_VENDOR` is set to a
//! nonzero value, and that library's parser has not been checked against this crate.

fn main() {
println!("cargo:rerun-if-env-changed=LIBGIT2_NO_VENDOR");
if std::env::var_os("LIBGIT2_NO_VENDOR").is_some_and(|value| value != "0") {
panic!(
"native-diff requires the libgit2 source vendored by libgit2-sys. \
Unset LIBGIT2_NO_VENDOR or set it to 0."
);
}
}
16 changes: 12 additions & 4 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use anyhow::{Context, Result, bail};
use git2::{Delta, Diff, Patch};
use serde::Serialize;

mod rename_names;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum Kind {
Context,
Expand Down Expand Up @@ -138,7 +140,9 @@ impl Document {
bail!("Combined merge diffs are not supported. Supply a two-sided patch.");
}
let normalized = empty_file_headers(input);
let diff = Diff::from_buffer(normalized.as_deref().unwrap_or(input))
let normalized = normalized.as_deref().unwrap_or(input);
let quoted = rename_names::quote_rename_names(normalized);
let diff = Diff::from_buffer(quoted.as_ref().map_or(normalized, |q| &q.bytes))
.context("Cannot parse this patch")?;
if diff.deltas().len() == 0 {
bail!("The input contains no recognized file changes.");
Expand Down Expand Up @@ -245,9 +249,13 @@ impl Document {
file.deletions += usize::from(kind == Kind::Deletion);
let largest = old.unwrap_or(0).max(new.unwrap_or(0)).max(1);
file.digits = file.digits.max(largest.ilog10() as usize + 1);
let offset = usize::try_from(line.content_offset())?
.checked_add(1)
.context("Source offset overflow")?;
let offset = usize::try_from(line.content_offset())?;
let offset = match &quoted {
Some(quoted) => quoted.original_offset(offset),
None => Some(offset),
}
.and_then(|offset| offset.checked_add(1))
.context("Source offset overflow")?;
let end = offset
.checked_add(line.content().len())
.context("Source length overflow")?;
Expand Down
Loading
Loading