Working Tree, Index & Repository

Git · Lesson 4 of 28 7 min read Updated

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

AreaAlso calledWhat it is
Working treeWorking directoryThe normal files and folders you see and edit
IndexStaging areaA list of exactly what will go into the next commit
RepositoryCommit history, .gitEvery commit ever made, stored safely inside .git

The flow of a change is always the same:

Text
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:

Terminal
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:   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:

Terminal
git diff
Output
diff --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:

Terminal
git add index.html
git diff
git diff --staged
Output
diff --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:

Terminal
git status
Output
On 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.

Terminal
git status -s
Output
M  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:

Terminal
git ls-files --stage
Output
100644 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 commit to save every change. It saves only what is in the index. Run git status before 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 diff as "all my changes". When it prints nothing, your changes may already be staged. Use git diff --staged or git 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 add copies changes from the working tree to the index; git commit saves the index as a new commit.
  • Files can be untracked, unmodified, modified or staged.
  • git diff compares working tree with index; git diff --staged compares 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
Terminal
git add notes.txt
git diff
git diff --staged
git status -s

git 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?

2. Plain git diff prints nothing, but you know you changed a file. Why?

3. In git status -s, what does M (space then M) mean?

4. Which area does git commit save?

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