CodecPath

Guide

Git Merge vs Rebase: Pick a History Policy That Stays Safe

Merge shared work, rebase private commits, and allow fast-forward updates when no divergent history needs to be preserved.

CodecPath editors8 min read
Fast-forwardMoves a reference,creates no commitMerge commitPreserves two branchesand integrationRebaseReplays commits with newidentitiesSquash mergeCombines a feature intoone commit
Four integration choices, distinguished by whether they preserve branches, rewrite commits, or create a single combined change.

Which history policy should a team choose?

Use ownership as the rule.

Rebase commits that remain private to one developer, merge work that teammates may already have fetched, and permit fast-forward updates when the target branch has not diverged.

That decision survives most arguments about a pristine graph. Shared history is the boundary. Rebasing rewrites commit identities because Git creates new commits with new parent relationships, even when the resulting files contain the same code.

For a typical feature branch, the policy can be simple:

  • Rebase your own unpublished commits onto the latest main branch.
  • Merge a branch once multiple contributors treat it as shared.
  • Fast-forward when the destination is already an ancestor of the source.
  • Squash disposable checkpoint commits at the pull-request boundary.
  • Protect the main branch from force pushes.

This gives developers room to reorder and clean local work without asking teammates to repair references that suddenly point at abandoned commits. It also keeps meaningful integration points where two branches actually came together.

Blanket rules fail here. “Always rebase” risks rewriting colleagues’ work, while “always merge” creates merge commits for routine updates that contain no useful decision.

When should a feature branch be merged?

Merge shared branches.

If contributors have fetched the feature branch, based new changes on it, or opened dependent work against it, merging preserves those commit identities and records the integration explicitly.

A normal merge joins two branches by creating a commit with two parents when a fast-forward is unavailable. The Git merge documentation defines both that behavior and the --ff-only, --no-ff, and --squash choices.

Git merge preserves history as it happened. That history may look busier, but the merge commit can represent a real event: review completed, release branch reconciled, or long-running feature accepted into the main branch.

Choose a merge commit when:

  • Multiple developers co-own the branch.
  • Other branches depend on its commits.
  • The integration itself is meaningful.
  • A rollback should remove the feature as one unit.
  • Audit or release tooling uses branch boundaries.

Merging allows easy co-ownership of branches because existing commit hashes remain reachable. It does not make conflicts disappear; it resolves them once at the integration point rather than replaying them across multiple commits.

Do not manufacture merge commits everywhere. Pulling a remote branch that is merely ahead of yours needs no artificial junction unless repository policy deliberately requires one.

When is Git rebase safe?

Rebase private work.

A rebase is safe when the affected commits belong to you, have not become a base for teammates, and can be replaced without breaking references in reviews or automation.

Git rebase takes a selected series of commits, resets the branch to a new base, and replays the changes in order. The documented rebase process explains why the rebased commits are new objects rather than the old commits moved somewhere else.

This is powerful before publication. Interactive rebase can squash fixups, edit a poor commit message, drop accidental files, or reorder commits so each one builds and tests cleanly.

Good uses include:

  • Updating an unpublished feature branch from main.
  • Combining local “fix typo” commits.
  • Splitting one tangled commit before review.
  • Removing credentials before any push.
  • Reordering dependent changes into a readable sequence.

The common mistake is treating “my branch” as “my history.” Once another developer has fetched it, the branch is shared in practice, regardless of who created it.

If rewriting is unavoidable, coordinate first and push with --force-with-lease, never a blind --force. The lease checks that the remote reference still has the value your local repository expects, which reduces the chance of erasing newer work.

Git pull merge vs rebase: which default is safer?

Default to explicit pulls.

For a mixed team, configure git pull to stop on divergent history and make the developer choose merge or rebase, because a silent global default hides the policy decision.

git pull first fetches and then integrates the selected remote branch. According to the Git pull manual, --rebase runs rebase after fetching, while the default integration path runs merge.

The safest shared configuration is:

Bash
git config --global pull.ff only

That setting accepts a simple fast-forward but refuses divergence with an error such as fatal: Not possible to fast-forward, aborting. The developer then inspects the graph and chooses deliberately.

Useful alternatives are:

  • git pull --rebase for a private local branch with unpublished commits.
  • git pull --no-rebase when preserving a shared branch topology.
  • git fetch followed by inspection when the repository is unfamiliar.
  • git pull --ff-only for release automation that must never create commits.

I would not set pull.rebase true across every repository on a developer workstation. A convenient default in a personal project becomes a confusing rewrite when that same configuration reaches a collaborative maintenance branch.

Repository instructions should name the expected command. Git configuration is layered, so a local repository value can override the user’s global choice without forcing unrelated projects into the same strategy.

Merge vs rebase vs fast-forward: what changes in the graph?

They change different things.

Merge preserves divergent lines and may create a two-parent commit, rebase copies commits onto a new base, while fast-forward merely advances a branch reference without creating anything.

