Rebasing a feature branch before merge Jump to heading

A feature branch created two weeks ago sits on a main that has since moved on. Before it merges, it needs to be brought up to date โ€” to resolve conflicts, to run CI against current code, and in many teams to keep history linear. Rebasing does that by replaying the branchโ€™s commits on top of todayโ€™s main, as if the work had started there. The result is clean, but rebasing rewrites every commit on the branch, which affects collaborators, invalidates review state tied to commit hashes, and can quietly change what a commit does if a conflict is resolved carelessly. This page walks through doing it well: preparing, rebasing, verifying that nothing changed unintentionally, and pushing so reviewers can follow, within merge vs rebase decision matrix.

When to use this approach Jump to heading

  • Your team prefers linear history, or requires branches to be up to date before merging.
  • The branch has conflicts with main that must be resolved before it can merge.
  • The branch is yours alone, or collaborators have agreed to the rewrite.
  • If others build on your branch, consider merging main in instead โ€” see merge vs rebase for long-running branches.

Step 1 โ€” Prepare: fetch, check status, note the old tip Jump to heading

Start from a clean state with the latest main, and record where the branch was so you can compare and recover.

git fetch origin
git status --short                              # commit or stash anything unfinished
old=$(git rev-parse HEAD)
echo "pre-rebase tip: $old"
git log --oneline origin/main..HEAD             # the commits that will be replayed
What a rebase does to a feature branchThe feature branch has commits F1 to F3 on an older main. Main has since gained M1 and M2. Rebasing replays F1 to F3 on top of M2 as new commits F1' to F3', with the same changes but new parents and new hashes. The branch name moves to F3'.same changes, new commits on the new basemainBM1M2beforeBF1F2F3afterM2F1' F2' F3'the old F1โ€“F3 still exist in the reflog until garbage collection What a rebase does to a feature branchThe feature branch has commits F1 to F3 on an older main. Main has since gained M1 and M2. Rebasing replays F1 to F3 on top of M2 as new commits F1' to F3', with the same changes but new parents and new hashes. The branch name moves to F3'.same changes, new commits on the new basemainBM1M2beforeBF1F2F3afterM2F1' F2' F3'the old F1โ€“F3 still exist in the reflog until garbage collection

Step 2 โ€” Rebase, with the settings that make it smoother Jump to heading

A few settings reduce the pain of rebasing: show the merge base in conflicts, remember resolutions, and stash uncommitted work automatically.

git config --global merge.conflictStyle zdiff3
git config --global rerere.enabled true
git config --global rebase.autoStash true
git rebase origin/main

If a commit conflicts, the rebase stops on it. Resolve the conflict so that the commit still does what it originally did, now on top of the new code, then continue.

git status --short | grep '^UU'
# resolve, then
git add -A && git rebase --continue

During a rebase, โ€œoursโ€ is main and โ€œtheirsโ€ is your commit โ€” the reverse of a merge, explained in ours and theirs during merge vs rebase.

Step 3 โ€” Verify nothing changed unintentionally Jump to heading

A clean-looking rebase can still change a commitโ€™s effect if a conflict was resolved wrongly. git range-diff compares each old commit with its rebased version and shows where they differ beyond the new base.

git range-diff "$old"...HEAD
# 1:  a1b2c3d = 1:  d4e5f6a feat: add schedule model           (identical)
# 2:  b2c3d4e ! 2:  e5f6a7b feat: add schedule API            (changed โ€” inspect)
Checking a rebase with range-diffCommits marked with an equals sign have the same change before and after the rebase. Commits marked with an exclamation mark changed, usually because a conflict was resolved; those need a look. Commits that appear on only one side were dropped or added, which is almost always a mistake.MarkerAction=same changenone!change differsread the inner diff< or >dropped / addedinvestigaterange-diff is the review of the rebase itself Checking a rebase with range-diffCommits marked with an equals sign have the same change before and after the rebase. Commits marked with an exclamation mark changed, usually because a conflict was resolved; those need a look. Commits that appear on only one side were dropped or added, which is almost always a mistake.MarkerAction=same changenone!change differsread the inner diff< or >dropped / addedinvestigaterange-diff is the review of the rebase itself

Then run the tests, ideally on every commit, so each rebased commit still builds on its own:

git rebase --exec 'make test >/dev/null' origin/main

Step 4 โ€” Push with a lease and tell reviewers Jump to heading

Rebased commits have new hashes, so pushing needs a force. Use a lease so you never overwrite a commit someone else pushed. Then leave a short note on the pull request, because reviewersโ€™ position in the diff is reset.

git push --force-with-lease --force-if-includes
gh pr comment --body "Rebased onto main ($(git rev-parse --short origin/main)). Only changes beyond the rebase: conflict in api.py resolved by keeping both validations. Range-diff: \`git range-diff $(git rev-parse --short "$old")...$(git rev-parse --short HEAD)\`"

Reviewers can see exactly what changed with the same range-diff, as described in reviewing a force-push with git range-diff.

โš ๏ธ SAFETY WARNING: If someone else has pushed commits to the branch, a plain --force deletes them. --force-with-lease refuses in that case. If a rebase went wrong after pushing, the old tip is still in your reflog: git reset --hard "$old" (or git reflog to find it), then push with a lease again.

Step 5 โ€” Decide how often to rebase Jump to heading

Rebasing once, just before merging, is usually enough. Rebasing every day keeps conflicts small but resets review state each time. A useful middle ground: rebase when conflicts appear or when the branch is ready for final review, and use a throwaway merge to check compatibility in between, as in testing a feature branch against the latest main.

When to rebase a feature branchIf the branch has conflicts with main, rebase now to resolve them while they are small. If it is ready for final review or merge, rebase so reviewers see it on current main. If neither, and review is in progress, leave it and check compatibility with a throwaway merge instead.What state is the branch in?conflicts with mainRebase nowsmall conflictsready to mergeRebase oncefinal reviewmid-review, no conflictsLeave itthrowaway merge checkeach rebase costs reviewers a re-orientation โ€” spend it when it buys something When to rebase a feature branchIf the branch has conflicts with main, rebase now to resolve them while they are small. If it is ready for final review or merge, rebase so reviewers see it on current main. If neither, and review is in progress, leave it and check compatibility with a throwaway merge instead.What state is the branch in?conflicts with mainRebase nowsmall conflictsready to mergeRebase oncefinal reviewmid-review, no conflictsLeave itthrowaway merge checkeach rebase costs reviewers a re-orientation โ€” spend it when it buys something

Step 6 โ€” Recover if the rebase goes wrong Jump to heading

If the rebase becomes confusing partway through, abort; if it finished badly, reset to the old tip. Both are safe because nothing is lost.

git rebase --abort                # mid-rebase: back to exactly where you started
git reset --hard "$old"           # after: back to the pre-rebase tip

The fuller recovery toolkit is in aborting and recovering a rebase gone wrong.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Does rebasing lose review comments? Jump to heading

Comments on lines that still exist usually stay attached; comments on commits may be marked outdated. A short note explaining the rebase, plus the range-diff command, lets reviewers pick up quickly.

Should I rebase or merge main into my branch? Jump to heading

Rebase if the branch is yours and the team prefers linear history; merge if others build on it or the branch is long-lived. The full trade-off is in the parent topic.

Why did a rebase drop one of my commits? Jump to heading

Usually because main already contains the same change โ€” someone cherry-picked it, or it landed through another pull request โ€” so the commit became empty and was skipped. range-diff shows it as dropped.