Sometimes, when working on our project with Git, we might commit some code that we are not happy with. One of the solutions that Git provides to undo that is the revert feature. This article looks into how it works and what we need to look out for when using it.
The idea behind Git Revert
While we might be tempted to think of the revert feature as a means of undoing our work, this is not the complete picture. When reverting, Git analyzes the changes we want to undo. Then, it creates a commit on top of them that reverses the changes.
1touch index.js
2echo "console.log('Hello world')" > index.js
3git add ./index.js
4git commint -m "Added the index.js file"Above, we’ve created the index.js file add committed it to the repository. Let’s take a look at the log.
1git logcommit 075913625d998dbccd74613f90dd21f74d725ca6 (HEAD -> master) Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 17:23:53 2021 +0200 Added the index.js file
We can see the hash of the commit above. It serves as an identifier generated based on the contents of the commit. We can use it to refer to this commit when attempting a revert.
If you want to know more about commit hashes, check out Getting geeky with Git #2. Building blocks of a commit
1git revert 0759136Usually just a few first characters of the hash is enought to uniquely identify a commit.
Running the above command opens a text editor.
Above, we can specify the commit message that will be attached when reverting the changes. The crucial thing here is that Git keeps this operation in history.
1git logcommit 9c3b233aa064a1d12436d869ed8ecc0e4ce9b39b (HEAD -> master) Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 17:36:57 2021 +0200 Revert “Added the index.js file” This reverts commit 075913625d998dbccd74613f90dd21f74d725ca6. commit 075913625d998dbccd74613f90dd21f74d725ca6 Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 17:23:53 2021 +0200 Added the index.js file
Let’s look closer into the revert commit.
1git show 9c3b23We can see that the revert commit deletes the index.js file.
Reverting a merge
So far, we’ve only reverted a simple commit. Reverting merges requires us to dig a little deeper into the revert feature.
1git checkout -b feature-a
2echo "console.log('Feature A')" > feature-a.js
3git add ./feature-a.js
4git commit -m "Implemented feature A"1git checkout master
2git checkout -b feature-b
3echo "console.log('Feature B')" > feature-b.js
4git add ./feature-b.js
5git commit -m "Implemented feature B"Above, we’ve created the feature-a and feature-b branches. Let’s now merge it to the master branch.
1git checkout master
2git merge feature-a
3git merge feature-bLet’s look into the logs to figure out what happened when we’ve merged two branches into our master branch.
1git logcommit 2e1ca724207ee83814c7593c7f992bba58abdd3a (HEAD -> master) Merge: 924a7a3 e3416e7 Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 18:26:22 2021 +0200 Merge branch ‘feature-b’ commit e3416e72e78761f40a880050427764b262cd05d5 (feature-b) Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 18:26:06 2021 +0200 Implemented feature B commit 924a7a3a93d9f0d26372b55cb26b9d76622f378a (feature-a) Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 18:12:00 2021 +0200 Implemented feature A
In the logs, we can see that we only have one merge commit. This is because Git performed a fast forward merge when merging feature-a into master.
A fast forward merge can occur when there is a linear path from the current branch tip to the target branch. It wasn’t the case when merging feature-b into master, and therefore, Git created a merge commit.
If you want to know more about merging, check out Getting geeky with Git #4. Fast-forward merge and merge strategies
Since Git didn’t create a merge commit for feature-a, performing a revert is as simple as reverting the commit that implements feature A.
1git revert 924a7aSetting a mainline
It gets a little bit more complicated when we want to revert feature-b. To understand it better, let’s take a closer look at the merge commit.
1git cat-file -p 2e1ca72parent 924a7a3a93d9f0d26372b55cb26b9d76622f378a parent e3416e72e78761f40a880050427764b262cd05d5 author marcin <wanago.marcin@gmail.com> 1621787182 +0200 committer marcin <wanago.marcin@gmail.com> 1621787182 +0200 Merge branch ‘feature-b’
In the second part of this series, we’ve learned that a commit’s parent is the previous commit. When Git creates a merge commit, it has multiple parents. Each parent is the tip of the branch involved in the merging process.
When we perform a revert on a merge commit, git doesn’t automatically know which branch we merged the changes into. Git refers to it as the mainline branch. When reverting the above merge, we have two possibilities:
1git revert --mainline 1 2e1ca721git revert --mainline 2 2e1ca72When we set the mainline with a number, we indicate which parent is mainline. To be able to do that, let’s use git log.
1git log -1 924a7a3commit 924a7a3a93d9f0d26372b55cb26b9d76622f378a (feature-a) Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 18:12:00 2021 +0200 Implemented feature A
1git log -1 e3416e7commit e3416e72e78761f40a880050427764b262cd05d5 (feature-b) Author: marcin <wanago.marcin@gmail.com> Date: Sun May 23 18:26:06 2021 +0200 Implemented feature B
Above, we can see, that the last commit on the first parent implements feature A. Therefore, we know that it is the master branch. To perform a successful revert, we need to set the mainline to 1.
1git revert --mainline 1 2e1ca72Issues to consider
The most important thing to grasp from this article is that reverting commits doesn’t erase them from Git history. Instead, it creates new commits that aim to reverse the changes. The above can cause some unforeseen consequences.
Let’s once again create a branch feature-a with a commit.
1git checkout -b feature-a
2echo "console.log('Feature A')" > feature-a.js
3git add ./feature-a.js
4git commit -m "Implemented feature A"Imagine a situation in which you merge feature-a into master by accident and you want to revert it.
1git checkout master
2git merge feature-a
3git revert HEADThe HEAD keyword is the pointer to the commit our repository is checked out on. In this case, it is the commit we’ve merged from feature-a. If you want to know more, check out Getting geeky with Git #3. The branch is a reference
The crucial thing about the above commands is that now the master branch contains a history of deleting the feature-a.js file. This can prove to be quite an issue later.
Now, let’s say that we’ve continued working on the feature-a branch. Meanwhile, our team pushed some important work to the master branch that we now need. Let’s merge master to feature-a.
1git checkout feature-a
2git merge masterBecause the master branch contains a history of deleting the feature-a.js file, merging it causes the file to disappear in the feature-a branch also.
Summary
The crucial thing to understand about reverting is that it doesn’t erase the changes from Git history. This can lead to issues similar to the one described above. Therefore, it might be a good idea to avoid reverting. If we are not aiming to revert from branches used by other developers, a suitable alternative would be to perform an interactive rebase. If you want to know more, check out the sixth part of this series.