Git Rebase --onto flag

I finally get it

For years when asked about this flag, I would respond that I didn’t really understand it, that it seemed unnecessary. But I finally ran into a situation where I needed its functionality and had to discover how to use it.

There’s a useful section about this flag in the Rebase docs, but I’ll explain in detail here.

rebase --onto is used when you want to specify where the rebase should start, instead of letting Git figure that out for itself.

Terminal window
# traditional rebase syntax:
git rebase <newbase> [end]
# syntax with --onto:
git rebase --onto <newbase> <start> [<end>]

As you can see, the benefit of the flag is that it lets you specify a <start> commit.

  • <newbase>: where the commits get replayed
  • <start>: the commit after which the replay begins (exclusive)
  • <end>: the last commit to replay (inclusive). Omit this and Git uses the branch you’re currently on

Say you’re here

ABN'N1N2T1T2mainnexttopic
  • topic was built on top of next
  • you want to move topic over to main
  • but next was already squash-merged into main

The traditional move, git rebase main topic, would take all commits in both topic and next and replay them on top of main; which could be messy because all the functionality in next is already in main, just squashed down.

In order to move only topic’s commits, use --onto

Terminal window
# git rebase --onto <newbase> <start> [<end>]
git rebase --onto main next topic

Which reads as: “take the commits after next, up to and including topic, and replay them onto main.”

git rebase --onto main next topic
topic was built on top of next, but you want its commits on main — without dragging all of next along.

Notice that next didn’t move an at all. The <start> argument is only used to decide which commits get copied so the rebase doesn’t touch it.

Because --onto gives you control exactly where the replay starts, you can also use it to surgically remove commits from the middle of a branch:

Terminal window
git rebase --onto B D

There’s no <end> argument here, so it defaults to the current branch. This says “replay everything after D onto B” — which just so happens to leave C and D behind.

git rebase --onto B D
We want to delete C and D but preserve E

Just like any other time you rewrite git history, be sure to run the standard post-rebase checks: look at the graph to confirm the new shape is what you intended, then git diff the old tip against the new one to make sure nothing came along that you didn’t invite.