
5 minutes read
Git merge vs rebase: the only mental model you need
Table of contents
- → Introduction
- → TL;DR (rebase AND merge)
- → What Git is really tracking
- → Git merge, explained like you are new to it
- → Git rebase, explained in plain words
- → A simple decision flow that actually works
- → My opinionated default setup
- → Practical workflows you can copy
- → Pitfalls and how to avoid them
- → Team policy you can paste in your README
- → FAQ: quick answers to common “merge vs rebase” questions
Introduction
I’ll be honest. Merge and rebase with Git confused me for a long time. All I knew was that rebase looked cleaner, so I did it. If that’s you, this post is for you.
TL;DR (rebase AND merge)
- Merge keeps the true history. Safe, sometimes messy.
- Rebase rewrites your local history so it looks linear. Clean, but only safe before you share it.
- Golden rule: rebase your own local work, merge shared work back to
main
. - My default: rebase locally, then merge into
main
with--ff-only
or a merge commit depending on team policy.
What Git is really tracking
Git is a history of snapshots. A merge joins two histories and records a merge commit. A rebase copies your commits and replays them on top of another base so it looks like you started later. The end state of your files can be identical either way. The difference is how the story reads.
Git merge, explained like you are new to it
Two branches exist. With merge, Git glues the two lines together and adds a merge commit that says “this is where they joined.” You keep every fork and join in the timeline. It is honest. It may be busy.
When I use merge
- Final integration to
main
when multiple people have touched things. - When I want an explicit join point for a big feature or release.
- When our policy requires a merge commit for traceability.
If you want a linear main
but still integrate safely, merge with fast-forward only:
git checkout main git fetch origin git merge --ff-only feature/add-payments
--ff-only
refuses to create a merge commit if a fast-forward is not possible. It keeps history straight and prevents surprise “merge bubbles.”
Git rebase, explained in plain words
With rebase, Git copies your commits and pastes them on top of the latest main
. It looks like you started after everyone else and never diverged. That linear look is why people love it.
The danger: rebasing rewrites commit IDs. If others already pulled those commits, you just changed the past under their feet. That is how you get “force push” drama. So do not rebase public history that other people may depend on.
When I use rebase
- While my branch is still private or only on my machine.
- Right before I open or update a PR, so the diff is clean.
- For commit cleanup with
git rebase -i
to squash “fix typo” noise.
A tidy pre-PR refresh:
git fetch origin git rebase origin/main # resolve conflicts if any git rebase --continue
If your team agrees on it, you can make git pull
rebase by default:
git config --global pull.rebase true
That tells Git to replay your local commits on top of the fetched branch instead of making a merge commit on every pull.
A simple decision flow that actually works
-
Working alone on a feature branch Rebase freely until you push. Clean history, minimal noise.
-
Opened a PR and people are reviewing Prefer
git pull --rebase
to stay fresh. Avoid rewriting commits others commented on unless your team is fine with it. -
Integrating into
main
- Option A: Rebase the feature on
main
, then fast-forward merge--ff-only
. Linearmain
, no merge commit. - Option B: Merge with
--no-ff
to keep an explicit merge commit for the feature. Useful for auditing and revert clarity.
- Option A: Rebase the feature on
-
Never rebase history that others already based work on, unless your team coordinates a forced update. If you must, use
--force-with-lease
to reduce collateral damage.
My opinionated default setup
I like a clean history that does not surprise teammates.
# Rebase on pull by default git config --global pull.rebase true # Refuse accidental non-linear merges when updating main git config --global pull.ff only # Make fast-forward only merges the norm (avoid accidental merge commits) # You can also use: git merge --ff-only
Why this mix: I rebase my local work to keep it linear, then I integrate with fast-forward where possible. If a feature truly needs a merge commit for context, I use --no-ff
on purpose.
Practical workflows you can copy
Keep a feature branch fresh without noise
git switch feature/refactor-cache git fetch origin git rebase origin/main # Fix conflicts if any. git push --force-with-lease # Only if the branch was already pushed.
Integrate a finished feature with a fast-forward
git switch main git fetch origin git merge --ff-only feature/refactor-cache git push
Prefer a visible merge commit for big features
git switch main git merge --no-ff feature/billing-v2 git push
--no-ff
forces an explicit merge commit even when fast-forward is possible. Use this when you want a clear “we shipped this feature here” marker.
Pitfalls and how to avoid them
- Rebasing after you pushed: coordinate or expect pain. If you do it, use
git push --force-with-lease
so you do not clobber someone else’s work. - Perpetual conflict hell: if a feature drifts for weeks, rebase more often or slice the work into smaller PRs.
- Dirty PR diffs: rebase on
main
right before you push the branch or request review.
Team policy you can paste in your README
- Rebase freely on private branches.
- Do not rewrite public history without agreement.
- Keep
main
linear with--ff-only
, except when an explicit merge commit is useful. - Before merging, rebase on
main
to reduce conflicts. - Use
--force-with-lease
when rewriting your own pushed branch.
This policy balances readability and safety using what Git actually supports out of the box.
FAQ: quick answers to common “merge vs rebase” questions
Does rebase change code differently than merge?
No. If you resolve conflicts the same way, the final snapshot can be identical. The history is what changes.
Is rebase dangerous?
Only when other people already pulled your commits. Rewriting those commits forces everyone else to reconcile. Keep rebases local or coordinate.
What about interactive rebase for cleanup?
Great for squashing fixups and editing messages before your code is public. It is the standard way to polish a branch.
Did you like this article? Then, keep learning:
- Learn fast-forward vs no-fast-forward merges relevant to integration strategies
- Understand how to handle errors efficiently in Laravel HTTP client
- Learn how to automate Laravel factory generation with AI for speed
- Explore effective Laravel upgrade guides for maintaining modern projects
- Improve your Laravel code with best practices and tips for 2025
- Learn to refactor Laravel collections with these 15 practical tips
- Discover how to maintain consistent Laravel migrations, essential for teamwork
- Deep dive into Laravel's query builder for advanced database control
- Explore managing cache strategies simply in Laravel for better performance
- Master merging strategies and the service container for dependency management
0 comments