Menu for Per-Arne's Git Good!
Courses and project Per-Arne's Git Good!

Three areas: working tree, staging area and HEAD

Module 1: Foundations · 11 min

The interactive lesson needs JavaScript. The text below is the complete lesson.

After this lesson you can

  • explain where a file version lives, in the working tree, the staging area or HEAD, and predict which version ends up in the next commit understand
  • read git status --short and say what its two columns mean understand
  • choose between git diff and git diff --staged to see what you actually want to know apply

Understand: Three places a file can live

A file can exist in three versions at once. The working tree is the files you see and edit. The staging area, or index, is the draft of the next commit. HEAD points to your branch's latest commit, and its snapshot is the third version. All three are the same right now.

When you edit the file, only the working tree changes. The staging area and HEAD still hold the old version. Git says the file is modified but not staged.

git add copies the file as it is right now into the staging area. It is a copy of the content, not a mark on the file.

If you edit again after git add, you get three different versions. The new line exists only in the working tree. The staging area still remembers the copy you took.

A diff always compares two versions. git diff compares the staging area with the working tree: what you have not staged yet. git diff --staged compares HEAD with the staging area: what you have staged.

Watch: The commit takes what is staged

We start with a repository with one commit. First we add a line to the README. Notice that only the card in the working tree changes.

$ echo "Install: npm install" >> README.md

git diff shows the difference between the staging area and the working tree. The plus sign marks the new line. It is not staged yet.

$ git diff

Now we copy this version into the staging area. The middle card gets the new version.

$ git add README.md

Then we keep editing, as people often do. We add one more line, after git add. Now the file has three different versions.

$ echo "Run: npm start" >> README.md

The short status shows two letters. The first column compares HEAD with the staging area. The second column compares the staging area with the working tree. So MM means one change that is staged, and a newer one that is not.

$ git status --short

git diff --staged shows what is staged: only the install line.

$ git diff --staged

Now we commit. What do you think ends up in the snapshot?

$ git commit -m "Add install instructions"

Status again. The first column is blank: the staging area and HEAD are equal. The second column says M: the npm start line is still waiting in the working tree.

$ git status --short

Tasks

  1. Add the line Install: npm install at the end of README.md.
  2. Show the changes that are not staged yet.
  3. Stage README.md.
  4. Show the status in short format.
  5. Commit what is staged, and only that.

Practise: Transfer: choose what goes in

A new project where several files are already changed, and one is partly staged. Start by inspecting the state.

Tasks

  1. todo.txt has a staged version and an even newer edit. Make a commit that contains the newest version of todo.txt, and no other files.
  2. Both app.js and notes.txt are changed. Commit only the change in app.js. notes.txt should still be changed, but not committed.

Check yourself: Recall it

You staged README.md, then edited it again. You want to see only the newest edit. Which command do you use?

  • git diff
  • git diff --staged
  • git diff HEAD

git status --short shows MM app.js. What does that mean?

  • The file was changed in two commits
  • The file has a staged change, and a newer one that is not staged
  • The file has a conflict

Review from the previous lesson: the repository tracks app.js, notes.txt and todo.txt. You change and stage app.js. notes.txt also has an edit you have not staged. You commit. Which files are in the new commit's snapshot?

  • Only app.js, because it was the only file that changed
  • All three: the new version of app.js, and the other two as they were in the previous commit
  • All three, as they look in the working tree right now

Common misconceptions

  • “The commit takes the file as it looks in my editor.” It takes the version in the staging area.
  • “git add marks the file, so later edits come along automatically.” git add copies the content as it is at that moment.
  • “git diff shows everything that will be committed.” Plain git diff shows only what is not staged yet.

Read more

The three areas, precisely

  • The working tree is ordinary files on disk. Your editor only sees this.
  • The staging area (index) is the file .git/index. It is a complete list of every tracked file and its content, not just a list of changes. Right after a commit the index equals HEAD. When people say "nothing is staged", they mean the index and HEAD are equal.
  • HEAD points to the branch you are on, and the branch points to the latest commit. The snapshot in that commit is the reference point for "what changed since last time".
  • A file is tracked when it is in the index. A new file is untracked until its first git add.

git commit builds the snapshot from the index. The working tree is not read. That is why an edit made after git add must be staged again to be included.

Which two versions are compared?

A diff always has two endpoints. Pick the command by the question you are asking:

Command Compares Answers
git diff index → working tree What did I change but not stage?
git diff --staged HEAD → index What goes into the next commit?
git diff HEAD HEAD → working tree Everything changed since the last commit

--cached means the same as --staged. Untracked files never appear in git diff, so a new file you forgot to stage only shows up in git status.

Here README.md has one line that is staged, and a newer one that is not:

$ git diff
diff --git a/README.md b/README.md
index 33684f8..f7ea923 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
 Welcome
 Install: npm install
+Run: npm start
$ git diff --staged
diff --git a/README.md b/README.md
index ba4c514..33684f8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
 Welcome
+Install: npm install
  • index 33684f8..f7ea923 are the IDs of the two file versions being compared. Notice that 33684f8 is "after" in one diff and "before" in the other. It is the staged version.
  • @@ -1,2 +1,3 @@ is the header of a hunk, one continuous block of changes: from line 1 of the old version, 2 lines, to line 1 of the new one, 3 lines.
  • Lines with + were added, lines with - were removed, and lines starting with a space are context.
  • Useful options: --stat gives an overview per file, --name-only gives just file names, and -- file limits the diff to one file.

Reading git status --short

$ git status --short
MM README.md
AM app.js
D  gone.txt
R  old.txt -> renamed.txt
?? new.txt

The first column (X) compares HEAD with the index. The second column (Y) compares the index with the working tree. A space means "no difference".

Code Meaning
M Modified and staged
M Modified, not staged
MM A staged change and a newer one that is not staged
A New file, staged
AM New file staged, then edited again
D Deletion staged, for example with git rm
D Deleted from disk, but the deletion is not staged
R Rename staged, for example with git mv
?? Untracked

The long git status says the same in words, and suggests commands. There you see git restore --staged <file>, which undoes staged changes by putting the HEAD version back in the staging area, and git restore <file>, which discards the change in the working tree. The latter cannot be undone, because the change was never stored in Git. The lesson on undoing covers both.

Ways to stage

  • git add notes.txt stages exactly the files you name. It is the most precise.
  • git add . stages everything under the folder you are in: modified, new and deleted files, but not ignored files.
  • git add -A does the same for the whole repository.
  • git add -u stages changes and deletions in files Git already tracks, but no new files.
  • git add -p lets you choose piece by piece. You learn it in the lesson on good commits.
  • git rm notes.txt deletes a file and stages the deletion. git mv old.txt new.txt renames and stages.

The habit that saves you the most mistakes: run git diff --staged right before git commit.

Common surprises

  • "My change did not make it into the commit." You ran git add before the last edit. git status --short shows M on the file. Stage it again and commit.
  • "The diff is empty, but status shows changes." git diff shows nothing when everything is staged. Use git diff --staged.
  • "The new file does not show in the diff." It is untracked. It is listed under ?? in status.
  • A typo in the file name:
$ git add REDME.md
fatal: pathspec 'REDME.md' did not match any files

Nothing was staged. Check the name with git status, and try again.

Further reading