Coding tips

git diff vs git diff --staged vs git diff HEAD: What Each One Shows, Branch Diffs, .. vs ... and Exit Codes (Tested Examples)

git diff shows changes you have not staged, git diff --staged shows what you are about to commit, and git diff HEAD shows both together. Here is exactly what each one compares, with tested output from Git 2.47, plus new files, branch diffs, .. vs ... and exit codes for scripts.

By 9 min read 0 views
Cover image for git diff vs git diff --staged vs git diff HEAD: What Each One Shows, Branch Diffs, .. vs ... and Exit Codes (Tested Examples)

git diff compares your working files with the staging area, so it shows only changes you have not run git add on yet. git diff --staged (same as --cached) compares the staging area with the last commit, so it shows exactly what git commit would save. git diff HEAD compares your working files with the last commit, so it shows staged and unstaged changes together.

That is why git diff sometimes prints nothing even though you changed files: you already staged them. The examples below make the three views visible on one small file.

Every command ran with Git 2.47.3 in a throwaway repository with fixed dates, so the hashes are the same each time I run the script.

In this guide:

  • git diff: changes you have not staged
  • git diff --staged: what the next commit contains
  • git diff HEAD: everything since the last commit
  • New files do not appear in git diff
  • Summaries: --stat, --name-only and --name-status
  • --word-diff for small edits in long lines
  • Comparing commits and branches
  • Exit codes for scripts: --quiet and --exit-code
  • git diff vs --staged vs HEAD compared
  • Common questions while reviewing changes
  • Before you commit: a short checklist
  • Frequently asked questions

git diff: changes you have not staged

I changed the version in config.toml from 1.0 to 1.1 without staging it:

$ git status --short
 M config.toml

$ git diff
diff --git a/config.toml b/config.toml
index 0fceb52..9a4f1c9 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
 name = "app"
-version = "1.0"
+version = "1.1"
 debug = false

$ git diff --staged

git diff shows the change, and git diff --staged shows nothing, because nothing is staged yet. The git diff documentation describes this default form as the changes you could still add to the index.

git diff --staged: what the next commit contains

After git add, the two views swap:

$ git add config.toml

$ git diff

$ git diff --staged
diff --git a/config.toml b/config.toml
index 0fceb52..9a4f1c9 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
 name = "app"
-version = "1.0"
+version = "1.1"
 debug = false

Now plain git diff is empty and --staged shows the version change. Running git diff --staged right before git commit is the best habit for catching debug code and secrets before they enter history. The git status documentation explains the short status codes, where a letter in the first column means staged.

git diff HEAD: everything since the last commit

The views differ most when a file has both staged and unstaged changes. Here the version change is staged, then I also edited the debug line:

$ git status --short
MM config.toml

$ git diff
diff --git a/config.toml b/config.toml
index 9a4f1c9..93f2c19 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
 name = "app"
 version = "1.1"
-debug = false
+debug = true

$ git diff --staged
diff --git a/config.toml b/config.toml
index 0fceb52..9a4f1c9 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
 name = "app"
-version = "1.0"
+version = "1.1"
 debug = false

$ git diff HEAD
diff --git a/config.toml b/config.toml
index 0fceb52..93f2c19 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
 name = "app"
-version = "1.0"
-debug = false
+version = "1.1"
+debug = true

MM in the status means modified in both places. git diff shows only the debug edit, --staged shows only the version, and git diff HEAD shows both lines. Note the index hashes: each diff compares a different pair of file versions. The Pro Git chapter on recording changes explains the staging area with diagrams.

New files do not appear in git diff

A brand-new file is untracked, so none of the views include it until Git knows about it:

$ git status --short
MM config.toml
?? .env

$ git diff
diff --git a/config.toml b/config.toml
index 9a4f1c9..93f2c19 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
 name = "app"
 version = "1.1"
-debug = false
+debug = true

$ git add -N .env

$ git diff
diff --git a/.env b/.env
new file mode 100644
index 0000000..29c30b1
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+TOKEN=secret
diff --git a/config.toml b/config.toml
index 9a4f1c9..93f2c19 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
 name = "app"
 version = "1.1"
-debug = false
+debug = true

$ git restore --staged .env

.env was missing from git diff at first. git add -N (intent to add) tells Git about the file without staging its content, so it shows up as a new file in git diff. The git add documentation describes --intent-to-add. In this case the diff revealed a secret token, so I removed the intent again with git restore --staged; a file like this belongs in .gitignore.

Summaries: --stat, --name-only and --name-status

$ git diff HEAD --stat
 config.toml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

$ git diff HEAD --name-only
config.toml

$ git diff HEAD --name-status
M	config.toml

These options work with every form of git diff. --stat gives a change count per file, --name-only lists files, and --name-status adds a letter such as M for modified, A for added and D for deleted. They are handy in scripts and in long reviews.

