Git: Revert to a Previous Commit

If I've learned anything in my 15+ years of programming, it's that mistakes are common, and I make a lot of them. This equally applies to version control tools as well. Whether you accidentally commit changes, or just realized your previous committed code isn't what you wanted, often times you'll need to revert a previous commit in Git.

In this article I'll show a few ways to revert your commits, depending on your use-case. This is a complicated topic (which is true for many Git topics in general), so make sure you follow the instructions that best suits your needs.

Delete Unpublished Commits

If you haven't yet published your commits to a remote repository, like GitHub, then you can essentially delete previous commits by using the reset command.

While this is an effective solution, it's a dangerous one since you're rewriting history and leaving the "deleted" commits unreferenced, or "orphaned". The only way to find and recover these unreferenced commits is with git reflog.

The reset command has three different options, two of which we'll describe here:

$ git reset --hard <hash-or-ref>

Using the --hard option, everything is reverted back to the specified commit. This includes the commit history reference pointers, the staging index, and your working directory.

This means that by using just this command you'll not only revert to a previous commit, but you'll lose all working changes in the process. To avoid losing any working changes, you can use the stash and stash pop commands:

$ git stash
$ git reset --hard <hash-or-ref>
$ git stash pop

The stash command saves your working changes (without any commits or changes to the tree), and then stash pop brings them back.

The other option you may consider is the --soft option. This option works much the same way as git reset --hard <hash-or-ref>, but it only affects the commit history, not your working directory or staging index.

$ git reset --soft <hash-or-ref>

So if you have uncommitted changes that you want to keep, then this is likely the option you want.

Deleting Published Commits

So let's say you committed your code and then pushed it to the remote repository. At this point it's highly advised that you do not use something like git reset since you'd be rewriting history.

Instead, the recommended approach would be to use the revert command. This command works by undoing changes that were made in the specified commit by creating a new commit and not actually removing any previous commits. This is ideal for published changes because then the true history of the repo is preserved. Here is the command:

$ git revert <hash-or-ref>

So let's say you have a text file in your repo with the following content:

This is my sample text  

And then you change it to:

This is my awesome sample text  

Your commit history might look something like this:

$ git log --pretty=oneline
676ec97a9cb2cebbb5c77904bbc61ced05b86f52 Added 'awesome' to text  
735c5b43bf4b5b7107a9cc3f6614a3890e2889f6 Initial commit  

If we decide we don't want "awesome" in our text anymore, but we don't want to delete the 676ec commit, we can use revert to undo that change:

$ git revert 676ec
[master f68e546] Revert "Added 'awesome' to text"
 1 file changed, 1 insertion(+), 1 deletion(-)

After being prompted to enter a commit message, we can now see in our commit history that there is actually a new commit:

$ git log --pretty=oneline
f68e546ac2ae240f22b2676b5aec499aab27f1ca Revert "Added 'awesome' to text"  
676ec97a9cb2cebbb5c77904bbc61ced05b86f52 Added 'awesome' to text  
735c5b43bf4b5b7107a9cc3f6614a3890e2889f6 Initial commit  

As a result of this, the first and third commits represent the exact same project state. The commit has been reverted, and no history was lost.

Note that there are quite a few other ways to use this command, like if you want to revert back 2 commits, you can use:

$ git revert HEAD~2

Or if you want to revert many non-continuous commits you specify them individually:

$ git revert 676ec 735c5

Temporarily Checkout a Previous Commit

By "reverting a commit", you might mean that you temporarily want to go back to a previous state in your repo, but without making any actual changes to the tree. In this case you'd probably just want to checkout the commit, allowing you to go back to master or any other state when you're done:

$ git checkout <hash-or-ref>

This will change your working directory to the contents of that commit, as well as the location HEAD points to, none of which is irreversible. Any changes you make here can either be committed to a branch or stashed for later use.