Git merge vs rebase: which one to use and when
The difference between git merge and git rebase isn't opinion — it's the shape of your history. Learn the golden rule of rebase and when each command makes sense.
The PR got merged, you're trying to understand what happened three weeks ago, and git log looks like a subway map during rush hour — branches crossing everywhere, merge commits that say "Merge branch 'feature/x' into main" and tell you absolutely nothing. If that's your situation, the team probably never discussed the difference between git merge and git rebase before starting.
If you're still getting comfortable with the basics, the post Git for beginners covers the foundation before you continue here. This post assumes you already commit, branch, and push — and you want to understand why your project history looks like a knot.
What each command actually does to the history
The core difference isn't about which one "wins" — it's about the shape of the history each approach produces.
git merge: preserves reality
git merge takes two points in the history and creates a merge commit that joins them. The result is a non-linear graph — you can see exactly when the branch was created, when it was merged, and what existed in parallel.
# On main, bringing in changes from the feature branch
git merge feature/auth
The history looks like this:
A---B---C---M (main)
\ /
D---E (feature/auth)
M is the merge commit. It has two parents: C and E. The history shows that D and E were developed in parallel with C.
Advantage: fidelity. You can see exactly what happened.
Disadvantage: with many short-lived branches, the graph gets hard to read. git log --oneline starts showing merge commits interspersed with real commits.
git rebase: rewrites history
git rebase takes your commits and replants them at the tip of another branch. It's like saying: "pretend I created my branch from here, not from back there."
# On the feature branch, rebasing on top of the current main
git rebase main
The history looks like this:
A---B---C---D'---E' (main + rebased feature)
D' and E' are new commits — same content, different SHA. The history is linear, as if the work had been done sequentially rather than in parallel.
Advantage: clean, linear history that's easy to follow in git log.
Disadvantage: you're rewriting history. SHAs change. And that's where the danger lives.
The golden rule of rebase
Never rebase a branch that other people already have locally.
This isn't a suggestion — it's the rule that, when broken, produces a specific kind of pain that makes devs hate Git.
The reason is straightforward: when you rebase, the original commits (D, E) are replaced by new ones (D', E'). If another dev has D and E locally and you did git push --force on the rebased branch, Git will try to reconcile two incompatible histories when they pull. The result is duplicate commits, strange conflicts, and a history that needs to be cleaned up manually.
# Disaster scenario
git rebase main feature/shared # rewrites the SHAs
git push --force origin feature/shared # overwrites the remote
# → colleague who had feature/shared locally now has a problem
The golden rule in practice:
main,develop,staging— branches the whole team uses: always merge, never rebase- your local feature branch, that only you are working on: rebase freely
When to use each one
The split isn't dogmatic — it's contextual.
Use merge when:
- You're integrating someone else's work into the team's main branch
- The branch had contributions from multiple devs
- You want to preserve exactly when the feature was completed (useful for auditing)
- Team policy is "faithful history above all"
Use rebase when:
- You want to update your feature branch with the latest changes from main before opening a PR
- You want to clean up a series of WIP commits ("fix", "fix2", "this time for real") before submitting
- You're working alone on the branch and want a more readable linear history
The most common flow that works well in practice:
# Working on the feature, main has moved forward
git fetch origin
git rebase origin/main # update the feature on top of current main
# When merging: in the PR, use squash merge or merge commit
# — depends on team policy
Interactive rebase: editing what hasn't gone to the remote yet
git rebase -i (interactive) is a separate feature worth mentioning. It lets you reorganize, squash, edit, or delete commits before publishing.
# Edit the last 4 commits
git rebase -i HEAD~4
This opens an editor with the list of commits. You can:
pick— keep the commit as issquash(ors) — fold into the previous commitreword— change the commit messagedrop— delete the commit
Example cleanup before a PR:
pick a1b2c3 feat: add CPF validation
squash d4e5f6 fix: fix regex
squash 7a8b9c fix2: this time for real
reword 0d1e2f feat: add registration endpoint
Result: two clean commits, with no "fix2: this time for real" in the project history.
This is rebasing local commits — exactly the case where it's safe.
The case for --force-with-lease
If you rebased a branch and need to push, Git will reject the normal push (because the remote and local histories have diverged). The instinct is to use git push --force, but there's a safer option:
git push --force-with-lease origin feature/my-branch
The difference: --force-with-lease fails if someone has pushed commits to the remote branch since your last fetch. This prevents accidentally overwriting someone else's work.
Using plain --force on a shared branch makes for a difficult conversation with the team.
Frequently asked questions
Which is better, merge or rebase?
It depends on what you value. If you want faithful history that shows what actually happened, use merge. If you want linear history that's easy to navigate in git log, rebase your local feature branch before merging. Most teams use both: rebase to keep feature branches updated, merge (or squash merge) to integrate into main.
Can I rebase main into my feature branch?
Yes, and it's a legitimate use — you're rebasing your branch on top of main, not main itself. What you cannot do is run git rebase feature/x while on main, rewrite the shared commits, and force push. The direction of the rebase matters.
What is squash merge and why does GitHub suggest it?
Squash merge takes all the commits from a feature branch and compresses them into a single commit on main. It's a middle ground: you keep free commits during development (including the "fix2" ones), but main's history stays clean — one commit per feature. Works well for teams that don't want to enforce commit discipline during development.
Does git rebase destroy commits?
Not exactly — it creates new commits with the same content and a different SHA. The original commits stay in the reflog for a while and can be recovered with git reflog if you need to undo. But for anyone who had the original commits on another machine, they're incompatible with the new ones.
History as a decision document
Git history isn't just for future-you — it's for whoever maintains the code after you. A clean, descriptive git log is documentation. A graph full of context-free merge commits is noise.
The choice between merge and rebase defines the format of that document. There's no absolute right answer — there's team consistency. Decide the policy, document it in the README, and follow it.
For comparing file versions outside Git — before opening a PR or when you want to see what changed in a file that isn't in a repository — I use the Text Diff Checker. You paste both versions and see the differences highlighted side by side, no terminal required.
- 01 UTF-8 and encoding problems: why accents break and how to fix them What UTF-8 is, code points, mojibake, and why accented text turns to garbage — the mechanism explained end to end.
- 02 MIT, Apache, and GPL Licenses: Practical Differences for Developers MIT, Apache 2.0, and GPL are not the same thing. Understand permissive vs copyleft, patent protection, and compatibility before shipping code to production.