8

After finishing working on a topic branch, I merged the topic branch into master like following:

          o---*---o---o topic
         /             \
o---o---o---*---o---o---+ master

The commits marked with '*' modified the same code, hence merging lead to merge conflicts that were resolved in the merge commit (marked with '+').

While I merged and resolved the conflicts, my colleague pushed new commits to master (marked as 'n'), resulting in following history:

          o---*---o---o topic
         /             \
o---o---o---*---o---o---+ master
                    \
                     n---n origin/master

Now, pushing my local master branch of course leads to an error, as it's no fast-forward-push, so I have two choices:

  1. Throw away my work, reset master to origin/master and redo the merge. I'd like to avoid that as I'd have to resolve the conflicts all over again.

  2. Rebase master onto origin/master and push. Here comes my problem: doing this rebase (even when using the -p switch) does not work smoothly, the merge commit shows the same conflicts again, even though the new commits ('n') didn't change anything touched by the topic branch. So I'd have to resolve the conflicts again during the rebase and would have the same result as with option 1.

What I'd like to achieve is rebasing the merge commit '+' without having to resolve the conflicts again:

          o---*---o---o-------- topic
         /                     \
o---o---o---*---o---o---n---n---+ master & origin/master

Edit:

The rerere switch is enabled but didn't seem to help in any way; do I have to do anything more than setting config.rerere to true to fix my problem?

Edit 2:

Merging the merge-commit ('+') to origin/master would also work (as proposed in the comments and an answer) but would lead to a kind of ugly history which I'd like to avoid by having one merge commit only.

  • You also can just backmerge origin/master before mergin you new master to it. – ScayTrase Feb 20 '15 at 11:16
  • @ScayTrase That would be possible, but would lead to a kind of ugly history; I'd prefer the clean solution with one merge commit only – rhabarbersaft Feb 20 '15 at 12:14
  • Unfortunately, there is no real way out of this: no matter which of your two options you end up choosing, you will most likely have to resolve the conflicts all over again. If you anticipate that you'll find yourself in the same situation in the future, you should perhaps consider activating [`rerere`](http://stackoverflow.com/questions/25670519/git-rebase-preserve-merges-fails/25671230#25671230). – jub0bs Feb 20 '15 at 12:30
  • @Jubobs Forgot to mention that config.rerere is enabled, didn't help either. Will add this to the question. – rhabarbersaft Feb 20 '15 at 12:34
  • @rhabarbersaft Can you specify why `rerere` didn't work? Did you try to rebase (option 2) and hit the same merge conflicts, despite `rerere` being activated? – jub0bs Feb 20 '15 at 12:42
  • @Jubobs When merging (and resolving the conflicts), I see the 'Recorded resolution for...' message of `rerere`, so that seems to work. But when rebasing, the same conflicts appear and `rerere` does not do anything (showing all conflicted files when calling `git rerere remaining`) – rhabarbersaft Feb 20 '15 at 13:29

4 Answers4

1

The first way would be the best. Undo your merge and redo it to get your colleague's changes into the merge commit. But you don't need to throw the changes away.

