12

Suppose my Git repository initially has two branches: Foo and Bar.

... ─ Foo

... ─ Bar

I create a third branch, FooBar in which I commit the merge of the two other branches.

... ─ Foo ──┐
            FooBar
... ─ Bar ──┘

FooBar is now one commit ahead of both Foo and Bar. Next, I do some more work, committing a few times on Foo only.

... ── A ───┬── B ── C ── D ── Foo
            FooBar
... ─ Bar ──┘

The question is: since the first parent of branch FooBar is no longer Foo, can I rebase the merge commit in branch FooBar to again have Foo and Bar as its two parents? In other words, can I incorporate the development in Foo into the previously merged FooBar along with the unchanged Bar?

... ── A ── B ── C ── D ── Foo ──┐
                                 FooBar
... ─ Bar ───────────────────────┘
svick
  • 236,525
  • 50
  • 385
  • 514
nccc
  • 1,095
  • 3
  • 11
  • 20
  • See also [using git-replace to change a parent pointer](http://stackoverflow.com/a/3811217/90527), though this has other consequences. – outis Mar 04 '12 at 02:02
  • See also [How do I use git rebase -i after git merge without messing things up?](http://stackoverflow.com/q/4152936/90527) – outis Mar 04 '12 at 11:53

3 Answers3

12

I realize this is quite an old topic but since I found this question while facing a similar situation I could as well tell what I used in the end.

git checkout FooBar
git rebase -p --onto Foo A

I tried to use the names from the original question. Note especially that A means the commit that is marked as A in the ascii art. You would probably use the hash of the commit instead or any other way to point to the commit where branches Foo and FooBar originally separate.

The -p flag is also known as --preserve-merges and it does pretty much what it says.

Note that the branch Bar doesn't play a role here so that line of commits might be a named branch or then not - it makes no difference.

Aki Koskinen
  • 142
  • 1
  • 8
  • 3
    Note that this will not keep any changes made in the merge commit. – Tgr May 28 '14 at 00:34
  • 2
    Thanks, this helped us a lot. It's indeed important to note that 'A' is the last commit of the target branch that was still included in the original merge. – Dibbeke Oct 22 '14 at 06:57
  • I found this answer hoping to avoid having to do the conflict resolution from the original merge commit again. As @Tgr says, it doesn't keep the original merge commit, so conflict resolution must continue as thought you just delete the merge and re-merge. It's not so bad, though, because conflicts can be resolved by checking out from the old merge commit. – lmat - Reinstate Monica Jun 02 '16 at 20:01
  • @LimitedAtonement `git rerere` sounds like it might help, although I never used it myself. – Tgr Jun 02 '16 at 23:00
  • Starting with Git 2.18 (Q2 2018), `git --rebase-merges` (`-r`) will ultimately replace the old `git --preserve-merges` (`-p`). See this answer https://stackoverflow.com/a/50555740/6309 by VonC https://stackoverflow.com/users/6309/vonc. – Eugen Labun Nov 28 '18 at 14:49
1

You couldn't rebase under branch FooBar without changing what defines FooBar. What you could do is merge FooBar and Foo. That would have the same contents that you desire.

outis
  • 75,655
  • 22
  • 151
  • 221
Andy
  • 44,610
  • 13
  • 70
  • 69
  • Yes, but the point is to avoid merge commits. That's why I want to rebase `BranchFooBar`. – nccc Feb 03 '12 at 23:54
  • Perhaps I'm confused with what you want. You say you want to avoid merge commits, but I'm interpreting your question as you wanting the final results to be a merge commit between `branchFoo` and `branchBar`. – Andy Feb 06 '12 at 16:59
  • You're right, this wasn't clear enough. I want to avoid a *2nd* merge commit. `BranchFooBar` will always be a merge commit, but I want it to be the *only* merge commit with `BranchFoo` and `BranchBar` as parents. – nccc Feb 07 '12 at 19:36
  • Checkout out `brachFooBar` and use [git revert](http://linux.die.net/man/1/git-revert) to remove the merge commit, than recreate a new merge commit with the tips of foo and bar. – Andy Feb 08 '12 at 17:06
  • `git revert` will keep the old merge commit, and will thus still result in two merge commits. In comparison with merging FooBar and Foo, `git revert` will also have an additional revert commit, which is a step in the opposite direction. – outis Mar 04 '12 at 01:09
0

You're basically trying to erase all traces of the previously made merge.

If you truly want to throw away all commits that only exists in FooBar (and in the working directory), then proceed as follows: first do git checkout FooBar, then make git forget about it's history and do git reset --hard Foo (if you intent to merge Bar into Foo). Then you recreate the merge with git merge Bar.

Mikko Rantalainen
  • 14,132
  • 10
  • 74
  • 112