MervCodes

Tech Reviews From A Programmer

Git Rebase vs Merge: When to Use Each (With Examples)

7 min read

I've had this argument at every company I've worked at. The rebase vs merge debate is one of the longest-running in software development, and teams get genuinely heated about it. Both integrate changes from one branch into another, but the way they do it has real consequences for your workflow, code reviews, and whether your git history is useful or a tangled mess.

TL;DR: Understand the real difference between git rebase and git merge. Learn when to use each with practical examples and team workflow guidelines.

What Merge Does

git merge creates a new "merge commit" that ties two branches together. The history of both branches is preserved exactly as it happened.

# You're on feature-branch, want to integrate changes from main
git checkout feature-branch
git merge main

The resulting history looks like this:

*   Merge branch 'main' into feature-branch  (merge commit)
|\
| * Fix: update API endpoint         (main)
| * Add rate limiting                 (main)
* | Add user profile page             (feature-branch)
* | Fix profile image upload          (feature-branch)
|/
* Initial commit

The merge commit (* at the top) has two parents, forming a visible junction in the history.

What Rebase Does

git rebase replays your commits on top of another branch. It rewrites history to make it appear as if you branched from the latest commit on the target branch.

# You're on feature-branch, want to integrate changes from main
git checkout feature-branch
git rebase main

The resulting history is linear:

* Add user profile page             (feature-branch, rebased)
* Fix profile image upload          (feature-branch, rebased)
* Fix: update API endpoint          (main)
* Add rate limiting                 (main)
* Initial commit

No merge commit. Your feature branch commits now sit on top of main's latest changes, as if you just started working on them.

A Practical Example

Let's walk through a real scenario.

Starting Point

You and a teammate are both working from the same main branch:

# main branch has commits A → B → C

# You create feature-branch from C
git checkout -b feature-auth

# You make commits D and E
git commit -m "Add login form"         # D
git commit -m "Add authentication API" # E

# Meanwhile, your teammate merges their work to main: commits F and G
# main is now A → B → C → F → G
# Your branch is A → B → C → D → E

Using Merge

git checkout feature-auth
git merge main

Result: A → B → C → D → E → M (where M is the merge commit that also includes F and G)

Your commits D and E stay in their original position. The merge commit M integrates the divergent histories.

Using Rebase

git checkout feature-auth
git rebase main

Result: A → B → C → F → G → D' → E'

Your commits D and E are rewritten as D' and E' (new commit hashes) and placed after main's latest commits. The history is linear.

When to Use Merge

1. Integrating Feature Branches into Main

When a feature branch is complete and reviewed, merge it to preserve the complete context:

git checkout main
git merge --no-ff feature-auth

The --no-ff flag forces a merge commit even if a fast-forward is possible. This creates a clear marker in history: "this group of commits was the auth feature."

2. Shared Branches

If multiple people are working on the same branch, never rebase. Rebase rewrites commit hashes, which means everyone else's local copies become incompatible:

# NEVER do this on a shared branch
git checkout shared-branch
git rebase main
git push --force  # This breaks everyone else's local copy

3. When History Accuracy Matters

For regulated environments or projects where you need to prove exactly when changes were made, merge preserves the true chronological order.

When to Use Rebase

1. Keeping Feature Branches Up to Date

While working on a feature branch, rebase onto main regularly to stay current:

git checkout feature-auth
git rebase main

This keeps your branch's diff clean for code review. Reviewers see only your changes, not a tangle of merge commits from integrating main.

2. Cleaning Up Local Commits Before Merging

Interactive rebase lets you squash, reorder, and reword commits before creating a pull request:

git rebase -i HEAD~3

This opens an editor where you can combine your "WIP", "fix typo", and "actually fix the bug" commits into a single clean commit.

3. When You Want a Linear History

Some teams prefer a linear main branch where every commit is a complete, working change. Rebase enables this:

# On feature branch, before merging
git rebase main
git checkout main
git merge --ff-only feature-auth  # Fast-forward: no merge commit

Handling Rebase Conflicts

Rebasing can produce conflicts at each replayed commit. This is the main pain point:

git rebase main
# CONFLICT in src/auth.ts
# Fix the conflict, then:
git add src/auth.ts
git rebase --continue

# If you want to abort and go back to before the rebase:
git rebase --abort

Pro tip: If you get the same conflict repeatedly (because your branch has many commits that touch the same lines), enable rerere:

git config --global rerere.enabled true

rerere (reuse recorded resolution) remembers how you resolved a conflict and applies the same resolution automatically if it encounters it again.

The Golden Rule of Rebasing

Never rebase commits that have been pushed to a shared branch.

If you have pushed your commits and someone else has pulled them, rebasing rewrites those commits' hashes. When you force-push the rewritten history, everyone else's local branch diverges from the remote. They will get confusing merge conflicts and potentially lose work.

# Safe: rebase local commits that only you have
git rebase main  # ✓ fine

# Dangerous: rebase commits others may have pulled
git push --force  # ✗ can break teammates' work

If you must force-push a rebased branch (common for PR branches where you are the only contributor), use --force-with-lease:

git push --force-with-lease

This fails if someone else has pushed to the branch since your last pull, preventing accidental overwrites.

Team Workflow Recommendations

For Most Teams: Merge Main, Rebase Features

This is the most common and practical approach:

  1. Developers rebase their feature branches onto main before creating a PR
  2. Feature branches are merged into main with a merge commit (--no-ff)
  3. Main branch shows a clear sequence of merged features
# Developer workflow
git checkout feature-x
git rebase main         # Clean up before PR
git push --force-with-lease

# After review, merge with merge commit
git checkout main
git merge --no-ff feature-x

For Small Teams: Squash and Merge

GitHub and GitLab offer "Squash and Merge" which combines all feature branch commits into a single commit on main:

# Equivalent to:
git checkout main
git merge --squash feature-x
git commit -m "Add feature X"

This produces the cleanest main branch history but loses individual commit granularity.

For Solo Projects: Rebase Everything

If you are the only contributor, rebase freely. A linear history is easier to read and git bisect works better with it.

Quick Decision Guide

Use merge when:

  • Integrating completed feature branches into main
  • Working on a shared branch with other developers
  • You need to preserve the complete history
  • You want to avoid rewriting commits

Use rebase when:

  • Keeping your feature branch up to date with main
  • Cleaning up messy local commits before a PR
  • You want a linear, readable history
  • You are the only person working on the branch

Key Takeaways

  • Merge preserves history as it happened; rebase rewrites it for clarity
  • Never rebase commits that others have pulled
  • Use --force-with-lease instead of --force when pushing rebased branches
  • Enable rerere to avoid re-resolving the same conflicts
  • Most teams benefit from rebasing feature branches and merging to main

Sources

  1. Git Documentation
  2. GitHub Docs
  3. Atlassian Git Tutorials

Looking for more? Check out Adaptels.

Related Articles

How to Debug Node.js Memory Leaks (Step-by-Step Guide)

Learn how to detect, diagnose, and fix Node.js memory leaks using heap snapshots, Chrome DevTools, and clinic.js — with real code examples.

How to Set Up GitHub Actions for CI/CD (Beginner-Friendly Guide)

Learn how to set up GitHub Actions for CI/CD pipelines — from your first workflow file to automated deployments with real YAML examples.

Building an AI Chatbot With LangChain: Practical Developer Guide

Build a production-ready AI chatbot with LangChain, Python, and OpenAI. Step-by-step guide with memory, RAG, streaming, and deployment tips.