DEV Community

AttractivePenguin
AttractivePenguin

Posted on

Git Merge vs Rebase: When to Use Which (and Why It Matters)

Git Merge vs Rebase: When to Use Which (and Why It Matters)

If you've ever stared at your terminal after a git pull, wondering why your commit history looks like a bowl of spaghetti, you're not alone. The merge vs rebase debate is one of the most common sources of confusion for developers—and for good reason. Both commands integrate changes from one branch into another, but they do it in fundamentally different ways that can dramatically affect your project's history.

Here's the thing: neither approach is universally "right." They're tools with different trade-offs, and knowing when to use each one is a skill that separates junior developers from seasoned pros. Let's demystify both approaches so you can make confident decisions.


The Core Difference

At a high level, both git merge and git rebase do the same thing: they integrate changes from one branch into another. But how they do it—and what happens to your commit history—is where they diverge.

Merge: Preserve Everything

When you merge, Git creates a new commit (a merge commit) that has two parents. It combines the histories of both branches without modifying existing commits.

Before merge:                After merge:

    A---B---C feature            A---B---C-------M feature
   /                            /               \
  D---E---F main               D---E---F---------M main
Enter fullscreen mode Exit fullscreen mode

That M is your merge commit. It says, "These two histories came together here." Your original commits (A, B, C, D, E, F) remain untouched with their original SHAs.

Rebase: Rewrite History

Rebase takes a different approach. It replays your commits on top of the target branch, effectively rewriting history.

Before rebase:               After rebase:

    A---B---C feature            A'--B'--C' feature
   /                            /
  D---E---F main               D---E---F main
Enter fullscreen mode Exit fullscreen mode

Notice that A, B, and C became A', B', and C'. They're new commits with new SHAs. The original commits no longer exist in your branch's history—it's as if you started your feature branch from the latest main all along.


When to Use Merge

Use Cases

1. Integrating feature branches into main or develop

When your feature is complete and ready to be merged into a shared branch, git merge --no-ff (no fast-forward) is often the safest choice. It preserves the context of your feature as a cohesive unit.

2. Documenting when and why branches converged

Merge commits act as historical markers. They tell future you (or future teammates) exactly when two streams of work came together.

3. Collaborating on shared branches

If multiple developers are working on the same branch, merge is your friend. It doesn't rewrite history, so you won't step on anyone's toes.

Code Example

# Switch to your target branch
git checkout main

# Merge your feature branch with a merge commit
git merge --no-ff feature/login

# Or let Git decide (fast-forward if possible)
git merge feature/login
Enter fullscreen mode Exit fullscreen mode

Pros and Cons

Pros Cons
Non-destructive: doesn't rewrite history Can create "cluttered" history with many merge commits
Preserves complete context of when branches merged Makes git bisect harder (bisect may stop at merge commits)
Safe for shared branches Can be confusing for beginners to read history
Easy to understand and reason about Commit graph can become complex

When to Use Rebase

Use Cases

1. Cleaning up local feature branches before a PR

Before you push your feature branch for review, rebasing onto the latest main ensures your changes integrate cleanly and your history is linear.

2. Squashing commits

Want to turn 15 "fix typo" commits into one clean "Implement user authentication" commit? Interactive rebase (git rebase -i) is your tool.

3. Maintaining a linear history

Some teams prefer a clean, linear history without merge commits. Rebase makes that possible.

Code Example

# Update your target branch
git checkout main
git pull origin main

# Switch to your feature branch
git checkout feature/login

# Rebase onto the updated main
git rebase main
Enter fullscreen mode Exit fullscreen mode

For interactive rebase (to squash, reorder, or edit commits):

# Rebase last 5 commits interactively
git rebase -i HEAD~5
Enter fullscreen mode Exit fullscreen mode

This opens an editor where you can mark commits to be squashed, edited, or dropped.

Pros and Cons

Pros Cons
Creates linear, readable history Destructive: rewrites commit SHAs
Makes git bisect and git log cleaner Dangerous on shared/public branches
Allows squashing and cleaning up commits Requires force-push after rebase
No merge commit clutter Can cause conflicts multiple times (once per commit)

The Golden Rule: Never Rebase Public Branches

