Once you can read @@ -12,7 +12,9 @@, diffs stop being scary

This is the line most people skip in git diff output:

@@ -12,7 +12,9 @@ export function calculateTotal(items) {

Everyone understands + and -. Far fewer can say what those four numbers mean. Being able to read them makes reviews sharper and makes failed patches and merge conflicts much easier to diagnose.

This is a reference that takes the unified diff format apart, one piece at a time.

The overall shape

A typical diff has three layers:

diff --git a/src/cart.js b/src/cart.js     ← which file
index 83db48f..bf269f4 100644              ← old/new object IDs and file mode
--- a/src/cart.js                          ← the "before" side (a)
+++ b/src/cart.js                          ← the "after" side (b)
@@ -12,7 +12,9 @@ export function calculateTotal(items) {   ← hunk header
   const subtotal = items.reduce(...)       ← context line (leading space)
-  return subtotal;                         ← removed line
+  const tax = subtotal * 0.1;              ← added line
+  return subtotal + tax;
 }

The a/ and b/ prefixes are a convention meaning “before” and “after” — they are not real directories. A new file shows --- /dev/null; a deleted one shows +++ /dev/null.

The four numbers in a hunk header

@@ -12,7 +12,9 @@
   │  │  │  └── after:  9 lines from there
   │  │  └───── after:  starting at line 12
   │  └──────── before: 7 lines from there
   └─────────── before: starting at line 12

So the form is -start,count +start,count. The key point: the count is the size of the range the hunk covers, not the number of changed lines — context lines are included. The example says a 7-line region became a 9-line region, a net gain of two lines.

When the count is 1 it may be omitted, giving @@ -1 +1 @@. The text trailing the closing @@export function calculateTotal(items) { above — is a section heading git guesses by scanning backwards for a less-indented line. It is context for humans, not part of the change.

The first character of each line decides everything

PrefixMeaning
(space)Context line, unchanged
-Present only in the old version (removed)
+Present only in the new version (added)
\A note about the file itself (see below)

There is no concept of a modified line. A line where you fixed a single character is represented as a removal plus an addition. This is the single biggest source of confusion when reading diffs: a wall of red and green may turn out to be one changed semicolon.

When you need to know what actually changed inside a line, compare at word level instead — git diff --word-diff in git, or paste both versions into the text diff tool in the browser.

Context defaults to three lines

The unchanged lines shown around a change are “context”, three lines by default. Whoever applies the patch uses that surrounding context to locate the right spot. That is why a patch still applies when line numbers have shifted — and equally why it fails when the surrounding lines have themselves been edited.

git diff -U0     # no context, changed lines only
git diff -U10    # ten lines on each side

If a review feels like it lacks surrounding context, raising -U usually helps more than scrolling.

\ No newline at end of file

-const config = {};
\ No newline at end of file
+const config = {};

This note means the file does not end with a newline. If two versions look identical but still produce a diff, a trailing newline was added or removed. Editors and .editorconfig (insert_final_newline) flip this without anyone noticing, and it shows up as review noise.

Why a whitespace-only change looks like a full rewrite

Converting indentation from tabs to spaces, or stripping trailing whitespace, changes the line content — so every affected line appears as a -/+ pair even though nothing looks different on screen.

git diff -w                    # ignore differences in amount of whitespace
git diff --ignore-blank-lines  # ignore added/removed blank lines

Line endings (LF vs CRLF) cause the same effect. If a repository shared between Windows and Linux shows every line as changed when you edited nothing, check this first.

@@@ on merge commits

Running git show on a merge commit can produce headers with three @ characters:

@@@ -12,7 -12,8 +12,9 @@@

This is a combined diff, showing the differences against both parents at once. There are two prefix columns instead of one: the first compares against the first parent, the second against the second. Only lines that differ from both parents appear — in other words, exactly what was changed by hand while resolving the merge.

Renames are detected, not recorded

diff --git a/src/utils.js b/src/helpers.js
similarity index 95%
rename from src/utils.js
rename to src/helpers.js

Git does not record renames. It infers them at diff time from content similarity, treating anything at or above 50% similar as a rename by default, and reporting the figure as similarity index. Rename a file and heavily rewrite it in the same commit and git will show a deletion plus a new file instead.

Comparing things locally

For text that isn’t committed — logs, config files, API responses — paste both versions into the text diff tool and the changes line up in colour. Everything runs in the browser, so production config and log excerpts can be compared without leaving your machine.

Formatting before comparing removes noise: run JSON through the JSON formatter or SQL through the SQL formatter first, and only meaningful differences remain. The basics of diffing — line versus word level, whitespace handling — are covered in reading and using text diffs.

Summary

  • @@ -12,7 +12,9 @@ is -start,count +start,count; the count covers the whole range including context, not just changed lines
  • The leading character is everything (space / - / +). There is no “modified” — only removed plus added
  • Context is three lines by default, and patches locate themselves by matching it
  • \ No newline at end of file is about the trailing newline — a frequent source of phantom diffs
  • Whitespace-only and line-ending changes look like full rewrites; isolate them with git diff -w
  • @@@ marks a combined diff on a merge commit, comparing against both parents
  • Renames are inferred from similarity, not stored (similarity index)

FAQ

What do the numbers in @@ -12,7 +12,9 @@ mean?

The - side describes the old version and the + side the new one, each as “start line, line count”. Here the old file contributes 7 lines starting at line 12 and the new file 9 lines starting at line 12. The counts include unchanged context lines, so they are not the number of edited lines.

Why does a one-character edit show up as a whole changed line?

The unified diff format has no way to express “this line was modified” — every change is expressed as a removal (-) plus an addition (+). To see what changed within the line, use git diff --word-diff or a tool that compares at word level.

Every line shows as changed but I edited nothing

Almost always line endings (LF vs CRLF) or indentation whitespace. If git diff -w makes the diff disappear, that is the cause. To fix it repository-wide, pin line endings with a .gitattributes entry.

Is the content I paste in sent anywhere?

No. The text diff tool runs entirely in your browser — what you paste is never transmitted to a server, so it is safe to use with production configuration and logs.