Rebasing (and When Not To)
Merging is not the only way to bring branches together. Rebasing takes the commits of your branch and replays them, one by one, on top of another branch, as if you had started your work from there. The result is a straight, easy-to-read history with no merge commits.
Rebase is powerful and also the easiest way to confuse your teammates if you use it on the wrong commits. This lesson teaches both: how to rebase, how to tidy your commits before sharing them, and the one rule that keeps rebasing safe.
The problem rebase solves
Priya started an order-form branch and made two commits. Meanwhile, a footer style was committed on main:
git log --oneline --graph --all -5* feca328 (HEAD -> main) Style the footer | * f0521e7 (order-form) Add phone field to order form | * 9926298 Add order form |/ * 8bfdef1 Merge branch 'new-prices' |\
She could merge main into her branch, but that adds a merge commit that says nothing useful. Instead she rebases her branch onto main:
git switch order-form
git rebase main
git log --oneline --graph --all -5Successfully rebased and updated refs/heads/order-form. * a73061d (HEAD -> order-form) Add phone field to order form * f3aad09 Add order form * feca328 (main) Style the footer * 8bfdef1 Merge branch 'new-prices' |\
Her two commits now sit on top of main in a straight line. Look carefully at the hashes: 9926298 became f3aad09 and f0521e7 became a73061d. Rebase does not move commits; it creates new copies with a new parent, and the originals are abandoned. Keep this in mind, because it is the source of every rebase danger.
A nice side effect: main can now take the branch with a simple fast-forward merge.
Merge or rebase?
| Merge | Rebase | |
|---|---|---|
| History shape | Shows the real branching, with merge commits | One straight line |
| Rewrites commits? | No, existing commits are untouched | Yes, every replayed commit gets a new hash |
| Safe on shared branches? | Yes | No |
| Conflicts | Resolved once, in the merge commit | May be resolved once per replayed commit |
Neither is "better". Many teams rebase their own feature branches to keep them up to date, then merge them into main through a pull request.
The golden rule
Never rebase commits that other people already have. If you rebase a branch that Kabir has pulled, his copy still has the old commits while yours has new copies with different hashes. When he pulls again, Git sees two versions of the same work, and the history fills with duplicates and conflicts.
In practice: rebase freely on your own local branches and on feature branches that only you push to. Never rebase main or any branch your team shares. If you rebased a branch you had already pushed (only you use it), you must replace the remote copy with git push --force-with-lease, which you will meet in Remotes.
Cleaning up commits with interactive rebase
Before opening a pull request, it is polite to tidy your commits: fix bad messages, combine "oops" commits, and drop experiments. Interactive rebase (-i) lets you edit the list of commits before Git replays them.
After reworking the form a little (so the hashes differ from the example above), Priya has three commits on her branch. Then she notices the phone field should be required. Instead of a vague "fix" commit, she makes a fixup commit that is marked for the commit it belongs to:
git commit --fixup=HEAD~1 -a
git log --oneline -51237ef3 fixup! Add phone field to order form c828a19 Add sweet name field 8233ccc Add phone field to order form f3aad09 Add order form feca328 Style the footer
Now she starts an interactive rebase onto main. --autosquash moves fixup commits next to their target automatically. Git opens her editor with a to-do list (comment lines starting with # explain the commands and are not shown here):
git rebase -i --autosquash mainpick f3aad09 # Add order form
pick 8233ccc # Add phone field to order form
fixup 1237ef3 # fixup! Add phone field to order form
pick c828a19 # Add sweet name fieldEach line is one commit, oldest first. Changing the word at the start of a line changes what happens to that commit:
pick: keep the commit as it is.reword: keep it but edit its message.edit: stop at this commit so you can change its content.squash: combine it with the commit above and edit the combined message.fixup: combine it with the commit above and keep only the above commit's message.drop: remove the commit. You can also reorder lines to reorder commits.
Priya saves and closes the file without changes, because autosquash already did the work:
Successfully rebased and updated refs/heads/order-form.
git log --oneline -4c378c08 (HEAD -> order-form) Add sweet name field b44ced3 Add phone field to order form f3aad09 Add order form feca328 (main) Style the footer
Three clean commits, and the fix lives inside the commit it belongs to. Reviewers see a story that makes sense.
When a rebase hits a conflict
Since rebase replays commits one at a time, a conflict stops it at a particular commit:
Auto-merging order.html CONFLICT (content): Merge conflict in order.html error: could not apply a421d3e... fixup! Add phone field to order form 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".
Resolve the file exactly as in Resolving Merge Conflicts, stage it, and continue. Git moves on to the next commit:
git add order.html
git rebase --continuegit rebase --abort returns the branch to how it was before you started, which is always a safe escape. During a rebase, "ours" and "theirs" are swapped compared with a merge: "ours" is the branch you are rebasing onto, and "theirs" is your commit being replayed.
Rebasing when you pull
When your teammate has pushed and you have local commits, git pull must combine them. git pull --rebase replays your local commits on top of what you fetched, avoiding a pointless "Merge branch 'main' of github.com..." commit. Many developers make it the default:
git config set --global pull.rebase trueThis is safe because the commits being replayed are your own unpushed ones.
Common mistakes
- Rebasing a shared branch. Teammates end up with duplicate commits. Only rebase work nobody else has based work on.
- Using plain
--forceafter a rebase. It can wipe out commits someone else pushed. Use--force-with-lease. - Panicking mid-rebase.
git statustells you which commit you are on;git rebase --abortundoes everything. - Thinking rebased commits are lost. The old commits stay reachable through
git reflogfor a while, so a bad rebase can be undone.
Key points
git rebase mainreplays your branch's commits on top ofmain, giving a straight history.- Rebase creates new commits with new hashes; the originals are abandoned.
- Golden rule: never rebase commits others already have; force-push your own rebased branch with
--force-with-lease. git rebase -ilets you reword, squash, fixup, reorder and drop commits;--fixupplus--autosquashautomates cleanup.- On conflicts, resolve,
git add, thengit rebase --continue, orgit rebase --abortto cancel.
Exercise
On a practice feature branch, make three commits where the second one is a small fix to the first (for example "Fix typo in header"). Make sure main has one new commit too. Then rebase your branch onto main with an interactive rebase that combines the typo fix into the first commit. Show the log before and after.
Show solution
git log --oneline --graph --all -6
git rebase -i main
git log --oneline --graph --all -6In the to-do list, change pick to fixup on the line "Fix typo in header" and make sure that line sits directly below the commit it fixes. After saving, the branch has two commits sitting on top of main's newest commit. Next time, create the fix with git commit --fixup=<hash> and use --autosquash, so you do not have to edit the list by hand.
Quick Quiz
1. What does git rebase main do when you are on a feature branch?
Rebase gives your commits a new base, so the branch continues from the tip of main.
2. Why do rebased commits get new hashes?
A commit's hash depends on its parent, so replaying on a new parent creates new commits.
3. Which situation breaks the golden rule of rebasing?
Rewriting commits others have causes duplicates and conflicts for them.
4. In an interactive rebase, what does fixup do?
squash also combines but lets you edit the joint message; fixup keeps the earlier message.
Learn Git with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
