Git Merge vs. Rebase: Comparison and Use Cases

Git Merge vs. Rebase: Comparison and Use Cases
Both git merge and git rebase integrate changes from one branch into another. The difference is how they do it and what the resulting history looks like. Choosing correctly affects how easy it is to read the history, bisect bugs, understand when features were integrated, and collaborate with teammates.
This guide explains exactly what each command does to the commit graph, when to use each, interactive rebase for cleaning up commits, and the rules that prevent history from being corrupted on shared branches.
What Git Merge Does
git merge creates a new commit (the merge commit) that has two parents — the tip of the target branch and the tip of the source branch. The existing commits are never modified.
# Starting state:
# main: A - B - C
# \
# feature: D - E - F
git checkout main
git merge feature
# Result:
# main: A - B - C - - - - M (M = merge commit with parents C and F)
# \ /
# feature: D - E - FThe merge commit M records the exact moment the feature was integrated. The history is complete and chronologically accurate.
When you should use merge:
- Integrating a long-lived feature branch into main via pull request
- Merging release branches
- Situations where preserving the exact branch topology matters for auditability
- Whenever you are merging a branch that other teammates are also working on
What Git Rebase Does
git rebase moves your commits to a new base. It creates new commits with the same changes but different parent commits (and therefore different SHA hashes). The originals are discarded.
# Starting state:
# main: A - B - C - G (G added since feature branched)
# \
# feature: D - E - F
git checkout feature
git rebase main
# Result:
# main: A - B - C - G
# \
# feature: D' - E' - F' (new commits, same changes)
# D', E', F' are NEW commits — different SHAs, same changes as D, E, FThe feature branch now starts from G (the current tip of main). The history is linear — no merge commit, no indication that these commits were ever developed in parallel.
When you should use rebase:
- Updating your local feature branch with the latest changes from main before opening a PR
- Cleaning up commits on a feature branch before code review
- Maintaining a linear project history on main
The Golden Rule of Rebasing
Never rebase a branch that has been pushed and other people are using.
When you rebase, existing commits are replaced with new ones (new SHAs). If your teammates have those old commits in their local copies, their history and your history diverge. When they pull, Git cannot reconcile the two timelines. You will see errors like:
! [rejected] feature-x -> feature-x (non-fast-forward)
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.The only fix is a force push — which overwrites the remote history and breaks everyone else's branches.
Safe rebase targets:
- Your own local branch that you have not pushed
- Your own branch that you are the only one using (safe to force push)
Never rebase:
main,develop, or any branch others commit to- Any branch from which others have created branches
Interactive Rebase: Polishing Commits
git rebase -i (interactive rebase) lets you rewrite the history of your own branch before sharing it. You can reorder, edit, squash, or drop commits.
# Interactively rebase the last 4 commits
git rebase -i HEAD~4
# Git opens an editor showing:
pick a1b2c3d feat: add payment form
pick e4f5g6h WIP: debugging
pick i7j8k9l fix typo
pick m0n1o2p address code review comments
# Commands:
# pick = use the commit as-is
# squash (s) = merge into previous commit
# fixup (f) = merge into previous commit (discard this commit's message)
# reword (r) = use commit but edit the message
# edit (e) = stop to amend this commit
# drop (d) = remove this commit entirelyTypical cleanup workflow:
# Before PR: clean up 4 commits into 1
git rebase -i HEAD~4
# Change to:
pick a1b2c3d feat: add payment form
fixup e4f5g6h WIP: debugging
fixup i7j8k9l fix typo
fixup m0n1o2p address code review comments
# Result: one clean commit "feat: add payment form" incorporating all changesAfter interactive rebase:
# Force push to update your PR branch (safe — you're the only one on this branch)
git push --force-with-lease origin feature/payment-form--force-with-lease is safer than --force: it refuses to push if the remote has commits you do not have locally, protecting against overwriting someone else's push.
Merge vs. Rebase: Side-by-Side Comparison
| Aspect | Merge | Rebase |
|---|---|---|
| History accuracy | Exact — shows parallel development | Rewritten — appears linear |
| Merge commits | Creates one per merge | None |
| SHA stability | Existing commits are unchanged | Creates new commits (new SHAs) |
| Conflict handling | One conflict resolution at the end | Conflict per commit (potentially many) |
| Safety on shared branches | Always safe | Unsafe — breaks teammates' branches |
Readability of git log | Busy with merge commits | Clean linear history |
| Best for | Feature integration, PRs | Local cleanup, syncing feature branch |
The Professional Workflow: Combining Both
The most effective approach uses rebase and merge together:
# 1. During development: keep your branch synced with main via rebase
git fetch origin
git rebase origin/main # Replay your commits on top of latest main
# Resolve any conflicts commit-by-commit
# 2. Clean up commits with interactive rebase before PR
git rebase -i origin/main
# 3. Force-push the cleaned-up branch to update your PR
git push --force-with-lease
# 4. When the PR is approved, merge via GitHub (creates a merge commit on main)
# The merge commit records when the feature was integratedThis gives you:
- Clean, meaningful commits in your PR (reviewers see logical changes, not debugging noise)
- An up-to-date branch that applies cleanly (no merge conflicts when merging the PR)
- A clear merge commit on main recording when the feature was integrated
Practical Examples
Syncing a feature branch with main
# You're on feature/dark-mode and main has advanced
git fetch origin
git rebase origin/main
# If conflicts arise:
git status # See which files have conflicts
# ... resolve conflicts in each file ...
git add resolved-file.ts
git rebase --continue # Move to next commit
# If you want to abort the rebase:
git rebase --abort # Returns to state before rebase startedSquashing WIP commits before PR
# You have 6 commits; only the first and last matter
git log --oneline
# f1234ab fix: handle edge case in cart calculation
# a5678bc test: add edge case test
# c9012de review: use optional chaining
# d3456ef wip: debugging total calculation
# e7890ab wip: started cart feature
# f2345cd feat: implement cart total calculation
git rebase -i HEAD~6
# Squash everything into the first meaningful commit
# After rebase, your branch has one clean commit:
# feat: implement cart total calculationUndoing a bad rebase
# If you've already rebased and it went wrong, use reflog to recover
git reflog
# HEAD@{0}: rebase (finish): ...
# HEAD@{5}: commit: feat: implement cart total calculation ↠your state before rebase
git reset --hard HEAD@{5} # Return to state before the bad rebaseFrequently Asked Questions
Q: Does git rebase cause more merge conflicts than git merge?
No — it causes the same conflicts, but distributes them differently. A merge shows all conflicts at once. A rebase shows conflicts one commit at a time. This is actually useful: you can see exactly which commit introduced each conflict, making resolution cleaner. However, if you rebase a branch that diverged far from main and has many commits, you may resolve the same conflict multiple times as each commit is replayed.
Q: When should I use git merge --squash instead of git rebase -i?
git merge --squash collapses all changes from a source branch into a single uncommitted set of changes on the current branch. You then create one commit. git rebase -i gives you more control — you can squash selectively, keep some commits, and edit messages. Use --squash for a quick one-shot collapse; use interactive rebase when you want to clean up history with more precision before a PR.
Q: Our team uses "squash and merge" for all PRs. Does that mean we should never rebase?
You can still use git rebase origin/main locally to keep your feature branch up to date with main — this is valuable regardless of the merge strategy used in PRs. What you do not need is to clean up commits with interactive rebase before the PR, because "squash and merge" will collapse them anyway at merge time. The rebase for syncing (staying up to date) is still valuable.
Q: What is git pull --rebase and should I use it?
git pull --rebase fetches remote changes and replays your local unpushed commits on top of them, rather than creating a merge commit. This keeps the history linear even during routine git pull operations. Many teams configure it as the default: git config --global pull.rebase true. It is safe as long as your commits have not been pushed to a shared branch — if they have, the rebase creates new SHAs and requires a force push.
Key Takeaway
Merge preserves accurate history at the cost of merge commits. Rebase rewrites history for a cleaner linear log, but must never be used on shared branches. The professional workflow combines both: rebase locally to stay current and clean up commits, then merge via PR to record when features are integrated. The golden rule never changes: once commits are shared and others are building on them, their history is immutable — only merge from that point forward.
Read next: Git Hooks: Automating Your Workflow with Scripts →
Part of the GitHub Mastery Course — masters of history.
