Sooner or later every Git user faces the same question: should I merge or rebase? Both commands integrate changes from one branch into another, and the end result — your branch contains the other branch’s commits — looks similar at first glance. The difference lies in how the history is shaped, and that has real consequences for your team.

Let’s look at what each command actually does, what it costs, and when to reach for one over the other.

The starting point

Both commands start from the same situation: two branches that have diverged. main moved forward after you created your feature branch, and now you want to bring your work up to date (or bring it back into main).

A --- B --- E        (main)
       \
        C --- D      (feature)

A and B are the common ancestors, E is a new commit on main, and C and D are your feature commits.

git merge

merge creates a new commit with two parents that ties the two histories together. Nothing that already exists is rewritten.

git checkout main
git merge feature

Or, if you want to update your feature branch with the latest main:

git checkout feature
git merge main

The resulting history keeps both lines visible and adds a merge commit M:

  gitGraph
    commit id: "A"
    commit id: "B"
    branch feature
    checkout feature
    commit id: "C"
    commit id: "D"
    checkout main
    commit id: "E"
    merge feature id: "M"

What merge buys you

  • It is non-destructive. Existing commits keep their hashes and identity. This makes merge safe on branches that other people already have.
  • It records context. The merge commit documents when and why two lines of work were joined. Tools like git log --graph show the true shape of the project’s history.
  • Conflicts are resolved once. If C and E touch the same lines, you resolve the conflict a single time, in the merge commit.

What merge costs you

  • A noisy history. On an active repository, merging back and forth produces a forest of merge commits that makes git log hard to read.
  • Bisecting gets harder. A merge commit can bundle several unrelated changes, so git bisect may point at a merge instead of the commit that actually broke something.
  • Fast-forward merges blur boundaries. If the target branch has not moved, git merge simply slides the pointer forward and creates no commit at all — the fact that a feature was merged disappears.

If you want a merge commit even when a fast-forward is possible, force it:

git merge --no-ff feature

This is a common convention: it preserves the “this was a feature branch” information in the graph.

git rebase

rebase takes the commits unique to your branch, reapplies them one by one on top of another base, and moves the branch pointer to the new copies. The originals are discarded.

git checkout feature
git rebase main

The result is a straight line — as if you had branched off the latest main in the first place:

  gitGraph
    commit id: "A"
    commit id: "B"
    branch feature
    checkout feature
    commit id: "C"
    commit id: "D"
    checkout main
    commit id: "E"
    branch feature-rebased
    checkout feature-rebased
    commit id: "C2"
    commit id: "D2"

Notice that C and D became C2 and D2. They are new commits with new hashes, even though their content may be identical.

What rebase buys you

  • A linear, readable history. git log reads like a changelog: every commit sits on one line, in order.
  • Cleaner pull requests. Rebasing onto main before opening a PR means the reviewer sees only your changes, without unrelated merge commits.
  • Easier bisecting. Each change is isolated in its own commit.
  • Powerful history editing. Interactive rebase lets you rewrite your own commits before sharing them:
git rebase -i main

In the editor you can reword messages, squash fixups into the previous commit, drop noise, or edit a commit to split it.

What rebase costs you

  • It rewrites history. Every rebased commit gets a new hash. Anyone who already pulled the old commits now has a diverged history.
  • Conflicts can repeat. If C and D both touch the same lines that changed in E, you may resolve the same conflict more than once, once per replayed commit.
  • It is a bigger mental model. Losing track of HEAD during a rebase can be intimidating, though git reflog and git rebase --abort are reliable escape hatches.

The golden rule

Never rebase commits that exist outside your local repository.

A rebase rewrites hashes. If someone else has based work on the old commits, their branch and yours no longer share history, and Git cannot reconcile them cleanly. The usual formulation is: rebase your own private work, merge shared work.

A reasonable policy for most teams:

  • Local, unpushed commits — rebase freely.
  • Your own feature branch that nobody else uses — rebase freely, but coordinate before force-pushing.
  • main, release branches, anything others pull from — merge only.

What happens on conflicts

This is where the two commands feel most different in practice.

Merge stops once, shows you all conflicting hunks, and you resolve them together:

git merge main
# ... resolve files ...
git add .
git commit

Rebase stops at the first commit it cannot replay, then continues commit by commit:

git rebase main
# ... resolve files ...
git add .
git rebase --continue
# may stop again for the next commit

If it goes wrong, you can always bail out:

git rebase --abort

And if you finish and regret it, the original branch tip is still in the reflog:

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

Force-pushing after a rebase

If you already pushed the feature branch, rebasing makes your local branch diverge from the remote. A normal push is rejected, so you need a force push — but never a bare --force:

git push --force-with-lease

--force-with-lease refuses to overwrite the remote if it has commits you have not seen. It is the difference between “I meant to rewrite my branch” and “I just erased a colleague’s work.”

Comparison at a glance

git merge git rebase
History shape Branching, with merge commits Linear
Existing commits Preserved, same hashes Rewritten, new hashes
Safe on shared branches Yes No
Conflict resolution Once, in the merge commit Possibly once per replayed commit
Traceability of feature boundaries Explicit merge commits (with --no-ff) Lost — commits appear inline
git bisect friendliness Lower (merge commits bundle changes) Higher (one change per commit)
Typical use Integrating finished work into main Updating a private branch, cleaning up commits

When to use which

Reach for rebase when:

  • you want to bring a feature branch up to date with main before review;
  • you are cleaning up local commits — squashing fixups, fixing messages, reordering;
  • the branch is yours alone and has not been shared;
  • you want the project history to read as a linear sequence of changes.

A common flow:

git checkout feature
git fetch origin
git rebase origin/main
# resolve conflicts, then:
git push --force-with-lease

Reach for merge when:

  • you are integrating a finished feature into a shared branch;
  • the branch has already been pushed and others may have based work on it;
  • you want to preserve the real topology of the project — who branched from where, and when it was joined;
  • you are pulling changes into a long-lived branch and want a record of the integration.

A common flow:

git checkout main
git pull --ff-only
git merge --no-ff feature
git push

Many teams combine both: rebase locally to keep the branch tidy, merge to integrate it. That gives you a clean feature history and a shared history nobody has to force-push.

Merge, squash, or rebase?

A third option worth mentioning: git merge --squash collapses all commits from a branch into a single set of staged changes, which you then commit yourself. It produces a linear history like rebase, but without replaying commits one by one — and without preserving the individual commits. Platforms like GitHub and GitLab offer the same thing as a “Squash and merge” button.

Rough guidance:

  • Merge commit — you care about the branch’s internal history and its boundaries.
  • Squash — you care only about the net change, and the branch’s intermediate commits are noise.
  • Rebase — you care about a linear history and the individual commits are meaningful.

Summary

merge and rebase are not rivals; they are two tools for two different jobs. Merge preserves history and is safe to use on anything shared. Rebase rewrites history to produce a clean, linear story and is safe only on work that has not left your machine.

The rule that keeps teams out of trouble is simple: rebase your private work, merge shared work — and if you must rewrite something that was already pushed, use --force-with-lease and make sure nobody else is standing on that branch.