DiffKit / Guides / How to read a git diff

How to read a git diff

A diff is a recipe for turning one file into another: keep these lines, delete those, insert these. Once you can read the format, every pull request, patch file and git diff becomes quicker to review.

The unified diff format, line by line

Git, GitHub, GitLab and the classic diff -u command all print the unified format, defined by GNU diffutils. It has three parts: a file header, one or more hunks (blocks of nearby changes), and inside each hunk a sequence of lines that each start with a single character: a space, a − or a +.

Click any line of this git diff output to see what it means.

The hunk header. “-12,7” means this block starts at line 12 of the old file and covers 7 lines; “+12,8” means line 12 and 8 lines in the new file. One line was added in total. The text after the second @@ is the enclosing function, added by git for context.

The three line prefixes

PrefixMeaningIn DiffKit
(space)Context: unchanged, in both versionsPlain line
−Only in the old versionMagenta, left side
+Only in the new versionCyan, right side

An edited line is always shown as a removal followed by an addition, because the format works with whole lines only. Side-by-side tools like the DiffKit diff checker pair those two lines up again and highlight just the changed words.

How does a computer find the differences?

Diff tools don’t hunt for changes directly. They look for the opposite: the longest common subsequence (LCS), the longest run of lines (or characters) that appear in both versions in the same order, though not necessarily side by side. Whatever isn’t part of it has to be a deletion or an insertion. Maximise what you keep and you get the shortest possible edit script.

The textbook way to find the LCS is dynamic programming: fill a grid where each cell answers “how long is the LCS of the first i characters of the old text and the first j characters of the new text?” Every cell depends only on its neighbours above, left and diagonally up-left. Step through it below with two words, or type your own.

Try it: build the table a diff algorithm uses. Each cell holds the length of the longest common subsequence (LCS) of the two prefixes.
0 / 42 cells
∅SITTING
∅00000000
K0
I0
T0
T0
E0
N0

Press Play or Step. The table fills row by row. Green-tinted cells are where the two characters match.

Walking back from the bottom-right corner rebuilds the answer. A diagonal step is a character both versions keep, a step left is an insertion, and a step up is a deletion. If you like learning this way, ahaboo has narrated, interactive explainers that show how everyday things work, one idea at a time.

Why real tools don’t fill the whole grid

The full table needs n × m cells: two 10,000-line files would mean 100 million. In 1986 Eugene Myers published “An O(ND) Difference Algorithm and Its Variations”, which explores the grid along diagonals and stops as soon as it reaches the corner. Its running time grows with the number of differences D rather than the file size squared, so near-identical files, the usual case in code review, are compared almost instantly. It is still git’s default algorithm, and DiffKit uses the same approach.

Git also offers --diff-algorithm=patience and histogram. Both first anchor on lines that appear exactly once in each file, such as function signatures, which keeps a moved block from being split across unrelated braces and blank lines.

Everyday git diff commands

git diff                    # unstaged changes
git diff --staged           # what the next commit contains
git diff main...feature     # changes on feature since it branched from main
git diff HEAD~3 -- src/     # last 3 commits, only under src/
git diff --word-diff        # inline word-level changes
git diff -w                 # ignore whitespace
git diff --stat             # files changed, insertions, deletions
git diff > fix.patch        # save as a patch; apply with: git apply fix.patch

Every one of these produces the unified format above, so you can paste the output or the two file versions into DiffKit for a side-by-side view. When two branches edit the same hunk, git can’t merge them automatically. That’s a merge conflict, and the merge conflict guide picks up the story there.

Questions people ask

What does @@ -12,7 +12,8 @@ mean in git diff?

It is a hunk header. The old file’s section starts at line 12 and spans 7 lines; the new file’s section starts at line 12 and spans 8 lines. If a count is 1, git omits it, so @@ -5 +5 @@ means a single line at line 5 on both sides.

What is the difference between git diff, git diff --staged and git diff HEAD?

git diff shows unstaged changes (working tree vs. index). git diff --staged (or --cached) shows what will go into the next commit (index vs. HEAD). git diff HEAD shows both together (working tree vs. last commit).

How do I see word-level changes in git?

Use git diff --word-diff for inline [-removed-]{+added+} markers, or git diff --color-words for coloured words without the brackets. DiffKit’s word highlighting does the same thing visually.

Which diff algorithm does git use?

Myers by default. You can switch with --diff-algorithm=patience, histogram or minimal. Histogram and patience often give more readable results for code where blocks move around, because they anchor on lines that occur only once.

What does “\ No newline at end of file” mean?

The last line of that version doesn’t end with a line-break character. POSIX defines a line as ending in a newline, so git flags it. Most editors can be set to add the final newline automatically.

Other comparisons