← Back to Blog

Git Rebase vs Merge in 2026: When to Use Each (Real Examples)

Git merge and git rebase both integrate changes from one branch into another, but they do it differently - with different effects on history, different conflict resolution experiences, and different rules about when it is safe to use them. Understanding the tradeoffs helps you make the right call every time.

The Problem Both Commands Solve

Imagine you created a feature branch from main three days ago. Since then, three other developers have merged their work into main. Your branch is now behind. You need to incorporate those changes to avoid conflicts at merge time - and to make sure your feature still works with the latest codebase. Both git merge and git rebase solve this problem, but with different approaches to how they represent the history of that integration.

How git merge Works

A merge takes two branch tips and creates a new "merge commit" that joins them. The history is preserved exactly as it happened:

# You are on your feature branch
git checkout feature/login
git merge main

Before merge:

      A---B---C  feature/login
     /
D---E---F---G    main

After git merge main:

      A---B---C---M  feature/login
     /           /
D---E---F---G---    main

M is the merge commit. It has two parents: C (the last commit on the feature branch) and G (the tip of main). The original branch topology is preserved in the history - you can see exactly when the branches diverged and when they joined.

Key characteristics of merge:

  • Creates a new merge commit
  • Preserves the complete history, including branch structure
  • Non-destructive - existing commits are never changed
  • Safe to use on public/shared branches
  • History can become cluttered with many merge commits in large teams

How git rebase Works

Rebase takes your branch commits and replays them on top of the target branch, as if you had branched from its current tip rather than from the older commit:

# You are on your feature branch
git checkout feature/login
git rebase main

Before rebase:

      A---B---C  feature/login
     /
D---E---F---G    main

After git rebase main:

              A'--B'--C'  feature/login
             /
D---E---F---G              main

The commits A', B', C' are new commits - they have the same changes as A, B, C but different SHA hashes because their parent has changed. The result is a perfectly linear history, as if the feature was developed after all of main's commits, with no divergence at all.

Key characteristics of rebase:

  • No merge commit - history is linear
  • Rewrites commit history (creates new commit SHAs)
  • Cleaner git log that reads like a single timeline
  • Dangerous on shared/public branches - see the golden rule below
  • Conflicts must be resolved commit-by-commit

The golden rule of rebasing: never rebase commits that have been pushed to a shared branch. When you rebase, you rewrite history. If others have based their work on your original commits, rebasing creates diverging histories that are very painful to reconcile.

When to Use Merge

Use git merge when:

  • Integrating a feature branch into main: The merge commit marks the point when the feature was completed and merged. This is the standard "squash and merge" or "merge commit" strategy used by GitHub and GitLab pull requests.
  • The branch has been pushed and shared: Other developers may have based work on your commits. Merging is safe because it never rewrites existing commits.
  • You want to preserve full history: For audit purposes, regulatory compliance, or post-mortem analysis, a merge commit makes the history of what merged when completely explicit.
  • Long-lived integration branches: develop, staging, release - branches where multiple feature branches converge benefit from merge commits that clearly mark each integration point.
# Merge feature into main (creates a merge commit)
git checkout main
git merge --no-ff feature/login   # --no-ff forces a merge commit even if fast-forward is possible
git push origin main

When to Use Rebase

Use git rebase when:

  • Keeping a feature branch up to date with main: Instead of littering your feature branch with merge commits from main, rebase onto the latest main periodically. This keeps your branch clean and conflicts smaller.
  • Cleaning up local commit history before opening a PR: Interactive rebase lets you squash "wip" commits, fix typos in commit messages, and reorder commits before your work is reviewed.
  • Private branches that have not been pushed: Rebase freely on work that exists only on your local machine.
  • You want a clean linear history: Some teams prefer a history where every commit represents a meaningful, working change - no merge commits, no "wip" commits.
# Update your feature branch with the latest main (rebase approach)
git checkout feature/login
git fetch origin
git rebase origin/main

# Resolve any conflicts, then:
git rebase --continue

# If something goes wrong, abort and return to pre-rebase state:
git rebase --abort

Interactive Rebase: Cleaning Up History

Interactive rebase (git rebase -i) is one of git's most powerful features for polishing commit history before sharing your work. It lets you squash, reorder, reword, edit, or drop commits interactively:

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

This opens an editor with a list like:

pick a1b2c3d Add login form HTML
pick b2c3d4e WIP: add validation
pick c3d4e5f fix typo
pick d4e5f6g Add unit tests for login

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# s, squash = meld into previous commit
# f, fixup = meld into previous commit, discard this commit's log message
# d, drop = remove commit

Change to squash the "wip" and "fix typo" commits into the first one:

pick a1b2c3d Add login form HTML
squash b2c3d4e WIP: add validation
fixup c3d4e5f fix typo
pick d4e5f6g Add unit tests for login

The result is a clean two-commit history: "Add login form HTML" (containing all the validation work) and "Add unit tests for login". This is much more reviewable and bisectable than four noisy commits.

The Three Main Merge Strategies on GitHub/GitLab

When you merge a pull request, most platforms offer three strategies. Understanding what each does helps you choose the right default for your team:

