Rebasing onto a new base with --onto Jump to heading

Plain git rebase main moves your branch so that it starts from the tip of main. It decides which of your commits to move by finding where your branch and main diverged. That works until the branch’s history contains commits that should not move with it: the commits of a parent branch that was squash-merged, a few experimental commits at the start, or a base that was the wrong branch entirely. git rebase --onto separates the two decisions β€” which commits to move, and where to put them β€” and handles all of these cases precisely. This page explains its three arguments and the situations where it is the right tool, within interactive rebase workflows.

When to use this approach Jump to heading

  • Your branch was built on top of another feature branch that has since been squash-merged into main.
  • You branched from the wrong base β€” main instead of a release branch, or the reverse.
  • You want to drop the first few commits of a branch while keeping the rest.
  • You manage dependent branches, a pattern described in stacked pull requests without a dedicated tool.

Step 1 β€” Learn the three arguments Jump to heading

The full form is git rebase --onto <newbase> <upstream> <branch>. It takes the commits in upstream..branch β€” reachable from the branch but not from upstream β€” and replays them on top of newbase.

The three arguments of rebase --ontoThe new base is where the moved commits will land. The upstream marks where the moved range starts, exclusively: commits it can reach are left behind. The branch is the tip of the range being moved, and it is the ref that gets updated.<newbase>where commits land<upstream>exclusive startof the range<branch>tip of the rangeref that movesmoved commits = upstream..branch, replayed onto newbase The three arguments of rebase --ontoThe new base is where the moved commits will land. The upstream marks where the moved range starts, exclusively: commits it can reach are left behind. The branch is the tip of the range being moved, and it is the ref that gets updated.<newbase>where commits land<upstream>exclusive startof the range<branch>tip of the rangeref that movesmoved commits = upstream..branch, replayed onto newbase
# Preview exactly which commits will move before running it
git log --oneline <upstream>..<branch>

That preview is the single most useful habit with --onto. If the list is wrong, the arguments are wrong.

Step 2 β€” Detach a branch from a squash-merged parent Jump to heading

You built feature/b on top of feature/a. feature/a was squash-merged into main, so its commits exist on main only as a single new squash commit. A plain git rebase main on feature/b tries to replay feature/a’s original commits too, producing conflicts with the squash.

# Move only feature/b's own commits: those after the old tip of feature/a
git log --oneline feature/a..feature/b          # preview: just b's commits
git rebase --onto main feature/a feature/b
Moving a branch off a squash-merged parentFeature B was built on A1 and A2 from feature A. Main received A as a single squash commit S. Rebasing B with --onto main feature/a moves only B1 and B2 onto main after S, leaving A1 and A2 behind.rebase --onto main feature/a feature/bmainMSfeature/aMA1A2feature/b beforeA1A2B1 B2feature/b afterSB1'B2'without --onto, A1 and A2 would be replayed and conflict with S Moving a branch off a squash-merged parentFeature B was built on A1 and A2 from feature A. Main received A as a single squash commit S. Rebasing B with --onto main feature/a moves only B1 and B2 onto main after S, leaving A1 and A2 behind.rebase --onto main feature/a feature/bmainMSfeature/aMA1A2feature/b beforeA1A2B1 B2feature/b afterSB1'B2'without --onto, A1 and A2 would be replayed and conflict with S

If feature/a has already been deleted, use the commit it pointed to β€” find it with git merge-base feature/b <old-a-sha> or from the pull request’s head commit.

# Verification: feature/b now contains main plus only its own commits
git log --oneline main..feature/b

Step 3 β€” Retarget a branch to a different base Jump to heading

You started a fix from main, but it needs to go to release/2.4. Move the fix’s commits from main to the release branch.

git log --oneline main..fix/timezone             # the fix's own commits
git rebase --onto release/2.4 main fix/timezone

Expect conflicts if main and the release branch differ in the area the fix touches. This is the same situation as a backport, and the analysis in resolving cherry-pick conflicts on older branches applies.

Step 4 β€” Drop the first commits of a branch Jump to heading

To discard the first N commits of a branch and keep the rest, use the Nth commit as the upstream and the original base as the new base.

git log --oneline --reverse main..feature/x      # 1 spike, 2 spike, 3 real, 4 real
git rebase --onto main feature/x~2 feature/x     # move only the last two commits
Plain rebase against rebase --ontoA plain rebase moves everything after the divergence point onto the new base, which is right when the branch's history is exactly its own work. --onto moves an explicit range, which is right whenever some commits below the tip should stay behind.git rebase maingit rebase --onto X U Bcommits movedmerge-base..branchU..B exactlydestinationmainX (any commit)parent was squashedreplays parent commitsskips themdrop first commitsneeds -ichoose U--onto is the general form; plain rebase is the special case where X and U are the same Plain rebase against rebase --ontoA plain rebase moves everything after the divergence point onto the new base, which is right when the branch's history is exactly its own work. --onto moves an explicit range, which is right whenever some commits below the tip should stay behind.git rebase maingit rebase --onto X U Bcommits movedmerge-base..branchU..B exactlydestinationmainX (any commit)parent was squashedreplays parent commitsskips themdrop first commitsneeds -ichoose U--onto is the general form; plain rebase is the special case where X and U are the same

⚠️ SAFETY WARNING: Every form of rebase rewrites the moved commits. If the branch was already pushed and others use it, they must reset to the new version, and you must push with git push --force-with-lease. If the result is wrong, git reset --hard ORIG_HEAD immediately after the rebase restores the branch; later, use git reflog show feature/x to find its previous tip.

Step 5 β€” Rebase several dependent branches at once Jump to heading

When feature/c depends on feature/b, which depends on feature/a, moving one means moving all. Since Git 2.38, --update-refs updates every branch pointing into the moved range, so a single rebase of the top branch moves the whole stack.

git rebase --onto main feature/a feature/c --update-refs
git log --oneline --decorate main..feature/c

The workflow is covered in detail in updating stacked branches with --update-refs.

Step 6 β€” Find the right upstream when the old parent is gone Jump to heading

The hardest part of --onto in practice is the middle argument when the old parent branch has been deleted after merging. You need the commit where your own work begins. Three sources usually have it.

# 1. The pull request of the parent branch records its final head commit
gh pr view 801 --json headRefOid --jq .headRefOid

# 2. Your branch's reflog records where it was created or last rebased
git reflog show feature/b | tail -3

# 3. Failing those, the first commit you authored on the branch
git log --reverse --format='%h %an %s' main..feature/b | awk -v me="$(git config user.name)" '$2==me {print $1; exit}'

With the first of your own commits identified as F, the upstream is its parent, so the command becomes git rebase --onto main F^ feature/b. Preview with git log --oneline F^..feature/b first: the list should start with F and contain nothing from the parent branch.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

What if I get the upstream argument wrong? Jump to heading

Too old an upstream moves extra commits; too new a one leaves some of your commits behind. The preview command catches both. If you already ran it, git reset --hard ORIG_HEAD undoes the rebase.

Can I combine --onto with -i? Jump to heading

Yes. git rebase -i --onto main feature/a feature/b opens the todo list for exactly the commits being moved, so you can also reorder, squash or drop them in the same operation.

Does --onto work with merges in the moved range? Jump to heading

By default, rebase flattens merges. Add --rebase-merges to keep them, as described in keeping merge commits with --rebase-merges.