Articles

Git Rebase vs. Merge: A Practical Guide

When should you rebase and when should you merge? A clear, example-driven breakdown of the trade-offs, plus a simple workflow you can adopt today.

The Lycoris Team The Lycoris Team · · Updated · 5 min read
Source code displayed on a screen

git merge and git rebase solve the same problem — combining work from two branches — but they do opposite things to your history. Merge ties the two lines together with a new merge commit and changes nothing that already exists; rebase replays your commits on top of a new base, rewriting them into brand-new commits with new SHAs. Once that distinction is clear, almost every “which one should I use?” question answers itself.

This guide assumes you’re comfortable creating branches and committing to them — if not, start with what Git actually is and come back.

What merge actually does

Say you branched feature off main and made three commits. Meanwhile, main moved on without you:

      A---B---C   feature
     /
D---E---F---G   main

Merging joins the two branch tips with a merge commit — a commit with two parents:

git checkout main
git merge feature
      A---B---C   feature
     /         \
D---E---F---G---M   main

Nothing that existed before is modified. A, B, and C keep their SHAs, and the new commit M permanently records that a branch existed, what it contained, and when it landed. Merge is honest and non-destructive. The cost is cosmetic: on a busy repository, dozens of parallel branches produce a tangled graph that’s genuinely hard to read.

What rebase actually does

Rebase takes the same starting point and replays your commits, one at a time, on top of the other branch:

git checkout feature
git rebase main
              A'--B'--C'   feature
             /
D---E---F---G   main

The apostrophes are the whole story. A', B', and C' contain the same changes as A, B, and C, but they are new commits — new parent, new SHA, new committer timestamp. The originals are abandoned. History now reads as if you started your work after G, which is tidier than the truth. That’s the trade: a clean, linear story in exchange for rewriting what happened.

When merge is the right tool

  • Shared branches. main, develop, release branches — anything other people build on should only ever move forward. Merge never rewrites existing commits, so it’s always safe there.
  • Preserving true history. A merge commit records exactly when a feature landed and what it included. That context is valuable when you’re debugging a regression months later, and an unrewritten history is easier to trust when supply-chain provenance matters.
  • Landing pull requests. The merge (or squash-merge) button on a PR is the natural end of a feature branch’s life.

When rebase is the right tool

  • Updating a feature branch on the latest main. Running git rebase main keeps your branch current without littering it with “merge main into feature” commits, and your eventual PR diff is against fresh code.
  • Cleaning up local history before review. Your local work-in-progress commits — “fix typo”, “actually fix it” — are drafts. Rewriting drafts before publishing them isn’t dishonest; it’s editing.

The golden rule — and why it exists

Never rebase commits that other people may have already pulled.

The reason follows directly from how rebase works. Rebase a pushed branch and the remote now holds A', B', C', while a teammate’s clone still holds A, B, C. To Git these are unrelated commits that happen to contain similar changes. On the teammate’s next pull, Git tries to reconcile the two histories — producing duplicated commits, spurious conflicts, and a merge nobody intended. Multiply that by every person tracking the branch and you’ve turned a cosmetic cleanup into a team-wide recovery exercise. Rebase your own unshared work as freely as you like; leave shared history alone.

Interactive rebase, briefly

git rebase -i main opens an editor listing your branch’s commits, each prefixed with a command you can change: pick keeps a commit, reword edits its message, squash melds it into the previous commit and combines the messages, fixup melds it and discards the message, and drop deletes it outright. Reordering the lines reorders the commits. It’s the standard way to turn eleven messy local commits into two coherent ones before opening a PR — and it’s still a rebase, so the golden rule applies in full.

Squash merges: the pragmatic team default

Many teams sidestep the whole debate. Contributors commit however they like on their branch, and the PR lands on main as a single squashed commit. main gets one commit per feature — easy to read, easy to revert, easy to bisect — and each commit becomes a meaningful unit for your CI pipeline to build and deploy. The branch’s fine-grained history is discarded, but the PR discussion usually preserves everything worth keeping.

Force pushes: --force-with-lease over --force

After rebasing a branch you’ve already pushed — your own PR branch, say — an ordinary git push is rejected because the histories diverge. You need a force push, and there are two kinds:

git push --force              # overwrite the remote, no questions asked
git push --force-with-lease   # overwrite only if the remote is where you last saw it

Plain --force will happily obliterate a commit a teammate pushed to the branch while you were rebasing. --force-with-lease refuses if the remote has moved since you last fetched, turning silent data loss into a visible error. There’s no good reason to type plain --force in day-to-day work.

Decision table

SituationReach for
Bring your feature branch up to date with maingit rebase main
Tidy local commits before opening a PRgit rebase -i
Land a finished PR on mainMerge or squash merge
Combine work on any shared branchMerge
Others have already pulled the branchMerge — never rebase
Push a branch you just rebasedgit push --force-with-lease

The takeaway

Merge joins two histories with a new commit and never touches what already exists; rebase rewrites your commits onto a new base, giving them new SHAs. Rebase your own unpushed work to keep it clean and current, merge or squash-merge anything shared, and never rebase commits someone else may have pulled. When a force push is unavoidable, make it --force-with-lease. Teams that adopt “rebase locally, squash-merge PRs” get a linear, readable main without ever breaking a teammate’s pull.

The Lycoris Team The Lycoris Team · · 4 min read

What Is a Git Worktree? Multiple Branches, One Repo

A git worktree lets you check out several branches at once in separate folders, sharing one .git history without cloning the repo again.

#Git #Developer Tools #Version Control
The Lycoris Team The Lycoris Team · · 3 min read

What Is Git? Version Control, Explained for Beginners

Git is a distributed version control system that tracks changes to code and enables collaboration. Learn the core concepts and everyday workflow.

#Git #Developer Tools #Version Control
Takina Takina · · 4 min read

What Is Semantic Versioning (SemVer)?

Semantic versioning encodes compatibility into a version number's three parts — major, minor, patch — so dependents know what a version bump might break.

#Developer Tools #Version Control #Software