Skip to content

Show real diffs for a staged addition instead of the whole file (issue #370) - #758

Merged
hannesa2 merged 1 commit into
gitx:masterfrom
pcambra:fix-370
Oct 8, 2026
Merged

hannesa2 merged 1 commit into
gitx:masterfrom
pcambra:fix-370

Conversation

@pcambra

@pcambra pcambra commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #370

Symptom

Stage a new file, edit it again: both the staged and the unstaged entry show the raw on-disk file with no +/- lines. The two sides are indistinguishable from a new file until the file is committed.

Cause

NEW only means the path is absent from the parent tree, but two places treated every NEW file as untracked:

  • Resources/html/views/commit/commit.js: showFileChanges short-circuits to showNewFile for any status == 0, and showNewFile reads the working file whole regardless of which list was clicked.
  • PBGitIndex readDiffForFile:staged:contextLines:: the staged side of a NEW file is answered with git show :0:<path> (a bare blob dump) and the unstaged side by reading the working file from disk.

Fix

Only a file without an index entry (status == NEW && !hasStagedChanges) is shown whole. A staged addition is diffed with diff-index --cached against the parent tree (the empty tree in an unborn repository), and its later edits with diff-files against the index, the same as any tracked file. PBChangedFile already exposes hasStagedChanges to the web view (isKeyExcludedFromWebScript returns NO), so the JS condition needs no bridging change.

Tests

Three new cases in PBGitIndexDiffCacheTests, using its existing stub harness:

  • an untracked file is read from disk without asking git
  • a partially staged addition asks diff-files for its unstaged side
  • a staged addition asks diff-index --cached HEAD for its staged side

make unit-test is green locally on arm64.

Manual check

New file, git add, edit again, open the repo in the built app: the staged entry shows the whole file as + lines, the unstaged entry only the later edit. On 1.6 both show unmarked text.

Notes

Same approach as @hbmartin's fork commit hbmartin/gitx@767dbf7, which never reached a PR; rebuilt against current master since upstream renders through the WebKit view and diffForFile: is now split into the cached readDiffForFile:. The screenshot comparison does not run on fork PRs; if any baseline shows a staged new file it will need updating.

Copilot AI left a comment

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.

Copilot review overview

🟢 Approval recommended

The implementation correctly addresses the reported behavior and includes focused regression coverage.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes staged additions so staged and subsequent unstaged changes render as actual diffs.

Changes:

  • Distinguishes untracked files from staged additions.
  • Uses diff-index and diff-files for staged additions.
  • Adds regression tests for all affected paths.
File Description
Resources/​html/​views/​commit/​commit.js Shows whole contents only for untracked files.
Classes/​git/​PBGitIndex.m Generates proper staged and unstaged diffs for additions.
GitXTests/​PBGitIndexDiffCacheTests.m Covers untracked and staged-addition behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@goneng goneng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @pcambra, the staged and unstaged sides of a new file now read as real diffs, and the unstaged side of a staged addition also stops failing when the file was deleted from disk afterwards. One thing worth handling before this lands, see the inline comment.

// Only a file git does not know at all is shown whole. Once an addition is
// staged, each side has a real diff: the index against the parent tree and
// the working file against the index
if (file.status == 0 && !file.hasStagedChanges) // Untracked file?

@goneng goneng Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With this, a staged addition goes through displayDiff, which also turns on line selection.
Unstaging only some of its lines then fails:
stageLines keeps the diff header, which for an addition carries
new file mode 100644 and --- /dev/null, and
git rejects a partial reverse of a creation patch:

error: new file n.txt depends on old contents

Steps:

  • create n.txt with lines a, b, c, d,
  • git add it,
  • select the staged entry,
  • select only the +b line and unstage it.

The line stays staged.

Before this PR the staged entry had no hunk or line controls, so this path was not reachable.

The hunk-level Unstage button is fine: it removes the whole entry from the index and the file becomes untracked again.

A possible fix:
when the diff is an addition, rewrite the header for line operations as a modification of the index entry,
i.e. drop new file mode and use --- a/<path> / +++ b/<path> with old start 1.
This applies cleanly with git apply --unidiff-zero --cached --reverse and
unstages just the selected line:

diff --git a/n.txt b/n.txt
--- a/n.txt
+++ b/n.txt
@@ -1,3 +1,4 @@
 a
+b
 c
 d

@pcambra

pcambra commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the feedback, not my turf :). Pushed 5bf518c trying to implement your suggestion: for a line operation on an addition, stageLines now emits the header as a modification of the index entry (diff --git a/p b/p, --- a/p, +++ b/p, old start 1). It only does so on partial selection; selecting every line keeps the creation header so the entry is removed, same as the hunk button. The two names are taken from the diff --git line by splitting it at its midpoint, so paths with spaces and quoted non-ASCII paths keep working.

Verification: n.txt with a, b, c, d, git add, unstage only +b from the staged entry: the index holds a c d, git diff shows the lone +b. Also checked x y.txt and café.txt the same way.

@hannesa2
hannesa2 requested a review from goneng October 6, 2026 05:23
@goneng

goneng commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

@pcambra @hannesa2 LGTM, with one comment: the new branch in stageLines runs on more than staged additions, so its code comment could say so.

The check is start_old == 0 && count[0] > 0. That is true for any hunk whose old side starts at line 0, which includes two cases where the file is already tracked:

  • a tracked empty file that gets lines added and staged
  • lines inserted at the top of a tracked file, with the context slider at 0

In both, unstaging some of the lines now goes through modificationHeader too. I tried both with git apply --unidiff-zero --cached --reverse and they unstage just the selected lines, so the behavior is fine. The only mismatch is the comment, which describes the branch as being about additions.

Either of these would line them up:

  • reword the comment to say the branch covers any hunk whose old side is empty, or
  • limit the branch to additions by also checking for new file mode in diffHeader.

The first is simpler, since the rewrite is correct for the other two cases as well.

@pcambra

pcambra commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Pushed the comment rewording. On run 1750 the failures were in PBGitWorktreeManagementTests (-waitForWorktrees timed out, then a nil reached an array literal); the diff-cache tests passed. Could someone approve a re-run when convenient?

@goneng

goneng commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

@pcambra , please squash the chance to a single commit - it keeps the repo more coherent. Thanks for your contribution!

@pcambra

pcambra commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

done!

@hannesa2
hannesa2 merged commit ac87ecd into gitx:master Oct 8, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Staging untracked files and then making more changes doesn't show the diff correctly

4 participants