2

I'm trying to help a friend of mine learn git and he made a blunder when merging that added to source files some of the git overhead like this

import org.junit.Test;

<<<<<<< HEAD
=======

>>>>>>> 7d9b8743f272a230f4079518be7a70a338ea55d8
import static org.junit.Assert.assertEquals;

Here is the Git tree:

|\ - merge to origin/master
| |
| | - new branch 
|/
| - faulty merge to origin/master that added <<<<< HEAD
|\
| |
| |
| |

I fixed it by doing revert of the merge and then i had to manually add/remove some files from the stage.

Any advise on whats the best way to do fix a faulty merge situation like this?

testing_kate
  • 187
  • 2
  • 13

1 Answers1

3

The sooner you catch and fix a mistake like this, the better. The problem is greatly complicated once the faulty merge has been pushed; and moreso once new work is based on the faulty merge.

In case you realize a merge is bad before pushing it, you can simply

git reset --hard HEAD^

and try the merge again, avoiding repeating whatever mistake was made before (like in this case committing conflict markers).

Once the commit has been shared and more work based on it, you have to make a choice. You can still get the same kind of "clean looking" result, if you can coordinate with everyone who shares the repo. But I'll come back to that (below, under rebase-based approach). The much easier (but not as nice-looking) option is to use new commits (revert-based approach). But there is a trick to making that work.

revert-based approach:

Given what you show you've done in the question, you probably already know that to revert a merge you use the -m option (to select a parent to which the merge should revert). So you'd have said

git revert -m1 id-of-merge-commit

and you'd get a new commit that "undoes" all the changes from the branch. Of course depending on what else has been based on the merge, you may have conflicts to deal with.

More than that, though, once you have a clean revert, git makes a weird assumption: it thinks you'll never want to reintegrate the changes from that branch. (I don't think this was deliberate; it's a consequence of the way merges are calculated. But it is documented behavior in any event.)

There are a couple tricks you could use to fix that. Probably the easiest is to create a new set of commits that make the same set of changes as the original branch. If the merge commit is M, you'd

git checkout M^2
git checkout -b temp-branch
git rebase -f `git merge-base M^ HEAD``

And now temp-branch has a new set of changes just like those in the original branch, in a new set of commits that have no history of being "already merged".

git checkout master
git merge temp-branch
# careful this time
git branch -D temp-branch

rebase-based approach:

There are things you could reasonably dislike about the above, so here's a solution with a "cleaner" end state (but more difficult execution):

First, have everyone push any outstanding work to a single repo (usually the shared origin). Work doesn't have to be fully merged, but everything has to be pushed.

That's because step 2 is for everyone to throw away their local repo. At the end they'll reclone. (You don't strictly have to do it this way, but it is my recommendation as it's simpler than having everyone try to "repair" their local repos).

Then you check out the first parent of the faulty merge and create a new, temporary branch there; and on that branch, you redo the merge without repeating any mistakes that broke the original merge.

Now everything that was based on the faulty merge has to be rebased onto the new, good merge. If there are multiple refs from which the old merge is reachable, this could be easier said than done. Also, if there are new merges from which the old merge is reachable, you may need to use the --preserve-merges option of rebase. BUT, if any of those merges had conflicts, they'll need to be re-resolved; and worse, if any of those merges were "evil" (i.e. were committed with a TREE different from the default merge result, even though a default merge would've resolved cleanly), then the rebase will not produce correct results.

So when all is done, you'll want to do some diffs to validate the result, and assuming you successfully navigate all of this, you can then let everyone re-clone.

You can see why, even though it is ugly, the revert-based approach is often the accepted compromise.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52