Skip to content

Merge vs. Rebase: The Great Debate

Ah, merge versus rebase. The eternal Git debate, responsible for countless hours of developer arguments. Both commands solve the same problem: how to get changes from one branch into another. But they do it in fundamentally different ways, and the one you choose reveals a lot about your philosophy on version control.

Let's cut to the chase. One method is simple but can lead to a messy, nonlinear history. The other demands a bit more care but produces a clean, easy-to-read log.

Merge

Merging is often seen as the "safe" and "easy" option. It takes all the new commits from main and smushes them into your feature branch with a special "merge commit." If there are conflicts, you resolve them once, right at the end.

Simple, right? But what happens when you do this repeatedly to keep your branch updated? Your history starts looking like a bowl of spaghetti, tangled with "merge loops" that make it nearly impossible to follow the actual work.

It's even worse if it's merged as-is on main, you'll have main incorporating people's plate of merging back-and-forth noodles. Good luck figuring out what happened there in six months.

Rebase

Rebase, on the other hand, can feel like black magic at first. You execute rebase expecting merge conflicts with your current code, yet you're fighting with pieces of code you forgot about two weeks ago... When it's actually behaving as designed.

Instead of creating a merge commit, rebase rewinds your branch's commits (i.e.: it removes all your commits), fast-forwards your branch to the latest version of main, and then replays your commits one by one on top.

Let's say you had three commits on your branch when main moved forward:

Rebase will replay them, one... by... one:

one...
by...
one

The result is a perfectly linear history. The catch? If you have conflicts, you might have to resolve them for each commit you're replaying, with the state of your code at each commit, which can be disorienting.

The Secret to Easy Rebasing

The trick is to have only one commit to rebase, either by committing once and amending, or by squashing all your commits into a single one before you pull.

This gives you the best of both worlds: you only resolve conflicts once (like merge), but you get the beautiful, linear history of a rebase.

My Take: Squash-Then-Rebase

I'll be honest: I am a huge proponent of the squash-then-rebase method. It's just as easy as merging but results in a branch that is neat, clean, and easy to read. It also perfectly mirrors what the main branch will look like after your squashed MR is merged.

"But you're losing history!"

Yes. And that's the point. In our Do’s and Don’ts, we already agreed to:

  • Make the smallest, most short-lived branches possible.
  • Squash the branch on merge.
  • Remove the branch after merge.

These branches are disposable by design. Their internal "save point" history is noise after the work is done. What matters is the final, polished commit that you're going to merge into main and share with everyone. What better way to prepare for that than to have a single, clean commit on top of main in your own branch?

Ready to merge
Merge
Main branch after

"But you have to force-push, and that's dangerous!"

It's only dangerous if you don't know what you're doing, or if you're force-pushing to a shared, long-lived branch like main. But as we said in Do’s and Don’ts, your feature branch is your sandbox. It's disposable. You should be free to do whatever you want in it without fear of breaking someone else's sandcastle.

And if you are collaborating on a feature branch, git push --force-with-lease --force-if-includes is your safety net:

  • --force-with-lease will block your push if your remote is up-to-date
  • --force-if-includes will block your push if there is a commit on the remote branch you don't have locally It prevents you from overwriting work you haven't seen. More importantly, if you're sharing a branch, you should be communicating. A quick "Hey, I'm rebasing now" is all it takes.

Ultimately, the choice is yours. But since your work will be squashed into a single commit on main anyway, you might as well keep your branch clean from the start. This way everyone keeps their ~noodles~ opinion out of main. 😉