Skip to content

feat(diff): add --diff-file to review a validated subset of hunks#357

Open
Th0rgal wants to merge 3 commits into
alibaba:mainfrom
lfglabs-dev:feat/selected-diff-review
Open

feat(diff): add --diff-file to review a validated subset of hunks#357
Th0rgal wants to merge 3 commits into
alibaba:mainfrom
lfglabs-dev:feat/selected-diff-review

Conversation

@Th0rgal

@Th0rgal Th0rgal commented Jul 10, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in --diff-file input to ocr review so a caller can review only a
subset of the changes in a range, instead of the whole --from..--to (or
--commit) diff.

Motivation: on large changesets, teams often want to select which hunks are in
scope for review (e.g. risk-ranked or path-filtered externally) and hand OCR
just that slice. Today OCR reviews the entire range. This change lets an
external tool express that selection as a plain unified git patch while keeping
all selection policy outside OCR.

Usage:

ocr review --from BASE --to HEAD --diff-file selected.patch

selected.patch is a standard unified git diff (git diff output) containing
an exact subset of complete hunks from the canonical BASE..HEAD diff. OCR
reviews only those hunks, while still reading the complete real HEAD tree, so
full-file tool reads and GitHub comment line numbers remain anchored to the true
HEAD.

Behavior & safety

  • internal/diff.ApplySelection narrows the canonical per-file diffs down to the
    selected hunks. It fails closed on anything that isn't an exact subset:
    invented/renamed/mutated hunks, malformed or truncated patches, path traversal
    (../absolute), mismatched header ranges, ambiguous or duplicate matches, and
    binary/submodule shapes.
  • NewFileContent and the original hunk headers are preserved, so line
    resolution still yields true-HEAD line numbers.
  • The flag requires an explicit --from/--to or --commit range (rejected in
    workspace mode) and cannot be combined with --resume, because narrowing the
    diff changes the resume fingerprint.
  • Default behavior is completely unchanged when --diff-file is not passed.

Tests

  • internal/diff/selection_test.go: table-driven unit tests (single/multiple
    hunks, multiple files, dropping unselected files, mutated body, wrong ranges,
    ambiguous/duplicate, rename, delete, binary, submodule, CRLF, Unicode,
    no-newline marker, and line-resolver accuracy vs true HEAD).
  • cmd/opencodereview/selected_diff_test.go: CLI flag/mode validation plus two
    integration tests on a real temp git repo (multi-hunk HEAD, subset selection)
    asserting review/preview scope and that full reads come from true HEAD.

Refs #313

Adds an opt-in `ocr review --from BASE --to HEAD --diff-file selected.patch`
input. The patch is a standard unified git diff carrying an exact subset of
complete hunks from the canonical BASE..HEAD (or --commit) diff. OCR then
reviews only those hunks while still reading the complete real HEAD tree and
preserving true-HEAD line numbers for comments.

ApplySelection narrows the canonical per-file diffs to the selected hunks and
fails closed on anything unsafe: invented/renamed/mutated hunks, malformed
patches, path traversal, mismatched header ranges, ambiguous or duplicate
matches, and binary/submodule shapes. NewFileContent and hunk headers are
preserved so downstream line resolution stays anchored at the true HEAD ref.

The flag requires an explicit --from/--to or --commit range (rejected in
workspace mode) and cannot be combined with --resume, since selection changes
the diff fingerprint. Default behavior is unchanged when the flag is absent.

Refs alibaba#313
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 2 issue(s) in this PR.

  • ✅ Successfully posted inline: 2 comment(s)

Comment on lines +249 to +258
func loadSelectedDiff(path string) (string, error) {
if path == "" {
return "", nil
}
data, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("read --diff-file %q: %w", path, err)
}
return string(data), nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike loadBackgroundFile which enforces a 1 MB size limit (maxBackgroundFileBytes) and checks for directory paths before reading, loadSelectedDiff reads the entire file into memory without any size constraint. A maliciously crafted or accidentally huge patch file could cause excessive memory consumption. Consider adding a similar size guard (e.g., os.Stat + size check) before calling os.ReadFile, consistent with the existing pattern in loadBackgroundFile.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in eed66a1: loadSelectedDiff now mirrors loadBackgroundFile with an os.Stat directory check and a size cap (8 MB, sized for diff subsets). It also rejects empty/whitespace-only files, which previously produced an empty selection that silently fell open into a full-range review. Tests added in selected_diff_test.go.

Comment on lines +243 to +244
case binaryRe.MatchString(line):
sf.isBinary = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The binaryRe regex (Binary files ) is unanchored and uses substring matching via MatchString. Because this loop iterates over ALL lines in the block (including hunk body lines), a hunk body line like +Binary files are compared here would falsely trigger sf.isBinary = true, causing the selection to be rejected with an error about binary files.

The other patterns in this switch are safe because they use HasPrefix or exact equality, and hunk body lines always start with +, -, , or \. But binaryRe.MatchString performs a substring search regardless of position.

Consider either:

  1. Anchoring the regex to the start of line: ^Binary files
  2. Or limiting this metadata scan to only lines before the first @@ hunk header (which is where git extended headers actually appear).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 9cdfe78: binaryRe is now anchored (^Binary files ) in internal/diff/parser.go, which corrects both this selection-side check and the same latent false positive in ParseDiffText. Regression tests added in selection_test.go and parser_test.go.

Th0rgal added 2 commits July 10, 2026 18:22
The unanchored `Binary files ` regex matched hunk body lines whose
content merely mentions the phrase (e.g. "+Binary files are compared
here"), falsely marking the file binary in both ParseDiffText and the
--diff-file selection parser, which then rejected valid selections.
Real git binary markers always start the line, and hunk body lines
always carry a '+', '-', ' ' or '\' prefix, so anchoring is exact.
An empty --diff-file produced an empty SelectedDiff, which the agent
treats as "no selection" — silently falling open into a full-range
review instead of the requested subset. Reject empty/whitespace-only
files explicitly, and mirror loadBackgroundFile's stat-based directory
and size guards (8 MB cap) so oversized or directory paths fail fast.
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