fix(display): report one-sided empty files as changes - #1040
Open
YuriNachos wants to merge 1 commit into
Open
YuriNachos wants to merge 1 commit into
YuriNachos wants to merge 1 commit into
Conversation
When diffing directories, a file that exists on only one side is read as empty content on the missing side (the mercurial-compatible behaviour in read_files_or_die). A one-sided empty file therefore compared equal to the missing side and collapsed to "No changes.", and --skip-unchanged silently dropped it. read_files_or_die now reports which side was missing, so diff_file can mark one-sided files as changes: for text files by forcing has_syntactic_changes (a one-sided file is a change even when its content produces no detectable differences, e.g. an empty file, carriage returns stripped by --strip-cr, or whitespace with no syntax tree nodes), and for binary-overridden files by reporting byte changes. JSON output now reports "changed" when there are changes without displayable hunks, consistent with the text display and --exit-code. Empty files present on both sides are still reported as unchanged. Addresses Wilfred#1027. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
difft foo barwherebar/bar.txtis an empty file that exists only inbar/printedbar.txt --- Text / No changes., and--skip-unchangedsilently dropped it. A file present in only one diffed directory is a change, so it is now reported as one. Empty files present on both sides are still reported as unchanged.Addresses #1027.
Root cause
Directory diffing pairs files by relative path (
relative_paths_in_either,src/files.rs) and compares each pair viadiff_filewithmissing_as_empty = true(src/main.rs,diff_directories). For a file that exists on only one side,read_files_or_die(src/files.rs:24) substitutes empty content for the missing side — the existing behaviour needed for mercurial removals. For an empty file on the present side, both sides then compare equal, sodiff_file_contenttook the early "identical sources" return (has_syntactic_changes: false), printingNo changes.and hiding the file under--skip-unchanged. A one-sided non-empty file never hit this because its content differs from the empty substitute.Changes
src/files.rs:read_files_or_dienow also returns aMissingFile { Neither, Lhs, Rhs }indicating which side (if any) was missing and substituted as empty. Single-file mode (missing_as_empty = false) behaviour is unchanged: an explicitly missing argument still errors with exit code 2.src/main.rs(diff_file):has_syntactic_changesis forced on. A one-sided file is a change even when its content produces no detectable differences — an empty file, content that normalizes to empty under the default--strip-cr=on, or whitespace-only content whose syntax tree matches the empty side's. Output is the existing header +Has changes./Has syntactic changes.;--skip-unchangedkeeps the file and--exit-codereports it.has_byte_changesis onlyNonewhen the bytes are equal and nothing was missing, so a one-sided empty binary file reportsBinary file added (0 B).rather thanNo changes.src/display/json.rs: with empty hunks, JSON status is nowchangedwhenhas_syntactic_changesis set (previously alwaysunchanged), keeping the JSON payload consistent with the text display and the--exit-coderesult. This also corrects--check-only --display=json, which previously reported changed files asunchanged.Tests
8 new integration tests in
tests/cli.rsusing new fixtures undersample_files/empty_files_1/andsample_files/empty_files_2/, all verified red on the pre-fix code and green after:Has changes.and exit code 1 with--exit-codeHas changes.No changes.(regression guard, anchored to the exact header + message)--skip-unchangedshows the one-sided empty file, hides the unchanged ones--strip-cr=on) →Has changes..py(syntax tree identical to the empty side's) →Has syntactic changes.--override-binaryone-sided empty file →Binary file added (0 B)."status":"changed"cargo fmt --check,cargo clippy --all -- -D warningsandcargo test(122 unit + 31 CLI) all pass.Known limitations, deliberately out of scope here: git's
/dev/nullform of one-sided files (used bygit difftoolfor added/deleted files) is indistinguishable from an explicit user/dev/nullcomparison and still reportsNo changes.; a one-sided empty binary file'sadded/removedwording keys off byte lengths (the pre-existingTODO: Fix this pedantic caseinprint_diff_result); JSON useschangedrather thancreated/deletedfor one-sided empty files because the missing side is not currently recorded inDiffResult.Disclosure
This change was developed with AI assistance (Claude) and human-reviewed; I have read all the code in the diff and can discuss every line.