BytePane

Git Rebase vs Merge: When to Use Each Strategy

Git14 min read

The Fundamental Difference Between Rebase and Merge

Git rebase and git merge both integrate changes from one branch into another, but they do so in fundamentally different ways. Understanding this difference is the key to choosing the right strategy for your team and maintaining a history that is easy to navigate.

A merge creates a new commit (a merge commit) that ties two branches together. The history of both branches is preserved exactly as it happened, resulting in a non-linear graph with visible merge points. A rebase moves the entire feature branch onto the tip of the target branch, rewriting commit hashes so the history appears as a straight line.

Neither approach is inherently better. The right choice depends on your team size, release cadence, and how much you value a linear history versus a complete audit trail. Many teams use both strategies in different contexts within the same project.

How Git Merge Works

When you run git merge feature-branch, Git finds the common ancestor of the two branches and creates a new merge commit that combines the changes. This merge commit has two parent commits, preserving the full topology of the branch history.

# Switch to main branch
git checkout main

# Merge feature branch into main
git merge feature/user-auth

# Result: a merge commit M is created
#
#   A---B---C  (feature/user-auth)
#  /         \
# D---E---F---M  (main)

# If no divergence occurred, Git does a fast-forward merge
# (no merge commit, just moves the pointer)
git merge --no-ff feature/user-auth  # Force a merge commit

Types of Merge

TypeCommandResult
Fast-forwardgit merge featurePointer moves forward, no merge commit
No fast-forwardgit merge --no-ff featureAlways creates a merge commit
Squash mergegit merge --squash featureCombines all commits into one, no merge commit

Squash merges are popular in open-source projects because they produce a clean, one-commit-per-feature history on the main branch. GitHub and GitLab both offer a "Squash and Merge" button in pull request UIs. You can compare your branch diffs before merging using a Diff Checker tool to review the combined changes.

How Git Rebase Works

Rebase replays your feature branch commits on top of the target branch. Each commit is re-applied one by one, and new commit hashes are generated. The original commits are discarded (though they remain in the reflog for a time).

# Start on your feature branch
git checkout feature/user-auth

# Rebase onto main
git rebase main

# Before rebase:
#   A---B---C  (feature/user-auth)
#  /
# D---E---F  (main)

# After rebase:
# D---E---F---A'---B'---C'  (feature/user-auth)
#             (main)
#
# A', B', C' are NEW commits with different hashes

# Now fast-forward main
git checkout main
git merge feature/user-auth  # Fast-forward, no merge commit

The result is a perfectly linear history. Every commit sits on a single line, making git log and git bisect easier to use. However, because commit hashes change, rebase rewrites history. This is safe for local branches but dangerous for branches that others are working on.

Interactive Rebase: Cleaning Up Commits

Interactive rebase (git rebase -i) is one of the most powerful Git features. It lets you reorder, squash, edit, drop, or reword commits before pushing them. This is ideal for cleaning up a messy feature branch before opening a pull request.

# Interactive rebase of the last 4 commits
git rebase -i HEAD~4

# Your editor opens with something like:
pick a1b2c3d Add user model
pick e4f5g6h Fix typo in user model
pick i7j8k9l Add authentication endpoint
pick m0n1o2p Add tests for auth

# Change to:
pick a1b2c3d Add user model
fixup e4f5g6h Fix typo in user model    # Squash into previous, discard message
pick i7j8k9l Add authentication endpoint
squash m0n1o2p Add tests for auth        # Squash and combine messages

# Commands available:
# pick   = use commit as-is
# reword = use commit but edit message
# edit   = pause to amend the commit
# squash = meld into previous commit (keep message)
# fixup  = meld into previous commit (discard message)
# drop   = remove commit entirely

A common workflow is to make many small, messy commits during development and then use interactive rebase to squash them into logical, well-described commits before requesting a review. This gives you the freedom to commit frequently without polluting the project history.

