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?
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...
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...
I can see three options: reverting the merge, rebasing, or filtering.
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 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.
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.
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.
revert
documentation
skip_commit
in the Git filter-branch
documentation
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With