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