Use git-diff-index to Check What Is Ready to Commit
You will use git diff-index to answer two practical questions: what differs from a commit in the working tree, and what is already staged in the index. You will finish with both a readable patch and a compact check suitable for scripts. Allow about ten minutes. You need an existing Git repository with at least one commit; no elevated privileges are required.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Check the installed command
This guide follows Git 2.43.0, supplied here by git-man package version 1:2.43.0-1ubuntu7.3. The command has a deliberately narrow job: compare the contents and modes recorded in a tree object with tracked paths in the working tree, or with paths in the index. The installed manual describes the raw format as the default.
$ git --version
git version 2.43.0
$ git diff-index -h
usage: git diff-index [-m] [--cached] [--merge-base] [<common-diff-options>] <tree-ish> [<path>...]
Use a commit, tag, or other valid tree-ish value for TREE in the examples below. In the usual review workflow, that value is HEAD.
2. See everything that could differ from HEAD
Run the command without --cached to compare HEAD with the index and with working-tree paths that are not in sync with the index:
$ git diff-index HEAD
With the default raw output, a changed path is represented by file modes, the old and new object names, a status letter, and the pathname. A new or locally modified working-tree file may show an all-zero new object name. That is a signal that there is no object for the current working-tree content yet; it is not a content hash you can diff directly.
There is a subtle performance trap here. The non-cached form can use the index stat information to report a path as tentatively changed without reading its contents. A timestamp or size change can therefore produce a raw record even when the bytes happen to be unchanged. If the index metadata needs refreshing, ask Git to refresh it, then inspect the result:
$ git update-index --refresh
$ git diff-index HEAD
git update-index --refresh changes index metadata, so do not use it as a substitute for understanding the source of a change. It is a normal unprivileged command, but it is still a repository state change.
3. Inspect exactly what is staged
Add --cached when the index is the thing you want to compare. This answers: what would the next tree contain compared with HEAD?
$ git diff-index --cached HEAD
This is the useful pre-commit checkpoint. A file that is modified only in the working tree does not appear in this cached comparison. A file already added with git add does appear. The command does not write a new tree object and does not alter the index.
For a human-readable review, request patch output:
$ git diff-index --cached --patch HEAD
diff --git a/settings.conf b/settings.conf
index 4a1f2d3..9d8c7b6 100644
--- a/settings.conf
+++ b/settings.conf
@@ -1 +1 @@
-mode=old
+mode=new
The exact object names and hunk context will differ. If there are no staged differences, the command prints nothing and exits successfully unless an option changes the exit-status rules.
4. Reduce the output for a quick review
Use --name-status when you need names and status letters rather than a patch. This is often easier to scan before opening a full diff:
$ git diff-index --cached --name-status HEAD
A settings.conf
M src/service.c
D old-notes.txt
--name-only prints only the changed paths. --stat gives a human-oriented summary, while --numstat reports added and deleted line counts in a machine-friendlier form. These options describe the same comparison; they do not stage, unstage, or discard anything.
To limit the comparison, put path arguments after the tree-ish value. Use the separator when a path could be confused with an option:
$ git diff-index --cached HEAD -- src/ docs/
$ git diff-index HEAD -- path/with-a-leading-dash
The first command checks only the selected directories. If it prints nothing, that means those paths have no difference in the selected mode, not that the whole repository is clean.
5. Use exit status in a check
Add --quiet to suppress diff output and --exit-code to make the result match the usual diff convention: status 0 means no differences, status 1 means differences, and another non-zero status indicates an error.
$ git diff-index --quiet --exit-code --cached HEAD
$ case "$?" in
0) echo 'index matches HEAD' ;;
1) echo 'index contains changes' ;;
*) echo 'git diff-index failed' >&2; exit 2 ;;
esac
index contains changes
Do not write a simple command && echo clean test unless you also handle status 1. A non-zero result is expected when differences exist. The manpage also states that --check, which finds conflict markers and whitespace errors, is not compatible with --exit-code; run that as a separate quality check.
6. Handle common traps safely
Do not confuse the two modes. Without --cached, you are checking what the commit differs from across the index and working tree. With --cached, you are checking only the proposed index contents. If a file was edited after git add, it can appear in the non-cached result but not in the cached result.
Do not treat raw output as a patch. Raw records are compact and useful for tools, but use --patch for content review. If a script reads pathnames that may contain unusual characters, use -z with raw, name, or numstat output so fields are terminated by NUL characters rather than newline-based quoting.
Do not use this command to undo work. It only compares. If you decide that a staged change should not be in the next commit, review it first, then use an intentional command such as git restore --staged -- PATH. That changes the index and should be checked again with git diff-index --cached HEAD. To discard working-tree content is more destructive; preserve a copy or commit the work before using any discard command.
Done means
- You know whether you need the working-tree comparison or the
--cachedindex comparison. - You can read the default raw record and can request a patch with
--patch. - You can narrow the check with path arguments after
--. - Your script handles exit status 0, 1, and actual command errors separately.
- You have not changed or discarded repository content merely to inspect it.