The Three States: Working Directory, Staging Area, and Repository
Explain Git's three-state architecture with diagrams and practical examples — understand how files flow between working, staging, and committed states.
Introduction
Git’s three-state architecture is the conceptual foundation that makes Git both powerful and, for beginners, confusing. Every file in a Git repository exists in one of three states at any given time: the working directory (your editable files), the staging area (files prepared for the next commit), and the repository (permanently recorded history). Understanding how files move between these states is the key to using Git effectively.
Most version control systems use a simpler two-state model — files are either modified or committed. Git’s staging area adds a deliberate intermediate step that gives you fine-grained control over what becomes part of each commit. This design choice is what enables atomic commits, partial file staging, and the ability to craft clean, reviewable commit histories.
This guide explains the three-state model in depth, with diagrams, practical examples, and real-world scenarios. Once you internalize this model, Git’s commands stop feeling arbitrary and start making logical sense. For a broader introduction to version control, see What Is Version Control?.
When to Use / When Not to Use
Understand the three states when:
- Learning Git for the first time — this model explains why Git works the way it does
- Debugging unexpected
git statusoutput - Crafting clean commits from messy working changes
- Using interactive staging (
git add -p) to split changes into logical commits - Understanding why
git resetandgit checkoutbehave differently - Teaching Git to others — the three-state model is the most important concept to convey
The staging area is less critical when:
- You commit all changes at once every time —
git commit -abypasses explicit staging - Working on solo projects with simple, linear workflows
- Using GUI Git clients that abstract the staging area away
Core Concepts
The three states represent three snapshots of your project:
Working Directory: The files you see and edit on your filesystem. This is your active workspace where you write code, fix bugs, and make changes. Files here may be untracked (new files Git does not know about), modified (changed since the last commit), or clean (identical to the last commit).
Staging Area (Index): A hidden file (.git/index) that records which changes will be included in the next commit. Think of it as a draft or preparation area — you selectively place changes here using git add, review them with git diff --staged, and only then make them permanent with git commit.
Repository (HEAD): The permanent, immutable history of your project. Each commit captures a snapshot of all staged files and links to its parent commit, forming a chain of history. Once committed, changes cannot be altered (only new commits can be added).
graph LR
A[Working Directory<br/>Your editable files] -->|git add| B[Staging Area<br/>Prepared for commit]
B -->|git commit| C[Repository<br/>Permanent history]
C -->|git checkout| A
C -->|git reset| B
B -->|git reset| A
A -->|git restore| A
Architecture or Flow Diagram
File State Transitions
stateDiagram-v2
[*] --> Untracked: New file created
Untracked --> Staged: git add
Staged --> Committed: git commit
Committed --> Modified: Edit file
Modified --> Staged: git add
Modified --> Unmodified: git restore
Staged --> Modified: git restore --staged
Committed --> Modified: git reset HEAD~1
Committed --> Staged: git reset --soft
Modified --> Untracked: git clean
note right of Untracked
Git does not track this file
It will not be committed
end note
note right of Staged
Changes are queued
for the next commit
end note
note right of Committed
Permanently recorded
in repository history
end note
The Complete File Lifecycle
graph TD
A[Create new file] --> B{Tracked?}
B -->|No| C[Untracked]
B -->|Yes| D{Changed?}
D -->|No| E[Unmodified<br/>Matches HEAD]
D -->|Yes| F[Modified<br/>Working directory changed]
C -->|git add| G[Staged<br/>New file]
F -->|git add| H[Staged<br/>Modified file]
G -->|git commit| I[Committed<br/>In repository]
H -->|git commit| I
I -->|Edit file| F
I -->|git checkout| E
F -->|git restore| E
G -->|git restore --staged| C
H -->|git restore --staged| F
Step-by-Step Guide / Deep Dive
Understanding Each State Through Examples
State 1: Working Directory
Create a file and observe its state:
# Initialize a repository
git init three-states-demo
cd three-states-demo
# Create a file
echo "Hello, World!" > hello.txt
# Check the status
git status
Output:
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
hello.txt
nothing added to commit but untracked files present (use "git add" to track)
The file exists in your working directory but Git does not track it yet. It is untracked — Git knows the file exists but will not include it in any commit until you explicitly add it.
State 2: Staging Area
# Stage the file
git add hello.txt
# Check the status
git status
Output:
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: hello.txt
The file has moved to the staging area. It is now “to be committed” — it will be included in the next commit, but it is not yet part of the permanent history.
State 3: Repository
# Commit the staged file
git commit -m "Add hello.txt"
# Check the status
git status
Output:
On branch main
nothing to commit, working tree clean
The file is now in the repository. It is permanently recorded in commit history. The working directory matches the repository — there are no uncommitted changes.
Modifying a Committed File
Now let’s see what happens when you edit a committed file:
# Modify the file
echo "Hello, Git!" > hello.txt
# Check the status
git status
Output:
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.txt
no changes added to commit (use "git add" and/or "git commit -a")
The file is now modified in the working directory but not yet staged. Git detects the difference between your working copy and the last committed version. This is the most common state during active development.
Partial Staging
One of Git’s most powerful features is the ability to stage only some changes in a file:
# Create a file with multiple changes
cat > app.py << 'EOF'
def greet():
print("Hello")
def farewell():
print("Goodbye")
def helper():
print("Helper function")
EOF
git add app.py
git commit -m "Add app.py with three functions"
# Now modify all three functions
cat > app.py << 'EOF'
def greet():
print("Hello, World!")
def farewell():
print("See you later!")
def helper():
print("Updated helper")
EOF
# Stage only the greet and farewell changes interactively
git add -p app.py
Git will present each change hunk and ask whether to stage it. You can stage greet and farewell while leaving helper unstaged, then commit them separately:
# Commit only staged changes
git commit -m "Update greet and farewell messages"
# Check what remains unstaged
git status
# modified: app.py (the helper change is still in working directory)
# Stage and commit the remaining change
git add app.py
git commit -m "Update helper function"
This produces two clean, atomic commits from a single file with mixed changes.
Moving Files Between States
# Working Directory → Staging Area
git add <file> # Stage specific file
git add . # Stage all changes
git add -p <file> # Stage interactively (hunk by hunk)
# Staging Area → Working Directory (unstage)
git restore --staged <file> # Unstage specific file
git reset HEAD <file> # Older syntax, same effect
# Staging Area → Repository
git commit # Commit all staged changes
git commit -m "message" # Commit with a message
# Working Directory → Last Committed State (discard changes)
git restore <file> # Discard working changes
git checkout -- <file> # Older syntax, same effect
# Repository → Working Directory (restore old version)
git checkout <commit> -- <file> # Restore file from specific commit
Production Failure Scenarios + Mitigations
| Scenario | Impact | Mitigation |
|---|---|---|
| Accidentally committing debug code left in working directory | Broken production, exposed debug output | Always review git diff --staged before committing; use pre-commit hooks |
| Forgetting to stage a critical file | Incomplete commit, broken build on remote | Review git status before every commit; use git diff --staged to verify |
| Staging too many unrelated changes | Unreviewable commits, hard to revert specific changes | Stage logically grouped changes; use git add -p for selective staging |
| Losing uncommitted work in working directory | Lost hours of development | Commit frequently (even WIP commits); use git stash for temporary saves |
git reset --hard on wrong branch | Permanent loss of uncommitted changes | Use --soft or --mixed first; verify with git reflog after mistakes |
| Merge conflict leaves files in partially staged state | Confused state, incomplete resolution | Use git status to identify conflicted files; resolve all before committing |
Trade-offs
| Approach | Advantages | Disadvantages | When to Use |
|---|---|---|---|
Explicit staging (git add + git commit) | Full control, atomic commits, reviewable history | More commands to type | Production code, team projects, code review workflows |
Skip staging (git commit -a) | Faster, fewer steps | Commits all tracked changes together, no selectivity | Solo projects, quick fixes, when all changes belong in one commit |
Interactive staging (git add -p) | Granular control, clean commits from messy work | Slower, requires understanding of hunks | Refactoring, multi-purpose changes, preparing PRs |
| GUI staging | Visual, intuitive | Abstracts away the model, harder to debug | Beginners, visual thinkers, complex merges |
Implementation Snippets
Visualizing the Three States
# See the complete picture at once
echo "=== Working Directory Changes ==="
git diff # Unstaged changes
echo "=== Staging Area ==="
git diff --staged # Staged changes (what will be committed)
echo "=== Repository Status ==="
git log --oneline -3 # Recent commits
echo "=== Overall Status ==="
git status # Summary of all three states
Committing with Review
# The safe commit workflow
git status # 1. See what changed
git diff # 2. Review unstaged changes
git add <files> # 3. Stage intended changes
git diff --staged # 4. Review what will be committed
git commit -m "message" # 5. Commit
git log -1 # 6. Verify the commit
Recovering from Mistakes
# Committed but forgot to stage a file
git add forgotten-file.txt
git commit --amend --no-edit # Adds to the last commit without changing message
# Committed with wrong message
git commit --amend -m "Correct message"
# Committed to wrong branch
git reset --soft HEAD~1 # Undo commit, keep changes staged
git checkout correct-branch # Switch to correct branch
git commit -m "message" # Re-commit on correct branch
# Staged something you should not have
git restore --staged <file> # Unstage without losing changes
Stashing: A Fourth State
Git stash provides a temporary holding area for uncommitted changes:
# Save working directory and staging area changes
git stash push -m "WIP: feature in progress"
# Working directory is now clean
git status
# nothing to commit, working tree clean
# List all stashes
git stash list
# Restore the most recent stash
git stash pop
# Restore a specific stash without removing it
git stash apply stash@{2}
# Drop a stash you no longer need
git stash drop stash@{0}
Observability Checklist
- Logs: Use
git statusas your primary observability tool — it shows all three states at once - Metrics: Track the ratio of staged to unstaged changes — large unstaged deltas indicate infrequent commits
- Traces: Use
git diffandgit diff --stagedto trace exactly what will be committed - Alerts: Set up pre-commit hooks that block commits exceeding size thresholds or containing patterns like
TODO,FIXME, orconsole.log - Audit: Run
git log --statto audit what files each commit touched - Health: Periodically run
git statusto ensure no long-running uncommitted work accumulates - Validation: Before pushing, always run
git diff --stagedto verify commit contents
Security/Compliance Notes
- Staging area is not a security boundary: Files in the staging area are stored in
.git/indexas plaintext references. They are not encrypted or protected beyond filesystem permissions - Committed secrets are permanent: Once a file with secrets is committed, it exists in the repository history forever — even if you delete it in a later commit. Use pre-commit hooks to scan for secrets before they reach the staging area
- Stash is not encrypted:
git stashstores changes in the repository’s object database. Anyone with repository access can view stash contents withgit stash show -p - Audit trails: The staging area enables clean, atomic commits that serve as better audit trails than monolithic commits. Each commit should represent a single logical change for compliance traceability
- Signed commits: Use
git commit -Sto cryptographically sign commits, proving that the staged changes were intentionally committed by the claimed author
Common Pitfalls / Anti-Patterns
- Treating
git add .as harmless: It stages everything including accidental debug files, temporary edits, and generated artifacts. Always review withgit statusbefore bulk staging - Confusing
git resetmodes:--softkeeps changes staged,--mixed(default) keeps changes in working directory,--harddiscards everything. Using the wrong mode causes data loss or confusion - Not understanding that
git commit -askips staging: It automatically stages all tracked modified files and commits them. Untracked files are still ignored. This bypasses the review step - Leaving the staging area in an inconsistent state: Staging some changes, getting distracted, and coming back days later leads to accidental commits of unrelated changes. Commit or unstage promptly
- Using
git checkoutto unstage:git checkout -- <file>restores the file from the repository, discarding both staged and unstaged changes. Usegit restore --stagedto unstage while keeping working changes - Ignoring the diff before commit: Skipping
git diff --stagedis the #1 cause of accidental commits with debug code, wrong files, or incomplete changes
Quick Recap Checklist
- Working Directory: your editable files on the filesystem
- Staging Area: the preparation zone for the next commit (
.git/index) - Repository: the permanent, immutable history of commits
-
git addmoves changes from working directory to staging area -
git commitmoves staged changes from staging area to repository -
git statusshows the state of all files across all three states -
git diffshows unstaged changes;git diff --stagedshows staged changes -
git add -penables interactive, hunk-by-hunk staging -
git stashprovides a temporary fourth state for uncommitted work - Always review staged changes with
git diff --stagedbefore committing -
git restore --stagedunstages without discarding working changes - Committed changes are permanent — the staging area is your last chance to review
Interview Q&A
The staging area (also called the index) is an intermediate state between your working directory and the repository. It acts as a preparation zone where you selectively choose which changes will be included in the next commit. Git has it because it enables atomic commits — you can modify ten files but only commit the three that form a complete logical change. Without the staging area, every commit would include all modified files, making it impossible to craft clean, reviewable history from messy working sessions.
These three modes control what happens to your changes when you undo a commit. --soft moves HEAD back but keeps all changes staged — perfect for amending a commit. --mixed (the default) moves HEAD back and keeps changes in the working directory but unstaged — useful for restaging selectively. --hard moves HEAD back and discards all changes entirely — dangerous, as working directory modifications are permanently lost. The mnemonic: soft keeps everything, mixed keeps working files, hard keeps nothing.
git add -p (patch mode) presents each changed hunk (contiguous block of changes) in a file and asks whether to stage it. You respond with y (yes), n (no), s (split the hunk smaller), or e (edit manually). Use it when a single file contains multiple unrelated changes that should be separate commits — for example, fixing a bug and adding a feature in the same file. It produces cleaner, more reviewable commit history.
A file can have different parts in different states. For example, lines 1-10 of a file might be staged while lines 11-20 remain modified but unstaged. This is the power of hunk-based staging with git add -p. However, at the file level, git status reports the most changed state — if any part is staged, the file shows as staged. The staging area tracks changes at the hunk level, not the file level, which is why partial staging is possible.
Resources
- Pro Git — Recording Changes — Official guide to the three states
- Git Status Documentation — Understanding status output
- Git Add Documentation — Staging area operations
- Git Reset Documentation — Moving between states
- Visualizing Git Concepts — Diagrams and explanations
Category
Related Posts
Centralized vs Distributed VCS: Architecture, Trade-offs, and When to Use Each
Compare centralized (SVN, CVS) vs distributed (Git, Mercurial) version control systems — their architectures, trade-offs, and when to use each approach.
Master git add: Selective Staging, Patch Mode, and Staging Strategies
Master git add including selective staging, interactive mode, patch mode, and staging strategies for clean atomic commits in version control.
What Is Version Control? The Developer's Safety Net
Learn what version control systems are, why they exist, what problems they solve, and why every developer needs one for modern software development.