Create a merge commit

A standard merge commit (git merge --no-ff). The branch history is preserved. The PR's commits appear in full in the main branch history, plus a merge commit. Best for teams that want complete history and clear PR boundaries.

Squash and merge

All commits in the PR are squashed into a single commit on main. The branch history is discarded. The main branch gets one clean commit per feature. Best for teams that want a clean main branch history and do not care about individual "wip" commits.

Rebase and merge

Each commit from the PR is replayed onto main without a merge commit. Linear history, but each individual commit is preserved. Best for teams that write clean, atomic commits and want linear history without squashing.

# Equivalent git commands for each strategy
# Merge commit:
git merge --no-ff feature/login

# Squash and merge:
git merge --squash feature/login && git commit -m "Add login feature (#42)"

# Rebase and merge:
git rebase main feature/login && git checkout main && git merge --ff-only feature/login

Step-by-Step: A Practical Feature Branch Workflow

  1. Create a feature branch from the latest main: git checkout -b feature/login origin/main
  2. Commit your work freely, including "wip" commits
  3. Before opening a PR, fetch and rebase onto the latest main: git fetch origin && git rebase origin/main
  4. Use interactive rebase to clean up commits: git rebase -i origin/main
  5. Push your branch: git push --force-with-lease origin feature/login (force required because you rewrote history)
  6. Open the PR. When approved, merge using your team's chosen strategy
  7. Delete the feature branch after merge

Note: --force-with-lease is safer than --force because it refuses to overwrite if someone else has pushed to the branch since your last fetch.

Compare File and Code Differences Visually

Working through a merge conflict? Use our free Diff Checker to compare two versions of a file side by side - no login required, runs in your browser.

Open Diff Checker

Handling Rebase Conflicts

Rebase conflicts are often more frequent than merge conflicts because git replays each commit individually. A single real conflict might trigger the same conflict resolution three times if three commits touched the same lines. Here is the workflow:

# Rebase starts, then hits a conflict
git rebase origin/main

# Git pauses and tells you which files conflict
# CONFLICT (content): Merge conflict in src/auth.js

# Resolve the conflict manually in your editor, then stage the file
git add src/auth.js

# Continue to the next commit
git rebase --continue

# Repeat until the rebase completes, or abort if it gets too messy:
git rebase --abort

A tip: if you find yourself resolving the same conflict repeatedly during a rebase, use git rerere (reuse recorded resolution). Enable it with git config rerere.enabled true - git will remember conflict resolutions and apply them automatically in future rebases.

FAQ

Is it ever safe to rebase a pushed branch?

Yes, with a caveat: it is safe if you are the only person working on that branch and you understand you are rewriting its history. Use git push --force-with-lease instead of --force to avoid overwriting another contributor's pushes you haven't fetched yet. Never rebase main, master, develop, or any branch that other developers track.

What is a fast-forward merge?

A fast-forward merge happens when the target branch has no new commits since the feature branch was created. Git simply moves the branch pointer forward to the feature branch tip - no merge commit is needed because the history is already linear. You can prevent fast-forwards with git merge --no-ff to always create a merge commit for PR tracking purposes.

How does git rebase differ from git cherry-pick?

git cherry-pick applies a specific commit (or range of commits) onto the current branch. git rebase replays an entire branch's commits onto a new base. Cherry-pick is useful for applying a hotfix commit from one branch to another without merging the entire branch. Rebase is for updating a whole feature branch.

Should I squash commits before merging a PR?

It depends on your team's philosophy. Squashing produces a clean main branch history where each line in git log corresponds to a feature or fix - easy to read, easy to revert a whole feature. Preserving individual commits maintains the detailed development narrative and enables more granular git bisect. Both are valid. The key is consistency: pick one and configure your PR platform's default merge strategy accordingly.

What does git pull --rebase do?

git pull --rebase fetches the remote branch and then rebases your local commits on top of it, rather than creating a merge commit. This keeps your local history linear when syncing with a shared branch. Many developers set this as the default: git config --global pull.rebase true. It avoids the "Merge branch 'main' into main" noise commits that appear when multiple developers push to the same branch.

How do I undo a rebase?

Before the rebase, git saves a reference to the original state as ORIG_HEAD. To undo a completed rebase: git reset --hard ORIG_HEAD. If ORIG_HEAD is gone, use the reflog: git reflog shows every state the branch has been in, and you can reset to any of them: git reset --hard HEAD@{5}.

Quick Decision Guide

  • Merging a completed feature into main via PR: merge (or squash merge)
  • Keeping your local feature branch current with main: rebase
  • Cleaning up commits before opening a PR: interactive rebase
  • The branch has been shared with other developers: merge (never rebase)
  • You want the simplest possible history: squash and merge
  • You want to preserve individual commits but keep history linear: rebase and merge

The Bottom Line

Merge preserves history; rebase rewrites it. Both are tools - neither is universally correct. The practical rule most teams land on: rebase your feature branch onto main to stay current and clean up commits locally; merge (or squash merge) to get the finished feature into main via a PR. Never rebase shared branches.

Use our free tool here → Diff Checker to compare file versions visually when resolving merge conflicts.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.