Blog

git merge vs. git rebase

Key Takeaways

  • Merge preserves existing commit IDs. It can fast-forward a branch or create a merge commit that records an integration point.
  • Rebase replays commits onto a new base. Rewritten commits get new IDs, and the resulting feature history can be linear.
  • Prefer merge when collaborators depend on your commits. Rebase is generally safe for private feature work.
  • Choose a team policy deliberately: preserving branch structure and maintaining a linear history serve different needs.

The core difference in git merge vs rebase is the history they produce: merge joins existing histories without rewriting their commits, while rebase rebuilds a commit series on a different base. Use merge to preserve shared commit identity and integration context; use rebase for private work when a linear series better suits your workflow.

Preface: Team Policy Matters

Neither graph shape is inherently better. A linear history can contain confusing commits, and a branching history can clearly explain how a feature came together. Agree on how developers update feature branches and how pull requests enter main; those are separate decisions. Our Git workflow best practices cover the broader collaboration policy.

What Does git merge Do?

Imagine you create a feature branch from main and make two commits. While you work, another developer advances main. You now want those updates in your feature branch without changing main.

Both histories have advanced since their common ancestor, B. In these diagrams, letters are illustrative commit IDs, and HEAD marks your checked-out branch. The label origin/main represents your locally recorded view of the remote main branch.

Before integration:

A---B---C---D     origin/main
     \
      E---F       feature (HEAD)

Assume an existing repository with a remote named origin, a local branch named feature, and a remote main branch. Commit or stash unfinished changes and confirm that git status is clean before proceeding.

git status
git fetch origin
git switch feature
git merge origin/main

With the usual fetch configuration, git fetch origin updates remote-tracking references such as origin/main. It does not integrate those changes into your feature branch. Switching to feature makes that branch the destination of the following merge; the merge does not update main or push anything.

A completed non-fast-forward merge of these two branches creates M, whose parents are F and D. Git combines the branch tips using their common ancestor—a three-way merge—and may stop for conflicts before it can finish. Existing commits A through F retain their IDs.

After merging origin/main into feature:

A---B---C---D     origin/main
     \       \
      E---F---M   feature (HEAD)

M has two parents: F and D.

Fast-Forward Merge: No New Commit

A merge does not always create a merge commit. If feature has no commits beyond B and origin/main has advanced to D, Git can move feature directly to D. This is a fast-forward: the destination already lies on the same line of history.

Before:
A---B             feature (HEAD)
     \
      C---D       origin/main

After git merge --ff-only origin/main:
A---B---C---D      feature (HEAD), origin/main

The merge reference distinguishes the controls:

  • --ff allows a fast-forward when possible and a merge commit when needed.
  • --ff-only refuses the merge if the histories have diverged.
  • --no-ff creates a merge commit even when a fast-forward would be possible. If there is nothing new to merge, Git still leaves the branch unchanged.

Benefits of Merging

Merge is a good fit when existing commit IDs must remain stable. A merge commit also preserves visible branch divergence and the point where the histories were integrated. A fast-forward preserves commit identity too, but adds no explicit integration boundary. That distinction matters more than calling merge universally “non-destructive.”

What Does git rebase Do?

Starting from the same diverged graph, choose rebase instead of merge:

git status
git fetch origin
git switch feature
git rebase origin/main

Here, rebase replays E and F after D, creating E' and F' with new IDs. It updates feature without advancing main. These are replacement commits, not the original objects moved to a different location.

Before:
A---B---C---D          origin/main
     \
      E---F            feature (HEAD)

After rebase:
A---B---C---D          origin/main
             \
              E'---F'  feature (HEAD)

E' and F' have different IDs from E and F.

A commit object records its parents, so changing a commit’s parent changes its identity, even when its patch and message remain the same.

Rebase selects commits for replay; it does not necessarily recreate every commit you see on the branch. Git can skip changes it recognizes as already present upstream, and a rebase that needs no changes can leave existing commits alone. See the rebase reference for the selection rules and options.

Benefits of Rebasing

For private work, rebasing lets you present a sequence based on the updated main branch without adding synchronization merge commits. That can make a patch series easier to review. The trade-off is that the resulting graph no longer shows the original divergence, and linearity alone does not make the commits meaningful.

Advanced Rebase Variants

Interactive rebase, git rebase -i, lets you reorder, edit, or combine selected commits; the Pro Git history-rewriting chapter walks through that workflow. The --onto option separates the new base from the boundary used to select commits, which is useful when transplanting part of a branch. Neither is necessary for the basic feature-on-main update.

Resolving Merge and Rebase Conflicts

Either operation can encounter conflicts: changes Git cannot reconcile automatically. Start by reading git status, edit the conflicted files to the intended result, remove conflict markers, and stage the resolved paths.

Continue or Abort a Rebase

This is a conflict-resolution fragment. Replace src/cart.js with an actual conflicted path, and stage every resolved file before continuing.

# Fragment: run while a rebase is paused for conflicts.
git status
# Edit the conflicted files and remove conflict markers.
git add -- src/cart.js
git rebase --continue

Repeat if a later commit conflicts. To abandon an in-progress rebase and return to the branch’s pre-rebase state, use git rebase --abort. Do not use git rebase --skip simply to dismiss an error: it omits the current commit from replay. GitHub’s rebase conflict guide explains these choices.

Complete or Abort a Merge

# Fragment: run while a merge is paused for conflicts.
git status
# Edit the conflicted files and remove conflict markers.
git add -- src/cart.js
git merge --continue

