Undo Changes in Git – Part III – The reset command

To undo multiple commits in Git, we need a more powerful command called the reset command.

The reset command allows us to manipulate where the HEAD in Git is pointing.  This is normally something we just let Git manage and usually should never have to think about it.  However, we all know from time to time there is a need, or perhaps just a strong desire to undo, step back, reset…whatever you want to call it.

There 5 options for the reset command, soft, mixed, hard, merge, and keep.  We’re gonna talk about soft, mixed, and hard here.  Each of the three always move the HEAD pointer, but the difference is in how Git treats your working directory and staging index.  Basically, when you move the HEAD pointer to whatever previous commit you’re telling it, you’re in essence then telling Git to start recording from that commit, essentially ‘recording over‘ (just like the cassette tape analogy we talked about) whatever was in front of it. The difference in the danger factor is what each does to your working directory and index.

To start things off, let’s create a new small project/directory with some files to work with.  As before, create 3 files with some text and commit them separately so we have some history to work with.

You should then have a git log that looks something like the following:

commit e46cdf0e593cf1732ce637309fef07316d90c72f
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:39:10 2017 -0400

Initial commit for file 3

commit eddb0cdc50dd0d63d2a91145278e2b2839312105
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:49 2017 -0400

Initial commit for file 2

commit 46846b1e438270b79100dd827cffe9a567e90125
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:26 2017 -0400

Initial commit for file 1

Now let’s make a new commit and revert it for a bit more history to get us to the following point:

commit 34be6f962d1b23c458713333176db68936085921
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:47:15 2017 -0400

Revert "Declaring file 3 the greatest"

This reverts commit ea326a278722d94cac4557751301c691cebaeda1.

commit ea326a278722d94cac4557751301c691cebaeda1
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:45:26 2017 -0400

Declaring file 3 the greatest

commit e46cdf0e593cf1732ce637309fef07316d90c72f
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:39:10 2017 -0400

Initial commit for file 3

commit eddb0cdc50dd0d63d2a91145278e2b2839312105
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:49 2017 -0400

Initial commit for file 2

commit 46846b1e438270b79100dd827cffe9a567e90125
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:26 2017 -0400

Initial commit for file 1

git reset –soft

Now we can see that we have a decent amount of history and our last commit was a reversion.  We are going to do a soft reset back to the commit prior to that reversion.  Of course, there are other ways to do this, you could simply revert it again, but that would defeat the purpose of this post :).

Let’s do a quick sanity check to verify the current HEAD is indeed pointing to the tip of the current branch:

➜ newgitproject git:(master) cat .git/HEAD
ref: refs/heads/master
➜ newgitproject git:(master) cat .git/refs/heads/master
34be6f962d1b23c458713333176db68936085921

And we see that as it should, it’s pointing to the tip of the current branch.

Now let’s go ahead and do our soft reset to the prior commit, that being commit ea326a278722d94c…

*Remember in Git, you don’t need to refer to whole SHAs, the first several characters should be good enough almost all the time.

➜  newgitproject git:(master) git reset --soft ea326a278722d94c

And check our log:

commit ea326a278722d94cac4557751301c691cebaeda1
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:45:26 2017 -0400

Declaring file 3 the greatest

commit e46cdf0e593cf1732ce637309fef07316d90c72f
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:39:10 2017 -0400

Initial commit for file 3

commit eddb0cdc50dd0d63d2a91145278e2b2839312105
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:49 2017 -0400

Initial commit for file 2

commit 46846b1e438270b79100dd827cffe9a567e90125
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:26 2017 -0400

Initial commit for file 1

We see that indeed our new HEAD, or tip of the current branch, is that commit ea326a278722d94 that we reset to.

What git reset –soft does is reset the HEAD pointer to where you specify while leaving the working directory and staging index alone.  By ‘alone’, I mean it doesn’t remove any of your work up to the point that you reset from.  A soft reset is the least destructive and therefore safest of the reset commands because it leaves those untouched.  Whatever commit you move back to, you still have all the changes up to what was the HEAD at the tip of the current branch and whatever’s still in your working directory and index.  To see what I mean, do a git status:

➜ newgitproject git:(master) ✗ git status
On branch master
Changes to be committed:
 (use "git reset HEAD <file>..." to unstage)

modified: file3.txt

And see that in our working directory and staging index, we still have that change that we reset from, that is the removal of the greatest file text.  This is why a soft reset is safest, because it preserves whatever work you’ve done up to whatever point you’re resetting from.  At this point it’s up to you whether you want to go all the way and undo the index and working copy to get back to what’s in that commit that you reset to or do something else such as make a new commit, which would in effect ‘record’ over the old commit that you reset from.

