正在加载,请稍候…

Git Rebase vs Merge: When to Use Each and How They Work

Understand the difference between git rebase and git merge, how each changes history, and which workflow to use for feature branches, hotfixes, and team projects.

The Same Goal, Different History

Both git merge and git rebase integrate changes from one branch into another. The difference is in how they handle commit history — and that difference has real consequences for readability, debugging, and collaboration.

Git Merge: Preserving History

git merge creates a new commit that joins two branch histories. It's non-destructive: no existing commits are changed.

Before merge:
main:    A --- B --- C
feature:          \ D --- E

After git merge main into feature:
main:    A --- B --- C
feature:          \ D --- E --- M (merge commit)
                   \___________/

M is the merge commit with two parents: E (tip of feature) and C (tip of main). All history is preserved exactly as it happened.

git checkout feature
git merge main
# or: git merge main --no-ff  # force a merge commit even for fast-forwards

Fast-forward merge: If main hasn't diverged (no new commits since feature branched), git can simply move the pointer forward without creating a merge commit:

Before:
main:    A --- B
feature:          \ C --- D

After git checkout main && git merge feature (fast-forward):
main:    A --- B --- C --- D
feature:          \________/  (now same as main)

Use --no-ff to always create a merge commit, preserving the fact that a feature branch existed.

Git Rebase: Linear History

git rebase replays commits from one branch on top of another. It rewrites history — the commits get new SHAs.

Before:
main:    A --- B --- C
feature:      \ D --- E

After git rebase main (run on feature branch):
main:    A --- B --- C
feature:              \ D' --- E'  (D and E reapplied on top of C)

D' and E' have the same changes as D and E, but different parent commits (and therefore different SHAs). The branch history is now linear — as if you had branched from C all along.

git checkout feature
git rebase main

Handling Conflicts

Both approaches require resolving conflicts when the same lines were changed differently.

During merge:

git merge main
# CONFLICT: resolve files
git add resolved-files
git merge --continue
# or: git merge --abort  (cancel the merge)

During rebase:

git rebase main
# CONFLICT on commit D: resolve files
git add resolved-files
git rebase --continue
# Conflict on commit E: resolve again
git add resolved-files
git rebase --continue
# or: git rebase --abort  (cancel, go back to pre-rebase state)

Rebase conflicts can appear multiple times — once per commit being replayed. With merge, you resolve all conflicts once in the merge commit.

Interactive Rebase: Cleaning Up History

git rebase -i (interactive) lets you rewrite, squash, reorder, and edit commits before sharing them:

git rebase -i HEAD~4  # interactive rebase of last 4 commits

This opens an editor:

pick a1b2c3 Add user authentication
pick d4e5f6 Fix typo in auth module
pick g7h8i9 Add token refresh logic
pick j0k1l2 WIP: fix edge case

# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = edit commit message
# e, edit <commit> = stop for amending
# s, squash <commit> = meld into previous commit
# f, fixup <commit> = meld, discard log message
# d, drop <commit> = remove commit

You can change to:

pick a1b2c3 Add user authentication
fixup d4e5f6 Fix typo in auth module  ← squash into previous
pick g7h8i9 Add token refresh logic
drop j0k1l2 WIP: fix edge case       ← delete this commit

Result: clean two-commit history, typo fix absorbed into the original commit, WIP commit gone.

The Golden Rule of Rebase

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

When you rebase, you're rewriting history — creating new commits with new SHAs. If teammates have based work on your original commits, their git histories diverge from yours. Merging becomes a mess.

# ❌ Don't do this if others have pulled from main
git checkout main
git rebase feature  # rewrites main history

# ✅ Rebase is safe here
git checkout feature
git rebase main  # rewrites feature (not yet shared or force-push to your own branch)

When is force-push acceptable? When working on your own feature branch that no one else is tracking:

git rebase -i HEAD~3  # clean up your feature branch
git push --force-with-lease origin feature  # safer than --force: fails if remote changed

--force-with-lease is safer than --force: it fails if the remote was updated since your last fetch, preventing accidental overwrites.

Comparison Table

Aspect Merge Rebase
History shape Non-linear (branching) Linear
Commit SHAs Unchanged Rewritten
Merge commit Yes (usually) No
Conflict resolution Once, in merge commit Per commit
Safe to use on shared branches ✅ Yes ❌ No
Good for feature branches ✅ Yes (no-ff merge) ✅ Yes (before push)
Debugging with git bisect Works Works better (linear)
CHANGELOG readability Shows feature as unit Harder to see features

Team Workflows

Feature Branch Workflow (Merge)

# Start feature
git checkout -b feature/user-auth main

# Work, commit, work, commit
git commit -m "Add login endpoint"
git commit -m "Add JWT validation"

# Merge back when done
git checkout main
git merge --no-ff feature/user-auth -m "Merge feature/user-auth"
git branch -d feature/user-auth

This preserves the feature as a unit in the history. Good for seeing which commits belong to which feature.

Rebase Before Merge (Clean History)

# Start feature
git checkout -b feature/user-auth main

# Work, work, work (messy commits OK)
git commit -m "WIP auth"
git commit -m "fix"
git commit -m "more fixes"

# Before merging, clean up with rebase
git rebase -i main  # squash WIP commits, fix messages
git rebase main     # bring up to date with main

# Now merge — one clean commit or a few logical commits
git checkout main
git merge feature/user-auth

Trunk-Based Development (Squash Merge)

Many teams merge feature branches with --squash, combining all feature commits into one:

git checkout main
git merge --squash feature/user-auth
git commit -m "Add user authentication (#142)"

Main always has one commit per feature. History is clean but you lose granular feature commits.

When to Use Each

Use merge when:

  • Merging long-lived feature branches back to main
  • Working on shared branches others are tracking
  • You want to preserve the exact sequence of when things happened
  • Code review happens at the PR level (merging the whole feature)

Use rebase when:

  • Updating a feature branch with the latest main before a PR
  • Cleaning up messy WIP commits before code review
  • You want a linear, readable git log
  • Working alone on a feature branch not yet shared

→ Reference common git commands with the Git Memo tool.