magit-diff-mode

magit-diff-mode: a read-only diff buffer with line-, hunk- and file-level stage/unstage (s/u) — repo-wide against HEAD, or scoped to one file and one baseline.

On this page

A read-only diff in its own buffer, with staging on top. :magit-diff opens the repo-wide view — git diff HEAD, staged and unstaged changes combined.

The headerline names the scope, and the path when the buffer is file-scoped: staged src/main.rs. Without it, three buffers showing three different baselines would be indistinguishable.

Unlike magit-status's inline =, this is a whole-buffer view — useful when a diff is too large to read comfortably expanded inside the status list.

Chords

ChordAction
sStage the hunk or file at cursor
uUnstage the hunk or file at cursor
xDiscard the hunk or file at cursor (asks first)
s / u / x in VisualAct on the selected lines only
aApply a committed hunk to the working tree
-Reverse a committed hunk out of the working tree
]c / [cNext / previous hunk
<CR>Visit the file at cursor
grRefresh (re-run the underlying git diff)
DDiff arguments — -w ignore whitespace, -s file summary, -U context lines
dvOpen the file at cursor side-by-side against its baseline

Everything above except <CR> and gr comes from magit-hunk-mode — the minor mode that owns diff content, active in every magit buffer that renders a diff. So the same keys work identically here, in magit-status's inline expansions, in a commit's staged region, in a revision and in a stash's patch. (x used to be missing here specifically, because these chords were once declared separately by each buffer and this one's set had drifted.)

]] / [[ / ]f / [f / TAB / q come from magit-core-mode, which is every magit buffer whether or not it shows a diff.

s and u act on the hunk under the cursor when there is one, so ]c then s stages exactly the hunk you landed on and leaves the file's others alone. With the cursor on a file header — or anywhere outside a hunk body — they fall back to the whole file, resolved from the nearest diff --git a/<path> b/<path> header above.

With a selection up they act on the selected lines rather than the whole hunk — V, extend with j / k, then s, and only those lines are staged. The rules are identical to magit-status's; that page has the details.

Which chord applies depends on the buffer's scope. A *magit:diff:<repo>:unstaged:* buffer stages with s; a *magit:diff:<repo>:staged:* one unstages with u; pressing the other says so rather than failing in git.

*magit:diff:<repo>* (against HEAD) stages files, not hunks. Its hunks combine staged and unstaged changes, so a single hunk there is not a patch against either the index or the working tree. s inside one reports that hunk staging isn't available in this view; move to the file header for the whole file, or open the scoped view (d on the file in magit-status) to work hunk by hunk.

The three scopes

BufferBaselineOpened by
*magit:diff:<repo>*git diff HEAD:magit-diff, or d in the repo dispatch
*magit:diff:<repo>:<path>*that file against HEADd in the file dispatch (C-c f)
*magit:diff:<repo>:staged:<path>*git diff --cachedd on a file in magit-status's Staged section
*magit:diff:<repo>:unstaged:<path>*git diff (worktree vs index)d on a file in magit-status's Unstaged section

The scope decides what <CR> opens: from a staged-scoped buffer it opens the index blob (read-only, magit-file-revision-mode), because that's what the diff you're reading describes. From the others it opens the live working-tree file.

How the diff is coloured

Added and removed lines get a full-width background tint as well as a coloured foreground, so a hunk reads as a block at a glance rather than as text that happens to start with + or -. The tint spans the whole row, past the end of the text — which is what makes a run of added lines look like one shape.

Four theme elements control it, and they apply anywhere a diff is shown, including magit-status's inline = expansions, the commit buffer's staged diff, a revision and a stash:

ElementWhat it colours
diff.add.textforeground of an added line
diff.add.linebackground of an added line
diff.remove.textforeground of a removed line
diff.remove.linebackground of a removed line

Hunk headers (@@), file headers and diff --git lines are deliberately not tinted — they describe the diff's structure rather than changed content, so tinting them would break the blocks apart.

There is no gutter + / - sign column here, unlike a file being edited under a diff session: the text already starts with the marker, so a second one would be redundant.

Behaviour worth knowing

  • Populated once on open; gr re-runs the diff.
  • s / u stage a hunk in the scoped views, a file in the HEAD view — see the note above. The same rule holds in magit-status.
  • x (discard) isn't bound here, only in magit-status.
  • Staging keeps your place. After s or u the buffer re-runs its diff and the cursor lands on the hunk that took the staged one's place, so you can work down a file hunk by hunk without hunting for where you were.
  • Not yet implemented: no side-by-side pane layout, no do / dp hunk transfer between panes, no visual-mode partial-hunk staging across MORE than one hunk (within one hunk it works — see above). For side-by-side diffing of two files (as opposed to reviewing git state), see diff-mode, which is a separate feature.

See also

  • magit-status-mode — inline = diffs and the sections d is pressed from.
  • diff-mode — lattice's general diff/merge surface, unrelated to git porcelain.