Suppose main points to commit A and a feature contains B and C. If main has not moved, merging the feature can fast-forward main directly to C.

The choices differ like this:

  • Fast-forward: advances the destination reference from A to C.
  • Merge commit: joins two divergent histories with a new commit.
  • Rebase: recreates B and C after the latest destination commit.
  • Squash: applies the combined file changes as one new commit.
  • No-fast-forward merge: creates a merge commit even when one is unnecessary mechanically.

Prefer fast-forward for pure advancement. It is the least destructive option because existing commits and their parent relationships stay intact.

Use --no-ff only when the branch boundary carries information your team actually reads. Otherwise the graph accumulates ceremonial bubbles representing no conflict, no independent release, and no meaningful integration choice.

Squash is different again. It gives the main branch one concise change but discards the feature branch’s individual commit structure, so bisecting and attributing intermediate work later becomes harder.

How should conflicts affect the choice?

Count repeated resolutions.

Choose merge when one integration conflict is easier to review than several replay conflicts, but choose rebase when each commit is coherent enough to resolve and test independently.

During merging, Git compares both branch tips with their common ancestor and asks for one combined result. During rebasing, Git applies each commit in sequence, so the same contested lines can produce conflicts multiple times.

That difference matters on old feature branches. Replaying thirty commits across a heavily changed codebase may be theoretically cleaner and operationally miserable, especially when early resolutions alter what later patches expect.

Use these thresholds as practical warnings:

  • More than about 20 unpublished commits: consider an interactive cleanup before rebase.
  • More than one active contributor: treat the branch as shared.
  • Repeated conflicts in the same file: abort and evaluate a merge.
  • Generated files or lockfiles changing on both sides: regenerate after integration.
  • A branch near release: prefer the option with the smaller review surface.

To stop a bad operation, run git rebase --abort or git merge --abort. Do that before improvising with resets, because the abort command understands the operation state stored under Git’s internal metadata.

Conflict count is not a moral score. A linear result is not simpler if nobody can explain how it was produced or verify that each replayed commit still works.

How can a repository enforce the policy?

Protect shared references.

Block force pushes and direct writes on the main branch, require reviewed pull requests, then document which integration button maintainers should use for ordinary features, releases, and emergency fixes.

Local configuration is advisory. Server-side branch protection is what stops a mistaken git push --force from replacing history that every team member and deployment pipeline already consumes.

A workable policy might say:

  • Contributors may rebase private branches before review.
  • Reviewed feature branches use squash merge.
  • Release branches use merge commits.
  • Main accepts no direct pushes.
  • Automation uses fast-forward-only updates.
  • Force pushes are disabled on protected branches.

Set the repository-level pull behavior where it helps:

Bash
git config pull.ff only
git config rebase.autoStash true

The second setting can temporarily stash a dirty working tree during rebase, but it does not make rewriting shared commits safe. The Git configuration reference documents pull.ff, pull.rebase, and rebase.autoStash separately because they control different parts of the process.

Write the exception path too. A maintainer handling a security fix may need a merge commit to preserve provenance, while a bot updating one pinned dependency may be allowed to fast-forward after required checks pass.

Enforcement should prevent damaging choices, not dictate aesthetics. If the team cannot explain what information a merge bubble preserves, requiring that bubble on every pull request is ceremony.

What should you do after history was rewritten?

Stop pushing immediately.

Fetch the remote state, preserve your current branch under a temporary name, inspect the reflog, and coordinate before moving any shared reference again.

Start with safe snapshots:

Bash
git branch rescue-before-repair
git fetch --all --prune
git reflog --date=iso

The reflog records recent local reference movements, including commits abandoned by rebase or reset. Those objects usually remain available until Git’s expiration and garbage-collection rules remove them, so delay makes recovery less certain.

The repair path depends on what happened:

  • If the remote is correct, reset or rebase your local work onto it.
  • If a force push erased colleagues’ commits, identify the old tip from a reflog or another clone.
  • If both sides contain new work, create rescue branches before merging anything.
  • If credentials entered history, rotate them even after removing the commits.
  • If a release used the rewritten commits, involve whoever owns deployment records.

Do not “fix” the graph with another blind force push. That often conceals the first mistake while deleting the only remaining reference to somebody else’s commits.

Once recovered, close the policy gap that permitted the incident. Protected branches, --force-with-lease, and explicit pull behavior are easier to enforce than asking every developer to remember which history happened to be shared at 3am.

FAQ

Git squash and merge vs rebase and merge: what is the difference?
Squash and merge creates one new destination commit from the branch’s combined changes, while rebase and merge recreates each selected commit on the destination. Choose squash when intermediate commits are disposable; choose rebase and merge when their individual authorship and sequence remain meaningful.
What does git config pull.rebase false do?
It tells git pull to integrate fetched divergent history with merge rather than rebase. It does not force a merge commit when a fast-forward is possible, and a command-line flag or more specific configuration scope can override it.

Related guides