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/mainWith 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/mainThe merge reference distinguishes the controls:
--ffallows a fast-forward when possible and a merge commit when needed.--ff-onlyrefuses the merge if the histories have diverged.--no-ffcreates 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/mainHere, 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 --continueRepeat 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 --continueTo 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 featureThis 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 mainThe 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=trueselects rebase;pull.rebase=falseselects merge. A branch-specific rebase setting can override the general choice.- When merging,
pull.ff=trueallows fast-forwards, whilepull.ff=falserequests a merge commit even when a fast-forward is possible. pull.ff=onlypermits only fast-forward integration and rejects divergent histories.
For example, this sets a repository-local fast-forward-only pull policy:
git config pull.ff onlyDo 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 situation | Recommended approach |
|---|---|
| Others depend on the feature commits | Merge updated main into feature rather than rewriting those commits. |
| Your feature commits are private and you want a linear series | Rebase feature onto origin/main. |
| Branch divergence and integration points matter | Use merge commits; choose --no-ff when you need an explicit boundary. |
| One pull request represents one logical deliverable | Consider 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
- Git merge reference — fast-forwards, merge commits, and recovery.
- Git rebase reference — replay rules, conflicts, and advanced options.
- Pro Git: Rebasing — graph examples and collaboration risks.
- Git push reference — force-with-lease behavior and caveats.
- Git pull reference — fetch-and-integrate choices.
- Git configuration reference — pull.rebase and pull.ff settings.
- GitHub pull-request merge strategies — merge, squash, and rebase outcomes.
- GitHub branch protection — enforceable integration requirements.