To abandon an in-progress merge instead, use git merge --abort. Starting with a clean working tree matters: Git warns that merge abort may not reconstruct pre-existing uncommitted changes. A merge resolves the combined histories at the integration point; a rebase can require conflict resolution at several replayed commits.

Private, Published, and Shared Branches

The important question is not just “Have I pushed?” It is “Does anyone depend on these commits?” Rebasing unpublished feature commits generally avoids coordination problems. Rebasing a published branch that only you use can be acceptable under an agreed policy; rebasing commits a teammate has built on changes the history beneath their work.

The Pro Git explanation of shared-history risks shows why both the original commits and their rewritten equivalents can become tangled in subsequent integration.

If your team intentionally rewrites a published feature branch, coordinate first, account for remote work, and confirm that repository policy permits the update. After the rebase and validation, a conditional push is preferable to an unqualified force push:

# Only for an intentionally rewritten, coordinated feature branch.
git push --force-with-lease origin feature

This form refuses the update if the remote branch no longer matches your local remote-tracking reference. Background fetches can update that reference and weaken the protection; an explicit lease tied to a reviewed expected commit avoids that dependency. See git push lease semantics. If the push is rejected, investigate rather than fetching and retrying blindly.

git pull: Rebase, Merge, and Fast-Forward Policies

Pull fetches changes and then integrates them into the currently checked-out branch. On feature, choose one of these alternatives to integrate remote main:

# Choose one alternative, not both.

# Fetch main, then rebase the current branch onto it:
git pull --rebase origin main

# Fetch main, then merge it into the current branch:
git pull --no-rebase origin main

The merge form can fast-forward; it does not guarantee a merge commit, and a fast-forward-only policy can reject divergence. Without an explicit remote and branch, pull normally uses the current branch’s configured upstream—not necessarily main. See the git pull reference.

Relevant pull configuration settings include:

  • pull.rebase=true selects rebase; pull.rebase=false selects merge. A branch-specific rebase setting can override the general choice.
  • When merging, pull.ff=true allows fast-forwards, while pull.ff=false requests a merge commit even when a fast-forward is possible.
  • pull.ff=only permits only fast-forward integration and rejects divergent histories.

For example, this sets a repository-local fast-forward-only pull policy:

git config pull.ff only

Do not rely on an unspecified pull default across machines and Git installations. Fetch followed by an explicit merge or rebase makes your intended destination and operation easier to inspect.

Which Is Right for You?

Your situationRecommended approach
Others depend on the feature commitsMerge updated main into feature rather than rewriting those commits.
Your feature commits are private and you want a linear seriesRebase feature onto origin/main.
Branch divergence and integration points matterUse merge commits; choose --no-ff when you need an explicit boundary.
One pull request represents one logical deliverableConsider squash merging at the repository boundary.

My default recommendation: rebase private work when it helps organize review, merge shared work to keep collaborators’ foundations stable, and let the repository’s integration policy determine what lands on main. Different teams can reasonably choose differently.

Squash Merge and Pull-Request Automation

Updating feature from main is separate from landing feature on main. GitHub’s pull-request merge strategies offer three distinct outcomes:

  • Merge commit: retains the branch commits and adds an explicit merge point.
  • Squash and merge: combines the pull request’s changes into one commit on the base branch.
  • Rebase and merge: adds the branch’s commits individually onto the base branch without a merge commit.

Squash merging is not the same as a normal git rebase: it deliberately collapses the feature’s commit sequence. GitHub’s rebase-and-merge operation creates new commit IDs, so do not assume that its results have identical IDs to a locally rebased branch.

Automate Policy, Not Conflict Decisions

GitHub auto-merge can merge a pull request after required reviews and status checks pass. Branch protection can require reviews, checks, or linear history and restrict force pushes. Use those controls to enforce policy; when Git cannot resolve a conflict, stop for a developer to decide and validate the intended behavior.

Common Mistakes

  • Integrating into the wrong branch. Check git status before merging or rebasing; the examples assume feature is checked out.
  • Calling every linear history “clean.” Review commit boundaries and messages, not just graph shape.
  • Treating a successful integration as proof of correctness. Run your project’s unit and integration checks on the resulting branch. The benefits of unit testing apply to conflict resolutions too.
  • Rewriting shared commits to satisfy a visual preference. Agree on the collaboration policy before changing published history.

FAQ

What is the difference between git merge and git rebase?

Merge joins histories while preserving existing commit IDs; it may fast-forward or create a merge commit. Rebase replays selected commits onto a new base, giving rewritten commits new IDs and potentially producing a linear history.

Does git merge always create a merge commit?

No. A fast-forward merge moves the current branch pointer without creating a commit. Use --no-ff when incorporating new history requires an explicit merge boundary, or --ff-only to reject divergent histories.

Is it safe to rebase commits that have been pushed?

It can be acceptable for a published feature branch that nobody else depends on, provided your team permits it. Avoid rebasing commits collaborators have built on; any intentional published rewrite needs coordination and a carefully checked force-with-lease push.

How do I continue or abort a rebase after a conflict?

Resolve the conflicted files, stage them with git add, and run git rebase --continue. Repeat for later conflicts, or run git rebase --abort to return to the branch’s pre-rebase state.

What is the difference between git pull --rebase and git pull --no-rebase?

Both fetch first. The rebase form then rebases the current branch onto the fetched branch; the no-rebase form selects merge integration, whose fast-forward behavior is also governed by flags and configuration.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. More about me