How to rollback changes with Git [updated]

Bad things happen…
Sometimes you commit a change that you want to rollback afterwards. There are multiple ways to rollback changes with Git. Each solution has it advantages and matches another use case. Here is an overview of some of the possible approaches that I tend to use.

Rollback of the latest (single) commit only

Imagine you have committed a change that you want to rollback – so you actually want to rollback the latest commit only.

You could either just cut off the latest commit from the commit history, or you could revert the latest commit.

Cut off latest commit from history

This is the scenario where you cut off the latest commit:

FROM:               TO:
A - B - C           A - B

While this is a good solution if you are either working locally only, or you haven’t pushed the “bad” commit.

!!! You should AVOID THIS SOLUTION if you are using a remote repository and have already pushed the “bad” commit. You could push the changes with a force push, but you have been warned. Don’t do this! ;-) !!!

You can accomplish this by using a hard reset:

$ git reset --hard B

Revert commit, resulting in a linear commit history

This is the scenario where you revert the latest commit by creating a new commit that is the opposite of the previous commit:

FROM:               TO:
A - B - C           A - B - C - D

Where the file tree of D is identical to B, which means that D is the revert commit of C.
You can accomplish this by using the revert command:

$ git revert C

This is my preferred solution if I am using a remote repository and have already pushed the “bad” commit.

Rollback of multiple commits

Now imagine you don’t want to just rollback the latest commits, but multiple commits – in our example going back to the file tree of commit A.
The solution using a hard reset does work for this scenario, too, as you can reset to any given commit. But as said before, this should be avoided when using a remote repository.

In that case you should go for a revert commit, reverting the changes C and B.

FROM:               TO:
A - B - C           A - B - C - D

Where the file tree of D is identical to A, which means D is the revert commit of C and B.

Rollback using multiple revert commands

You can accomplish this by using the revert command:

$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "Going back to A"

By using the --no-commit switch (short -n), you can combine multiple reverts into one single commit (in this case commit D).

This could also be a nice solution if you want to rollback certain commits that are not in series.

Rollback using a single revert command with a commit range

You can create a single revert commit that reverts a range of commits using the following commands:

$ git revert --no-commit HEAD~2..HEAD
$ git commit -m "Going back to A"

In this example, HEAD~2 is the lower bound (excluding) and HEAD the upper bound (including) of the range that you want to revert – so this will revert the last two commits.

Instead, you could also use the hashes of the commits which form the range to revert.

Rollback using a temporary branch

If you don’t want to revert every single commit (e.g. you want to revert hundreds of commits, going back to a very old state), there is another solution.
You can bring your working copy to the state of commit A and then commit the delta to commit C.

We do this by creating a temporary branch from commit A, switch to that branch, do a soft reset to the latest commit C (moving the branch pointer to commit C, but keeping the working copy at the state of commit A), commit (the revert commit between A and C) and merge the temporary branch into the original branch. You can now push your original branch and delete the temporary branch.
(thanks to Steffen Schäfer for pointing this out)

So for the master branch, here is the example:

$ git branch temp A
$ git checkout temp
$ git reset --soft master
$ git commit -m "going back to A"
$ git checkout master
# I recommend doing a fetch and merge, to make sure we are up to date with the remote repository.
$ git merge temp
$ git push

# delete the temporary branch
$ git branch -d temp

Rollback using Git reflog

There is even a simpler approach that I found at StackOverflow using more Git magic (Git reflog).

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is C
$ git commit -a

[update]
Added paragraph on how to rollback using a single revert command with a commit range.
[/update]

Short URL for this post: http://blog.oio.de/lyIrX
This entry was posted in Build, config and deploy and tagged , , , , , , . Bookmark the permalink.

One Response to How to rollback changes with Git [updated]

  1. Jay says:

    I usually just do a git revert on a range of commits, and then do an interactive rebase to squash all commits. The visual block in vim helps me do this in a jiffy, when used with interactive git rebase.

    But it was fun reading this article, knowing different ways to do the same thing

Leave a Reply