A Classic Scenario
The most common Git debate on any team: when integrating a feature branch, merge or rebase?
main: A --- B --- C --- D
\
feature: E --- F
Both integrate feature into main, but the history looks completely different—affecting code review, rollback, and collaboration.
In One Sentence
- merge: preserves true history with a merge commit—for shared branches
- rebase: rewrites history into a straight line—for tidying local commits
Merge: Preserve True History
Operation
git checkout main
git merge feature
Result
main: A --- B --- C --- D -------- G (merge commit)
\ /
feature: E --- F
Pros
- Fully traceable history:
--graphshows real branch forks and joins - Existing commits untouched:
E,Fhashes stay the same; pushed branches stay safe - Safe retry: failed resolution can be aborted anytime with
git merge --abort - Zero cognitive load: commits stay as they are
Cons
- Lots of merge commits (especially when syncing main often) clutter
git log - Harder to see what a feature actually changed—
--first-parenthelps - Non-linear history adds minor noise to
git bisect
When to Use
- Shared long-lived branches (main/develop)
- Multi-developer environments that forbid rewriting history
- Release processes needing a full audit trail
Rebase: Rewrite into Linear History
Operation
git checkout feature
git rebase main
git checkout main
git merge feature # fast-forward now—no merge commit
Result
main: A --- B --- C --- D --- E' --- F'
E' and F' are new copies of E, F (completely different hashes) with identical content.
Pros
- Clean linear history:
git logreads top to bottom - Review-friendly: no noisy forks in
git log --oneline --graph - More precise bisect: linear history makes binary search more reliable
- Squash support: multiple WIP commits become one logical commit
Cons
- Rewrites history: hashes change; pushed commits pollute shared repos
- Repeated conflict resolution: every replayed commit can conflict again
- Costly recovery: needs reflog or a backup branch
- Fast-forward loses branch context: no record of when the feature was integrated
When to Use
- Tidying local dev branches (squash/reorder before merging)
- Personal repos / solo projects
- Open-source contributions (rebase onto upstream tip after review)
Comparison Table
| Dimension | merge | rebase |
|---|---|---|
| History shape | forks + merge commits | linear |
| Commit hashes | unchanged | all rewritten |
| Shared-branch safe | ✅ | ❌ (golden rule) |
| Conflict resolution | once | repeated per commit |
| Mistake recovery | --abort is enough |
needs reflog |
| Code review experience | fair | better |
| bisect | slightly noisy | more precise |
| Records integration time | ✅ merge commit | ❌ lost |
Golden Rule & Workflow Advice
The Golden Rule (non-negotiable)
Never rebase commits that have already been pushed to a shared repository.
Once teammates have pulled those commits, rebasing forks everyone's history, producing duplicate commits on pull—only force-push can repair it. This is a top source of collaboration accidents.
Solo Workflow
# Tidy locally however you like
git commit -m "wip: scaffold"
git commit -m "wip: core logic"
git rebase -i HEAD~2 # squash into one clean commit
git push
Feature-Branch Workflow (recommended combo)
# 1. Rebase onto latest main before merging—resolve conflicts, stay linear
git fetch origin
git rebase origin/main
# 2. Merge into main (fast-forward now)
git checkout main
git pull --ff-only
git merge feature
git push
Merge-Only Workflow (conservative teams)
git checkout main
git pull
git merge --no-ff feature # force a merge commit, keep branch context
git push
--no-ff is a common convention for shared branches: even when fast-forward is possible, create the merge commit so "when was this integrated" is clearly recorded.
Conflict Handling
merge conflicts (once):
git merge feature
# resolve → git add → git commit
rebase conflicts (per commit):
git rebase main
# commit 1 conflicts → resolve → git add → git rebase --continue
# commit 2 conflicts → resolve → git add → git rebase --continue
# worst case → git rebase --abort to start over
Recovery: reflog Is Your Safety Net
Rebase merely "moves" commits; it doesn't physically delete them. Every HEAD movement is recorded in the reflog:
git reflog
# find the commit hash before the operation, e.g. abc1234
git reset --hard abc1234
| Scenario | Recovery command |
|---|---|
| Bad rebase result | git reflog → git reset --hard <hash> |
| Accidentally deleted branch | git reflog → git branch <name> <hash> |
| Regret after squash | find the pre-squash hash in reflog |
Lost after --abort |
reflog entry before the rebase |
Note: reflog keeps 30 days by default; recovery is harder if commits were gc'd or never referenced by any branch.
Decision Guide
| Scenario | Recommendation |
|---|---|
| Tidying local WIP commits | rebase -i (squash + reword) |
| Solo projects | either; rebase is cleaner |
| Feature branch into shared main | rebase to latest, then merge (linear) |
| Conservative / audit teams | merge --no-ff (full history) |
| Already-pushed commits | never rebase; use merge |
| Open source | follow upstream convention, usually rebase |
Bottom line: merge public history, rebase private history—this one rule prevents 90% of Git collaboration accidents.
For everyday Git commands and a quick reference, see our Git command cheat sheet.