Read Git Changes Safely with git diff
Before you commit, run git diff and find out what you actually changed, not what you think you changed. It answers three questions that matter during a review: what is different in the working tree, what is staged, and how one commit or branch differs from another. The examples use Git 2.43.0, installed from the local git-man package, and the whole walkthrough takes 10 to 15 minutes if you already have a working tree to point it at.
The route
Jump straight to the step you need, or tick off Done means at the end.
It only reads repository state, unless you ask it to write a report with --output. It does not stage, commit, reset or discard anything, which makes it a safe first command whenever you are not sure what happened.
Before you start
Move into the repository you want to inspect and confirm the Git version before you trust anything it tells you:
$ cd /path/to/project
$ git --version
git version 2.43.0
$ git status --short
M src/example.c
?? notes.txt
The status output is not a diff, but it tells you which kind of change you are looking at:
- A leading
Mmeans the working file differs from the index. - A leading
Mmeans the index differs fromHEAD. - An untracked file has no diff until you add it to the index, so check it separately with a suitable viewer before staging it.
1. Inspect unstaged changes
Run the command with no revision arguments:
$ git diff
diff --git a/src/example.c b/src/example.c
index 3d2f1ab..91c4e77 100644
--- a/src/example.c
+++ b/src/example.c
@@ -12,3 +12,4 @@ int main(void) {
return 0;
}
+/* explain the return value */
This compares the working tree with the index, also called the staging area, and shows changes you have made but not yet staged. The patch format is the default. Lines beginning with - are removed from the old side, lines beginning with + are added on the new side, and the surrounding context carries neither marker.
Put -- before a path to focus on it, a habit worth keeping since a filename can otherwise be mistaken for a revision:
$ git diff -- src/example.c docs/usage.md
Long output gets unwieldy fast. --stat gives a compact summary, while --name-only lists only the changed paths:
$ git diff --stat
$ git diff --name-only
src/example.c
docs/usage.md
2. Inspect staged changes before committing
Once you run git add, the ordinary command stops showing those edits because they now match the index. Compare the index with the latest commit instead:
$ git diff --cached
$ git diff --staged
--staged is a synonym for --cached. With no commit named, Git compares the index with HEAD, and this is the review that should happen immediately before a commit. If the repository has no HEAD yet, the command shows all staged changes.
Tip
A clean-looking git diff does not mean nothing changed, it only means nothing is unstaged. Check both views when you are unsure:
$ git diff --quiet; echo "unstaged status: $?"
$ git diff --cached --quiet; echo "staged status: $?"
unstaged status: 1
staged status: 0
Here, the first command found unstaged differences and the second found none. --quiet suppresses the patch and leaves the exit status as the signal, so do not treat a non-zero status as a shell failure until you know which comparison you asked for.
3. Compare commits and branches without guessing about ranges
Name two endpoints to compare two snapshots:
$ git diff HEAD~1 HEAD -- src/example.c
$ git diff main feature/login --stat
The two-dot spelling, such as main..feature/login, is accepted as another way to compare two commits, but git diff is comparing endpoints, not listing every commit in a range. For the change introduced by a topic branch since it split from main, use three dots instead:
$ git diff main...feature/login --
That compares the merge base of the two names with the tip of feature/login. It answers "what does this branch add compared with where it started?" It does not answer "what commits exist on both sides?" Use git log for that question.
If you want your current work compared with a branch tip, one revision is enough:
$ git diff main --
This includes both staged and unstaged changes in the working tree relative to main. Use git diff --cached main when you specifically want the staged snapshot compared with that commit.
4. Make review output easier to trust
For a quick review, these options earn their keep:
--checkwarns about conflict markers and whitespace errors in the proposed change. Its definition of a whitespace error followscore.whitespace; by default that includes trailing spaces and a space followed by a tab in initial indentation.--word-diffshows changed words instead of whole changed lines. Its default plain mode uses[-removed-]and{+added+}, which can be ambiguous if those delimiters occur in the input.-U20gives 20 lines of context instead of the usual three: clearer for review, bigger for the report.- Disable colour when copying a report into logs or scripts, so terminal control codes do not become part of the data.
Run the whitespace check before committing:
$ git diff --cached --check
src/example.c:15: trailing whitespace.
A clean run prints nothing and exits successfully. A reported problem gives a non-zero status. Do not combine --check with --exit-code; the manual marks those two options as incompatible.
5. Compare ordinary files when Git is not the point
--no-index compares two filesystem paths, including files outside a repository entirely:
$ git diff --no-index -- /tmp/config.before /tmp/config.after
diff --git a/tmp/config.before b/tmp/config.after
index ...
--- a/tmp/config.before
+++ b/tmp/config.after
This form implies --exit-code, so identical files return zero and different files return one. A non-zero result can therefore mean "differences found", not "the command broke". It does not alter either input. Keep paths after -- when you want to make the boundary obvious.
Common traps and recovery
- Nothing appears. You may be looking at the wrong comparison. Run
git status --short, then check bothgit diffandgit diff --cached. Untracked files are not included in either diff until they are staged. - The diff looks noisy. Check whether whitespace or generated files are involved.
--ignore-space-at-eolignores end-of-line whitespace differences; use it only when whitespace is not part of what you are reviewing. A cleaner display does not change the underlying files. - You used the wrong revision. Stop before staging or committing anything. Re-run with explicit names and a path separator, for example
git diff HEAD~1 HEAD -- path/to/file. These inspection commands are reversible because they do not change repository state. - Watch output redirection.
git diff --output=review.patchwrites a report, while shell redirection such as> review.patchtruncates an existing file before Git even starts. Choose a new filename if the old report matters.
Done means
- You can distinguish working-tree changes from staged changes.
- You have reviewed the exact staged patch with
git diff --cached. - You know that two-dot and three-dot comparisons answer different questions for branch work.
- You have used
--checkwhere whitespace or conflict markers could matter. - You have not confused an exit status of one, meaning differences found, with a failed inspection.