Skip to content

Merge vs Rebase

Both merge and rebase integrate changes from one branch into another, but they produce different histories.

Creates a merge commit that joins two branch histories:

Before:
A---B---C main
\
D---E feature
After merge:
A---B---C---F main
\ /
D---E

F is the merge commit. History shows exactly when and how branches joined.

Terminal window
git switch main
git merge feature/login

Advantages:

  • Non-destructive — no existing commits are changed
  • Complete record of when branches merged
  • Safe for shared branches

Disadvantages:

  • Creates an extra commit for every merge
  • History can become cluttered with merge commits

Re-applies commits from one branch on top of another, rewriting history:

Before:
A---B---C main
\
D---E feature
After rebase:
A---B---C main
\
D'---E' feature (new commits, same changes)

D' and E' are new commits with the same changes as D and E but different parent commits and hashes.

Terminal window
git switch feature/login
git rebase main

Advantages:

  • Linear, clean history — easy to read with git log --oneline
  • No noise from merge commits
  • Makes git bisect and git blame easier to use

Disadvantages:

  • Rewrites history — changes commit SHAs
  • Dangerous on shared branches (others may have based work on the original commits)

Never rebase commits that have been pushed to a shared branch.

Rebasing rewrites history. If others have based work on the original commits, rewriting creates divergent histories and painful conflicts.

Safe to rebase:

  • Your local feature branch (before pushing)
  • After pushing but before anyone else has based work on it

Unsafe to rebase:

  • main, develop — shared branches
  • Any branch others are actively using

Rewrite history for your local commits:

Terminal window
git rebase -i HEAD~3 # rewrite last 3 commits

Opens an editor where you can:

pick abc1234 Add user model
pick def5678 Add user controller
pick ghi9012 Fix typo in controller
# Commands:
# p, pick = use commit
# r, reword = change commit message
# e, edit = stop and amend commit
# s, squash = merge into previous commit
# f, fixup = merge into previous (discard message)
# d, drop = remove commit

Squash commits before merging a PR:

Terminal window
git rebase -i origin/main
# Mark all but the first commit as 'squash'
# Write a single clean commit message
git push --force-with-lease

After rebasing a branch you’ve already pushed, you need to force push:

Terminal window
# Safe — fails if the remote has been updated by someone else
git push --force-with-lease
# Dangerous — overwrites remote regardless
git push --force

Always use --force-with-lease when force pushing.

ScenarioRecommendation
Merging a PR into mainSquash and merge (clean history) or merge commit
Keeping a feature branch up to dateRebase onto main (linear history)
Integrating long-running branchesMerge (preserves context)
Shared branch that others useMerge only
Cleaning up local commits before PRInteractive rebase + squash

GitHub offers three merge strategies per repository:

  • Merge commitgit merge --no-ff — preserves full branch history
  • Squash and merge — all PR commits squashed into one — cleanest main history
  • Rebase and merge — commits replayed linearly — no merge commit

Squash and merge is the most popular choice for keeping main clean.