Skip to content
C Codeloom
Git

Git Rebase vs Merge: When to Use Which

A clear, practical guide to choosing between git rebase and git merge, with safe workflows for feature branches, shared branches, and pull requests.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What merge and rebase actually do to history
  • When fast-forward, no-ff merge, or rebase is right
  • Why never to rebase a shared branch
  • A safe rebase workflow for feature branches
  • How to recover from a bad rebase

Prerequisites

  • Comfortable with basic git add, commit, and push

What and Why

Git lets you integrate one branch into another two main ways: merge and rebase. Both produce the same final source code. They produce very different history. The choice affects readability, bisect-ability, and team trust. Picking one consistently across a team matters more than which one you pick.

Mental Model

  • Merge creates a new commit that has two parents. History fans out and joins. Nothing is rewritten.
  • Rebase replays your commits one by one on top of another branch. History stays linear. Commit hashes change.
Start:
main:   A --- B --- C
                     \
feat:                 D --- E

After merge:
main:   A --- B --- C ---------- M
                     \         /
feat:                 D --- E --

After rebase + fast-forward:
main:   A --- B --- C --- D' --- E'
Merge vs rebase outcomes

The ' marks indicate D and E are new commits with new hashes, even though the patches are the same.

Hands-on Example

A safe rebase workflow for a feature branch:

git checkout feat/login
git fetch origin
git rebase origin/main
# resolve any conflicts, then:
git rebase --continue
git push --force-with-lease origin feat/login

--force-with-lease is critical. Plain --force overwrites whatever is on the remote, including a teammate’s commit you have not seen. --force-with-lease only pushes if the remote still points to the commit you expected.

A merge workflow integrating main into your branch:

git checkout feat/login
git fetch origin
git merge origin/main
# resolve conflicts, commit, then:
git push origin feat/login

And the team-side integration once a PR is approved. Repository settings usually pick one of three strategies:

  • Merge commit: preserves the full branch shape. Honest history, busier graph.
  • Squash and merge: collapses the PR into one commit on main. Clean main, lost intra-PR history.
  • Rebase and merge: replays PR commits on main with no merge commit. Linear, preserves commit granularity.

Common Pitfalls

Rebasing a shared branch. Never rebase a branch that other people have based work on. You rewrite history they have already pulled, and their next push will create duplicate commits or be refused entirely.

Force-push without --with-lease. Easy way to destroy a teammate’s work. Configure git to refuse plain push --force:

git config --global alias.pushf 'push --force-with-lease'

Resolving conflicts wrong during rebase. During a rebase, “ours” and “theirs” flip relative to merge. Use the commit message and patch context to decide which side is correct, not the labels.

Forgetting to update local main first. Rebasing onto a stale local main puts your work behind the real tip. Always git fetch first; prefer git rebase origin/main over git rebase main.

Rebasing merges. By default, rebase flattens merge commits. If your branch has intentional merges you want to keep, use git rebase --rebase-merges.

Practical Tips

Pick one team policy and stick to it. A common, balanced choice:

  • Feature branches: rebase onto main while in progress.
  • Pull request integration: squash and merge (or rebase and merge) into main.
  • Long-running release branches: regular merge from main, no rebase.

For an interactive cleanup before pushing, use:

git rebase -i origin/main

Reorder commits, squash WIP commits into meaningful ones, fix commit messages. Then force-push your feature branch.

Recover from a bad rebase using the reflog:

git reflog
# find the commit hash before the rebase started, e.g. HEAD@{8}
git reset --hard HEAD@{8}

The reflog is your time machine. Anything you did locally in the last 90 days is there.

For PRs that show up out of date with main, enable “Rebase before merging” in your platform settings rather than letting a stale merge land.

Use git pull --rebase by default to avoid pointless merge bubbles in your local history:

git config --global pull.rebase true

Wrap-up

Merge preserves history exactly as it happened. Rebase rewrites history into a cleaner story. Use rebase on your own feature branches to keep the log readable, use merge (or squash) at integration time, and never rewrite history that lives on someone else’s clone. With --force-with-lease, a clear team policy, and the reflog as a safety net, you can move between both fluently without losing work or trust. The right answer is rarely “always merge” or “always rebase” — it is “match the tool to the lifecycle stage of the branch.”