You've been heads-down on a feature branch for four days. The tests pass, the code is clean, you're proud of it. Then you open the pull request and someone leaves a one-line comment: "Can you rebase onto main first?"

And you freeze. Not because you don't know the command — you do, it's right there in your shell history — but because the last time you ran it, something went sideways. Commits appeared twice. A teammate's work vanished from the log and then reappeared. Someone had to be summoned to fix it. So now you nod, close the tab, and quietly git merge main instead, hoping nobody notices the extra merge commit.

If that scene feels familiar, you're in very good company. Merge versus rebase is one of those topics where everybody has an opinion, most explanations start with a diagram of circles and arrows, and almost nobody tells you the one thing that actually resolves the confusion. So let's start there.

The question is really two questions

Here's the reframe that makes everything else click:

"Merge or rebase?" is unanswerable. "How do I sync my branch, and how do I land my branch?" has clear answers.

Those are two entirely different operations, and lumping them together is why the debate never ends.

Syncing is what you do while your feature branch is still in flight. Main has moved on — three other people shipped things this week — and you want their changes underneath yours so you're not building on a stale foundation.

Landing is what happens at the end, when your finished work becomes part of main. That's a one-time event, usually handled by a merge button in GitHub or GitLab rather than by you at the terminal.

Once you separate them, the guidance stops being a philosophical argument and becomes a pretty boring rule of thumb: rebase to sync, merge to land. Everything below is just the reasoning behind that sentence.

What each one actually does to your history

A merge takes two divergent lines of history and ties them together with a new commit that has two parents. Nothing that already existed is touched. Your commits keep their original hashes, their original timestamps, their original parents. It is a fundamentally non-destructive operation, which is exactly why it's safe.

A rebase does something different. It takes your commits, sets them aside, moves your branch pointer to the tip of main, and then replays your commits one at a time on top. The word "replay" is doing a lot of work there — each replayed commit is a brand-new commit with a new hash. The old ones still exist in the reflog for a while, but as far as the visible history is concerned, they've been rewritten.

# Sync: replay my commits on top of the latest main
git fetch origin
git rebase origin/main

# vs. Sync by merging: creates a merge commit on my branch
git fetch origin
git merge origin/main

The result of the first command is a straight line: main's history, then your commits, in order, as if you'd started work this morning. The result of the second is your branch with a "Merge branch 'main' into feature/x" commit sitting in the middle of it — and if your branch lives for two weeks, you'll accumulate five or six of those, each one a small speed bump for whoever reviews the diff later.

That's the real cost of merge-to-sync. Not that it's wrong, but that it makes git log noisier and git bisect less pleasant. When you're hunting a regression six months from now, a linear history means every commit is a clean, testable state. A history braided with sync merges means some commits are just bookkeeping.

Why rebase gets a bad reputation

Because of one rule, and one rule only:

Never rebase commits that other people have already pulled.

That's the whole thing. Rebase rewrites history, and rewriting history that someone else has a copy of means their copy and yours no longer agree about what happened. When they next pull, Git tries to reconcile two versions of the same work and produces duplicate commits, phantom conflicts, and the kind of confusion that ends in someone deleting their local clone and starting over.

On a personal feature branch that only you have touched, this risk is essentially zero. Rebase freely. On main, on develop, on a release branch, on a feature branch you're pairing on with someone else — don't. Use merge there.

There's one more wrinkle worth knowing. After you rebase a branch you'd already pushed, your local and remote versions have diverged, and a plain git push will be rejected. The temptation is to reach for --force. Don't. Reach for this instead:

git push --force-with-lease

--force-with-lease refuses the push if someone else has added commits to the remote branch since you last fetched. Plain --force would silently erase their work. It's a two-word difference that has saved a lot of afternoons.

The conflict experience is genuinely different

This is the part nobody warns you about, and it surprises people the first time.

When you merge, conflicts arrive all at once. One stop, one resolution, done. If your branch has fifteen commits and main has changed a file you touched, you resolve that file once.

When you rebase, conflicts arrive commit by commit. Git replays commit one, hits a conflict, stops. You fix it, run git rebase --continue, and it replays commit two — which may conflict in the same file, in a slightly different way, because commit two was written assuming commit one's version of the code. On a long branch with a messy conflict, this can feel like resolving the same problem four times.

Two things make this bearable. First, turn on rerere ("reuse recorded resolution"), which remembers how you resolved a conflict and applies the same resolution automatically the next time it sees it:

git config --global rerere.enabled true

Second, rebase often. A branch that syncs with main daily rarely has more than one conflicting commit. A branch that syncs after three weeks is going to hurt no matter which strategy you pick. The pain isn't caused by rebase; it's caused by branch age. Rebase just makes you feel it earlier, which is arguably a feature.

And if a rebase does go wrong mid-flight, you're never trapped:

git rebase --abort        # put everything back the way it was
git reflog                # find the commit you were on before the rebase
git reset --hard HEAD@{5} # and return to it

The reflog is Git's undo history for branch tips. It's the reason "I destroyed my repo with a rebase" is almost always recoverable.

Landing the branch: three buttons, three histories

When the PR is approved, your platform offers three ways to put it into main. They produce meaningfully different histories.

OptionWhat main getsBest when
Merge commit (--no-ff)Every commit, plus a merge commit grouping themThe branch has several meaningful, self-contained commits
Squash and mergeOne commit containing the whole changeThe branch is "wip", "fix typo", "actually fix it" — one logical change, messy history
Rebase and mergeEvery commit, replayed linearly, no merge commitYou want a strictly linear main and every commit stands alone

For most teams most of the time, squash and merge is the sane default. It gives main one clean commit per pull request, which makes git log read like a changelog and makes reverting a feature a single command. The cost is that the individual commits inside the branch are gone from main's history — which matters far less than people expect, because the PR itself preserves them.

Save the true merge commit for branches where the intermediate steps carry information: a large refactor split into reviewable stages, a migration where each commit is independently deployable.

Putting it together

Here's the full loop, start to finish, for a typical feature:

git switch -c feature/user-export        # branch off main
# ... work, commit, work, commit ...

git fetch origin                          # sync, daily
git rebase origin/main
git push --force-with-lease               # your branch only — safe

# open PR, get review, then use "Squash and merge" in the UI

git switch main && git pull               # come home
git branch -d feature/user-export         # tidy up

That's it. No philosophy required.

If your team hasn't settled this, the fastest way to end the recurring debate is to write three lines in your CONTRIBUTING file — sync with rebase, land with squash, never rewrite shared branches — and enable the corresponding setting in your repo so the merge button only offers the option you've agreed on. Ninety percent of Git arguments are really arguments about an undocumented default.

The short version: rebase is a tool for tidying work that's still yours. Merge is a tool for combining work that belongs to everyone. Use --force-with-lease instead of --force, turn on rerere, and sync often enough that conflicts stay small.

Git rewards people who understand its model rather than memorizing its commands — and the model here is genuinely simple once the two questions are pulled apart. The next time someone asks you to rebase before merging, you'll know exactly what they're asking for, exactly why it's safe, and exactly how to undo it if it isn't.