Ad – 728×90
🚀 Getting Started

git diff – Viewing Changes in Git

git diff answers the question: "What exactly changed?" It shows you the precise lines added, removed, or modified — in your working directory, in the staging area, between commits, or between branches. Once you can fluently read diff output, you'll catch bugs before committing and understand any change at a glance.

⏱️ 12 min read 🎯 Beginner 📅 Updated 2026

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 output
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 diffWhat 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.

Bash
# Show all unstaged changes
git diff

# Show unstaged changes for one specific file
git diff src/utils.py
ℹ️
No output from git diff?

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.

Bash
# 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.

Pre-commit review habit

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.

Ad – 336×280

git diff HEAD – All Changes vs Last Commit

git diff HEAD combines both: it shows all changes (staged + unstaged) compared to the last commit.

Bash
# 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).

Bash
# 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
Output of --stat
 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.

Bash
# 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
ℹ️
Two dots vs three dots

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

Bash
# 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

Bash
# 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
ToolPlatformNotes
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 vscode for a visual experience.

FAQ

Why does git diff show nothing even though I changed files? +

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.

What is the difference between git diff and git show? +

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?"

Can I diff a single function instead of the whole file? +

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.

How do I see what changed between my local branch and the remote? +

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.