This is the most important thing you'll learn about rebase, so read it twice:

Never rebase branches that others have pulled.

Here's why: rebase creates new commits with new SHAs. If someone else has pulled your branch and based work on commits A-B-C, and you rebase to create A'-B'-C', their repository still has the old commits. When they try to pull again, Git will be confused—those commits don't exist on the remote anymore.

The result? Duplicate commits, merge conflicts, and a very sad team.

What happens when you rebase a shared branch:

Your teammate's history:  A---B---C---D (their work based on your commits)
Your history after rebase: A'--B'--C' (you rewrote history)

Git sees: "Hey, A-B-C and A'-B'-C' look similar but have different SHAs!"
Result: Both sets of commits exist → merge hell
Enter fullscreen mode Exit fullscreen mode

The rule: If a branch is local and you haven't pushed it yet, rebase away. If others have pulled it, merge instead.


Real-World Workflow Example

Let's walk through a realistic scenario. You're working on a feature branch, and main has moved forward. Here's how both approaches play out:

Scenario: Feature Branch Behind Main

Your feature branch:  A---B---C (feature)
                     /
Main branch:         D---E---F---G---H (main)
Enter fullscreen mode Exit fullscreen mode

Approach 1: Merge

git checkout feature
git merge main
Enter fullscreen mode Exit fullscreen mode

Result:

    A---B---C-------M feature
   /               /
  D---E---F---G---H main
Enter fullscreen mode Exit fullscreen mode

You get a merge commit M. Your history now shows exactly when your feature caught up with main. But your feature branch history now has an extra commit, and the graph is more complex.

Approach 2: Rebase

git checkout feature
git rebase main
Enter fullscreen mode Exit fullscreen mode

Result:

                  A'--B'--C' feature
                 /
  D---E---F---G---H main
Enter fullscreen mode Exit fullscreen mode

Your commits A-B-C are replayed on top of H. Your history is linear and clean. But note: A-B-C no longer exist—they've been replaced by A'-B'-C'.

Which to Choose?

  • Use rebase if the branch is local (not pushed) and you want a clean PR.
  • Use merge if the branch is shared or you want to preserve the exact history.

FAQ / Troubleshooting

Q1: "I rebased and now I can't push!"

You're seeing:

! [rejected]        feature -> feature (non-fast-forward)
Enter fullscreen mode Exit fullscreen mode

This is expected. Rebase rewrote history, so your local branch diverged from the remote. You need to force-push:

git push --force-with-lease origin feature
Enter fullscreen mode Exit fullscreen mode

Use --force-with-lease instead of --force—it's safer and won't overwrite others' work if the remote has commits you don't know about.

Q2: "Should I use rebase or merge for git pull?"

By default, git pull does a merge. If you prefer rebase:

git pull --rebase origin main
Enter fullscreen mode Exit fullscreen mode

Or set it as your default:

git config --global pull.rebase true
Enter fullscreen mode Exit fullscreen mode

Q3: "I rebased and have conflicts in every commit. Help!"

This happens when rebasing onto a branch with conflicting changes. You can:

  • Resolve each conflict as it arises (tedious but accurate)
  • Or use git rebase --abort to cancel and merge instead

Q4: "Can I rebase after I've already pushed?"

Technically, yes—but you'll need --force-with-lease. Whether you should depends on whether others have pulled your branch. If you're working alone on the branch, go ahead. If it's shared, merge instead.

Q5: "What about git merge --squash?"

git merge --squash takes all commits from your feature branch and squashes them into a single commit on your target branch, without creating a merge commit. It's useful when you want a clean, single-commit integration but don't want to rebase.


Conclusion

Merge and rebase aren't enemies—they're tools for different jobs:

  • Use git merge when you want to preserve history, work collaboratively on shared branches, or document when work converged. It's safe, non-destructive, and beginner-friendly.

  • Use git rebase when you're working on a local feature branch and want a clean, linear history before submitting a PR. It's powerful but requires understanding its destructive nature.

The golden rule is simple: never rebase public branches. If others have pulled it, merge. If it's local and personal, rebase away.

And remember: a messy history with working code beats a "perfect" history with broken merges. Choose the approach that serves your team, your workflow, and your sanity.

Top comments (0)