Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to undo a git merge, but keep history?

Tags:

git

Our repository has a master branch, but it seems like one of our colleagues had a master branch with a different history. Yesterday he merged his master into the main master and pushed. So today some of us already pulled and started working. When we realized the issue a couple of hours of work had already gone by...

So my question is how can I undo his merge but still keep the code changes done today? The left side is our current state, the right side is what I am shooting for. Should I rebase? or there is a way to undo a merge? what's the best approach here?

enter image description here

Edit 1: I don't think this is a duplicate of Undo a Git merge that hasn't been pushed yet because the commit was pushed to the origin as well as I have other commits AFTER the mistake was made and I want to preserve history.

Edit 2: I tried to rebase, problem is that the rookie was merging into his master branch for the last 2 weeks... So even if I revert I don't get rid of the new "timeline" he created... I have no problems re-writing history as long as I get rid of his timeline...

enter image description here

Edit 3:

In the end, I ended up finding a version of the repository which was untouched and replying the modifications done after the incident...

like image 847
Mac Avatar asked Oct 27 '15 17:10

Mac


1 Answers

I can see three options: reverting the merge, rebasing, or filtering.

Reverting

The "correct" solution, as others have advocated, is to revert the merge -- this means you're not "rewriting history", which is generally considered a Good Thing, not least because it means everyone else working on this code won't need to deal with the history changing.

You can't just use git revert as-is, however, because it doesn't know which branch of the history to keep. The solution is simply to give Git that extra bit of information:

git revert -m 2 <sha-of-B>

That -m 2 specifies you want to keep the second parent, i.e. the one that contains commit C; switch to -m 1 for the alternative.

One caution: if you ever want to merge that other branch into your main branch, you'll hit trouble due to Git thinking that branch is already in the master branch. There are several solutions to this, but the easiest (IMO) is to put the revert on a branch off master and, as well as merging it into master, merge it into that other branch. That branch will probably look like whatever version of master it was forked off, at which point you can revert the revert, and everything will be fine when you merge in future.

Rebasing

Rebasing is probably the simplest option: git rebase <sha-of-B> master --onto <sha-of-C>.

This will move all the commits from B until master onto C. The catch is that it will "linearise" the merge history. The merge history in the image is very simple, but that may be a problem if you want to preserve the history or if you're doing this on a more complicated repository.

This also has the catch that it's "rewriting history", and everyone else who's using this repository will find they're working on a completely different code branch to the one that's been reverted, since every change from C onwards will have a different sha1 hash.

Filtering

Using git filter-branch will allow you to get exactly what you're looking for in your image, i.e. preserving the merge history, but it's the most complicated option, and still involves rewriting history.

Effectively, you want to use git filter-branch to filter your commits to keep everything except that one duff merge. Doing this is sufficiently complex that I'm not going to try to write instructions; you'd need to combine --commit-filter to remove the offending merge and --tree-filter to reverse the changes of the merge.

Filtering redux

Okay, here's a fourth method that I'm including for completeness's sake. You could just checkout C, then manually cherry-pick and merge each of the changes you want to have in your master branch one-by-one.

Further reading

  • "Undoing Merges" at GitHub's Git blog.
  • "How to revert a faulty merge" by Linus Torvalds, as documented in the Git codebase itself (I didn't look at this, but it may be interesting)
  • The Git revert documentation
  • The description of skip_commit in the Git filter-branch documentation
like image 135
me_and Avatar answered Oct 07 '22 17:10

me_and