Git Working Tree
Trace a change through the working tree, the index, and Git objects so status and add stop feeling like magic.
Three places, two comparisons, one next snapshot
The three-place model
WORKING TREE INDEX OBJECT DATABASE
Files you edit Proposed next snapshot Durable Git objects
README.md v3 ──add──▶ README.md v2 ─commit─▶ commit C
app.css v1 app.css v1 tree T
blobs B1, B2
The working tree can keep changing after the index was prepared.The working tree and index are both mutable. Editing changes the working tree. Staging updates the index to reflect selected working-tree content. A commit then creates durable objects from the index and moves the current history reference to the new commit.
The working tree is the editable checkout
The working tree contains the directories and files checked out at your current graph position, plus untracked files Git has not selected. Your editor, formatter, compiler, and tests operate on these filesystem files. Git does not record every save automatically.
Unchanged tracked path
Working content matches the version represented in the index and current commit.
Modified tracked path
Working content differs from the index. The edit is not automatically part of the next commit.
Deleted tracked path
The working path is absent while the index or current commit still expects it.
Untracked path
The file exists in the working tree but has no index entry. Git will not commit it by accident.
Untracked does not mean ignored. An ignored path matches an exclusion rule and is normally hidden from routine status output. The later ignore lesson will cover that boundary carefully.
The index is the proposed next snapshot
For each tracked path, the index records a file mode, an object identifier, and a pathname. It does not store a vague “include this file later” flag. It identifies the exact content currently selected for the next commit.
When you stage a file, Git reads its current content, stores or reuses a blob object, and updates the index entry to that object. If you edit the file again, the index does not follow the editor. Now one path has two different new versions: the staged version in the index and the later unstaged version in the working tree.
Test what you learned
Which Git place contains the exact version currently selected for the next commit?
Status labels come from two comparisons
PATH HEAD INDEX WORKING TREE STATUS
README.md v1 v1 v1 unchanged
app.css v1 v1 v2 modified
logo.svg — — v1 untracked
nav.js v1 v2 v3 staged + modified“Modified” and “staged” are not permanent properties of a file. They describe comparisons:
- 01HEAD versus index
If they differ, the path has a staged change proposed for the next commit.
- 02Index versus working tree
If they differ, the path has an unstaged working change.
- 03No index entry
If a working path is outside the tracked index and not ignored, it is untracked.
- 04Both comparisons differ
The same path can be staged and modified at the same time.
In the matrix, nav.js is staged because index v2 differs from HEAD v1. It is also modified because working v3 differs from index v2. A commit now would record v2; v3 would remain in the working tree.
Use commands as observation windows
git status
git diff
git diff --cached
git ls-files --stage
# Read-only object inspection used later
git cat-file -t <object-id>
git cat-file -p <object-id>git diff
- Compares working tree with index.
- Answers what changed after staging.
- Does not show untracked file content.
- Does not describe the whole next commit.
git diff --cached
- Compares index with HEAD.
- Answers what the proposed snapshot changes.
- Also spelled
--staged. - Should be reviewed before committing.
git status summarizes both comparisons and untracked paths. git ls-files --stage exposes index entries directly. The next lesson will create a repository and use these commands in a real first-commit workflow.
Commit reads the index, not the editor
A commit is built from the index. Git writes a tree representing those selected entries, then creates a commit object that points to the root tree and parent history. Unstaged working changes remain outside that commit.
EDITWorking tree changesYou save README v2. The index may still describe README v1.
STAGEIndex selects v2The proposed snapshot now contains README v2.
EDITWorking tree advances to v3The proposal stays at v2 until staged again.
COMMITHistory records v2The commit reads the index. README v3 remains an unstaged edit.
Test what you learned
You staged version 2 and then edited version 3 without staging again. Which version would the next commit record?
The object database stores immutable values
Inside the repository metadata, Git stores objects addressed by a hash derived from object type and content. Once written, an object’s identity and content do not change. New content creates another object. This immutability makes history verifiable and allows snapshots to reuse unchanged content.
commit c7a1
│ points to
▼
tree t91f ── README.md ──▶ blob b120
├─ app.css ──▶ blob a884
└─ src/ ──▶ tree 1e03
└─ nav.js ──▶ blob 8dd2
The commit records the root tree, parent commit, author, dates, and message.Blob
Stores file content. A blob does not know the project pathname, author, or original filename.
Tree
Maps names and modes to blobs and nested trees, giving a snapshot its directory structure.
Commit
Points to a root tree and parent commit, then records author, committer, dates, and message.
Tag
An annotated tag can point to another object with a name, tagger, date, message, and optional signature.
A filename belongs to a tree entry, not a blob. If two paths contain identical bytes, their tree entries can point to the same blob. If a file is renamed without changing content, Git can represent the new snapshot using the same blob under a different tree entry.
Content addressing explains reuse and integrity
An object identifier is calculated from the object’s type and content. Identical input produces the same identifier; changed input produces a different identifier. Git can therefore avoid storing duplicate immutable content and can detect when stored bytes no longer match their expected identity.
Modern repositories may use SHA-1 or the newer SHA-256 object format depending on how they were created and supported tooling. The mental model is the same: object identity derives from content, and object references connect a graph.
HEAD supplies the committed comparison
HEAD identifies the current checked-out history position, usually through a branch name. Status and diff commands commonly compare the index against the commit reached by HEAD. The branch and HEAD lesson later in the course will examine those references in depth.
For now, read HEAD as “the current committed snapshot.” Then the core comparisons become:
HEAD snapshot ⇄ index proposal ⇄ working files
staged changes unstaged changesWorked example: one file in four states
A project starts with committed README.md v1. You edit v2, stage it, then edit v3. Predict every place:
HEAD
Still points to a commit whose tree selects README v1.
Index
Selects the blob for README v2, so v2 is staged.
Working tree
Contains README v3, so a second modification is unstaged.
Next commit
Would record v2. Staging again would replace the index proposal with v3.
git diff --cached would describe v1→v2. Plain git diff would describe v2→v3. Neither comparison alone tells the entire story; together they explain the state precisely.
Independent lab: predict the three places
Do this as a paper simulation before the command workflow in the next lesson:
- Start with HEAD, index, and working tree all containing
notes.txtv1. - Edit the working file to v2. Record both comparisons.
- Stage v2. Record both comparisons again.
- Edit the working file to v3 and create untracked
todo.txt. - Predict what a commit would contain without staging again.
- Predict what remains after that commit.
Safety boundaries around repository internals
Ask Git to explain itself
- Use
statusand both diff views. - Use
ls-filesto inspect the index. - Use
cat-filefor read-only objects. - Copy a disposable repository for experiments.
Do not edit internals blindly
- Do not hand-edit index bytes.
- Do not delete unknown objects.
- Do not treat
.gitas cleanup clutter. - Do not run destructive reset commands from memory.
The hidden .git directory is the repository database and configuration. Removing it from a working tree removes that folder’s Git history and repository identity. The visible files may remain, but their version-control graph is gone.
Common model failures
“Staged means saved.”
Saving updates a working file. Staging updates the index proposal.
“Commit takes every changed file.”
Commit reads tracked entries from the index, not every working-tree change.
“A blob is a file.”
A blob is content. Trees provide project names and structure.
“Modified and staged are exclusive.”
One path can differ across both comparisons at the same time.
Lesson review
The working tree is your editable checkout. The index is the exact proposed next snapshot. The object database stores immutable blobs, trees, commits, and tags by content-derived identifiers. HEAD provides the current committed baseline. Status and diff become predictable when you name which two places are being compared.
- I can distinguish working-tree content from indexed content for the same path.
- I can derive untracked, modified, staged, and staged-plus-modified states.
- I know that a commit records the index proposal rather than every editor change.
- I can explain how blobs, trees, and commits divide content, names, and history.
- I can choose the correct status or diff view to inspect a suspected difference.