257

Take the following case:

I have some work in a topic branch and now I'm ready to merge back to master:

* eb3b733 3     [master] [origin/master]
| * b62cae6 2   [topic]
|/  
* 38abeae 1

I perform the merge from master, resolve the conflicts and now I have:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | eb3b733 3                     [origin/master]
|/  
* 38abeae 1

Now, the merge took me some time, so I do another fetch and notice that the remote master branch has new changes:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
| | * e7affba 4                   [origin/master]
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

If I try git rebase origin/master from master, I'm forced to resolve all conflicts again, and I also lose the merge commit:

* d4de423 2       [master]
* e7affba 4       [origin/master]
* eb3b733 3
| * b62cae6 2     [topic]
|/  
* 38abeae 1

Is there a clean way to rebase the merge commit so I end up with a history like the one I show below?

*   51984c7 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | e7affba 4                     [origin/master]
* | eb3b733 3
|/  
* 38abeae 1
Chirlo
  • 5,989
  • 1
  • 29
  • 45
jipumarino
  • 2,573
  • 2
  • 15
  • 5
  • 92
    TL;DR: `git rebase --preserve-merges origin/master` – Ilia K. Mar 01 '12 at 07:38
  • 6
    With respect to having to re-resolve conflicts, you might want to take a look at [git rerere](https://git-scm.com/docs/git-rerere). – Parker Coates Feb 19 '16 at 07:37
  • 1
    `git config --global pull.rebase preserve` to always preserve the merge commits during a rebase – galath Jan 19 '18 at 11:15
  • 13
    Warning: starting with Git 2.18 (Q2 2018, 5 years later), `git --rebase-merges` will ultimately replace the old `git --preserve-merges`. See [What exactly does Git's “`rebase --preserve-merges`” do (and why?)](https://stackoverflow.com/a/50555740/6309) – VonC May 27 '18 at 19:30
  • shorter still `git rebase -p origin/master` – Felipe Alvarez Aug 18 '18 at 11:16
  • 14
    `--preserve-merges` is deprecated. Use `git rebase --rebase-merges origin/master` – Arjun Sreedharan Oct 11 '19 at 11:38

5 Answers5

196

There are two options here.

One is to do an interactive rebase and edit the merge commit, redo the merge manually and continue the rebase.

Another is to use the --rebase-merges option on git rebase, which is described as follows from the manual:

By default, a rebase will simply drop merge commits from the todo list, and put the rebased commits into a single, linear branch. With --rebase-merges, the rebase will instead try to preserve the branching structure within the commits that are to be rebased, by recreating the merge commits. Any resolved merge conflicts or manual amendments in these merge commits will have to be resolved/re-applied manually."

Joshua Goldberg
  • 5,059
  • 2
  • 34
  • 39
siride
  • 200,666
  • 4
  • 41
  • 62
  • 18
    I tried the -p option, and it does indeed leave the commit history as I wanted, but it forces me to resolve the conflicts again, even in files which were not edited in origin/master. As of your first suggestion, which would be the precise sequence of commands? – jipumarino Jan 24 '11 at 16:49
  • 3
    @jipumarino: git rebase -i (tell it to edit the merge commit), when it gets to the merge commit, git reset --hard HEAD^, git merge, fix conflicts, git commit, git rebase --continue. You might also want to look at git rerere which is supposed to help with this kind of thing (but I've never used, so I can't offer any advice or help). – siride Jan 24 '11 at 17:10
  • 3
    Thanks. I enabled rerere and tried with rebase -p and it's working as it should. – jipumarino Jan 24 '11 at 18:51
  • 3
    Here's an excellent blog post describing this exact situation: [Rebasing Merge Commits in Git](http://web.archive.org/web/20150527173852/http://marketblog.envato.com/general/rebasing-merge-commits-in-git/) – kynan Apr 23 '12 at 11:30
  • 1
    rere is not the solution, as you still have to resolve merges manually the first time across. – Flimm Jan 07 '14 at 11:16
  • @Flimm: it's part of a long-term solution if you are doing frequent rebasing, though. Note that I said I'd never used it (which was true at the time), so forgive my optimistic outlook. – siride Jan 07 '14 at 13:47
  • 1
    When I do `git rebase -i` (interactive), it filters out all the merge commits. Does git rebase actually allow you to edit or squash merge commits? The solution that worked for me instead is this one: http://stackoverflow.com/a/4138485/1172352 – peterflynn Apr 01 '16 at 20:15
  • @peterflynn: did you include the `-p` option? – siride Apr 01 '16 at 23:35
  • The help for `git` (version 2.19.2) states that `--preserve-merges` and `--interactive` are incompatible options. – Jeff Evans Jan 28 '19 at 16:30
  • @JeffEvans It looks like `-p` went from being buggy, to being disallowed with `-i`, to now being deprecated in favor of `--rebase-merges`. I'll update my answer accordingly. – siride Jan 11 '20 at 17:34
  • `git rebase -i --rebase-merges` did exactly what I needed it to do. Could reorder my own commits while preserving the existing merge commits` – GameSalutes Dec 10 '20 at 21:38
57

Ok, it's an old question and it already has an accepted answer by @siride, but that answer wasn't enough in my case, as --preserve-merges forces you to resolve all conflicts a second time. My solution is based on the idea by @Tobi B but with exact step-by-step commands

We'll start in the same state found in the original question:

*   8101fe3 Merge branch 'topic'  [HEAD -> master]
|\  
| * b62cae6 2                     [topic]
| |
| | * f5a7ca8 5                   [origin/master]
| | * e7affba 4
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

Note that we have 2 commits ahead of master, so cherry-picking won't work.

  1. First of all, let's create the correct history:

     git checkout -b correct-history # create new branch to save master for future
     git rebase --strategy=ours --preserve-merges origin/master
    

    We use --preserve-merges to save our merge commit in history. We use --strategy=ours to ignore all merge conflicts as we don't care about what contents will be in that merged commit, we only need a nice history.

    The history will look like this (ignoring master):

     *   51984c7 Merge branch 'topic'  [HEAD -> correct-history]
     |\  
     | * b62cae6 2                     [topic]
     * | f5a7ca8 5                     [origin/master]
     * | e7affba 4
     * | eb3b733 3
     |/  
     * 38abeae 1
    
  2. Let's get the correct index now.

     git checkout master # return to our master branch
     git merge origin/master # merge origin/master on top of our master
    

    We may get some additional merge conflicts here, but that would only be conflicts from files changed between 8101fe3 and f5a7ca8, it doesn't include already resolved conflicts from topic

    History will looks like this (ignoring correct-history):

     *   94f1484 Merge branch 'origin/master'  [HEAD -> master]
     |\  
     * | f5a7ca8 5                   [origin/master]
     * | e7affba 4
     | *   8101fe3 Merge branch 'topic'
     | |\  
     | | * b62cae6 2                     [topic]
     |/ /
     * / eb3b733 3
     |/  
     * 38abeae 1
    
  3. The last stage is to combine our branch with correct history and branch with correct index

     git reset --soft correct-history
     git commit --amend
    

    We use reset --soft to reset our branch (and history) to correct-history, but leave index and working tree as is. Then we use commit --amend to rewrite our merge commit, that used to have the incorrect index, with our good index from master.

    In the end we will have this state (note another id of top commit):

     *   13e6d03 Merge branch 'topic'  [HEAD -> master]
     |\  
     | * b62cae6 2                     [topic]
     * | f5a7ca8 5                     [origin/master]
     * | e7affba 4
     * | eb3b733 3
     |/  
     * 38abeae 1
    
Dave Mackey
  • 4,306
  • 21
  • 78
  • 136
Ivan Naydonov
  • 671
  • 5
  • 4
  • This is awesome and helped a lot! But I did not get the trick with commit --amend: could you add more info on that? What exactly happens after you run it -- I noticed SHA of commit got changed - but why? Or what happens if you don't run this? – ZenJ Jul 27 '18 at 21:45
  • 1
    @ZenJ `git commit --amend` adds the changes to the last commit (HEAD, in this case the merge commit). Because the commit contents change, the hash is updated. – Dries Staelens Aug 10 '18 at 09:56
  • 1
    For people that don't have 'rerere' enabled prior to fixing conflicts, this solution is great because it saves you from having to fix conflicts again. Thanks! – Shackleford May 08 '19 at 14:34
  • So, to summarize: ```#!/bin/bash onto=$1; branchname=$(git rev-parse --abbrev-ref HEAD); git branch -D tmp-correct-history; git checkout -b tmp-correct-history; git rebase --strategy=ours --preserve-merges $onto; git checkout $branchname; git merge $onto; git reset --soft tmp-correct-history; git commit --amend ``` – qbolec Jun 02 '23 at 18:59
8

Given that I just lost a day trying to figure this out and actually found a solution with the help of a coworker, I thought I should chime in.

We have a large code base and we have to deal with 2 branch heavily being modified at the same time. There is a main branch and a secondary branch if you which.

While I merge the secondary branch into the main branch, work continues in the main branch and by the time i'm done, I can't push my changes because they are incompatible.

I therefore need to "rebase" my "merge".

This is how we finally did it :

1) make note of the SHA. ex.: c4a924d458ea0629c0d694f1b9e9576a3ecf506b

git log -1

2) Create the proper history but this will break the merge.

git rebase -s ours --preserve-merges origin/master

3) make note of the SHA. ex.: 29dd8101d78

git log -1

4) Now reset to where you were before

git reset c4a924d458ea0629c0d694f1b9e9576a3ecf506b --hard

5) Now merge the current master into your working branch

git merge origin/master
git mergetool
git commit -m"correct files

6) Now that you have the right files, but the wrong history, get the right history on top of your change with :

git reset 29dd8101d78 --soft

7) And then --amend the results in your original merge commit

git commit --amend

Voila!

Claude Peloquin
  • 101
  • 1
  • 2
1

It looks like what you want to do is remove your first merge. You could follow the following procedure:

git checkout master      # Let's make sure we are on master branch
git reset --hard master~ # Let's get back to master before the merge
git pull                 # or git merge remote/master
git merge topic

That would give you what you want.

Antoine Pelisse
  • 12,871
  • 4
  • 34
  • 34
  • 4
    With rerere enabled, this seems to gives the same result as the rebase -p solution given above by siride. – jipumarino Jan 24 '11 at 18:52
-2
  • From your merge commit
  • Cherry-pick the new change which should be easy
  • copy your stuff
  • redo the merge and resolve the conflicts by just copying the files from your local copy ;)
Tobi B
  • 5
  • 1