62

I have

A--B--C master
       \
        D branch0

Then I squash B and C into B'. How do I rebase branch0 such that it looks like this:

A--B' master
    \
     D branch0
erensezener
  • 847
  • 1
  • 7
  • 10

3 Answers3

82

Use the --onto argument to git rebase, which changes the baseline that git replays work on.

git checkout branch0

At this state you should still see C in your git history.

git rebase --onto B' C

This translates to: Take all commits in my current branch since commit C (In your case that's just D) and play back on top of B'

You can also use the optional <branch> argument to do both the checkout and rebase at once:

git rebase --onto B' C branch0
Rick Moritz
  • 1,449
  • 12
  • 25
pcrost
  • 836
  • 6
  • 3
  • 2
    Without checkout (shorter): `git rebase --onto master C branch0` – jbaptperez Mar 10 '16 at 09:14
  • Thanks. I have added that suggestion to the answer. I have also reordered the onto and upstream arguments to rebase to match yours (even though reversed works doing a brief test) which matches the git man page. – pcrost Mar 10 '16 at 21:13
  • You can skip merge conflicts by telling git to use whatever the `C` branch has to offer with `strategy-option`: `git rebase --strategy-option ours --onto B' C` – Philippe Creux Jan 22 '19 at 18:05
  • 2
    It's worth clarifying that "since commit C" is exclusive, i.e., this should be one commit before the first commit that you wish to be replayed. – tobias.mcnulty Jul 06 '22 at 18:38
34

I've run into a similar problem when using squash-merge on PRs when you have branches off of branches.

My common example is if I have one feature branch (feature-a). I push some commits to that branch. Then I create another feature branch off of that (feaure-b-off-a). Then I squash-merge feature-a into main, I'll see duplicate commits in the PR of feature-b-off-a into main.

To fix this, we can use a rebase --onto like the accepted answer, here. But the manual work of that is finding C (I refer to common-ancestor below). Rewritten another way:

git rebase --onto main common-ancestor feature-b-off-a

  • Take all the commits between common-ancestor and feature-b-off-a and rebase them onto main

Fortunately, git has a way of finding the common ancestor between two branches:

git merge-base feature-b-off-a feature-a


TL;DR:

# This will print out the 'common-ancestor' commit between the commits/branches causing the problem
git merge-base feature-b-off-a feature-a
# Take all the commits between common-ancestor and feature-b-off-a and rebase them onto main
git rebase --onto main <common-ancestor> feature-b-off-a
# Then you'll need to force push since it's a rebase
git push -f origin feature-b-off-a

Your PR into main should now only show the new commits.


Bonus git-alias

Note: This assumes your flow is squash-merging both into main

git config --global alias.rebase-onto '!f() { git rebase --onto main $(git merge-base "$1" "$2") "$1"; }; f'

Which you'd call with:

git rebase-onto feature-b-off-a feature-a
# Then you'll need to force push since it's a rebase
git push -f origin feature-b-off-a
Kyle Venn
  • 4,597
  • 27
  • 41
0

One quick way I can think of is,

git checkout branch0

Note down the sha of the D commit by git log

git checkout master

Now rename the branch0 to branch1

git branch -m branch0 branch1

A--B' master \ D branch1

Now delete branch1

git branch -D branch1

Create a branch0 again as follows. git checkout -b branch0

A--B' - master&branch0 After the above command master and branch0 are same. Only change we need to make in branch0 is get the commit D.

git cherry-pick D

Now branch0 looks as follows

A--B' master \ D branch0

Drad
  • 96
  • 1
  • 3