Teams spend hours debating rebase vs. merge as if it's a moral question. It's not. They're tools with different trade-offs. The problem isn't choosing one. It's not understanding when to use each.

What each does

Merge creates a new commit that combines the history of two branches. Preserves history exactly as it happened, with all feature commits and the merge commit.

# merge
* merge branch 'feature' into main
|\
| * add user validation
| * create user model
|/
* main commit

Rebase reapplies commits from one branch on top of the other. History becomes linear, as if commits were made in sequence.

# rebase
* add user validation
* create user model
* main commit

When to use merge

Merge is the right choice when:

  • The branch has multiple authors and contribution history matters
  • You want to preserve the context of when changes happened
  • The branch is long-lived (e.g., release branch that lives for weeks)
  • You're working with Git Flow or a similar model

On GitHub/GitLab, merge via pull request is the standard. It creates a merge commit that documents when code entered, who reviewed, and the context of the change.

When to use rebase

Rebase is the right choice when:

  • The branch is short (1-3 commits) and you want to keep history clean
  • You need to update your branch with main changes without creating unnecessary merge commits
  • The team prefers a linear history for navigation with git log --oneline
  • You're preparing commits for squash merge
# Update branch with main changes via rebase
git fetch origin
git rebase origin/main

# If conflicts occur, resolve and continue
git rebase --continue

The golden rule: don't rebase public branches

Rebase rewrites history. If another developer is working on the same branch, rebase creates divergent history that forces push and causes confusion.

Rebase only branches that are yours alone. If the branch is shared, use merge.

Squash merge: the best of both worlds

Many teams have adopted squash merge as the standard: feature commits are squashed into a single commit before merging to main.

# On GitHub, configure in repo Settings > General > Pull Requests
# ☑ Allow squash merging

# Or via CLI:
git checkout main
git merge --squash feature-branch
git commit -m "feat(auth): add JWT refresh token rotation"

Advantages: main history stays clean with one commit per feature. Disadvantages: the detailed history of the feature (decisions, discussions, attempts) is lost.

The flow that works in practice

# 1. Create branch from main
git checkout main && git pull
git checkout -b feature/user-validation

# 2. Work, make commits locally
git add . && git commit -m "feat: add user model"
git commit -m "feat: add validation logic"

# 3. Before opening PR, rebase on main
git fetch origin
git rebase origin/main

# 4. Resolve conflicts if any
git rebase --continue

# 5. Open PR: squash merge on GitHub

This gives the best of both worlds: rebase keeps your branch updated without merge commits, and squash merge keeps main history clean.

What not to do

Don't force push on shared branches. Don't rebase commits that were already pushed and that other people are using. Don't do interactive rebase on public branches. And don't argue about this on Slack as if it's religion. Choose the tool that works for your team's context.