Since you know that your colleague's changes didn't affect the files that you resolved the conflicts in. You could create a new branch (we'll call it conflict-fix) from your current state of master. Reset your branch and redo the merge.

Rather than using git mergetool or whatever editor that you use. You can bring the file into master from your other branch using git checkout conflict-fix -- <file names>. git add the files and commit to complete the merge. Then you can delete the conflict-fix branch.

This is fairly easy to do and will result in the single merge commit that you are looking for plus allowing you to push your changes. If the new commits affected the files that you resolved the conflicts in you would have to redo them anyways.

EDIT

I am not completely familiar with git rerere but that should have worked. However based on your comment, you should not need to rebase. You would still have undone the merge commit, git fetch the updates and re-performed the merge. You would have to just call the git rerere command and it would have resolved the conflicts in the files for you. With your tree looking like this:

          o---*---o---A topic
         /             \
o---o---o---*---o---o---+ master
                 \
                  n---n origin/master

You would do the following:

git reset --hard A
git checkout master
git pull
git merge topic
git rerere
//Fix other conflicts
git push

And you should end up with:

          o---*---o---o-------- topic
         /                     \
o---o---o---*---o---o---n---n---+ master & origin/master

There should be no need to rebase anything.

http://git-scm.com/docs/git-rerere

Schleis
  • 41,516
  • 7
  • 68
  • 87
  • Just tried it, works fine. But: if origin/master has changes in the same file that lead to conflicts (modified in '*') but on other lines (not leading to additional merge conflicts), those changes would be reverted if I just checkout the `conflict-fix`-versions of the conflicting files. Hence I'm still searching for a solution with Git automatically re-applying the conflict solution I made in the first merge (and I thought `rerere` would do that) – rhabarbersaft Feb 20 '15 at 14:42
  • Unfortunately `rerere` doesn't work in this case, just tried it again to be sure. It even says 'Recorded resolution for...' when merging for the first time but doesn't do anything at all when merging again as proposed. – rhabarbersaft Feb 20 '15 at 15:29
0

What if you rebase topic on top of master, then reset master to the previous commit and pull from the remote, so that you'd have this:

          o---*---o---o 
         /             \
o---o---o---*---o---o---+ topic
                    \
                     n---n master & origin/master

And then you merge topic into master, resulting in this (new merge commit marked with '#'):

          o---*---o----o 
         /              \
o---o---o---*---o---o----+ topic
                    \     \
                     n--n--# master

Does that cause conflicts? Would it work for you?

sammalfix
  • 310
  • 4
  • 9
0

rerere is the way to avoid this being such a hassle, but if you didn't have it enabled before you did the first merge it doesn't help you. You can know if it is enabled because it will give messages about 'recording preimage'

I ran into this myself recently because I had a new development machine and forgot to enable rerere before an ugly merge. There isn't a great git only solution to this scenario, but the following is the easiest recovery of this situation that I could come up with.

  1. Make sure your working directory is exactly the merge result (git reset HEAD --hard) and copy the source tree from your merge commit somewhere safe
  2. reset your local branch back before any merged commits (git reset --hard master~ or git reset HEAD~n where n is how far back you need to go)
  3. git pull
  4. git merge topic
  5. copy source files back into your working tree overwriting.
  6. git mergetool (to handle any deleted vs modified conflicts)
  7. commit and push

Since we were working with gerrit I also made sure that the Change-Id was the same as my previous merge commit so I could verify that nothing had gone wrong.

Naylor
  • 752
  • 7
  • 20
  • rerere helps but unfortunately doesn't completely solve the problem, as it only records resolutions for conflicts, but there may have been additional changes required to get a successful build which were not originally conflicts (eg. some purely-added code might pass the wrong parameters to a method which was changed "outside"). – Miral Jan 25 '19 at 03:42
0

As far as I've read/understood about rerere it should be able to help with this exact situation if you use the rerere-train.sh script to record your already committed resolutions.

So the following should do the trick:

  1. Run the script, using args <merge commit>^..<current branch> → now rerere knows about your conflict resolutions of exactly/only this merge
  2. Make sure you've got rerere.enabled=true in your config
  3. Do option 1 (redo the merge with updated master, see question) → now rerere should be able to reuse the previously recorded resolutions
  4. Optionally unset rerere.enabled again (there might be reasons to not leave it enabled, see Are there any downsides to enabling git rerere?)

If you don't want to use the script, you could also do the training manually, see https://stackoverflow.com/a/4155237/6309


Update:

Keep in mind, that rerere only records conflicts/resolution for conflict hunks (with the <<<< and >>>> in it). It will not record resolutions for conflicts that occur because of deletion on one branch and modification on the other branch for the same file. Also it does not record any additional changes you made outside the conflict hunks.

You have another option though, although it is not very popular, especially if origin/master has been published to an unknown set of people: Rebase origin/master on your local master and do a force push.

I chose this option multiple times in the past, not necessarily coming from the same situation but with other cases where history rewriting of origin/master was desirable. If you have a small team this will just be matter of communication/coordination.

msa
  • 702
  • 7
  • 14