To make this a little bit clearer, if we now say we want to reset back to what was our latest commit, that being the reversion, 34be6f962d1b23c, we can do that and see that a git status will reveal that everything is once again clean:

➜ newgitproject git:(master) ✗ git reset --soft 34be6f962d1b23c
➜ newgitproject git:(master) git status
On branch master
nothing to commit, working tree clean

So, to summarize a soft reset, it is the safest choice because it does not remove any work, it simply resets the pointer and gives you the option of what to do with the work leading up to the reset.

git reset –mixed

A mixed reset does exactly what a soft reset does except that it changes the staging index to match the repository.  It is also the Git default reset mode.

Let’s do the same reset as we did for the soft reset and use a mixed mode.

➜ newgitproject git:(master) git reset --mixed ea326a278722d94c
Unstaged changes after reset:
M file3.txt

And you can see that it changed the staging index to match the repository as evidenced by the message. Do a git status:

➜ newgitproject git:(master) ✗ git status
On branch master
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git checkout -- <file>..." to discard changes in working directory)

modified: file3.txt

no changes added to commit (use "git add" and/or "git commit -a")

So we still have the change (the reversion of the greatest text) in our working directory, but not in our staging index like in the soft reset.  This is a great tool if you make a few commits and realize you want to go back and perhaps just redo those commits in any different way.

One other thing to point out is that if you recall from the first post in this series, we went over how to unstage a change using get reset HEAD filename.  Well now you can see that that’s just a variation on git reset –mixed.  As I mentioned, mixed is the default reset mode so that command is simply telling git to reset to HEAD, i.e. the latest commit and it’s replacing the staging index with what’s in the repository.

Let’s verify where the HEAD is now by checking the log:

commit ea326a278722d94cac4557751301c691cebaeda1
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:45:26 2017 -0400

Declaring file 3 the greatest

commit e46cdf0e593cf1732ce637309fef07316d90c72f
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:39:10 2017 -0400

Initial commit for file 3

commit eddb0cdc50dd0d63d2a91145278e2b2839312105
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:49 2017 -0400

Initial commit for file 2

commit 46846b1e438270b79100dd827cffe9a567e90125
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:26 2017 -0400

Initial commit for file 1

Now let’s get back to the original reversion again by issuing a mixed reset with that latest SHA:

➜ newgitproject git:(master) ✗ git reset --mixed 34be6f962d1b23c
➜ newgitproject git:(master) git status
On branch master
nothing to commit, working tree clean

git reset –hard

This is the most destructive of the reset commands, and you should be careful with it.  It’s pretty much a nuclear option if you ever feel that things have gotten out of control and you want to just redo everything from scratch.

➜ newgitproject git:(master) git reset --hard ea326a278722d94c
HEAD is now at ea326a2 Declaring file 3 the greatest
➜ newgitproject git:(master) git status
On branch master
nothing to commit, working tree clean

Notice, we’ve reset back to before the reversion and doing a git status reveals that we’ve indeed destroyed all traces of the previous reversion.  There’s nothing in our working directory or staging index.

It’s important to remember that as long as you still have that last SHA that we reset from, you can still get those changes back.  That’s why it’s always important to keep a text backup of your SHAs when you’re doing resets, just in case.  Let’s see this:

➜ newgitproject git:(master) git reset --hard 34be6f962d1b23c
HEAD is now at 34be6f9 Revert "Declaring file 3 the greatest"
➜ newgitproject git:(master) git status
On branch master
nothing to commit, working tree clean

And git log:

commit 34be6f962d1b23c458713333176db68936085921
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:47:15 2017 -0400

Revert "Declaring file 3 the greatest"

This reverts commit ea326a278722d94cac4557751301c691cebaeda1.

commit ea326a278722d94cac4557751301c691cebaeda1
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:45:26 2017 -0400

Declaring file 3 the greatest

commit e46cdf0e593cf1732ce637309fef07316d90c72f
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:39:10 2017 -0400

Initial commit for file 3

commit eddb0cdc50dd0d63d2a91145278e2b2839312105
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:49 2017 -0400

Initial commit for file 2

commit 46846b1e438270b79100dd827cffe9a567e90125
Author: Tom Casper <tmcasper@gmail.com>
Date: Tue Apr 4 21:38:26 2017 -0400

Initial commit for file 1

See, it’s back!

As you’ve seen, git reset can be a powerful tool in your Git toolbox.  But you know what they say…with great power comes great responsibility…or something like that.

Once you make a couple sandbox projects and play around with these commands, you’ll quickly get an intuitive feel for them and have a lot more confidence using them in the future.  So make a sandbox, play around and git to learning!  Hah…you like that, see what I did there…

Until next time brothers and sisters \,,/


Also published on Medium.

Leave a Reply

Your email address will not be published. Required fields are marked *