DEV Community

Gabriel Guzman
Gabriel Guzman

Posted on

Git Revert Merge git revert, merge commits, confusion

I ran into a gap in my understanding of git and merge commits yesterday. Most of the time, the projects I contribute to work with a "mergeless" commit history, so we usually rebase+squash our changes into a single commit and apply those to master leaving us with a commit history that looks like:

1---2---3---4---5 master
Enter fullscreen mode Exit fullscreen mode

Generally, if I need to revert commit 3, all I need to do is git revert 3 and that commit gets reverted and committed as commit 6:

1---2---3---4---5---6 master
Enter fullscreen mode Exit fullscreen mode

Yesterday, I needed to revert a change in a project that works with multiple remote branches that are merged to master via a merge commit:

1---2---3---4---5a master
     \a---b---c/ feature
Enter fullscreen mode Exit fullscreen mode

So, I tried git revert 5a and got a message I'd never seen before:

error: commit 5a is a merge but no -m option was given.

WAT?!? I pulled up the git revert docs and it turns out that since a merge commit has two parents (4 and c, I think -- nope, 2 and c) there is no way for git to know which one should be considered "mainline." That's my paraphrase of the docs, here's the actual relevant section:

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.

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.

Ok, so I have to tell git which parent is the mainline, but how do I figure that out? If I look at the merge commit (git show 5a) there's a line that says: parents 2, c. In my simple example, I think the mainline is 2, so I think I want to do git revert -m 1 (since 2 is the first of the parents listed) since commit 2 is on master, which I think is equivalent to mainline, but I'm not really sure.

I believe that this will then revert all the differences between master and feature branch at c, but I find this fairly confusing.

Also, what is that bit about "you will never want the tree changes brought in by the merge"? Does that mean these changes that I'm reverting will never be applied if I try to re-merge them later from the same branch? So, if I add a new commit to my feature branch, and then try to merge that branch back into master, will I only get the new changes and not the changes I reverted? If so, how do I get the changes I reverted back?

I haven't had a chance to sort all this out fully or test my assumptions as after about an hour of futzing around with this I figured it would be easier to just find the problem and fix it instead of trying to revert the change that caused it.

This definitely makes me feel like a "mergeless" git history is easier to work with since it's simpler and each commit is a discreet unit which can be easily backed out, but I know some people prefer the merge commit workflow so I'll have to spend some more time playing with that to figure it out.

Top comments (7)

Collapse
 
4nduril profile image
Tobias Barth • Edited

There is a nice explanation here: git-scm.com/book/en/v2/Git-Tools-A... Look for the "Undoing merges" section further down.

If you revert a merge commit and later on you want to "re-merge" that same branch, you should revert the earlier revert commit (the commit that reverted the first merge) which effectively restores that merge. And if you have new commits in the feature branch since you merged for the first time you'll have to git merge <feature> again.

So:
You create a merge: git merge <feature>
You revert that (creating a new commit, say, <revmerge>): git revert -m 1 <mergecommit>

Later, you revert the revert: git revert <revmerge>
And merge in any changes to after the initial merge: git merge <feature>

Collapse
 
gabeguz profile image
Gabriel Guzman

Ah, this is the piece of documentation I was missing on Thursday, thanks for that!

Collapse
 
sanedragon profile image
Dag Arneson

This is esoteric but easy to resolve: start your new branch where you fix the merge commit with a revert of the revert of the merge commit.

Collapse
 
gabeguz profile image
Gabriel Guzman

Thanks for the tip Dag!

Collapse
 
jaysonesmith profile image
Jayson Smith

Git can be so confusing sometimes. :(

Collapse
 
pstorch profile image
Peter Storch

I would move the pointer for master back to commit a:
git branch -f master 4

Collapse
 
gabeguz profile image
Gabriel Guzman

I'm not sure I follow exactly, but I'll play with this and see how it goes. Thanks!