GitThe Index (Staging Area)

The Git Index (Staging Area)

The Git index — also called the staging area or cache — is the intermediary between your working directory and the Git object store. It is a binary file at /.git/index that records exactly what the next commit will look like. Understanding the index clarifies why staging exists, what git add and git reset actually do, and how Git detects changes so quickly.

What the Index Stores

For each tracked file, the index stores:

  • Filename — the full path relative to the repository root

  • Mode — file permissions (100644 for regular, 100755 for executable)

  • Blob SHA — the hash of the file content at the staged version

  • Stat information — inode, file size, mtime, ctime (for fast change detection)

  • Stage number — 0 for normal, 1/2/3 during merge conflicts (base/ours/theirs)

Note
The stat cache (inode, mtime, size) is the key to Git's speed. When you run `git status`, Git first checks whether the stat info has changed. If not, it skips re-hashing the file entirely. This is why `git status` is nearly instant even in large repos.
Inspecting the Index with git ls-files

List files tracked by the index

Bash
# Show all files in the index
git ls-files

# Show with stage number and blob hash (--stage)
git ls-files --stage

git ls-files --stage output

Text
100644 8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c 0	README.md
100644 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b 0	package.json
100644 9e2b0f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a 0	src/index.ts
100644 6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f 0	src/auth.ts

Format: <mode> <blob-hash> <stage-number>  <filename>
Stage 0 = normal (no conflict)

Other useful ls-files flags

Bash
# Show only modified files (staged vs working tree)
git ls-files --modified

# Show untracked files
git ls-files --others

# Show deleted files
git ls-files --deleted

# Show files with their stat information (verbose)
git ls-files --debug src/index.ts
The Three-Tree Model

Git operates on three distinct "trees" simultaneously. Understanding all three is the key to understanding git diff, git add, git reset, and git checkout:

Three-tree model diagram

Text
Working Directory        Index (Staging Area)         HEAD (last commit)
─────────────────        ────────────────────         ──────────────────
Your actual files        Proposed next commit         Current committed state
on disk                  (.git/index)                 (a commit object)

      ←───── git restore ──────←          ←──── git restore --staged ────←

      ─────── git add ──────→             ──────── git commit ──────────→

git diff            git diff --staged             (compare from HEAD)
(working vs index)  (index vs HEAD)

git status shows both differences at once

Seeing all three trees in action

Bash
# Edit a file
echo "new feature" >> src/index.ts

# Check differences between all three trees:
git diff              # working directory vs index (unstaged changes)
git diff --staged     # index vs HEAD (staged changes)
git diff HEAD         # working directory vs HEAD (all changes)

# Stage the change
git add src/index.ts

# Now the change moved from "unstaged" to "staged":
git diff              # (empty — working dir matches index)
git diff --staged     # (shows the change — index differs from HEAD)
git diff HEAD         # (same as --staged — HEAD is behind)
What git add Does to the Index

git add in detail

Bash
# Before add: check the blob hash in the index for src/auth.ts
git ls-files --stage src/auth.ts
# 100644 6e7f8a9b0c1d... 0  src/auth.ts

# Edit the file
echo "// new comment" >> src/auth.ts

# After editing but before add: working dir differs from index
git diff src/auth.ts   # shows the new comment

# Run git add
git add src/auth.ts

# After add: index now has the new blob hash
git ls-files --stage src/auth.ts
# 100644 b2c3d4e5f6a7... 0  src/auth.ts   ← new hash!

# The blob b2c3d4e5... is now in .git/objects/
git cat-file -p b2c3d4e5f6a7   # shows the updated file content
Merge Conflicts and the Index Stages

During a merge conflict, the index stores three versions of the conflicting file — one for each parent plus the common ancestor. These are called stages 1, 2, and 3:

Stage Number

Name

Meaning

0

Normal

No conflict — single version

1

Base (ancestor)

The common ancestor version before the branches diverged

2

Ours

The version from the branch we are merging INTO (HEAD)

3

Theirs

The version from the branch being merged in

Inspecting conflict stages

Bash
# During a merge conflict, ls-files -u shows all stages
git ls-files -u
# 100644 1a2b3c4d... 1  src/auth.ts   ← stage 1: base
# 100644 9e2b0f1c... 2  src/auth.ts   ← stage 2: ours (HEAD)
# 100644 6e7f8a9b... 3  src/auth.ts   ← stage 3: theirs

# View each version individually
git cat-file -p :1:src/auth.ts   # base version
git cat-file -p :2:src/auth.ts   # our version
git cat-file -p :3:src/auth.ts   # their version

# Accept "theirs" for a file (resolve by taking their version)
git checkout --theirs src/auth.ts
git add src/auth.ts   # mark as resolved (stage returns to 0)
Reset Modes and the Index

The three git reset modes differ in which trees they update:

Reset Mode

Moves HEAD?

Updates Index?

Updates Working Dir?

--soft

Yes

No

No

--mixed (default)

Yes

Yes

No

--hard

Yes

Yes

Yes

Reset modes in practice

Bash
# --soft: undo commit, keep changes staged
git reset --soft HEAD~1
git status   # changes are staged, ready to re-commit

# --mixed (default): undo commit AND unstage, keep working dir
git reset HEAD~1
git status   # changes are in working dir, not staged

# --hard: undo commit AND discard all changes
git reset --hard HEAD~1
git status   # working dir is clean, changes are gone
Warning
`git reset --hard` discards all uncommitted changes permanently. There is no undo for changes that were never committed. Always double-check before using `--hard`.
Partial Staging: Adding Hunks

Stage only part of a file's changes

Bash
# Interactive hunk-by-hunk staging
git add -p src/auth.ts

# Git shows each changed hunk and asks:
# Stage this hunk [y,n,q,a,d,/,e,?]?
# y = yes, stage this hunk
# n = no, skip this hunk
# s = split into smaller hunks
# e = manually edit the hunk
Tip
Partial staging (`git add -p`) lets you make multiple logical changes in one editing session and then commit them as separate, focused commits. This is how professional developers maintain a clean, readable commit history.
The Index as an Optimization

The index is also a performance optimization. Git stores the stat info (mtime, inode, size) alongside each entry. On git status, Git first checks if the stat has changed. If not, it skips hashing entirely. On a repository with 100,000 files, this makes git status run in milliseconds instead of seconds.

Rebuild the stat cache (fix false positives)

Bash
# If git status shows everything as modified after a filesystem operation:
git update-index --refresh

# Force re-stat every file
git status  # after refresh, false positives should be gone