Git Rebase vs Merge: When to Use Each (With Examples)
One of the most heated debates in engineering teams isn't about tabs versus spaces—it's about whether to rebase or merge. Both approaches integrate code, but they work fundamentally differently, and choosing the wrong one for your situation creates headaches down the line. I'll walk you through both strategies with practical examples so you can make an informed decision for your team.
The Core Difference
Merge creates a new commit that combines two branches. It preserves history exactly as it happened.
Rebase replays your commits on top of another branch. It rewrites history to create a linear timeline.
That single difference cascades into everything else: readability, collaboration challenges, debugging difficulty, and team workflow.
Understanding Merge
A merge creates a "merge commit" that ties two branches together. Let's see it in action.
Say you're on a feature branch and the main branch has moved ahead:
# Starting state:
# main: A --- B --- C
# feature: \--- D --- E
# After merge:
# main: A --- B --- C --- M (merge commit)
# \ /
# feature: D --- E
Here's the actual git command:
git checkout main
git merge feature-branch
Git creates a merge commit M that has two parents. The commit message looks like:
Merge branch 'feature-branch' into 'main'
When to Use Merge
Use merge when:
- You're integrating a completed feature into a shared branch
- Your team values preserving the exact history of how work progressed
- You're working with less experienced developers (merge is safer)
- The branch is public or shared across a team
Real-world example: Your team finishes a 3-week payment integration feature on a branch. You merge it into main. The merge commit serves as a clear marker: "Everything from this merge commit works together."
Merge Workflow Example
# Create and work on feature branch
git checkout -b feature/user-authentication
# ... make commits D and E
git commit -m "Add login form"
git commit -m "Add password validation"
# Meanwhile, main has moved forward with commits B and C
# Time to integrate your feature
git checkout main
git pull origin main # Now you have A, B, C
git merge feature/user-authentication
# Creates merge commit M that ties everything together
# Now main has: A --- B --- C --- M
# The merge commit M has both B→C and D→E in its ancestry
Merge commit message:
Merge pull request #342 from team/feature/user-authentication
Adds complete user authentication system including login,
signup, and password reset flows. Tested in staging.
Understanding Rebase
Rebase replays your commits on top of another branch, rewriting history in the process.
# Starting state (same as above):
# main: A --- B --- C
# feature: \--- D --- E
# After rebase:
# main: A --- B --- C
# feature: \--- D' --- E'
# (D' and E' are NEW commits with different SHAs)
Here's the command:
git checkout feature-branch
git rebase main
Git takes commits D and E, and replays them on top of C. The new commits (D' and E') are technically different—they have different parent references and SHAs—even though the code changes are identical.
When to Use Rebase
Use rebase when:
- You're working on a local, unpublished branch
- Your team values a clean, linear history
- You want to review commits before they hit main
- You're syncing your local branch with main frequently
- You're the only one working on the branch
Real-world example: You're working on a bug fix on your local feature branch. Main gets updated three times while you're working. You rebase to stay current, keeping a clean linear history for when you finally push.
Rebase Workflow Example
# Create feature branch locally
git checkout -b fix/memory-leak
# ... make some commits
git commit -m "Identify memory leak in cache"
git commit -m "Fix cache cleanup logic"
# Meanwhile, main has advanced
# You want your fix to be on top of the latest main
git fetch origin # Get latest remote changes
git rebase origin/main
# Now your commits are replayed on top of the latest main
# History is linear: A --- B --- C --- D' --- E'
If there are conflicts during rebase, you'll resolve them commit-by-commit:
git rebase origin/main
# Conflict in file.js
# Fix the conflict in your editor
git add file.js
git rebase --continue
# If next commit also has conflicts, repeat
# If you mess up, git rebase --abort to start over
The Trade-offs
| Aspect | Merge | Rebase |
|---|---|---|
| History | Preserves exact timeline; can be messy with many branches | Linear, clean, but rewrites history |
| Debugging | Merge commits provide clear integration points | Harder to trace when specific code arrived |
| Collaboration | Safe for shared/public branches | Only for local, unpublished work |
| Learning curve | Straightforward | More complex; easier to mess up |
| Conflict resolution | Resolve once in merge commit | Resolve per-commit during rebase |
| Readability | More verbose git log | Cleaner, easier to follow |
Interactive Rebase for Local Cleanup
Interactive rebase is different from regular rebase—you use it to clean up your local commits before pushing.
# See your last 3 commits
git log --oneline -3
# abc1234 Fix typo in comment
# def5678 Add feature
# ghi9012 Work in progress code
# Interactive rebase to clean up
git rebase -i HEAD~3
Your editor opens with:
pick ghi9012 Work in progress code
pick def5678 Add feature
pick abc1234 Fix typo in comment
Change pick to squash (or s) on commits you want to combine:
pick ghi9012 Work in progress code
squash def5678 Add feature
squash abc1234 Fix typo in comment
Result: three commits become one. This is powerful for cleaning up before creating a pull request.
Critical Rule: Never Rebase Public Branches
This cannot be overstated: do not rebase branches that other people are working on.
# DON'T DO THIS
git checkout main
git rebase feature-branch # ❌ Breaks everyone's local main
# They'll try to pull and get cryptic conflicts
# Then they'll come to your desk asking angry questions
If you're on main and it's a shared branch, use merge. The only rebase-safe workflow on shared branches is during pull requests via git rebase when you pull, never when you push.
Team Workflow Recommendations
For Smaller Teams or Solo Work
Use rebase-and-fast-forward:
# Feature branch work
git checkout -b feature/api-endpoint
# ... make commits
git commit -m "Add GET endpoint"
git commit -m "Add POST endpoint"
# Ready to integrate
git fetch origin
git rebase origin/main
# Push and create PR
git push origin feature/api-endpoint
# In GitHub/GitLab, merge with "rebase and fast-forward"
# This keeps main's history linear
For Larger Teams or Strict History Tracking
Use conventional merge commits:
# Feature branch work is the same
git checkout -b feature/api-endpoint
git commit -m "Add GET endpoint"
git commit -m "Add POST endpoint"
# Integrate via merge
git checkout main
git pull origin main
git merge --no-ff feature/api-endpoint
# --no-ff ensures a merge commit is created, even for fast-forwards
# Push
git push origin main
The --no-ff flag ensures you always get a merge commit, preserving the visual grouping of feature work.
GitHub/GitLab Configuration
Most platforms let you enforce merge strategy:
- Allow squash and merge: Combines all commits into one. Good for messy local history.
- Allow rebase and merge: Replays commits cleanly. Good for clean histories.
- Allow regular merge: Creates merge commit. Good for tracking integration points.
You can restrict which are available in repository settings.
Practical Workflow for Production Code
Here's what I recommend for real production teams:
# 1. Create feature branch from latest main
git checkout main
git pull origin main
git checkout -b feature/user-dashboard
# 2. Make commits with clear messages
git commit -m "Create dashboard layout"
git commit -m "Add chart components"
git commit -m "Connect to analytics API"
# 3. Before pushing, rebase locally to stay current
git fetch origin
git rebase origin/main
# Resolve any conflicts
# 4. Push to your remote branch
git push origin feature/user-dashboard
# 5. Create pull request on GitHub/GitLab
# The PR shows your clean commits against main
# 6. Team reviews; you might need to:
git commit -m "Address PR feedback: improve error handling"
git push origin feature/user-dashboard
# 7. When approved, merge via UI with "Squash and merge"
# This creates one clean commit on main
# OR use "Rebase and merge" if you want individual commits preserved
Fixing Mistakes
Accidentally rebased a public branch?
# You can revert the rebase
git reflog
# Find the original HEAD before rebase
git reset --hard abc1234 # SHA from reflog
git push --force-with-lease origin main
Use --force-with-lease instead of --force—it's safer and won't overwrite others' changes.
Need to undo a merge commit?
git revert -m 1 <merge-commit-sha>
# This creates a new commit that undoes the merge
# Safer than resetting because it preserves history
Summary: Making the Choice
- Rebase when: working locally, keeping history clean, single developer per branch
- Merge when: integrating finished features, collaborating, prioritizing history preservation
The best approach is often a hybrid: rebase locally to keep your work clean, then merge into shared branches to preserve integration points.
Your team's consistency matters more than perfection. Document your choice in a CONTRIBUTING.md file and enforce it with branch protection rules. The worst version control practice is when developers use different strategies unpredictably.