Git workflow comparison
Git merge vs rebase
"Should I merge or rebase?" is one of the most discussed Git questions of all time. Both produce the same code; they differ only in how the commit history reads afterwards.
TL;DR
- Merge joins two branches with a new commit that has two parents. History preserves the fact that work happened in parallel.
- Rebase replays your commits on top of the target branch. History looks linear, as if you'd been working on the target branch all along.
- Rule of thumb: rebase your private feature branch before opening a PR for a clean review; merge shared branches.
Side-by-side comparison
| Aspect | Merge | Rebase |
|---|---|---|
| Final code | Same | Same |
| History shape | Branched graph with merge commits | Linear, no merge commits |
| Commit SHAs | Unchanged — preserves originals | Rewritten — new SHAs for replayed commits |
| Safe on shared branches | Yes | No — rewriting public history breaks others |
| Conflict resolution | Once, at merge time | Per commit being replayed |
| Bisect / blame | Merge commits can obscure | Clean — every commit stands alone |
| Default in | git pull (until you change it) | git pull --rebase |
| Reversibility | Easy: git revert -m 1 | Hard once pushed; reflog only locally |
History visualised
Merge
* m (main) Merge feature into main
|\
| * c (feature) Add user avatar
| * b (feature) Refactor profile
|/
* a (main) Initial work Rebase
* c' (main) Add user avatar
* b' (main) Refactor profile
* a (main) Initial work
# b and c are replayed as b' and c'
# with new SHAs; no merge commit Workflows side-by-side
Merge workflow
git checkout feature
# ...work, commit, commit, commit
git checkout main
git pull
git merge feature
# Creates a merge commit
git push Rebase workflow
git checkout feature
git fetch origin
git rebase origin/main
# Replays your commits on top of main
# Resolve any conflicts per commit
git push --force-with-lease
# (force needed because SHAs changed) When to use merge
- Shared / long-running branches. Other people have already pulled — rewriting history would break them.
- You want to preserve "feature group" context. Merge commits let you see "everything from PR #1234" as a unit.
- Releases / hotfixes. Merging release branches into main and develop is the standard GitFlow pattern.
- Team is junior with Git. Merge is more forgiving; rebase failures are harder to recover from.
When to use rebase
- Cleaning up your private feature branch before a PR. Squash WIP commits, fix messages, reorder logically.
- Keeping main linear. Many teams configure `git pull --rebase` and squash-merge PRs for a clean main history.
- Picking up upstream changes during long work. Rebase frequently onto main to surface conflicts early, one commit at a time.
- Open-source contributions. Maintainers often request a rebase to integrate cleanly without merge commits.
English phrases engineers use
Merge conversations
- "Let's merge it in — the PR is approved."
- "Use --no-ff so the feature branch shows up in the graph."
- "This is a merge conflict on package.json — both branches added dependencies."
- "Revert the merge commit with
git revert -m 1 <sha>." - "We do squash merges on main — one commit per PR."
Rebase conversations
- "Rebase onto main before opening the PR — your branch is 47 commits behind."
- "This is an interactive rebase — I'm squashing the WIP commits."
- "You'll need to force-push after rebasing — use
--force-with-lease." - "Don't rebase shared branches — you'll rewrite everyone else's history."
- "The rebase has 12 commits to replay — resolve conflicts step by step."
Quick decision tree
- Branch is private and not pushed → Rebase freely
- Branch is shared with the team → Merge (or coordinate before rebase)
- Cleaning up local commits before PR → Interactive rebase (git rebase -i)
- Bringing in upstream changes during long work → Rebase onto main
- Integrating PR into main → Team policy (merge, squash, or rebase merge)
- Releasing / GitFlow → Merge with --no-ff
- Open-source PR, maintainer requests it → Rebase
- Unsure → Merge (safer)
Common Mistakes and Trade-offs
A remarkably common mistake teams make – particularly smaller ones – is treating `merge` as inherently 'safe' simply because it doesn't alter the commit history directly. They assume this means a cleaner record, overlooking that each merge creates an additional merge commit, effectively bloating the project's historical narrative with potentially unnecessary branching points. This isn't inherently bad; however, when coupled with poor branch naming conventions and infrequent merging, it can lead to a confusing, difficult-to-follow history, especially as projects grow. The perceived simplicity of `merge` often masks the responsibility of carefully managing those branches in the first place – a task many teams don't adequately address.
At scale, the performance trade-off between `rebase` and `merge` becomes far more pronounced. While `merge`'s impact is largely limited to commit metadata updates, `rebase` involves rewriting history across the entire branch, which can be incredibly resource intensive when dealing with a large number of commits or frequent rebases. This isn't just about CPU cycles; it impacts network bandwidth as Git pushes rewritten references to remote repositories and affects synchronization times for all team members. The cumulative effect of numerous `rebase` operations on a production server during peak hours can lead to significant performance degradation, something rarely considered in smaller development environments.
The misconception that 'rebasing is always better' stems from the desire for a linear history – a pristine, unbroken chain of commits. This ignores the fundamental purpose of Git: tracking changes and collaboration. `Merge` represents a collaborative agreement on how different lines of work should be integrated; it acknowledges that multiple individuals were working independently and provides a clear record of those integrations. Blindly rebasing to achieve this linear history can erase valuable context – branch point resolutions, conflict discussions, and the rationale behind specific merge decisions – making future debugging and understanding significantly harder.
Successfully migrating a codebase from a heavily rebased workflow to one that prioritizes `merge` – or vice-versa – requires careful consideration of existing commit messages and branching strategies. Simply switching tools doesn't magically fix underlying issues; it exposes them. Teams frequently find themselves needing to 'undo' rebases to restore the original history, leading to a chaotic state. A more effective approach involves establishing clear guidelines for both workflows before any migration begins, coupled with automated tooling that can detect and prevent common mistakes like accidentally rewriting shared branches – often involving a phased rollout where critical areas are initially merged while less-used features remain rebased.
Frequently asked questions
What is the practical difference between git merge and git rebase?
Merge takes two branches and joins them with a new "merge commit" that has two parents — your branch history shows that two lines of work came together. Rebase takes your commits and replays them on top of the target branch one by one, producing a single linear history with no merge commit. Same final code; different commit history.
When should I use git merge?
Use merge when you want to preserve the exact history of how work happened, when the branch is shared with other people (rebasing rewrites history and forces everyone else to re-fetch), or when your team's policy is to keep all feature work visible as a separate "stream" on the graph.
When should I use git rebase?
Use rebase to keep a clean, linear history before merging — typically on your private feature branch before opening a pull request. Rebase is also useful for interactive history cleanup: squashing WIP commits, reordering, or editing commit messages.
Is rebase dangerous?
Only on shared branches. Rebase rewrites commit SHAs, so anyone who has already pulled the original commits will have a divergent history. The golden rule: never rebase commits that exist on a branch other people are using. Rebasing your local branch before opening a PR is safe.
What is git merge --no-ff?
A "no fast-forward" merge forces Git to create a merge commit even when the target branch could be moved forward without one. Teams use --no-ff to keep an explicit record of every feature branch in the history graph, so you can later see "everything that came from PR #1234" as a group.
What is git pull --rebase?
Instead of creating a merge commit when you pull, Git rebases your local commits on top of the freshly-fetched remote commits. Most teams configure this as the default (git config --global pull.rebase true) because it keeps history linear without thinking about it.
How do I undo a bad rebase?
Use the reflog: git reflog shows every state of HEAD, including pre-rebase. Find the SHA before the rebase, then git reset --hard <that-sha>. Git keeps reflog entries for 90 days by default, so even "lost" commits are recoverable.