Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions book/07-git-tools/sections/reset.asc
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The Git `cat-file` and `ls-tree` commands are ``plumbing'' commands that are use
[[_the_index]]
===== The Index

The Index is your *proposed next commit*.
The _index_ is your *proposed next commit*.
We've also been referring to this concept as Git's ``Staging Area'' as this is what Git looks at when you run `git commit`.

Git populates this index with a list of all the file contents that were last checked out into your working directory and what they looked like when they were originally checked out.
Expand All @@ -71,10 +71,10 @@ The index is not technically a tree structure -- it's actually implemented as a

===== The Working Directory

Finally, you have your working directory.
Finally, you have your _working directory_ (also commonly referred to as the ``working tree'').
The other two trees store their content in an efficient but inconvenient manner, inside the `.git` folder.
The Working Directory unpacks them into actual files, which makes it much easier for you to edit them.
Think of the Working Directory as a *sandbox*, where you can try changes out before committing them to your staging area (index) and then to history.
The working directory unpacks them into actual files, which makes it much easier for you to edit them.
Think of the working directory as a *sandbox*, where you can try changes out before committing them to your staging area (index) and then to history.

[source,console]
----
Expand All @@ -90,7 +90,7 @@ $ tree

==== The Workflow

Git's main purpose is to record snapshots of your project in successively better states, by manipulating these three trees.
Git's typical workflow is to record snapshots of your project in successively better states, by manipulating these three trees.

image::images/reset-workflow.png[]

Expand All @@ -100,38 +100,38 @@ Now we run `git init`, which will create a Git repository with a HEAD reference

image::images/reset-ex1.png[]

At this point, only the Working Directory tree has any content.
At this point, only the working directory tree has any content.

Now we want to commit this file, so we use `git add` to take content in the Working Directory and copy it to the Index.
Now we want to commit this file, so we use `git add` to take content in the working directory and copy it to the index.

image::images/reset-ex2.png[]

Then we run `git commit`, which takes the contents of the Index and saves it as a permanent snapshot, creates a commit object which points to that snapshot, and updates `master` to point to that commit.
Then we run `git commit`, which takes the contents of the index and saves it as a permanent snapshot, creates a commit object which points to that snapshot, and updates `master` to point to that commit.

image::images/reset-ex3.png[]

If we run `git status`, we'll see no changes, because all three trees are the same.

Now we want to make a change to that file and commit it.
We'll go through the same process; first we change the file in our working directory.
We'll go through the same process; first, we change the file in our working directory.
Let's call this *v2* of the file, and indicate it in red.

image::images/reset-ex4.png[]

If we run `git status` right now, we'll see the file in red as ``Changes not staged for commit,'' because that entry differs between the Index and the Working Directory.
Next we run `git add` on it to stage it into our Index.
If we run `git status` right now, we'll see the file in red as ``Changes not staged for commit,'' because that entry differs between the index and the working directory.
Next we run `git add` on it to stage it into our index.

image::images/reset-ex5.png[]

At this point, if we run `git status`, we will see the file in green under ``Changes to be committed'' because the Index and HEAD differ -- that is, our proposed next commit is now different from our last commit.
At this point, if we run `git status`, we will see the file in green under ``Changes to be committed'' because the index and HEAD differ -- that is, our proposed next commit is now different from our last commit.
Finally, we run `git commit` to finalize the commit.

image::images/reset-ex6.png[]

Now `git status` will give us no output, because all three trees are the same again.

Switching branches or cloning goes through a similar process.
When you checkout a branch, it changes *HEAD* to point to the new branch ref, populates your *Index* with the snapshot of that commit, then copies the contents of the *Index* into your *Working Directory*.
When you checkout a branch, it changes *HEAD* to point to the new branch ref, populates your *index* with the snapshot of that commit, then copies the contents of the *index* into your *working Directory*.

==== The Role of Reset

Expand Down Expand Up @@ -159,14 +159,14 @@ With `reset --soft`, it will simply stop there.

Now take a second to look at that diagram and realize what happened: it essentially undid the last `git commit` command.
When you run `git commit`, Git creates a new commit and moves the branch that HEAD points to up to it.
When you `reset` back to `HEAD~` (the parent of HEAD), you are moving the branch back to where it was, without changing the Index or Working Directory.
You could now update the Index and run `git commit` again to accomplish what `git commit --amend` would have done (see <<_git_amend>>).
When you `reset` back to `HEAD~` (the parent of HEAD), you are moving the branch back to where it was, without changing the index or working directory.
You could now update the index and run `git commit` again to accomplish what `git commit --amend` would have done (see <<_git_amend>>).

===== Step 2: Updating the Index (--mixed)

