datarekha
Git June 7, 2026

Git merge vs rebase: when to use which (without wrecking history)

Git merge vs rebase both integrate branches but produce different histories. Learn when each is right, the golden rule, and how to recover when things go wrong.

12 min read · by datarekha · gitmergerebaseversion controlbranching

Every developer eventually stares at a git log that looks like a bowl of spaghetti and wonders: should I have used rebase? Then they try rebase on the wrong branch and spend an afternoon untangling force-push fallout. Both operations integrate one branch into another. The history they leave behind is completely different, and that difference is what determines which one belongs where.

This post maps out the mechanics of each approach, explains the golden rule that keeps teams out of trouble, and walks through the practical decision points you will encounter on real projects.

What merge and rebase actually do

When you run git merge feature on your main branch, Git looks at three commits: the tip of main, the tip of feature, and the common ancestor they share. If your branch has not diverged at all, Git fast-forwards the pointer and no merge commit is created. Once divergence exists, Git performs a three-way merge and records a new merge commit with two parents. That commit is an honest record in the graph: it says “at this point in time, these two lines of work came together.”

When you run git rebase main from your feature branch, Git does something fundamentally different. It finds the common ancestor, lifts each of your feature commits off the branch one by one, and replays them on top of the current tip of main. The result looks as if you had started your feature work from main’s latest commit all along. The history is linear, the log is tidy, but here is the catch: every replayed commit gets a new SHA. The old commits still exist in the reflog, but they are no longer reachable from any branch pointer.

The diagram below shows both outcomes side by side.

git mergeABCDEMmerge2 parentsmainfeaturegit rebaseABCD’E’replayed, new SHAsmain tipfeature (rebased)
Left: merge creates a merge commit M with two parents. Right: rebase replays D and E as new commits D’ and E’ on the updated main tip — linear, but with rewritten SHAs.

Merge: when the honest record matters

A merge commit carries something that a rebase throws away: proof of parallel work. When you look at a project a year from now and see that commit M, you can reconstruct exactly what state main was in when feature landed, and exactly what the feature branch contained. That context is invaluable during incident investigations, audits, and archaeology sessions.

Three-way merges also have a soft advantage in conflict resolution. Because Git is integrating two complete branch states, the conflict markers you see are usually the full context of what each side was trying to do. You resolve once at integration time and move on.

Merge is the right default for:

  • Integrating a finished, reviewed feature branch into main or develop
  • Any branch that one or more colleagues have checked out or rebased against
  • Situations where you want the graph to show the true shape of how work progressed — especially on long-lived projects that do post-mortems

If the branch has not diverged (no new commits on main since you branched), Git automatically fast-forwards: the pointer moves and no merge commit appears at all. You get the clean history without doing anything special.

For more on how branches work under the hood, see Git branches.

Rebase: when clean linearity is the goal

A linear history is genuinely easier to read when you are reviewing changes commit-by-commit with git log --oneline or bisecting to find a regression. Each commit sits directly after the previous one with no forks. git bisect performs fewer iterations because the graph is a straight line.

Rebase shines when you want to:

  • Keep a local feature branch current as main moves forward before opening a pull request
  • Clean up your own private commit history before sharing — squash “WIP” saves, reorder logical steps, rewrite poor commit messages — using git rebase -i
  • Apply git pull --rebase to integrate upstream changes without creating a noisy merge commit in your local history every time you sync

Interactive rebase (git rebase -i HEAD~N) is one of the most useful tools in the Git toolkit. It lets you squash, fixup, reword, drop, and reorder commits before anyone else ever sees them. The result is a thoughtful, readable chain of commits that tells a coherent story rather than a raw transcript of every “oops” save.

For a workflow that ties this together in practice, see GitHub Flow and pull requests.

The golden rule

The second diagram shows exactly what happens.

The golden rule: shared branch rebase diverges collaboratorsBeforeABCorigin/shared — everyone has pulled A, B, CAfterYou force-pushed → origin/shared has A’, B’, C’A’B’C’diverged from collaborator’s copyABCCollaborator still has A, B, C locally
After a force-push rebase, origin/shared has A’, B’, C’ while your collaborator still holds A, B, C — two incompatible versions of the same branch.

