Git is an excellent tool that boosted by productivity tenfold. I like Git’s clean data model and the directed, acyclic graph of commits. I use Git on a daily basis and–naive as I am–thought I knew all the tricks and corner cases until recently when I stumbled over a reverted merge request. Have you ever reverted a merge request? To quote the Git documentation, reverting a merge request “may or may not be what you want.” So let’s dissect git revert -m to see what it does and what the consequences are.

For this article, I assume that some sort of feature-branch workflow is used. I can think of two typical scenarios when one might be tempted to revert a previous merge request.

When it is OK to revert a merge commit:
Obsolete features

A software project grows, and more and more features are added to the software. At some point, one of the features is considered obsolete or should be removed for some other reason. The developer team thinks there is zero chance that the feature should be resurrected anytime in the foreseeable future. Removing the feature from the software is a well-informed decision. If the code of the feature is loosely coupled to the rest of the code or newer updates, it might be sufficient to revert the merge commit that brought the feature into master.

I think this is an typical case when reverting a merge commit is a viable solution.

When it is not OK to revert a merge commit:
Failed merge

The other scenario includes an ill-informed decision. I have prepared a repository to show-case this scenario. Consider the following commit graph.

Git graph with reverted merge Git commit graph

Assume that the feature is not yet fully mature (B), but you would like to use it already on the master branches for some limited application. After merging the feature branch into the master branch (C), you realize that you didn’t solve all the conflicts. The master branch could be considered as broken. You might not have realized this directly, but now you regret merging the half-baked feature into master (C). Since this is Git, you can revert (E) everything you did and go back to the version before you attempted merging the two branches.

(For simplicity, I assumed that the merge happened into master. However, this is not mandatory. You could also merge into another feature branch which is then later merged into master.)

Everything looks fine. The master branch is in good shape, and the development of the feature branch continues. This is the final state depicted in the sketch.

At some point, the feature has matured and we want to port it to the master branch: git merge feature-branch. That’s a simple task. You do it every day. Does the master branch include the new feature? Well yes, but actually no.

What happened?

When you try to revert a merge commit, Git will complain with an error message like

console $ git revert HEAD error: Commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx is a merge but no -m option was given. fatal: revert failed `

OK, so reverting a merge commit is difficult because Git doesn’t know to which of the two parent commits it should revert. Git already told you the solution: -m. The documentation reads:

Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.

However, the documentation continues with a clear word of warning.

Reverting a merge commit declares that you will never want the tree changes brought in by the merge. As a result, later merges will only bring in tree changes introduced by commits that are not ancestors of the previously reverted merge. This may or may not be what you want.

See the revert-a-faulty-merge How-To for more details.

After reverting the merge, you are not able to merge changes before the merge into master again. These are serious consequences that one might not expect. The issue will become apparent if you look at the Git graph again.

Git commit graph

The merge commit (G) does not consider any commit before (B) on the feature branch since they have been merge into maser already (C). It doesn’t matter whether the changes were reverted (E) or you undid the changes manually. Furthermore, this issue cannot be solved by choosing a merge strategy (e.g. -X theirs), since all the strategies only consider the changes (D-F) yet merged into the target.

How to solve this?

To solve the issue, I think there are two approaches.

  • Rebase your feature branch from (A) to (F) and merge the rebased branch. The reverted merge shouldn’t interfere since it acts on different commits.

  • Revert the revert.

Which solution you choose depends on the specific problem. I highly recommend reading the article How to revert a faulty merge that’s linked in the documentation.