Reading a Git Diff Without the Guesswork

The unified diff is the lingua franca of code review. Git prints it, pull requests render it, and patch files store it. At first glance it is a jumble of pluses, minuses and @@ symbols, but the format follows a small, consistent grammar. Once you know the pieces, you can read any diff at a glance.
The header and the hunks
A diff starts with two lines naming the files being compared: one prefixed with --- for the original and one with +++ for the modified version, conventionally written as a/file and b/file. After that come one or more hunks — the actual regions of change. Each hunk opens with a line wrapped in @@ markers, like @@ -12,7 +12,9 @@. Those numbers say where the hunk sits: minus is the old file, plus is the new one, and each pair is a starting line number and how many lines the hunk spans.
You rarely need to do arithmetic with those numbers by hand, but knowing what they mean removes the mystery. When a review tool lets you jump between hunks, it is simply moving from one @@ block to the next.
The lines before the first hunk
Git prints more than the --- and +++ pair. A file’s diff opens with diff --git a/path b/path, followed by an index line such as index 8a1f3c2..b74d90e 100644 — the abbreviated ids of the two blobs and the file mode. A new file shows new file mode 100644 in place of an old blob, a removed one shows deleted file mode, and a permission change on its own appears as old mode and new mode with no content lines at all. Binary files are summarised in a single line rather than printed.
Renames are the interesting case, because Git does not record them. It detects them while producing the diff, by comparing the content of deleted and added files, and reports what it found as similarity index 95% followed by rename from and rename to. Rename detection is on by default; copy detection, -C, is not, because it costs considerably more. A similarity index below 100% means the file was both moved and edited, and the hunks that follow describe those edits.
What the numbers in @@ actually count
The two pairs in @@ -12,7 +12,9 @@ are a start line and a length, and the length counts every line the hunk prints for that side, context included. The old length equals the number of space-prefixed and minus-prefixed lines; the new length equals the space-prefixed and plus-prefixed ones. When a length is exactly one it is omitted, giving headers like @@ -12 +12,3 @@, and a file created from nothing starts at zero: @@ -0,0 +1,40 @@.
Two smaller details are worth recognising. Whatever follows the closing @@ is the enclosing function or section, printed purely as a navigation aid; Git guesses it with a per-language heuristic you can correct by assigning a diff driver in .gitattributes, for example *.py diff=python. And a line reading \ No newline at end of file is not content but a note that the line above it ends without a newline — which is sometimes the entire difference a hunk is reporting.
Plus, minus and context
Inside a hunk every line carries a one-character prefix. A minus means the line was in the original and was removed. A plus means the line is new. A space means the line is unchanged and is shown only for context, so you can see the surroundings of the edit. By default a diff includes three context lines above and below each change, which is usually enough to locate it without printing the whole file.
A modification appears as a minus line immediately followed by a plus line: the old version, then the new one. Trained on this pattern, your eye learns to pair them automatically and read a replacement as a single change rather than two unrelated events.
From reading a patch to applying one
Context lines are not only for the reader. When a patch is applied, the three lines above and below each change are how the tool finds the right place even if the file has shifted since the patch was made: it searches near the recorded line numbers and accepts a match a little further along. That is why git apply and patch usually succeed against a file that has moved on, and why a patch generated with no context at all, -U0, is far more fragile.
The a/ and b/ prefixes explain the -p1 you see in patch commands: it strips one leading path component before looking for the file. Git understands its own prefixes, and git apply --check reports whether a patch would apply cleanly without changing anything. If it would not, git apply -3 falls back to a three-way merge using the blob ids from those index lines, turning a flat refusal into an ordinary conflict you can resolve.
Unified, split, and reviewing well
The raw text format above is the unified view. Many tools also offer a split view that places the old and new files in two columns side by side, which some people find easier for large changes. Neither is more correct — they present the same hunks differently. DiffScope gives you both from the same comparison, plus word-level highlighting the plain-text format cannot show.
To review efficiently, read a hunk's context first to anchor yourself, then the minus lines, then the plus lines, and ask whether the change does what the surrounding code implies it should. Paste a before-and-after into DiffScope, download the .patch, and you have a portable record of exactly what changed.
The takeaway
A unified diff names the files with --- and +++, splits changes into @@ hunks, and marks each line with minus (removed), plus (added) or space (context). Read context, then removals, then additions — and switch to a split view when a change is large.
Open the tool and try it now
More guides

How Diff Algorithms Work
Every diff tool answers one question: what is the smallest set of edits that turns one text into another? Here's the idea behind LCS and the Myers algorithm.

Line Diff vs Word Diff: When to Use Each
The same change can be shown as whole swapped lines or as a few highlighted words. Here's why both granularities exist and when each one is clearer.

Comparing Config Files Safely
Config drift causes real outages. Here's how to diff configuration files so you catch the meaningful change without leaking secrets or drowning in noise.