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 merge does 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

  • 9d1a0ef and 4f3c2bb no longer exist — replaced by 2a9e1cc and d8b4f0a with 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 squash before 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 regular git pull to “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 --continue after resolving conflicts during rebase: rebase state stays “hanging”, git status will clearly remind you that you’re in the middle of a rebase.
  • Using merge repeatedly on a personal branch fills the log with small merge commits, hard to read — should rebase to update instead of merging main back into your branch many times.
  • Over-squashing: merging many unrelated changes into one commit makes git bisect and partial git revert useless.

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.

gitrebasemergeversion-controlworkflow