Three areas: working tree, staging area and HEAD
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 --shortand say what its two columns mean understand - choose between
git diffandgit diff --stagedto 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
- Add the line
Install: npm installat the end ofREADME.md. - Show the changes that are not staged yet.
- Stage
README.md. - Show the status in short format.
- 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
todo.txthas a staged version and an even newer edit. Make a commit that contains the newest version oftodo.txt, and no other files.- Both
app.jsandnotes.txtare changed. Commit only the change inapp.js.notes.txtshould 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 diffgit diff --stagedgit 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 addmarks the file, so later edits come along automatically.”git addcopies the content as it is at that moment. - “
git diffshows everything that will be committed.” Plaingit diffshows 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..f7ea923are the IDs of the two file versions being compared. Notice that33684f8is "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:
--statgives an overview per file,--name-onlygives just file names, and-- filelimits 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.txtstages 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 -Adoes the same for the whole repository.git add -ustages changes and deletions in files Git already tracks, but no new files.git add -plets you choose piece by piece. You learn it in the lesson on good commits.git rm notes.txtdeletes a file and stages the deletion.git mv old.txt new.txtrenames 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 addbefore the last edit.git status --shortshowsMon the file. Stage it again and commit. - "The diff is empty, but status shows changes."
git diffshows nothing when everything is staged. Usegit 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
- Recording Changes to the Repository in the Pro Git book
- git add, git status, git diff and git restore
Per-Arne's Git Good!