Git Rebase

Rebasing rewrites a branch's history so it looks as if it were built starting from a newer commit, producing a cleaner, linear project history than merging does.

What rebase actually does

When you run git rebase main while on a feature branch, Git finds the commits unique to your branch, temporarily sets them aside, moves your branch pointer to the tip of main, and then replays each of your commits one by one on top of that new base. The result is a branch whose commits look like they were written after all of main's latest work, even though you actually wrote them earlier.

Rebasing a feature branch onto main

$ git checkout feature/search-filters
$ git rebase main
First, rewinding head to replay your work on top of it...
Applying: add filter dropdown component
Applying: wire filter state to search query
Applying: add tests for filter combinations

Rebase vs. merge

Merging joins two branches by creating a new merge commit that has two parents, preserving exactly what happened and when. Rebasing instead rewrites commits with new hashes so history looks linear, as if there had been no divergence at all. Neither is objectively better — they trade off honesty of history against readability of history.

AspectMergeRebase
History shapeBranching, preserves realityLinear, rewritten
Commit hashesUnchangedChanged for every replayed commit
Safe on shared branchesYesNo, unless everyone knows
Extra commit createdYes, a merge commitNo merge commit
Best used forIntegrating finished work into mainCleaning up a feature branch before review
Note: The golden rule of rebasing: never rebase commits that other people have already pulled. Rebasing rewrites commit hashes, so anyone else's local copy of the old commits will diverge painfully from the new ones.

Interactive rebase

Interactive rebase, triggered with git rebase -i, opens an editable list of your recent commits and lets you reorder, combine, edit, or drop them before they're replayed. It's the standard tool for tidying a messy feature branch — squashing three 'fix typo' commits into the commit they were fixing, for instance — before opening a pull request.

Interactive rebase to squash the last 3 commits

$ git rebase -i HEAD~3

pick a1b2c3d add filter dropdown component
squash e4f5g6h fix dropdown alignment
squash h7i8j9k address review comment

# Rebase 0f9e8d7..h7i8j9k onto 0f9e8d7
# Commands: p=pick, r=reword, e=edit, s=squash, f=fixup, d=drop

Changing pick to squash on the second and third lines tells Git to fold those commits into the one above them, prompting you to write a single combined commit message. The rest of the file — the comment lines starting with # — is just reference documentation for the available commands and is ignored when the rebase runs.

Resolving conflicts during a rebase

Because each commit is replayed individually, a rebase can pause on any commit that conflicts with the new base. Unlike a merge conflict, which you resolve once, a rebase may require resolving the same logical conflict multiple times across several commits. Git tells you exactly which commit is paused and gives you three ways forward.

Handling a conflict mid-rebase

$ git rebase main
Auto-merging src/search.js
CONFLICT (content): Merge conflict in src/search.js
error: could not apply e4f5g6h... wire filter state to search query
$ git add src/search.js
$ git rebase --continue
Note: If a rebase goes badly wrong, git rebase --abort restores your branch exactly to how it looked before you started, no harm done.

Exercise: Git Rebase

What does `git rebase` fundamentally do to your commits?