When to Use Merge

Merge is the safer default choice and is preferable in several common scenarios. Use merge when you want a complete, unaltered record of exactly when branches diverged and converged, or when multiple developers are collaborating on the same feature branch.

  • Shared branches -- When multiple people are pushing to the same branch, merging avoids the chaos of rewriting shared history. Force-pushing a rebased branch can erase teammates' commits.
  • Release branches -- Long-lived release branches should be merged, not rebased, because the merge commits serve as clear markers of which features were included in each release.
  • Open-source contributions -- Most open-source projects prefer merge or squash-merge for pull requests because it preserves the contributor's original commit metadata.
  • Audit requirements -- In regulated industries (finance, healthcare), the unaltered commit history that merge provides can be a compliance requirement.
  • Complex conflict resolution -- When a merge conflict is resolved, the resolution is recorded in the merge commit. With rebase, you may have to resolve the same conflict multiple times across rebased commits.

When to Use Rebase

Rebase excels when you want a clean, linear history that reads like a story. It is ideal for solo feature branches and for teams that prioritize a tidy commit graph.

  • Solo feature branches -- When only one developer works on a branch, rebasing onto main before merging keeps the history perfectly linear.
  • Keeping up with main -- Instead of merging main into your feature branch (which creates noisy merge commits), rebase your branch onto main to incorporate upstream changes cleanly.
  • Pre-PR cleanup -- Interactive rebase lets you squash "WIP" and "fix typo" commits into meaningful units before opening a pull request.
  • Bisect-friendly history -- A linear history makes git bisect far more effective at finding the exact commit that introduced a bug.
  • Small teams with conventions -- Teams of 2-5 developers who agree on rebase conventions can maintain an exceptionally clean project history.

The Golden Rule of Rebase

Never rebase commits that have been pushed to a shared remote branch. This is the single most important rule for using rebase safely. When you rebase, you rewrite commit hashes. If someone else has based their work on the original commits, their branch will diverge from the rewritten history, causing confusion and potential data loss.

# SAFE: Rebase your local feature branch onto main
git checkout feature/my-work
git rebase main
# (only you have these commits)

# DANGEROUS: Rebase main or any shared branch
git checkout main
git rebase some-branch
git push --force  # This BREAKS everyone else's local main

# SAFER force push (only if you must push rebased branch)
git push --force-with-lease
# This fails if someone else pushed new commits you don't have

If you need to rewrite a shared branch (for example, to remove a secret that was accidentally committed), coordinate with your team first. Have everyone stash their work, delete their local copy of the branch, and re-fetch after the rewrite.

Handling Rebase Conflicts

Rebase conflicts work differently from merge conflicts. During a rebase, Git replays each commit one at a time. If a conflict occurs, Git pauses and asks you to resolve it before continuing. You may encounter conflicts multiple times if several commits touch the same lines.

# During a rebase, a conflict occurs
git rebase main
# CONFLICT (content): Merge conflict in src/auth.ts
# Fix the conflict manually in your editor

# After fixing, stage the resolved file
git add src/auth.ts

# Continue the rebase (applies next commit)
git rebase --continue

# If things go wrong, abort and return to pre-rebase state
git rebase --abort

# Skip a problematic commit entirely
git rebase --skip

To compare conflicting versions side by side, paste both versions into a Diff Checker to see exactly what changed. This is especially useful when the conflict markers are buried in a large file.

A useful tip: enable git rerere (reuse recorded resolution). When enabled, Git remembers how you resolved a conflict and automatically applies the same resolution if the same conflict appears again during future rebases.

# Enable rerere globally
git config --global rerere.enabled true

# Git will now record conflict resolutions
# and reapply them automatically during future rebases

Common Team Workflows

Most successful teams combine rebase and merge in a structured workflow. Here are three popular patterns that balance clean history with team safety.

1. Rebase Locally, Merge to Main

