Working Directory, Staging, Repository
Git has a unique design that confuses almost everyone at first: every file you work on exists in three places at once. Once you internalise these three places, almost every Git command becomes obvious — they are all about moving content between them.
The three trees
The mental picture
┌──────────────┐ git add ┌──────────────┐ git commit ┌──────────────┐
│ Working │ ─────────────▶ │ Staging │ ────────────▶ │ Repository │
│ Directory │ │ (index) │ │ (.git) │
│ │ ◀───────────── │ │ ◀──────────── │ │
└──────────────┘ git restore └──────────────┘ git restore └──────────────┘
--staged
your editor / .git/index .git/objects + refs
filesystem (binary file) (objects database)
1. The Working Directory
The working directory (also called the working tree) is what you actually see and edit. It is just the files in the folder outside the .git/ directory. Open your editor and you are looking at the working directory.
You can change anything here freely — Git tracks but does not interfere.
A file in the working directory might be tracked, untracked, modified, or unchanged.
Changes here are invisible to Git history until you stage and commit them.
2. The Staging Area (Index)
The staging area is a holding pen for the next commit. Think of it as a draft of the commit you are about to make.
Implemented as the file
.git/index(binary).Updated by
git add— staging tells Git: “include this version of this file in my next commit.”Lets you commit some changes but not others — even within the same file (via
git add -p).After a commit, the staging area still holds the just-committed content (now matching the repo).
3. The Repository
The repository is the permanent, content-addressed history — every commit, every file, every branch — stored inside the .git/ folder. Once content reaches the repository it is immutable (and reachable through the commit graph).
Updated by
git commit— moves staged content into a new commit.Holds all past states of the project, not just the latest.
Survives even if you delete or replace your working directory —
git restore .rebuilds it.
Watch the three trees move
A full lifecycle in one terminal session
mkdir trees-demo && cd trees-demo git init # Step 1: create a file. Only exists in the working directory. echo "hello" > greeting.txt git status # Untracked files: greeting.txt # Step 2: stage it. Now in both working dir AND index. git add greeting.txt git status # Changes to be committed: new file: greeting.txt # Step 3: commit it. Now in all three trees. git commit -m "Add greeting" git status # nothing to commit, working tree clean # Step 4: modify the file. Working dir != index now. echo "world" >> greeting.txt git status # modified: greeting.txt git diff # diff: working dir vs index # +world # Step 5: stage the change git add greeting.txt git diff # nothing — wd matches index git diff --staged # diff: index vs repo # +world # Step 6: commit git commit -m "Add world" git log --oneline # 2nd commit: Add world # 1st commit: Add greeting
Moving things backward
Restoring files
# Discard changes in the working directory (back to the index state) git restore greeting.txt # Unstage a file (back to the working directory) git restore --staged greeting.txt # Both: throw away local edits AND unstage git restore --source=HEAD --staged --worktree greeting.txt
File states
Each file is in one of these states based on where it differs:
Untracked — exists in the working directory; Git has never seen it.
Tracked, unmodified — present in all three trees and identical.
Tracked, modified — working dir differs from index.
git statussays “modified.”Staged — working dir matches index, but index differs from the last commit.
git statussays “Changes to be committed.”Staged & modified — you staged a version, then made more changes. The file is in all three states at once.
git status reads all three trees
Anatomy of git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed: ← differences: staging vs repo
(use "git restore --staged <file>..." to unstage)
new file: index.html
Changes not staged for commit: ← differences: working dir vs staging
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: index.html
Untracked files: ← in working dir, not in staging
(use "git add <file>..." to include in what will be committed)
notes.txtThat output is just git status walking each tree and reporting the differences. Once you can read the output in terms of the three trees, you understand git status completely.
A useful question
When something confuses you in Git, ask yourself: “which tree is the content in, and which tree do I want it in?” The answer almost always names the command you need:
Working dir → staging:
git addStaging → repository:
git commitRepository → staging:
git restore --staged --source=HEADStaging → working dir:
git restoreRepository → working dir AND staging:
git restore --source=HEAD --worktree --stagedRepository → branch pointer:
git reset