--word-diff for small edits in long lines

$ git diff --word-diff
diff --git a/config.toml b/config.toml
index 9a4f1c9..93f2c19 100644
--- a/config.toml
+++ b/config.toml
@@ -1,3 +1,3 @@
name = "app"
version = "1.1"
debug = [-false-]{+true+}

Instead of a removed line and an added line, --word-diff marks the changed words inline, [-false-]{+true+}. It is much easier to read for config files, Markdown and prose.

Comparing commits and branches

git diff also compares any two commits or branches. I committed the config change, then created a feature branch with one more commit:

$ git log --oneline --all
67dd520 greet world
dc27949 bump version, enable debug
8e670f9 initial

$ git diff HEAD~1 HEAD --stat
 app.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git diff main feature
diff --git a/app.py b/app.py
index 11b15b1..9f69d32 100644
--- a/app.py
+++ b/app.py
@@ -1 +1 @@
-print("hello")
+print("hello, world")

$ git diff main..feature --name-only
app.py

$ git diff main...feature --name-only
app.py

$ git switch -q main

$ git diff main..feature --name-only
app.py
config.toml

$ git diff main...feature --name-only
app.py

At first, main..feature and main...feature listed the same file. After I added a new commit on main, they differed. Two dots compare the two branch tips directly, so main's new config change shows up too. Three dots compare feature with the point where it branched off, so only the feature's own work appears, which is what a pull request shows. The gitrevisions documentation defines both notations.

Exit codes for scripts: --quiet and --exit-code

$ git diff --quiet
[exit 0]

$ git diff --quiet
[exit 1]

$ git diff --exit-code --stat
 config.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
[exit 1]

git diff --quiet prints nothing and exits with 0 when there are no unstaged changes and 1 when there are. --exit-code does the same while still printing output. Use them in CI or pre-commit scripts, for example to fail a build if code formatting changed files: git diff --exit-code after running the formatter.

git diff vs --staged vs HEAD compared

CommandComparesShows
git diffWorking files vs staging areaChanges you have not staged yet
git diff --staged (= --cached)Staging area vs last commitWhat the next commit will contain
git diff HEADWorking files vs last commitEverything since the last commit, staged or not
git diff A BCommit or branch A vs BDifference between two snapshots
git diff A...BMerge base of A and B vs BOnly what B added since it branched off A
git diff --stat / --name-onlyAny of the aboveA summary instead of full patches
git diff --quietWorking files vs staging areaNo output; exit code 1 if anything differs

A handy way to remember: plain git diff answers "what have I not added?", --staged answers "what am I about to commit?", and HEAD answers "what have I changed since my last commit?". If you want to undo what these commands show, my guide to git reset vs revert vs restore covers each case.

Common questions while reviewing changes

  • git diff shows nothing but git status shows changes. The changes are staged (use --staged) or the files are untracked (use git add -N or git status).
  • I want to see one file only. Add the path at the end: git diff --staged config.toml.
  • I want the diff of the last commit. git diff HEAD~1 HEAD, or git show.
  • I want to compare with the remote. Run git fetch, then git diff main origin/main. My post on git fetch vs git pull explains why fetching first matters.

Before you commit: a short checklist

  1. Run git status to see which files are staged, modified or untracked.
  2. Run git diff to review anything you forgot to stage.
  3. Run git diff --staged to review exactly what will be committed.
  4. Unstage anything that should not go in with git restore --staged <file>.

If a file should never be committed at all, such as .env, add it to .gitignore. If it is already tracked, my guide to removing a file from Git but keeping it locally shows the steps.

Frequently asked questions

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

git diff shows changes in your working files that are not staged yet. git diff --staged shows changes that are staged and will go into the next commit.

Are --staged and --cached the same?

Yes. git diff --staged is a synonym for git diff --cached. Both compare the staging area with the last commit.

What does git diff HEAD show?

All changes in your working files compared with the last commit, whether they are staged or not. It combines git diff and git diff --staged.

Why does git diff show nothing?

Either you already staged the changes, so use git diff --staged, or the files are new and untracked, which git diff does not show.

How do I see changes in a new file with git diff?

Run git add -N <file> to mark it as intent to add. Then git diff shows it as a new file. Or stage it and use git diff --staged.

What is the difference between git diff A..B and A...B?

A..B compares the two tips directly. A...B compares B with the common ancestor of A and B, so it shows only the changes made on B, like a pull request.

How do I check in a script if there are uncommitted changes?

Use git diff --quiet for unstaged changes and git diff --staged --quiet for staged ones. Both exit with 1 when there are differences and 0 when there are none.

Last checked: 9 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.

Share X LinkedIn WhatsApp Facebook
Was this helpful?

0%