Inspect Git's Index Without Guessing What It Stores
You will inspect a Git index safely, identify its version and entry count, and relate the binary file to paths shown by ordinary Git commands. The workflow is read-only: it does not stage, unstage, commit or rewrite anything. Allow 10 to 15 minutes for a repository with a small or medium index.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Choose a repository and confirm the tools
- 2. Locate the active index
- 3. Read the header, without parsing it by hand
- 4. Inspect entry metadata and stages
- 5. Recognise extensions before interpreting unusual entries
- 6. Check sparse and intent-to-add flags through Git
- 7. Verify the index after an operation
This guide describes the installed Git 2.43.0 and the gitformat-index(5) format documented with it. The index is an implementation file, not a hand-editing target. Use Git commands to change it; use the byte-level checks below when you need to understand or diagnose it.
1. Choose a repository and confirm the tools
Change /path/to/repository to a working tree you can inspect. Do not use a repository with an unrecorded operation if you are not sure what its state means. The commands below need no elevated privileges when you can read the repository normally.
$ cd /path/to/repository
$ git --version
git version 2.43.0
$ git rev-parse --show-toplevel
/path/to/repository
Checkpoint: the version should be explicit in your notes. Index details can change between Git releases, even when the normal commands remain compatible.
2. Locate the active index
Git normally stores the index at $GIT_DIR/index. Ask Git for the path rather than assuming that .git is a directory: worktrees and other layouts can use a different Git directory.
$ index_path=$(git rev-parse --git-path index)
$ printf '%s\n' "$index_path"
/path/to/repository/.git/index
$ test -r "$index_path" && echo 'index is readable'
index is readable
$ stat -c 'bytes: %s' "$index_path"
bytes: 1234
Your path and size will differ. A missing index is not automatically an error: a newly created repository can have no entries and may not have an index file until the first staging operation.
3. Read the header, without parsing it by hand
The file begins with a 12-byte header. Its first four bytes are the ASCII signature DIRC, followed by a four-byte version and a four-byte entry count. Binary integers use network byte order, so a hex dump is safer than trying to read the fields as decimal text.
$ od -An -tx1 -N12 "$index_path"
44 49 52 43 00 00 00 02 00 00 00 01
In this example, 44 49 52 43 spells DIRC, 00 00 00 02 is version 2, and the final four bytes say there is one entry. Git 2.43.0 supports index versions 2, 3 and 4. Most users should leave the version choice to Git rather than forcing a format for a tool that has not demonstrated a need for it.
For a machine-readable check using Git itself, compare the header interpretation with the entries Git exposes:
$ git ls-files --stage
100644 ce013625030ba8dba906f756967f9e9ca394464a 0\treadme.txt
The object ID and mode in this output describe the staged entry. The trailing 0 is its merge stage. This command is a better first diagnostic than opening the binary file in an editor.
4. Inspect entry metadata and stages
Use --debug when you need the cached stat information Git keeps beside each path. It shows ctime, mtime, device, inode, uid, gid, size and flags. These values help explain why Git refreshes an entry, but they are not a second copy of the working file.
$ git ls-files --debug
readme.txt
ctime: 1790227816:665821456
mtime: 1790227816:665821456
dev: 2306 ino: 125592963
uid: 1004 gid: 1004
size: 6 flags: 0
Exact numbers are host-specific. The index entry contains cached filesystem data, the object name, a mode and flags. Its path is relative to the repository root and entries are sorted by unsigned-byte path order. That ordering is why names with unusual bytes should not be analysed using locale-sensitive sorting.
During a merge conflict, one path can have multiple index entries. The stage field distinguishes the base and the two sides. Check this before attempting a repair:
$ git ls-files --unmerged
100644 1111111111111111111111111111111111111111 1 conflicted.txt
100644 2222222222222222222222222222222222222222 2 conflicted.txt
100644 3333333333333333333333333333333333333333 3 conflicted.txt
The object IDs above are illustrative only; use the values printed by your repository. Do not copy them into a command. Stage 1 is the common ancestor, while stages 2 and 3 are the conflicting sides. After you resolve the file, git add conflicted.txt replaces the higher-stage entries with a stage-0 entry. That changes the index, so review git status first and keep a recovery plan for the working file.
5. Recognise extensions before interpreting unusual entries
After the entries, an index can contain extensions. Each has a four-byte signature, a 32-bit size and extension data. A signature beginning with an uppercase ASCII letter marks an optional extension that an implementation may ignore. The final checksum covers everything before it and uses SHA-1 or SHA-256 to match the repository's object format.
Common extensions explain behaviour that otherwise looks surprising:
TREEcaches directory tree information so Git can build or compare trees more quickly.REUCpreserves information needed to recreate some resolved conflicts with commands such asgit checkout -m.linkrecords a split index, where most entries live in a shared index file under$GIT_DIR/sharedindex.<hash>.UNTRcaches untracked-file information, whileFSMNrecords file-system-monitor state.sdirmarks sparse-directory entries that need sparse-index-aware tooling.
Do not delete an extension or its shared index file as a cleanup experiment. That is destructive and can make Git rebuild or reject the index. If an index appears corrupt, preserve the repository first, then use normal Git recovery procedures and a copy of the working tree.
6. Check sparse and intent-to-add flags through Git
Version 3 and later can carry an extended flags field. The skip-worktree bit is used by sparse checkout, and the intent-to-add bit is used by git add -N. These flags are state, not instructions to edit bytes manually. Inspect them through Git:
$ git ls-files --stage
$ git ls-files -v
H readme.txt
The exact letter from git ls-files -v depends on each entry's flags and repository state, so treat it as a report to investigate rather than a universal expected line. If sparse checkout is active, confirm its configuration before changing anything:
$ git config --get core.sparseCheckout
true
$ git config --get core.sparseCheckoutCone
true
Do not infer that an absent output means sparse checkout is broken: an unset configuration key simply produces no output. Use the documented sparse-checkout commands when you intend to change the selected paths.
7. Verify the index after an operation
After any command that legitimately changes staging state, verify the result with Git rather than comparing timestamps alone:
$ git status --short
M readme.txt
$ git diff --cached --name-status
M readme.txt
$ git ls-files --stage -- readme.txt
100644 ce013625030ba8dba906f756967f9e9ca394464a 0 readme.txt
The first column in short status describes the index, and the cached diff shows what is staged. If you changed the index by mistake, git restore --staged -- path/to/file removes that file from the index while retaining the working-tree file. Review the command's target carefully before running it; restoring a whole index or discarding working-tree changes is a different and potentially destructive action.
Done means
- You recorded the installed Git version and located the active index with
git rev-parse --git-path index. - You verified the
DIRCheader without modifying the file. - You used
git ls-filesto inspect entries, metadata and merge stages. - You treated extensions, sparse flags and shared-index files as managed state.
- You know how to verify a deliberate staging change and how to unstage one path safely.