git rebase vs git merge — when to actually use each

Two commands, one job — get changes from one branch into another — and somehow this is the topic that turns otherwise calm engineers into superstitious campfire storytellers. "Don't rebase, ever." "Always rebase, merge commits are pollution." Both camps are right, and both are wrong, because they're answering a question that was never asked.

The real question isn't "which is better." It's "whose history am I about to touch?" Answer that, and the choice between merge and rebase stops being a debate and becomes obvious. Here's the whole picture.

In this post
  • What merge actually does — and what it costs you
  • What rebase actually does — and why it rewrites history
  • The one golden rule that keeps you out of trouble
  • A concrete workflow for when to reach for each
  • Interactive rebase — the feature that makes it all worth it

The problem both commands solve

You're working on a feature branch. Meanwhile, main has moved ahead — bug fixes landed, other features merged. Now you need to reconcile the two: either pull main's changes into your branch so you're not building on stale code, or fold your finished work back into main.

Both merge and rebase accomplish that integration. The difference is entirely about what the history looks like afterward — and whether the commits you had before are the same commits you have after.

How merge works

git merge ties two histories together with a new merge commit — a commit with two parents, one from each branch. Nothing that already exists is changed.

merge.txt
# on the feature branch, pull in main's latest
git checkout feature
git merge main

# before:                  after:
# main:    A─B─C─D─E       main:    A─B─C─D─E
#               \                       \     \
# feature:       F─G          feature:    F─G───M   ← merge commit M

The merge commit M has two parents — E from main and G from your branch. Every original commit is still there, untouched. The history is complete and honest: you can see exactly when the branches diverged and when they came back together.

The cost? With lots of branches and frequent syncing, your history turns into a subway map — merge commit after merge commit, and git log starts reading like a group chat.

How rebase works

git rebase takes a different approach. Instead of tying the histories together, it replays your commits on top of the target branch. It sets your commits aside, moves your branch to the tip of main, then re-applies each of your changes as a brand-new commit.

rebase.txt
# on the feature branch, replay onto main's latest
git checkout feature
git rebase main

# before:                  after:
# main:    A─B─C─D─E       main:    A─B─C─D─E
#               \                       \
# feature:       F─G          feature:    F'─G'   ← new commits, new SHAs

Notice the primes: F and G are gone from the branch, replaced by F' and G'. They're new commits with new SHAs, even though the changes are identical. The old commits still exist (you can find them in the reflog), but nothing points to them anymore.

The result is a clean, linear history — it looks as if you had simply started your work from the latest main. Beautiful for review, beautiful for git log. And that "rewrites history" part is both the superpower and the danger.

The golden rule

Everything you need to stay safe fits in one sentence:

Never rebase a branch others are using

Rebase rewrites history. Rewriting history that only you have is tidying up. Rewriting history that your teammates have already pulled is vandalism — their local commits no longer match yours, and everyone ends up in a detached, conflicting mess. If a branch has been pushed and shared, it's merge-only.

The corollary is a clean division of labor:

  • Shared branches (main, develop, anything a teammate has pulled) — merge only.
  • Your own branches (local, or pushed only for backup/CI) — rebase freely.

That's it. Most of the horror stories you've heard are someone rebasing a shared branch and force-pushing over other people's work. Follow the rule and rebase is harmless; break it and you'll understand why the campfire stories exist.

When to use each

With the golden rule in place, the day-to-day guidance is simple:

  • Use rebase to sync your feature branch with the latest main while you're still working on it. Keeps your branch current and your eventual diff clean.
  • Use rebase (interactive) to tidy your local commits before opening a PR — squash the "wip" and "fix typo" noise into coherent units.
  • Use merge to integrate finished work back into main — usually via a pull request, where the merge (or squash-merge) happens on the shared branch.
  • Use merge when the branch was collaborative. If two people committed to it, its history belongs to both of you — don't rewrite it.
A mental model that sticks

Rebase is for your drafts, merge is for the published record. You rewrite your own rough notes; you don't rewrite the chapter that's already in print.

The workflow most teams converge on looks like this:

the-workflow.sh
# 1 · branch off main, commit freely (messy is fine, it's yours)
git checkout -b feature/login main

# 2 · before opening the PR, tidy + sync
git rebase -i main      # squash/reword your rough commits
git fetch origin
git rebase origin/main  # replay onto the latest main

# 3 · push (force-with-lease if the branch was already pushed)
git push --force-with-lease

# 4 · open the PR; the merge into main happens on the shared branch

Interactive rebase — the killer feature

Plain rebase replays commits; interactive rebase lets you edit them as they're replayed. Run git rebase -i HEAD~3 and your editor opens with your last three commits, oldest first, each prefixed with a command:

git-rebase-todo
pick   a1b2c3d add login form
squash e4f5g6h wip
squash i7j8k9l fix typo
reword m0n1o2p update readme

# pick    keep the commit as-is
# squash  fold it into the previous commit
# reword  keep it, but change the message
# drop    delete it entirely
# (you can also reorder the lines to reorder the commits)

This is how a messy afternoon of wip, wip, fix typo, actually fix it becomes one clean, well-described commit before anyone else ever sees it. Paired with a decent commit-message convention — see the Conventional Commits cheat sheet — your PR history reads like a table of contents instead of a stream of consciousness.

When it goes wrong — getting out alive

Rebase has a reputation for eating work, but it almost never actually loses anything. You just need to know the exits:

  • Mid-rebase, conflicts everywhere, you regret everything: git rebase --abort returns you to exactly where you were before you started. No harm done.
  • Mid-rebase and you want to keep going: resolve the conflicts, git add the files, then git rebase --continue.
  • Rebase already finished and it's wrong: git reflog lists every position HEAD has held. Find where you were and git reset --hard HEAD@{n} to go back. The reflog is your undo history — commits are almost never truly gone.
Push with --force-with-lease, never --force

If you must force-push a rebased branch, use --force-with-lease. It refuses to push if someone else has pushed to the branch since you last fetched — a seatbelt against overwriting a teammate's work. Plain --force has no seatbelt.

Wrap-up

Merge preserves history; rebase rewrites it. Merge for shared branches and finished work; rebase for your own drafts — to sync with main and to tidy your commits before anyone sees them. The golden rule ("never rebase what others have pulled") is the whole safety story, and git reflog is your parachute when you still manage to slip.

Once your history is clean, make the messages worth keeping — the commit message generator drafts a conventional commit in seconds, and the cheat sheet covers the types and the 50-character rule. And if your clean, rebased branch still won't deploy because the browser is yelling about origins, that's a different post entirely.