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 +.
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
| Prefix | Meaning | In DiffKit |
|---|---|---|
(space) | Context: unchanged, in both versions | Plain line |
− | Only in the old version | Magenta, left side |
+ | Only in the new version | Cyan, 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.
| ∅ | S | I | T | T | I | N | G | |
|---|---|---|---|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| K | 0 | |||||||
| I | 0 | |||||||
| T | 0 | |||||||
| T | 0 | |||||||
| E | 0 | |||||||
| N | 0 |
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.patchEvery 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.