The Three Trees and git reset
(excerpted from the Objects and Trees Exercise).
The Three Trees
Git manages your local changes using three trees: HEAD, Index, and Working Tree.
Tree | Role |
---|---|
HEAD | The latest commit |
Index | The commit-in-progress, aka "staging" |
Working Tree | Your local filesystem (except for the .git directory itself |
You typically start by making changes in the working tree, which flow to the index (aka "staging") via git add
, and finally into the repo's commit history via git commit
(that is, the branch pointed to by HEAD moves to the next commit):
Tree: | Working Tree | Index | HEAD |
---|---|---|---|
Operation: | 1. <make changes> | 2. git add (stage changes) | 3. git commit |
(Note that HEAD typically points to a branch name, not directly to a commit; this is what it means to be "checked out" on a branch. So when git commit
or git reset
is used to move HEAD, they actually move the branch to which HEAD points).
See also this workflow diagram from git-scm.com. Notice that a git checkout
updates all three trees:
git reset
Sometimes you'll find it necessary to move changes the other way. For instance, you may want to...
- Add a forgotten file to the same set of already-committed changes,
- Change a commit message, or
- Revert a commit.
git reset
can assist with all this and more.
Why is it called "reset"? Because it resets your branch to a commit that you specify (with the optional side effect of updating your index and working tree to that commit as well).
git reset
needs to know two things:
- The "hardness"--that is, how many trees are to be reset, and
- Which commit SHA to (re)set to.
NOTE: If you just type "git reset", the default hardness is "--mixed", and the default commit SHA is HEAD.
Effects of git reset
's --soft, --mixed, and --hard options
Tree | Role | git reset "hardness"needed to move the tree |
---|---|---|
HEAD | The latest commit | --soft |
Index | The commit-in-progress | --mixed (also moves HEAD). The default. |
Working Tree | Your local filesystem | --hard (also moves HEAD and Index.) |
Another view of the effects of git reset --soft/mixed/hard <ThisCommit>
"hardness" | Trees that are reset to <ThisCommit> | ||
---|---|---|---|
Working Tree | Index | HEAD | |
--soft | - | - | YES |
--mixed | - | YES | YES |
--hard | YES | YES | YES |
Moving Objects Among Trees
Let's move a single file through this workflow, starting with working-tree changes, which will move into the index, and then into a commit. Then, we'll revert it, tree by tree, all the way back using git reset
.
1. Move a change forward through the trees
Make a change (which tree are you working in now, as you run the following commands?) such as adding a menu item link...
code index.html # (or whatever file you may have in your working tree)
# Make a minor change to index.html in your editor, and save it. Then...
git status # or use the 'gs' alias
git diff # or use the 'gd' alias
Add to the index.
git add index.html # or 'ga' aFile.txt
git status
git diff
git diff --staged # or use the 'gds' alias
Which tree (or trees) have the change now?
Commit it...
git rev-parse HEAD
git commit -m "Added item to menu in index.html" # or use the 'gc' alias: gc -m "Added..."
git status
Now which tree (or trees) have the change?
2. Move the same change backward through the trees
Recall that besides specifying "hardness", we need to tell git reset
which SHA to reset to.
(What is the previous value of HEAD?)
git rev-parse HEAD # See what commit sha HEAD resolves to
git reset
git rev-parse HEAD # Did HEAD change? Why or why not?
git rev-parse HEAD^ # Check the value of HEAD's parent commit sha
git status
git reset --soft HEAD^ # Recall which trees are changed by --soft
git rev-parse HEAD # What is HEAD pointing to now?
git status
git diff # Diff between working tree and index
git diff --staged # Diff between index and HEAD
NOTE: The caret (^) in HEAD^
above means "first parent".[^git-revisions]
What happened? What is git status
telling you, and why?
What happened to the commit that we were on before doing a `git reset```? How might we get back to it?
Now the branch that HEAD points to has been "reset", back to where it was before we committed.
Which tree(s) changed? hover
Let's change the next tree...
git reset # Same command, but now something happened. Why?
git status
git diff
git diff --staged
Which tree(s) changed this time? hover
Let's change the third tree...
git reset --hard
git status
git diff
git diff --staged
Questions for thought:
What happened to each tree at each step?
How is git reset <paths>
the opposite of git add <paths>
?
When would each variation of git reset
come in handy? For instance, how would you employ git reset --mixed
to add more files to an existing commit? Or to amend a commit by adding more changes to the same file?
[^git-revisions] for more on ^
, ~
, @{push}
, and other revision notation, see Git Revisions.