Git Rebase vs Merge: When to Use Which
A clear, practical guide to choosing between git rebase and git merge, with safe workflows for feature branches, shared branches, and pull requests.
What you'll learn
- ✓What merge and rebase actually do to history
- ✓When fast-forward, no-ff merge, or rebase is right
- ✓Why never to rebase a shared branch
- ✓A safe rebase workflow for feature branches
- ✓How to recover from a bad rebase
Prerequisites
- •Comfortable with basic git add, commit, and push
What and Why
Git lets you integrate one branch into another two main ways: merge and rebase. Both produce the same final source code. They produce very different history. The choice affects readability, bisect-ability, and team trust. Picking one consistently across a team matters more than which one you pick.
Mental Model
- Merge creates a new commit that has two parents. History fans out and joins. Nothing is rewritten.
- Rebase replays your commits one by one on top of another branch. History stays linear. Commit hashes change.
Start:
main: A --- B --- C
\
feat: D --- E
After merge:
main: A --- B --- C ---------- M
\ /
feat: D --- E --
After rebase + fast-forward:
main: A --- B --- C --- D' --- E' The ' marks indicate D and E are new commits with new hashes, even though the patches are the same.
Hands-on Example
A safe rebase workflow for a feature branch:
git checkout feat/login
git fetch origin
git rebase origin/main
# resolve any conflicts, then:
git rebase --continue
git push --force-with-lease origin feat/login
--force-with-lease is critical. Plain --force overwrites whatever is on the remote, including a teammate’s commit you have not seen. --force-with-lease only pushes if the remote still points to the commit you expected.
A merge workflow integrating main into your branch:
git checkout feat/login
git fetch origin
git merge origin/main
# resolve conflicts, commit, then:
git push origin feat/login
And the team-side integration once a PR is approved. Repository settings usually pick one of three strategies:
- Merge commit: preserves the full branch shape. Honest history, busier graph.
- Squash and merge: collapses the PR into one commit on main. Clean main, lost intra-PR history.
- Rebase and merge: replays PR commits on main with no merge commit. Linear, preserves commit granularity.
Common Pitfalls
Rebasing a shared branch. Never rebase a branch that other people have based work on. You rewrite history they have already pulled, and their next push will create duplicate commits or be refused entirely.
Force-push without --with-lease. Easy way to destroy a teammate’s work. Configure git to refuse plain push --force:
git config --global alias.pushf 'push --force-with-lease'
Resolving conflicts wrong during rebase. During a rebase, “ours” and “theirs” flip relative to merge. Use the commit message and patch context to decide which side is correct, not the labels.
Forgetting to update local main first. Rebasing onto a stale local main puts your work behind the real tip. Always git fetch first; prefer git rebase origin/main over git rebase main.
Rebasing merges. By default, rebase flattens merge commits. If your branch has intentional merges you want to keep, use git rebase --rebase-merges.
Practical Tips
Pick one team policy and stick to it. A common, balanced choice:
- Feature branches: rebase onto main while in progress.
- Pull request integration: squash and merge (or rebase and merge) into main.
- Long-running release branches: regular merge from main, no rebase.
For an interactive cleanup before pushing, use:
git rebase -i origin/main
Reorder commits, squash WIP commits into meaningful ones, fix commit messages. Then force-push your feature branch.
Recover from a bad rebase using the reflog:
git reflog
# find the commit hash before the rebase started, e.g. HEAD@{8}
git reset --hard HEAD@{8}
The reflog is your time machine. Anything you did locally in the last 90 days is there.
For PRs that show up out of date with main, enable “Rebase before merging” in your platform settings rather than letting a stale merge land.
Use git pull --rebase by default to avoid pointless merge bubbles in your local history:
git config --global pull.rebase true
Wrap-up
Merge preserves history exactly as it happened. Rebase rewrites history into a cleaner story. Use rebase on your own feature branches to keep the log readable, use merge (or squash) at integration time, and never rewrite history that lives on someone else’s clone. With --force-with-lease, a clear team policy, and the reflog as a safety net, you can move between both fluently without losing work or trust. The right answer is rarely “always merge” or “always rebase” — it is “match the tool to the lifecycle stage of the branch.”
Related articles
- Git Git Cherry-pick and Revert Tutorial
Learn how to copy specific commits across branches with cherry-pick and how to safely undo merged changes with revert, including conflict handling and recovery.
- Git Git Stash Tutorial: Saving Work in Progress
Learn how to use git stash to safely shelve uncommitted changes, switch contexts, and recover work using push, pop, apply, and branch workflows.
- Git Git Bisect Tutorial: Find the Bad Commit Fast
Use git bisect to binary-search through history and pinpoint the commit that introduced a regression, with manual and automated examples.
- Git Git Large File Storage (LFS) Tutorial
Set up Git LFS to version large binaries like images, models, and datasets without bloating your repository, including tracking, migration, and CI tips.