How to Read a Diff
Before running any git diff command, learn to read the output format. Here is a real diff with annotations:
diff --git a/src/utils.py b/src/utils.py
index 7b9e1a3..a3f8c2d 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -10,6 +10,9 @@ def calculate(a, b):
result = a + b
+ # New: validate inputs
+ if a < 0 or b < 0:
+ raise ValueError("Inputs must be positive")
return result
| Part of the diff | What it means |
|---|---|
--- a/src/utils.py |
The "before" version of the file (prefixed with a/) |
+++ b/src/utils.py |
The "after" version of the file (prefixed with b/) |
@@ -10,6 +10,9 @@ |
Hunk header: the - numbers show the before-range; the + numbers show the after-range. -10,6 means starting at line 10, 6 lines shown; +10,9 means starting at line 10, 9 lines shown (3 lines were added). |
Lines starting with + |
Lines that were added (shown in green in most terminals) |
Lines starting with - |
Lines that were removed (shown in red) |
| Lines with no prefix | Context lines — unchanged, shown for orientation |
git diff – Unstaged Changes
Running git diff with no arguments shows changes in your working directory that have not yet been staged — i.e. what's different between your files and the staging area.
# Show all unstaged changes
git diff
# Show unstaged changes for one specific file
git diff src/utils.py
If git diff produces no output, either nothing has changed, or all your changes have already been staged with git add. Use git diff --staged to see staged changes, or git diff HEAD to see everything at once.
git diff --staged – Staged Changes
git diff --staged (also written as git diff --cached — they are identical) shows what will go into your next commit: the difference between the staging area and the last commit.
# Show staged changes (what will be committed)
git diff --staged
# Equivalent alternative (older syntax)
git diff --cached
# Show staged changes for one file only
git diff --staged src/utils.py
Running git diff --staged right before git commit is a good habit — it lets you review exactly what's about to be committed.
Before every commit, run: git status to see what's staged, then git diff --staged to review the exact changes. This two-step check prevents accidentally committing debug code, credentials, or unfinished work.
git diff HEAD – All Changes vs Last Commit
git diff HEAD combines both: it shows all changes (staged + unstaged) compared to the last commit.
# Show everything changed since the last commit (staged + unstaged)
git diff HEAD
Comparing Commits
You can diff any two commits by passing their hashes (or refs like branch names and HEAD~N syntax).
# Diff between two specific commits
git diff a3f8c2d 7b9e1a3
# Diff between the last commit and the one before it
git diff HEAD~1 HEAD
# Diff between last commit and 3 commits ago
git diff HEAD~3 HEAD
# Show only the filenames that changed between two commits (no diff content)
git diff --stat HEAD~5 HEAD
src/utils.py | 3 +++
src/helpers.py | 12 ++++++++++++
README.md | 5 ++---
3 files changed, 17 insertions(+), 3 deletions(-)
The --stat flag is great for a quick overview: it shows how many lines were inserted and deleted per file without printing the full diff content.
Comparing Branches
You can diff between branches to see what one branch has that the other doesn't — useful before merging or opening a pull request.
# Show all changes in feature-branch that are NOT in main
git diff main..feature-branch
# Three-dot syntax: show changes since the branches diverged
# (ignores commits on main that happened after the branch was created)
git diff main...feature-branch
# Summary: how many lines changed between branches
git diff main..feature-branch --stat
# Only list file names that differ between branches
git diff main..feature-branch --name-only
main..feature (two dots) shows all changes between the tips of both branches. main...feature (three dots) shows changes on feature since the two branches last shared a common ancestor — essentially, "what did feature add on top of main?" The three-dot form is usually what you want when reviewing a feature branch.
GUI Diff Tools
Terminal diffs are powerful but visual diff tools are often easier to read for large changes. Git supports any external tool via git difftool.
Configure VS Code as your diff tool
# Set VS Code as the default diff tool globally
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
# Now use it like this:
git difftool
# Or diff a specific file in VS Code
git difftool src/utils.py
Other popular diff tools
# List all built-in supported tools
git difftool --tool-help
# Use vimdiff (built-in, no config needed)
git difftool --tool=vimdiff
# Use meld (Linux, free and excellent)
git difftool --tool=meld
| Tool | Platform | Notes |
|---|---|---|
| VS Code | All | Built-in diff view; Source Control panel shows diffs inline with syntax highlighting |
| vimdiff | All (terminal) | Zero config needed; steep learning curve; perfect for remote SSH work |
| Meld | Linux / Windows | Free, excellent 3-way merge view for conflict resolution |
| Beyond Compare | All | Paid; professional-grade with folder comparison |
| GitHub / GitLab PR view | Web browser | Most convenient for reviewing pull requests with inline comments |
📋 Summary
- Reading a diff:
+lines are added,-lines are removed, plain lines are context.@@markers show the line range. - git diff — unstaged changes vs the last commit (or staging area).
- git diff --staged (or
--cached) — staged changes vs the last commit; review before every commit. - git diff HEAD — all changes (staged + unstaged) vs the last commit.
- git diff <commit1> <commit2> — compare any two commits.
- git diff main..feature-branch — compare two branches;
...compares from the common ancestor. - git diff --stat — summary of files changed, insertions, and deletions (no line-by-line diff).
- Configure VS Code as difftool with
git config --global diff.tool vscodefor a visual experience.
FAQ
The most common reason: all your changes have been staged with git add. git diff (no flags) only shows unstaged changes. Run git diff --staged to see staged changes, or git diff HEAD to see everything at once. Another possibility: the file is listed in .gitignore, so Git doesn't track it at all.
git diff compares two states (working tree, staging area, or commits) and shows what changed between them. git show <commit> displays the metadata of a specific commit (author, date, message) plus the diff introduced by that commit compared to its parent. Think of git show as "what did this specific commit change?" and git diff as "what is the difference between these two things?"
Not directly with standard git diff, but you can narrow the output. The -U flag controls context lines: git diff -U0 shows only the changed lines with zero context. For language-aware function-level diffs, some languages are supported via .gitattributes configuration. Most developers find it easier to just use a visual diff tool like VS Code and navigate to the function they care about.
After running git fetch (to update your local knowledge of the remote without merging), use git diff main origin/main to compare your local main with the remote origin/main. The origin/ prefix refers to the last-fetched remote branch. If you haven't fetched yet, your local view of the remote may be stale — always git fetch first for an accurate comparison.