This is the most popular hybrid approach. Developers rebase their feature branches onto main before opening a PR, then the PR is merged (with a merge commit or squash merge) into main. This gives you a clean feature branch history and clear merge points on main.

# Developer workflow
git checkout -b feature/new-api
# ... make commits ...

# Before opening PR, rebase onto latest main
git fetch origin
git rebase origin/main

# Push (first time: -u to set upstream)
git push -u origin feature/new-api

# If you rebased after pushing, force-push with lease
git push --force-with-lease

# PR is merged via GitHub/GitLab UI (merge commit or squash)

2. Squash Merge Everything

Some teams configure their repository to only allow squash merges. Every PR becomes a single commit on main, regardless of how many commits the feature branch had. This produces an extremely clean main branch history where every commit represents one complete feature or fix.

3. Trunk-Based with Short-Lived Branches

In trunk-based development, feature branches live for hours or at most a day. Developers rebase onto main frequently and merge as soon as the feature is ready. Because branches are short-lived, conflicts are rare and rebases are trivial. This approach requires strong CI/CD pipelines and feature flags for in-progress work. See our CI/CD Pipeline Guide for setup details.

Side-by-Side Comparison

AspectMergeRebase
History shapeNon-linear (diamond pattern)Linear (straight line)
Commit hashesPreservedRewritten
Merge commitsYes (unless fast-forward)No
Conflict resolutionOnce per mergePer commit being replayed
Safe for shared branchesYesNo (rewrites history)
git bisectWorks but follows branchesLinear walk, easier to use
Undo difficultyEasy (revert the merge commit)Harder (use reflog)

Recovering from a Bad Rebase

If a rebase goes wrong, Git's reflog is your safety net. The reflog records every change to HEAD, including the state before the rebase started. You can always go back.

# View the reflog to find the pre-rebase state
git reflog
# Example output:
# a1b2c3d HEAD@{0}: rebase (finish): ...
# e4f5g6h HEAD@{1}: rebase (start): ...
# i7j8k9l HEAD@{2}: commit: my last commit before rebase

# Reset to the state before the rebase
git reset --hard HEAD@{2}

# Or use the ORIG_HEAD shortcut (set automatically by rebase)
git reset --hard ORIG_HEAD

The reflog keeps entries for about 90 days by default. As long as you notice the problem within that window, you can always recover your original commits.

Git Configuration for Rebase and Merge

Git provides several configuration options that influence how rebase and merge behave by default. Setting these up correctly saves time and prevents mistakes.

# Make 'git pull' rebase instead of merge by default
git config --global pull.rebase true

# Auto-stash uncommitted changes before rebase
git config --global rebase.autoStash true

# Auto-squash fixup commits during interactive rebase
git config --global rebase.autoSquash true

# Remember conflict resolutions
git config --global rerere.enabled true

# Default merge strategy for pull
git config --global merge.ff only  # Only allow fast-forward merges on pull

These settings can also be configured per-repository in the .gitconfig or .git/config file. You can generate a .gitignore file for your project with our Gitignore Generator.

Decision Flowchart

Use the following decision tree to determine whether to rebase or merge in a given situation.

  1. Is the branch shared with other developers? If yes, use merge. Never rebase shared branches.
  2. Are you updating your feature branch with changes from main? If yes, rebase your feature branch onto main for a cleaner result.
  3. Are you integrating a feature branch into main? Merge (or squash merge) to preserve a clear integration point.
  4. Do you need to clean up commit history before a PR? Use interactive rebase on your local branch.
  5. Is your team small and experienced with Git? Rebase workflows are feasible and produce cleaner history.
  6. Is your team large or has mixed Git experience? Default to merge to avoid rebase-related mistakes.

Compare Code Changes Side by Side

Before merging or rebasing, use our free Diff Checker to review changes between branches. Paste two code versions and see additions, deletions, and modifications highlighted instantly.

Open Diff Checker

Related Articles