Skip to content

diff: Parse truncated hunks through parse options - #3688

Merged
huacnlee merged 2 commits into
longbridge:mainfrom
citizenhicks:diff-lenient-hunk-counts
Oct 26, 2026
Merged

huacnlee merged 2 commits into
longbridge:mainfrom
citizenhicks:diff-lenient-hunk-counts

Conversation

@citizenhicks

@citizenhicks citizenhicks commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Description

Add DiffFile::parse_with(patch, DiffParseOptions) for parsing a unified or Git diff with opt-in relaxations, starting with one: truncated_hunks(true) accepts a hunk that ends before its declared line counts and counts it from the lines present. DiffFile::parse is unchanged and stays strict.

Declared counts remain an upper bound, so a line beyond them is still an error, as are malformed hunk headers (including overflowing ranges), invalid paths, unordered or overlapping hunks, source lines on a missing side and combined diffs. A short hunk ends at the first line that is not a context, deletion or addition line, or at a paired unified file header when treating that pair as source cannot complete the declared hunk. A complete source interpretation takes precedence for ambiguous --- / +++ lines. Hunk positions remain ordered on both sides even when a truncated hunk supplies no source lines. An empty line inside it is an empty context line, as it already is in strict parsing. Its source line numbers stay within the declared range, and the hunk's original line range covers only the lines present, so DiffHunkSeparator::LineInfo counts hidden lines from what arrived. The @@ label keeps the header text as written.

DiffFile::is_truncated() reports whether any hunk of the file ended early. It is always false for files from parse, unchanged and parse_conflicts.

English and Chinese Diff documentation describe the option.

Public API

gpui-component

  • diff::DiffParseOptions — Opt-in parse relaxations; Copy, Default, private fields, every relaxation off by default.
  • diff::DiffParseOptions::new() -> Self — Options that parse exactly as DiffFile::parse does.
  • diff::DiffParseOptions::truncated_hunks(self, truncated_hunks: bool) -> Self — Accept hunks that end before their declared line counts, counting them from the lines present.
  • diff::DiffParseOptions::has_truncated_hunks(&self) -> bool — Read the truncated_hunks setting.
  • diff::DiffFile::parse_with(patch: &str, options: DiffParseOptions) -> Result<Vec<DiffFile>, DiffParseError> — Parse like parse, applying the enabled relaxations.
  • diff::DiffFile::is_truncated(&self) -> bool — Whether a hunk of this file ended before its declared line counts.

Breaking Changes

None. DiffFile::parse keeps its signature and strict behavior; the new items are additive.

How to Test

cargo test -p gpui-component --features test-support diff
cargo clippy -p gpui-component --locked -- --deny warnings
cargo fmt --check -p gpui-component

New tests in crates/component/src/diff/parser_tests.rs:

  • truncated_hunks_count_the_lines_present — one patch with hunks cut before the next file, before the next hunk, and at the end of the patch. Strict parse rejects it. parse_with yields the lines present, additions and deletions, source line numbers, hidden lines before the following hunk, and is_truncated(); an empty line inside a short hunk is empty context; a complete hunk is not truncated.

  • truncated_hunks_keep_structural_errors — with the option on, lines beyond the declared counts, unordered hunks, an overflowing or invalid hunk header (including a zero-count side at usize::MAX), an invalid quoted path and source lines on a missing side are still errors. A short hunk near usize::MAX keeps its line numbers within the declared range.

  • non_ascii_lines_after_a_short_hunk_do_not_panic — a non-ASCII line after a short hunk returns a strict parse error or ends the accepted truncated hunk, without slicing inside a UTF-8 character.

  • truncated_unified_hunks_stop_before_the_next_file_headers — separate files without diff --git, while preserving complete hunks containing header-looking source.

  • empty_truncated_hunks_preserve_order_on_both_sides — reject backwards positions on either side after an empty truncated hunk, while accepting forward positions.

Existing strict-parser tests, including malformed_hunks_fail_without_partial_documents, pass unchanged.

Follow-up validation: both new regressions fail before the fix. After the fix, cargo test -p gpui-component --features test-support --lib diff --locked passed 56 tests. cargo clippy -p gpui-component --features test-support --tests --locked -- --deny warnings, cargo fmt --all --check and git diff --check passed. The parser fixes, regression tests and bilingual documentation updates were AI-assisted.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes.
  • Tested macOS, Windows and Linux platforms performance (if the change is platform-specific)

Implementation, tests and documentation were written with AI assistance and reviewed by the author.

@citizenhicks
citizenhicks marked this pull request as ready for review October 25, 2026 11:51
Recognize paired unified file headers when the current hunk cannot complete, while preserving complete hunks containing header-looking source. Track hunk positions independently of source lines so empty truncated hunks still constrain ordering on both sides.

Add regressions for both failures and document the ambiguous-header rule in English and Chinese.

AI-assisted implementation and regression validation.
@huacnlee
huacnlee enabled auto-merge (squash) October 26, 2026 03:54
@huacnlee
huacnlee merged commit 75da1a0 into longbridge:main Oct 26, 2026
19 checks passed
@citizenhicks
citizenhicks deleted the diff-lenient-hunk-counts branch October 26, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants