Working Tree, Index & Repository
Most confusion about Git comes from one idea that beginners skip: Git keeps your project in three places at once. When you understand the working tree, the index and the repository, commands such as add, commit, diff and restore stop feeling like magic and start making sense.
This lesson explains the three areas, the states a file can be in, and how to see the differences between the areas with git status and git diff.
The three areas
| Area | Also called | What it is |
|---|---|---|
| Working tree | Working directory | The normal files and folders you see and edit |
| Index | Staging area | A list of exactly what will go into the next commit |
| Repository | Commit history, .git | Every commit ever made, stored safely inside .git |
The flow of a change is always the same:
working tree --- git add ---> index --- git commit ---> repository
^ |
+------------------ git restore / git switch -------------+You edit files in the working tree. You choose which changes are ready with git add, which copies them into the index. Then git commit takes everything in the index and saves it as a new permanent snapshot in the repository.
Why have a middle step at all? Because real work is messy. You might fix a bug and also add some half-finished experiment in the same hour. The index lets you commit just the bug fix now, with a clear message, and leave the experiment for later. Think of it as a packing table: you put on it exactly the items that go into this parcel, then seal the parcel.
File states
Every file in your project is in one of these states:
- Untracked: the file exists in the working tree but Git has never been told about it.
- Unmodified: the file is tracked and matches the last commit.
- Modified: the file is tracked and you have changed it, but the change is not in the index yet.
- Staged: the changed version has been added to the index and will go into the next commit.
Watching a change move through the areas
Priya's sweet-shop repository already has two commits. She adds a line about opening hours to index.html and asks for the status:
git statusOn 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: index.html no changes added to commit (use "git add" and/or "git commit -a")
The file is modified: the working tree differs from the index. git diff with no arguments shows exactly that difference, working tree compared with index:
git diffdiff --git a/index.html b/index.html index 5bdfc91..6038166 100644 --- a/index.html +++ b/index.html @@ -7,5 +7,6 @@ <body> <h1>Sharma Sweets</h1> <p>Fresh mithai every morning since 1998.</p> + <p>Open 8 am to 10 pm, all seven days.</p> </body> </html>
Lines starting with + were added and lines with - were removed; the rest is context. The @@ -7,5 +7,6 @@ line means "starting at line 7, five lines became six".
Now she stages the file and runs both kinds of diff:
git add index.html
git diff
git diff --stageddiff --git a/index.html b/index.html index 5bdfc91..6038166 100644 --- a/index.html +++ b/index.html @@ -7,5 +7,6 @@ <body> <h1>Sharma Sweets</h1> <p>Fresh mithai every morning since 1998.</p> + <p>Open 8 am to 10 pm, all seven days.</p> </body> </html>
Only one diff appears. Plain git diff printed nothing, because the working tree and the index now match. git diff --staged (the older name --cached means the same) compares the index with the last commit, so it shows the staged change. Remember the pair:
git diff: working tree vs index, "what have I not staged yet?"git diff --staged: index vs last commit, "what will my next commit contain?"
Two files in two different areas
Before committing, Priya also edits style.css but does not stage it. Now the two files sit in different areas:
git statusOn branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: index.html 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: style.css
If she commits now, only index.html goes in. style.css stays modified in the working tree, waiting. The short status format shows the same thing in two columns: the left column is the index, the right column is the working tree.
git status -sM index.html M style.css
M (M on the left) means "modified and staged"; M (M on the right) means "modified, not staged". A file can even show MM: you staged one change, then edited it again. The index keeps the version you staged, and the newer edit waits in the working tree until you add it too. This surprises people who think git add means "track this file forever"; it really means "put the file as it is right now into the next commit".
Looking inside the index
The index is a real file, .git/index. You can list what it contains:
git ls-files --stage100644 6038166b0002e4d1073876d574f3595fc6822461 0 index.html 100644 9d23ceb088625a513284633bef64c5a173de9eee 0 style.css
Each line is a file mode (100644 is a normal file), the ID of the stored content, a stage number (0 means no conflict) and the path. Notice that the ID for index.html matches the 6038166 in the diff header above: the index points at the exact version you staged. You will see why in How Git Stores Data.
Common mistakes
- Expecting
git committo save every change. It saves only what is in the index. Rungit statusbefore committing and check the "Changes to be committed" section. - Editing after
git add. The later edit is not staged. Add the file again, or you will commit the older version. - Reading
git diffas "all my changes". When it prints nothing, your changes may already be staged. Usegit diff --stagedorgit diff HEAD(working tree vs last commit).
Key points
- Git has three areas: the working tree (your files), the index (the next commit) and the repository (all commits).
git addcopies changes from the working tree to the index;git commitsaves the index as a new commit.- Files can be untracked, unmodified, modified or staged.
git diffcompares working tree with index;git diff --stagedcompares index with the last commit.- In
git status -s, the left column is the index and the right column is the working tree.
Exercise
In any practice repository with at least one commit, change two tracked files. Stage only the first file. Predict what git diff, git diff --staged and git status -s will show, then run them and check your prediction. Finally, edit the first file again and look at git status -s once more.
Show solution
git add notes.txt
git diff
git diff --staged
git status -sgit diff shows only the second file (changed but not staged), git diff --staged shows only the first file, and git status -s prints M notes.txt and M todo.txt. After editing notes.txt again, it shows MM notes.txt: one version is staged and a newer one is in the working tree.
Quick Quiz
1. What does git add do?
git add stages a snapshot of the file; git commit is what saves it to history.
2. Plain git diff prints nothing, but you know you changed a file. Why?
Plain git diff compares the working tree with the index, so staged changes do not appear.
3. In git status -s, what does M (space then M) mean?
The left column is the index and the right column is the working tree.
4. Which area does git commit save?
A commit records exactly what is staged in the index, nothing else.
Learn Git with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
