Git Rebase or Merge? Choose the right strategy for your team
1. Overview
git merge and git rebase are both used to integrate changes between two branches, but they create very different commit histories. Choosing the wrong strategy makes logs messy, hard to review, or worse — breaks your teammates’ history. This article helps you understand the mechanism of each command through concrete examples, know when to merge, when to rebase, and avoid the most common mistakes when using rebase in a team.
2. Example Context
Suppose you’re working on a feature/payment branch from main. While you’re coding, your teammates have merged several commits into main.
git log --oneline --graph --all
* c3f9a1d (main) Add PaymentValidator
* 7b2e4aa (main) Fix OrderService bug
| * 9d1a0ef (feature/payment) Add PaymentService.processPayment()
| * 4f3c2bb (feature/payment) Initialize PaymentService
|/
* a1b2c3d Shared base commit
The two branches have diverged from a1b2c3d. Now you want to bring feature/payment up to speed with main before creating a pull request. There are two ways: merge or rebase.
3. Git Merge: preserve history, create a merge commit
3.1. How to use
git checkout feature/payment
git merge main
This command creates a new merge commit with two parents: the last commit of feature/payment and the last commit of main.
* e5f6a7b (HEAD -> feature/payment) Merge branch 'main' into feature/payment
|\
| * c3f9a1d (main) Add PaymentValidator
| * 7b2e4aa (main) Fix OrderService bug
* | 9d1a0ef Add PaymentService.processPayment()
* | 4f3c2bb Initialize PaymentService
|/
* a1b2c3d Shared base commit
3.2. Characteristics
- The original history of both branches is preserved 100%, no commit is modified.
- There’s an extra “junk” merge commit that records when the two branches meet.
- Absolutely safe with pushed branches — no one is affected.
Note:
git mergedoes not rewrite the SHA of any existing commit. This is why it’s always safe with shared branches.
4. Git Rebase: rewrite history, create a linear branch
4.1. How to use
git checkout feature/payment
git rebase main
Rebase takes each commit on feature/payment starting from the branching point (a1b2c3d), then “replays” them on top of main. The result is completely new commits with different SHAs.
* 2a9e1cc (HEAD -> feature/payment) Add PaymentService.processPayment()
* d8b4f0a Initialize PaymentService
* c3f9a1d (main) Add PaymentValidator
* 7b2e4aa (main) Fix OrderService bug
* a1b2c3d Shared base commit
The history is now a straight line (linear history), no merge commit.
4.2. Characteristics
9d1a0efand4f3c2bbno longer exist — replaced by2a9e1ccandd8b4f0awith new SHAs.- Clean log, easy to read, easy to
git bisect. - Dangerous if the branch has been pulled by others, because they’re holding old commits that have “disappeared”.
5. Handling conflicts: the biggest difference when operating
With merge, conflicts (if any) only need to be resolved once at the merge commit:
git merge main
# CONFLICT (content): Merge conflict in PaymentService.java
# ... fix file, resolve ...
git add PaymentService.java
git commit
With rebase, if there are multiple commits, conflicts can occur at each commit during replay:
git rebase main
# CONFLICT in commit 4f3c2bb
# fix file, resolve then:
git add PaymentService.java
git rebase --continue
# if next commit also conflicts, repeat
If you want to stop and return to the state before rebase:
git rebase --abort
Note: Rebasing a branch with 10 commits sometimes means you have to resolve conflicts 10 times — one reason why
squashbefore rebase (see section 7) is so useful.
6. Golden Rule: never rebase a shared branch
This is the most important rule when using rebase.
# main branch has been pulled by 5 other people
git checkout main
git rebase feature/old-stuff # ❌ EXTREMELY DANGEROUS
git push --force origin main
Because rebase creates new SHAs for all commits, after push --force, anyone who pulled old main will have a diverged history with remote. Their next pull will fail or create duplicate commits.
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind
If you must force-push after rebase, use --force-with-lease instead of --force to avoid overwriting someone else’s commits:
git push --force-with-lease origin feature/payment
--force-with-lease checks if remote hasn’t had new commits pushed by others since your last fetch; if it has, the command fails instead of blindly overwriting.
Golden Rule: only rebase personal branches that haven’t been shared or that only you’re working on. Never rebase main, develop, or any shared branch.
7. Interactive rebase: clean up history before merge
A very practical application of rebase is cleaning up “junk” commits on your personal branch before creating a pull request.
git checkout feature/payment
git rebase -i HEAD~3
The editor opens:
pick 4f3c2bb Initialize PaymentService
pick 9d1a0ef Add PaymentService.processPayment()
pick 1c8f3aa fix typo
Change the last two lines to squash to merge them into the first commit:
pick 4f3c2bb Initialize PaymentService
squash 9d1a0ef Add PaymentService.processPayment()
squash 1c8f3aa fix typo
Save and exit; Git will ask you to write a new commit message. Result: 3 messy commits become 1 clean, meaningful commit — perfect before opening a PR so reviewers don’t have to read “fix typo”, “wip”, “oops forgot import” commits.
8. Practical strategy: combine both
Most efficient teams don’t choose absolutely one or the other, but combine them based on context:
| Situation | Should use |
|---|---|
Update personal branch with latest main |
rebase |
| Clean up messy commits before opening PR | rebase -i (squash) |
Merge reviewed feature branch into main |
merge (usually via PR, can squash-merge) |
| Branch being worked on/pulled by many people | merge, absolutely no rebase |
| Need to preserve trace of integration time for audit | merge |
Common workflow in practice:
# 1. Before opening PR: rebase onto main to update, avoid late conflicts
git checkout feature/payment
git rebase main
# 2. Clean up commits if needed
git rebase -i main
# 3. Push personal branch (safe because only you use it)
git push --force-with-lease origin feature/payment
# 4. Merge into main via PR (merge commit or squash-merge, don't rebase main)
This approach keeps the feature/payment log clean, linear, easy to review in the PR, while main remains safe because it only receives merge commits (or squash commits) from approved PRs.
9. Squash merge: middle-ground option
Many platforms (GitHub, GitLab) have a “Squash and merge” button — combines all PR commits into one single commit and merges into main.
git checkout main
git merge --squash feature/payment
git commit -m "Add payment processing feature (#42)"
Result: main has exactly one new commit, no merge commit, the history of feature/payment (on remote) remains unchanged if you haven’t deleted the branch. This is the most popular choice for teams using PR workflow, because it keeps the log on main clean while not touching the original history.
10. Common mistakes
- Rebase then push normally (without
--force): Git will error because history has diverged. Don’t use regulargit pullto “fix” it — it will create an unwanted merge commit, breaking the linear history you just created. - Forgetting the branch being rebased is shared: always check
git log origin/<branch>to see if anyone else is relying on old commits. - Forgetting to
--continueafter resolving conflicts during rebase: rebase state stays “hanging”,git statuswill clearly remind you that you’re in the middle of a rebase. - Using
mergerepeatedly on a personal branch fills the log with small merge commits, hard to read — should rebase to update instead of mergingmainback into your branch many times. - Over-squashing: merging many unrelated changes into one commit makes
git bisectand partialgit revertuseless.
11. Conclusion
merge is safe, preserves honest history, suits shared branches. rebase creates clean, linear history, but rewrites SHAs so can only be used on unshared personal branches. In practice, you don’t need to choose one side absolutely: rebase to update and clean up your personal branch, merge (or squash-merge) to integrate into main via PR. Remember the Golden Rule — never rebase a branch that others depend on — and you’ll avoid most Git troubles in your team.