Have you ever faced a situation where you had to undo changes in the master branch because some unwanted code was committed, potentially leading to production bugs?
Well, we faced a similar issue in our project when a developer accidentally cherry-picked a commit that wasn’t tested and deployed it to production. Luckily, our app is an in-house product used by the company itself, so the impact was minimal.
Such cases can happen when you have a distributed team spread across a few countries and different time zones. Here's how we removed a particular commit from our master (production) branch and resolved the issue quickly.
Git revert to the rescue.
git revert
is used to create new commits that reverse the effects of earlier commits (often just a faulty one).
The git revert
command can be considered an "undo" type of command, but it is not a traditional undo operation. Instead of removing the commit from the project history, it inverts the changes introduced by the commit and appends a new commit with the resulting inverse content. This prevents Git from losing history, which is crucial for the integrity of your revision history and for reliable collaboration.
In simple terms, git revert
is used to undo a commit (say hash 2afe34
), but it does so by creating a new commit that removes the changes from the commit (2afe34
) you are reverting.
For example, if you’re tracking down a bug and find that it was introduced by a single commit, instead of manually fixing it and committing a new change, you can use git revert to automatically handle this for you.
Syntax:
git revert [--options] <commit>
git revert
takes a specified commit from the history, inverts the changes from that commit, and creates a new "revert commit."
Let's demo it:
I have this index.html file I created for demo purposes. It contains an ordered list of fruits. Below are my commits with the changes made in each one:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fruits</title>
</head>
<body>
<div>
<h3>Fruits list</h3>
<ol>
<li>Mango</li>
<li>Orange</li>
<li>Banana</li>
</ol>
<h3>Other list</h3>
<ul>
<li>Apple</li>
<li>Pineapple</li>
</ul>
</div>
</body>
</html>
7afe5e3 (HEAD -> master) add other list
c8862d9 add banana
8db1cd2 add orange
f4e9e1f add mango
065a354 initial commit
My first commit is an initial commit with just the basic HTML structure like head and body. Rest commits are self explanatory. I added mango in commit f4e9e1f
, orange in 8db1cd2
, and banana in c8862d9
inside the first ordered list (<ol>
). I then added a second ordered list with more items in commit 7afe5e3
, which is the current HEAD commit.
Let’s say I want to remove banana from the list. In this simple example, we could just remove banana from the code and create a new commit reflecting that change.
However, in a collaborative project with multiple contributors, commit history becomes important to track the project's state at any given point in time. Moreover, manually undoing large code changes wouldn’t be feasible, and git revert becomes an inevitable solution.
Going back to our demo, to remove banana from the first ordered list, I can simply run git revert c8862d9
and this creates a new commit explaining the reason for the revert. You’ll see that banana is now removed from the list.
So new index file and commit history will look like below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fruits</title>
</head>
<body>
<div>
<h3>Fruits list</h3>
<ol>
<li>Mango</li>
<li>Orange</li>
</ol>
<h3>Other list</h3>
<ul>
<li>Apple</li>
<li>Pineapple</li>
</ul>
</div>
</body>
</html>
Commit history after git revert: git log --oneline
828ef17 (HEAD -> master) Revert "add banana"
7afe5e3 add other list
c8862d9 add banana
8db1cd2 add orange
f4e9e1f add mango
065a354 initial commit
Sometimes, Git is not smart enough to figure out the exact code that needs to be removed, leading to merge conflicts. In these cases, you, as a developer, need to help Git a bit.
It’s important to understand that git revert
undoes a single commit—it does not "revert" the project to a previous state by removing all subsequent commits like git reset
.
Reverting has two important advantages over resetting:
It doesn’t change the project history, making it a “safe” operation for commits that have already been published to a shared repository.
git revert can target an individual commit at any arbitrary point in the history.
Happy coding! 😊
Top comments (6)
I'd prefer dropping the commit through interactive rebase as it makes my git log look cleaner. Also, it avoids any merge or rebase conflicts whenever we rebase to latest merge-base.
However, this has a risk of losing the history, leaving no trace of what has been done and reverted.
In our case keeping git history was essential as it help us to document what was done and when.
Reminds me that many times I have tried to get commercial dll files for sdk libraries on github :P
Awesome post!
I have a question:
7afe5e3 add other list
c8862d9 add suffix banana -> bananaPlus
8db1cd2 add banana
if i do git revert 8db1cd2, what is the result of my operation?
In this case you might get merge conflict. It all depend on code context, removing last item from list(c8862d9) does not create conflict as git has to just remove last line, but while reverting(basically removing) middle item(8db1cd2) git has to understand how to handle position of bananaPlus(c8862d9) because when it(banana - 8db1cd2) was added there was no context of last item(c8862d9) present.
Hope this makes it clear. In our case also we got conflict in few section of the code when doing git revert. Sometimes its good to get conflict because then we know for sure which part of code git is reverting when your file is long(1500+ LOC in our case). It helps in doing sanity testing after revert