git rebase --onto: Transplant a Range of Commits
git rebase --onto <newbase> <upstream> [<branch>] replays only the commits in upstream..branch onto newbase, letting you relocate or excise a precise range.
The three-argument form of rebase is the one people forget. It separates "what to replay" (upstream..branch) from "where to put it" (newbase), which is how you drop the lower commits of a branch or move a feature onto a different base.
What it does
git rebase --onto A B C takes the commits reachable from C but not from B and replays them on top of A, then moves the branch ref to the new tip. Setting A and B to different commits is what lets you skip the commits between them, effectively removing them from history.
Common usage
# move feature (based on develop) onto main instead
git rebase --onto main develop feature
# drop the bottom 3 commits of the current branch
git rebase --onto HEAD~3 HEAD~1
# replay just one commit onto a release branch
git rebase --onto release-1.0 abc1234~1 abc1234Options
| Argument / flag | What it does |
|---|---|
| <newbase> | Commit the range is replayed on top of |
| <upstream> | Exclusive lower bound: commits after this are replayed |
| <branch> | Branch (or HEAD) whose commits are replayed |
| --continue | Resume after resolving a conflict |
| --abort | Cancel and restore the original branch |
| --committer-date-is-author-date | Preserve author dates as committer dates |
In CI
Rebase rewrites SHAs, so a later push needs --force-with-lease, which CI tokens may be blocked from doing on protected branches. Prefer rebase --onto in throwaway worktrees for building, not for pushing to shared branches. A shallow clone may lack <upstream>; fetch enough depth first.
Common errors in CI
"fatal: invalid upstream 'develop'" means the ref is missing locally (shallow clone or never fetched). "error: could not apply <sha>" is a conflict; resolve and --continue or --abort. "There is no tracking information for the current branch" appears if you omit arguments and rely on upstream that is not set.
Using this in CI
CI checkouts are shallow and detached by default, which changes the answer this command gives you. Commands that read history, branch names, or tags need the checkout configured for it.
- uses: actions/checkout@v4
with:
fetch-depth: 0 # history, tags, and git describe all need this
- run: |
git rev-parse --is-shallow-repository # expect false
git rev-parse --abbrev-ref HEAD # prints HEAD when detached