Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reverting a broken merge and reapplying selected commits with Git

We recently encountered a problem where a merge somehow led to all the changes leading to one parent being undone in the merge commit, and several commits have been applied after that point. All this has been pushed to our shared origin repository. I want to be able to revert the bad merge and reapply the other change sets. What is the easiest way to do this?

ASCII art example:

    A-B-C-D
   /       \
P-Q         M-X-Y-Z
   \       /
    1-2-3-4

  ---time--->

The commit M has included changes A to D but all changes 1 to 4 were reverted by that commit.

I would like to be able to revert back, eg. to changeset 4, and reapply changes A-D and X to Z, ideally without manually redoing each change.

If this is not possible, I'd like to hear of the best workarounds - eg. maybe branch from 4, merge in D again being careful not to break anything, then manually reapplying X to Z?

(An ideal situation would be to know how to do this using TortoiseGit, although just knowing the command line should allow me to deduce the rest.)

like image 328
Kylotan Avatar asked Feb 04 '12 13:02

Kylotan


1 Answers

You want to wind up with something whose logical effect is:

P-Q-1-2-3-4-A-B-C-D-X-Y-Z

There are a couple of approaches. Let's assume master points to Z right now.


Revert the merge commit (safest but messier)

We need to revert the broken merge commit M first. We'll do that with:

git revert -m 1 M

Next, let's re-apply the negated or potentially negated commits:

git cherry-pick 1
git cherry-pick 2
git cherry-pick 3
git cherry-pick 4
git cherry-pick A
git cherry-pick B
git cherry-pick C
git cherry-pick D

Now we have this:

# M reverts some of A..D and 1..4
# !M undoes the logical effect of the merge

    A-B-C-D
   /       \
P-Q         M-X-Y-Z-!M-1'-2'-3'-4'-A'-B'-C'-D'
   \       /
    1-2-3-4

This is the safest approach because it doesn't touch any previous history, so no one's upstream repos will be affected. But it's also the messiest because it leaves a lot of detritus. Alas, that's the price you pay for a sloppy merge.


Throw away the broken history (most dangerous but cleanest)

This will require getting consensus from everyone that your approach is sound. You will break people's history, and anyone who has the tainted merge commit M will experience issues if you do it this way. But your history will look cleaner.

Because you will break the history of any team members who have the tainted commit until they pull down the remote repository and until they reset their local masters to that point, this is the most dangerous approach.

First, we rewind master back to Q, the last commit without problems.

git checkout master
git reset --hard Q       # rewind `master` branch to `Q`

Next, we need to interactively rebase the broken commit sequence.

git checkout Z         # move to Z
git branch tmp         # make a branch pointing to Z
git rebase -i master   # rework this branch onto master

You'll be presented with a list of commits in your editor that looks like this:

pick aaaaaaa Commit message one
pick bbbbbbb Commit message two
pick ccccccc Commit message three
# ...

One of these will be the broken merge commit. Delete this line; save and close.

Git will now apply the commits in the order you asked for, and you will be on tmp. If everything looks good, then reset master:

git checkout master
git reset --hard tmp

Now force-push to origin:

git push -f origin master

The broken history will be removed and master will point to a new history.


Finally

You should consider asking your team to use something like nvie's git-flow to avoid future messiness and prevent snafus.

Update: It looks like nvie abandoned support for git-flow. However, Peter van der Does forked and is maintaining a version

like image 55
John Feminella Avatar answered Oct 20 '22 15:10

John Feminella