Note that if you run `git status` now you'll see in green the difference between the Index and what the new HEAD is.
Note that if you run `git status` now you'll see in green the difference between the index and what the new HEAD is.

The next thing `reset` will do is to update the Index with the contents of whatever snapshot HEAD now points to.
The next thing `reset` will do is to update the index with the contents of whatever snapshot HEAD now points to.

image::images/reset-mixed.png[]

Expand All @@ -178,7 +178,7 @@ You rolled back to before you ran all your `git add` and `git commit` commands.

===== Step 3: Updating the Working Directory (--hard)

The third thing that `reset` will do is to make the Working Directory look like the Index.
The third thing that `reset` will do is to make the working directory look like the index.
If you use the `--hard` option, it will continue to this stage.

image::images/reset-hard.png[]
Expand All @@ -187,31 +187,31 @@ So let's think about what just happened.
You undid your last commit, the `git add` and `git commit` commands, *and* all the work you did in your working directory.

It's important to note that this flag (`--hard`) is the only way to make the `reset` command dangerous, and one of the very few cases where Git will actually destroy data.
Any other invocation of `reset` can be pretty easily undone, but the `--hard` option cannot, since it forcibly overwrites files in the Working Directory.
Any other invocation of `reset` can be pretty easily undone, but the `--hard` option cannot, since it forcibly overwrites files in the working directory.
In this particular case, we still have the *v3* version of our file in a commit in our Git DB, and we could get it back by looking at our `reflog`, but if we had not committed it, Git still would have overwritten the file and it would be unrecoverable.

===== Recap

The `reset` command overwrites these three trees in a specific order, stopping when you tell it to:

1. Move the branch HEAD points to _(stop here if `--soft`)_
2. Make the Index look like HEAD _(stop here unless `--hard`)_
3. Make the Working Directory look like the Index
2. Make the index look like HEAD _(stop here unless `--hard`)_
3. Make the working directory look like the index

==== Reset With a Path

That covers the behavior of `reset` in its basic form, but you can also provide it with a path to act upon.
If you specify a path, `reset` will skip step 1, and limit the remainder of its actions to a specific file or set of files.
This actually sort of makes sense -- HEAD is just a pointer, and you can't point to part of one commit and part of another.
But the Index and Working directory _can_ be partially updated, so reset proceeds with steps 2 and 3.
But the index and working directory _can_ be partially updated, so reset proceeds with steps 2 and 3.

So, assume we run `git reset file.txt`.
This form (since you did not specify a commit SHA-1 or branch, and you didn't specify `--soft` or `--hard`) is shorthand for `git reset --mixed HEAD file.txt`, which will:

1. Move the branch HEAD points to _(skipped)_
2. Make the Index look like HEAD _(stop here)_
2. Make the index look like HEAD _(stop here)_

So it essentially just copies `file.txt` from HEAD to the Index.
So it essentially just copies `file.txt` from HEAD to the index.

image::images/reset-path1.png[]

Expand All @@ -228,8 +228,8 @@ We would just run something like `git reset eb43bf file.txt`.

image::images/reset-path3.png[]

This effectively does the same thing as if we had reverted the content of the file to *v1* in the Working Directory, ran `git add` on it, then reverted it back to *v3* again (without actually going through all those steps).
If we run `git commit` now, it will record a change that reverts that file back to *v1*, even though we never actually had it in our Working Directory again.
This effectively does the same thing as if we had reverted the content of the file to *v1* in the working directory, ran `git add` on it, then reverted it back to *v3* again (without actually going through all those steps).
If we run `git commit` now, it will record a change that reverts that file back to *v1*, even though we never actually had it in our working directory again.

It's also interesting to note that like `git add`, the `reset` command will accept a `--patch` option to unstage content on a hunk-by-hunk basis.
So you can selectively unstage or revert content.
Expand Down Expand Up @@ -268,7 +268,7 @@ Like `reset`, `checkout` manipulates the three trees, and it is a bit different
Running `git checkout [branch]` is pretty similar to running `git reset --hard [branch]` in that it updates all three trees for you to look like `[branch]`, but there are two important differences.

First, unlike `reset --hard`, `checkout` is working-directory safe; it will check to make sure it's not blowing away files that have changes to them.
Actually, it's a bit smarter than that -- it tries to do a trivial merge in the Working Directory, so all of the files you _haven't_ changed will be updated.
Actually, it's a bit smarter than that -- it tries to do a trivial merge in the working directory, so all of the files you _haven't_ changed will be updated.
`reset --hard`, on the other hand, will simply replace everything across the board without checking.

The second important difference is how `checkout` updates HEAD.
Expand Down