Git Rebase

git rebase takes a series of commits and replays them on top of a different base. Where a merge ties two histories together with a merge commit, a rebase rewrites your commits so they look as if you'd started from the latest code all along. The result is a clean, linear history that's easier to read, bisect, and review.

Rebase is also Git's main history-editing tool. Interactive rebase lets you squash noisy "fix typo" commits, reword messages, reorder, and split commits before anyone reviews them. With that power comes one rule you must respect: never rewrite commits other people have already based work on.

TL;DR

Quick Example

Update a feature branch with the latest main, clean up its commits, and push:

The interactive todo list:

The branch now has three clean commits sitting directly on top of main.

Core Concepts

Rebase vs Merge

Many teams use both: rebase locally to keep feature branches current and tidy, then merge (often squash) into main via pull request.

Interactive Rebase Commands

Reorder commits by reordering lines. git rebase -i --exec "npm test" main runs tests after every replayed commit, verifying each commit builds on its own.

Fixup Commits and Autosquash

During review, instead of amending in place:

Set git config --global rebase.autoSquash true to make this the default.

--onto: Transplanting Commits

git rebase --onto <newbase> <oldbase> <branch> moves the commits after oldbase onto newbase. It's useful when a branch was accidentally based on another feature branch:

It's also how you update stacked branches after the bottom branch merges. git rebase --update-refs updates every branch in a stack in one go.

Resolving Conflicts During a Rebase

Rebase applies commits one at a time, so conflicts are reported per commit:

  1. Git stops and lists conflicted files.
  2. Edit them to the correct result; remember you're resolving this commit's change against the new base.
  3. git add <files>, then git rebase --continue.
  4. git rebase --skip drops the current commit; git rebase --abort restores everything to before the rebase.

Enable rerere ("reuse recorded resolution") with git config --global rerere.enabled true, and Git will remember how you resolved a conflict and reapply it automatically if it recurs.

Best Practices

Follow the Golden Rule

Don't rebase commits that exist outside your repository and that others may have built on. Rebasing a shared branch forces every collaborator to reconcile duplicated, rewritten history. For your own pushed feature branch, rebasing is fine, since nobody else is building on it.

Use --force-with-lease

After rewriting a pushed branch, git push --force-with-lease refuses to overwrite the remote if someone else pushed in the meantime. Plain --force silently destroys their commits.

Make Pull the Rebasing Kind

git config --global pull.rebase true makes git pull rebase your local commits on top of fetched ones instead of creating throwaway "Merge branch 'main' of ..." commits.

Tidy Before Review, Not After Approval

Clean up commits before requesting review, and use fixup commits during review so reviewers can see what changed. Autosquash just before merging. Rewriting heavily mid-review makes it hard for reviewers to track changes.

Common Mistakes

Rebasing a Shared Branch

Integrate into shared branches with merges or PRs; rebase only your branch onto them.

Panicking Mid-Rebase

A conflict-heavy rebase can feel like lost work, but nothing is lost. git rebase --abort returns to the starting point, and even after a finished rebase, git reflog shows the old branch tip, so git reset --hard <old-sha> restores it. See undoing things in Git.

Squashing Everything Into One Commit by Default

One giant commit hides the structure of a large change. Squash noise (typo fixes, "wip"), but keep logically separate steps (refactor, feature, tests) as separate commits when they aid review and bisecting.

FAQ

Should I use rebase or merge?

Use rebase to keep your own feature branch current and its commits clean. Use merge, or squash-merge through a PR, to integrate into shared branches like main. The combination gives a readable history without rewriting anything other people depend on.

What does "rewriting history" actually mean?

Commits are identified by a hash of their content and their parent. Rebasing changes a commit's parent, so Git creates new commits with new hashes and moves the branch to them. The originals still exist, unreferenced, until garbage collected, which is why reflog can recover them.

Why do I get the same conflict repeatedly during a rebase?

Each replayed commit can touch the same lines, so a conflict can recur commit after commit. Squashing related commits first, or enabling rerere, reduces the repetition. For branches with many commits touching the same area, a single merge can be less work.

How do I undo a rebase?

Find the pre-rebase commit in git reflog (an entry like rebase (start), or the branch tip before it), then git reset --hard <sha>. ORIG_HEAD also points to the pre-rebase tip immediately afterwards: git reset --hard ORIG_HEAD.

Related Topics

References