January 17, 2016
By: Wayne Dyck

Git revert

If you've ever pushed a series of public changes to a repository and then realized there is a problem with one of the commits, git revert is a way to cleanly "undo" one or more of those changes. git revert creates a new commit that's the exact opposite of the given SHA. Anything added in the old commit will be removed in the new commit and anything removed in the old commit will be added back in the new commit.

Let's see how this works with the following example repository.

$ git tree
* 662b249 Nineth commit
* 858b9a1 Eigth commit
* 2ee1496 Seventh commit
* 4ee6fb6 Sixth commit
*   60b949b Merge branch 'feature-branch'
|\  
| * 5e9c10e Fifth commit
| * 8e7c992 Fourth commit
|/  
* 787608b Third commit
* abe5f9b Second commit
* a44ce08 First commit

In this scenario, you recently discover an issue with SHA 2ee1496, the 'Seventh commit'. The repository is public so you can't simply do a git reset or git reset --hard because with would rewrite the history.

Let's use git revert to undo the commit instead.

$ git revert 2ee1496

By default, unless you specific the -n or --no-commit option, the command automatically launches the text editor and creates a commit log message stating which commits are being reverted.

Revert "Seventh commit"

This reverts commit 2ee14969e39d09e0b25f8561de3b9f5d95dae1b9.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#       deleted:    file7.txt
#

Saving the file and exiting the editor creates the actual commit.

$ git revert 2ee1496
[master d6a2eae] Revert "Seventh commit"
 1 file changed, 1 deletion(-)
 delete mode 100644 file7.txt

Viewing the log we see a new commit and message has been added. It is important to note that we have not changed any history at this point. The original SHA 2ee1496 commit is still there, however, our new SHA d6a2eae commit has undone what the original one did.

$ git tree
* d6a2eae Revert "Seventh commit"
* 662b249 Nineth commit
* 858b9a1 Eigth commit
* 2ee1496 Seventh commit
* 4ee6fb6 Sixth commit
*   60b949b Merge branch 'feature-branch'
|\  
| * 5e9c10e Fifth commit
| * 8e7c992 Fourth commit
|/  
* 787608b Third commit
* abe5f9b Second commit
* a44ce08 First commit

SHA 2ee1496 originally added the file7.txt text file and if we list our directory we can see the file is no longer there.

$ ls -l
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:34 file1.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:35 file2.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:38 file3.txt
-rw-r--r--  1 Wayne  staff    22 Jan 17 15:40 file4.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:40 file5.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:42 file6.txt
-rw-r--r--  1 Wayne  staff    22 Jan 17 15:44 file8.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:45 file9.txt

That was easy, however, what if the commit you want to revert is the result of a merge? Let's attempt to revert SHA 60b949b which was a merge of our 'feature-branch' and see what happens.

$ git revert 60b949b
error: Commit 60b949b1cbabf0d5519bba9fb7ad5386fd58e18c is a merge but no -m option was given.
fatal: revert failed

What happened? In order for git to revert a merge it needs to know which side of the merge should be considered the mainline. The -m or --mainline option allows you to specify the parent number starting from 1. The revert will then reverse the change relative to the specified parent. Let's do a git show on our merge commit and review the information it provides.

$ git show 60b949b
commit 60b949b1cbabf0d5519bba9fb7ad5386fd58e18c
Merge: 787608b 5e9c10e
Author: Wayne Dyck <Wayne@localhost>
Date:   Sun Jan 17 15:40:35 2016 -0800

Merge branch 'feature-branch'

Next to the "Merge:" label you will see the 787608b 5e9c10e SHAs which are the two parent mainlines that make up this merge commit. The first one is from the master mainline and the second from the 'feature-branch' mainline. In this case we want to reference SHA 787608b as the first parent by using the -m 1 option.

$ git revert -m 1 60b949b

Now that we've told git which parent mainline to reference the text editor again launches and creates a commit log message stating which commits are being reverted.

Revert "Merge branch 'feature-branch'"

This reverts commit 60b949b1cbabf0d5519bba9fb7ad5386fd58e18c, reversing
changes made to 787608b8e83a55ed44fcdc1c4ac459f0791bde34.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#       deleted:    file4.txt
#       deleted:    file5.txt
#

Saving the file and exiting the editor creates the commit.

$ git revert -m 1 60b949b
[master ca39bb5] Revert "Merge branch 'feature-branch'"
 2 files changed, 2 deletions(-)
 delete mode 100644 file4.txt
 delete mode 100644 file5.txt

Viewing the log again we see the new commit and message that has been added.

$ git tree
* ca39bb5 Revert "Merge branch 'feature-branch'"
* d6a2eae Revert "Seventh commit"
* 662b249 Nineth commit
* 858b9a1 Eigth commit
* 2ee1496 Seventh commit
* 4ee6fb6 Sixth commit
*   60b949b Merge branch 'feature-branch'
|\  
| * 5e9c10e Fifth commit
| * 8e7c992 Fourth commit
|/  
* 787608b Third commit
* abe5f9b Second commit
* a44ce08 First commit

Listing the directory you will see that file4.txt and file5.txt, which were added in our 'feature-branch' merge commit, are no longer there.

$ ls -l
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:34 file1.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:35 file2.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:38 file3.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:42 file6.txt
-rw-r--r--  1 Wayne  staff    22 Jan 17 15:44 file8.txt
-rw-r--r--  1 Wayne  staff    21 Jan 17 15:45 file9.txt
Tags: Git