Git & GitHub · 13 min · 140 XP

Rebase, and when not to

Replay a branch onto main with rebase, and the one rule that keeps it safe.

When main moves on while you work on a branch, Branching and merging joined the two with a merge commit. git rebase main is the other way. It lifts your branch's commits off, moves the branch to the tip of main, and replays them one at a time. The result is a straight line, as if you'd started your branch today.

Before: main moved on after you branched
* 77a0661 (main) Raise free shipping to 60
| * b5d5efc (HEAD -> feature/discount) Add discount field
|/
* 28a6e49 Add config
After: git rebase main, on feature/discount
* f7f043c (HEAD -> feature/discount) Add discount field
* 77a0661 (main) Raise free shipping to 60
* 28a6e49 Add config

Look at the ID: b5d5efc became f7f043c. Rebase doesn't move commits, it makes new copies and abandons the old ones. That gives you the one rule: don't rebase commits other people already have. Their copies still carry the old IDs, and their next pull tangles both versions together. Rebasing your own branch that nobody else works on is fine, even after you've pushed it for a pull request.

A replayed commit can conflict, just like a merge. Git stops at that commit, and you fix the file, git add it, and run git rebase --continue. git rebase --abort puts everything back as it was before you started. For unpushed work on a shared branch, git pull --rebase does the same trick when you pull: your commits go on top of what came down instead of getting a merge commit.

After rebasing a branch you'd already pushed, a plain git push is rejected, because the remote still has the old copies. Git's hint suggests pulling, but here that's wrong: a pull would merge the old copies back in. Use git push --force-with-lease. It replaces the remote branch with yours, but refuses if someone else pushed to it since you last fetched, a check that plain --force skips.

Scenario: a rebase stops
$ git rebase main
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
error: could not apply 64476ac... Add discount code pattern
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Scenario: pushing your rebased pull request branch
$ git push
 ! [rejected]        feature/discount -> feature/discount (non-fast-forward)
error: failed to push some refs to 'github.com:acme/shop.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.

Try it on your machine: branch off main and commit. Switch back to main and commit something else. Return to your branch, run git rebase main, and compare git log --oneline --graph --all before and after. Note that your commit's ID changed.

Loading your workspace…