The practical boundary is simple: rebase is safe on commits that only exist in your local repository or on a remote branch that only you use. Once a commit is on a branch other people have pulled, it is shared history and must not be rewritten.

Squash merge: the third option

Many teams settle on a hybrid approach: squash-merge. When a pull request is merged, GitHub (and GitLab, Bitbucket) can squash all commits from the feature branch into a single commit on main. The result is a clean, linear main with one commit per feature — no merge commit bubbles, no individual “WIP save” noise. The original branch detail is discarded but the PR conversation and diff remain accessible in the pull request UI.

This works well when:

  • Individual commits on a feature branch are not expected to be bisectable units
  • The team wants a tidy main without requiring each contributor to do an interactive rebase manually
  • The PR review flow is the authoritative record, not the raw commit graph

The tradeoff: git bisect will only be able to identify which feature introduced a bug, not which commit within that feature.

Handling conflicts

Both operations force you to resolve conflicts when the same lines diverge — there is no escape hatch. The experience differs in one important way.

With merge, you resolve the conflict once for the entire integration. With rebase, you may resolve conflicts per replayed commit. If your feature branch has ten commits and three of them touch a file that also changed on main, you will be prompted three times. The upside: each resolution is smaller and more contextual. The downside: it is more total work.

When a rebase conflict happens mid-way, git rebase --abort restores the branch to its pre-rebase state. Once you fix a conflict, git rebase --continue moves to the next commit.

git pull --rebase in daily use

The noisiest history problem in most repos is not feature-branch merges — it is the merge commit that appears every time someone runs git pull while the remote has moved forward. Those “Merge branch ‘main’ of github.com/…” commits clutter the log.

git pull --rebase replays your unpushed local commits on top of the fetched remote state instead of creating a merge commit. Because you have not shared those local commits yet, the golden rule is not violated. Most teams configure this as the default:

git config --global pull.rebase true

This single setting removes a large fraction of the noise in collaborative repositories.

Recovery with reflog

When a rebase goes wrong, git reflog is your escape hatch. Git records every movement of HEAD in the reflog, including the state before the rebase began. Running git reflog will show a line like HEAD@{5}: rebase (start): checkout main. The entry immediately before that represents the state of your branch before the rebase.

git reflog
git reset --hard HEAD@{N}

Replace N with the index of the pre-rebase state. This restores the branch pointer to exactly where it was. The reflog entries expire after 90 days by default, so this recovery window is wide for anything you would realistically need to undo.

See the main Git reference for a broader map of the commands and concepts covered here, or check the glossary if any term above is unfamiliar.

A practical decision guide

SituationUse
Merging a finished feature PR into maingit merge (or squash-merge via the host UI)
Keeping a local feature branch current with maingit rebase main
Cleaning up your own unpushed commitsgit rebase -i
Pulling while the remote has moved forwardgit pull --rebase
Shared branch, team has pulled itgit merge only — never rebase
Want one clean commit per feature on mainSquash merge

Frequently asked questions

Can I rebase after I have already pushed a branch?

Yes, but only if you are the sole user of that remote branch and no one else has pulled it. You will need git push --force-with-lease (prefer this over --force — it refuses to overwrite if the remote has moved since your last fetch). The moment a colleague has cloned or pulled that branch, it is shared history and the golden rule applies.

Does rebase lose my commits?

No. Rebased commits become unreachable from the branch pointer but remain in the reflog for 90 days. If something goes wrong, git reflog will show the pre-rebase state and you can git reset --hard to it. The old commits are not deleted; they are just no longer pointed to by a branch.

Which does GitHub use when I click “Merge pull request”?

GitHub offers three options per repository: “Create a merge commit” (standard three-way merge), “Squash and merge” (squash all commits into one on the target branch), and “Rebase and merge” (rebase the PR commits onto the target without a merge commit). Repository admins control which options are available. The default is a merge commit unless the repo owner has changed it.

When should I use interactive rebase?

Before opening a pull request, git rebase -i HEAD~N is ideal for tidying your local commit history: squashing “fix typo” saves into the commit they belong to, rewording vague messages, and reordering commits so reviewers see a logical progression. It has no effect on shared history as long as you do it before pushing. After pushing to a shared branch, interactive rebase violates the golden rule and should not be used.

Skip to content