Git Good
Git Good
Git Good
The Rebase Wars
S1 E918m · Feb 21, 2026
Git developers have feuded for 15 years over whether history should show what actually happened or what you wish had happened—and it all comes down to one command: rebase.

The Rebase Wars

The Prettiest Lie in Version Control

Last episode, we ended on a cliffhanger. Some teams hate merge commits. They look at a project history full of those two-parent snapshots, those little knots where parallel timelines rejoin, and they see clutter. Noise. A tangled web of criss-crossing lines that makes it impossible to read the story of what actually happened to this codebase. And they asked: is there another way?

There is. It is called a rebase. And few topics in the history of software development have generated as much heated, stubborn, occasionally personal debate as whether you should use it.

Teams have split over this. Style guides at major companies mandate one approach or the other. Senior developers have opinions they will defend at length, with the kind of conviction usually reserved for text editor preferences and tab-versus-space arguments. Entire conference talks have been devoted to making the case. Blog posts with hundreds of comments. Mailing list threads that run for weeks.

All over a cosmetic preference about how history should look.

Or is it cosmetic? Because underneath the surface argument about clean logs and messy graphs, there is a deeper question. A philosophical question. Should your version control history reflect what actually happened, or what you wish had happened? That question has no right answer. And that is exactly why people have been arguing about it for fifteen years.

Replaying History

So what does a rebase actually do? Let us walk through it slowly, because the mechanics matter.

You are working on a feature. You created a branch three days ago, and since then you have made five commits on it. Meanwhile, your colleagues have been busy on the main branch. They have merged in bug fixes, other features, small improvements. The main branch has moved forward without you.

Now you want to bring your work back in. With a merge, the approach we covered last episode, Git finds the common ancestor of your branch and the main branch, compares all three versions, combines the changes, and creates a new merge commit with two parents. Your branch history and the main branch history both remain intact. The merge commit is the knot where they rejoin. The result is accurate. It shows exactly what happened. Two streams of work proceeded in parallel and then came together at this point.

A rebase does something completely different. Instead of combining the two histories at a single point, it picks up your five commits, lifts them off the branch point where you started, and replays them one by one on top of the current main branch. As if you had started your work today, after all those bug fixes and features were already in place. As if the parallel development never happened.

The mechanics are precise. Git finds the common ancestor of the two branches. It extracts the diff introduced by each of your commits and saves those diffs to temporary files. It resets your branch to point at the tip of the main branch. Then it applies each of your saved diffs in order, creating brand new commits with new fingerprints. The old commits, the ones with the original fingerprints, still exist in the repository but nothing points to them anymore. They are orphans, invisible unless you know where to look.

The result is a perfectly linear history. No merge commit. No forking and rejoining lines. Just a clean straight sequence of commits, as if everyone had worked one after another in an orderly queue.

It is beautiful. It is simple. And it is a lie.

That is not what happened. You did not write those commits on top of the latest main branch. You wrote them three days ago, against a different version of the code, and then the tool rewrote them to look like they came later. The timestamps on the commits tell one story. The order in the log tells another. Anyone reading the history will see a clean sequence and assume that is how the work unfolded. It was not.

The Clean Line

So why would anyone want this? Why rewrite history to create a fiction?

Because the fiction is sometimes more useful than the truth.

Imagine you are a new developer joining a project with ten years of history. You need to understand why a certain module works the way it does. You run git log and start reading. With a merge-heavy history, you see a web. Branches forking and rejoining, parallel streams of work interleaving, merge commits every few entries that tell you nothing about the actual changes and everything about the organizational structure of the team that happened to exist at the time. It is accurate, but it is hard to follow. The signal is buried in the structure.

With a rebased history, you see a story. One commit after another. Each change building on the last. The logical progression of the work laid out in order. You can follow the thread without getting tangled in the web.

This is the argument for rebasing, and it is a strong one. History is not just a record. It is a communication tool. When you look at a project's commit log, you are reading a narrative written by the developers who came before you. A merge-heavy history is an honest but noisy narrative. A rebased history is an edited narrative, cleaned up for the reader.

The rebase camp points out that nobody reads first drafts for a reason. When you write a document, you revise it. You move paragraphs around. You cut the parts that do not serve the reader. You do not leave in every false start and deleted sentence because that would be more honest. You clean it up. Why should code history be different?

This is where a command called git rebase with the dash-i flag comes in. Interactive rebase. It is the power tool of history editing. When you run it, Git opens a list of your recent commits in a text editor and lets you rewrite the story. You can squash multiple commits into one, collapsing a morning's worth of trial and error into a single clean change. You can reorder commits so the logical sequence makes sense even if the chronological sequence was messy. You can reword commit messages to be clearer. You can drop commits entirely, erasing experiments that went nowhere.

This is version control as editorial process. You draft a rough version while you are working, full of fixup commits and half-finished thoughts and messages like "trying something" and "ugh, revert that." Then, before you share it with anyone, you sit down with interactive rebase and clean it up. Combine the fixups. Write proper messages. Tell the story of what you built, not the story of how you stumbled through building it.

The result, its advocates argue, is not dishonest. It is considerate. You are doing the work of editing so that every future reader does not have to do the work of deciphering.

The Golden Rule

But there is a catch. And the catch is so important that Linus Torvalds, a man not known for hedging his language, stated it as an absolute commandment.

Thou shalt not rebase trees with history visible to others.

Do not rebase commits that exist outside your repository. Do not rebase anything that other people have based their work on. Do not rebase anything you have pushed to a shared branch. This is the golden rule of rebasing, and the official Git documentation states it plainly: if you follow this guideline, you will be fine. If you do not, people will hate you, and you will be scorned by friends and family.

That sounds dramatic. It is not dramatic enough.

Here is what happens when you break the golden rule. You push five commits to a shared branch. Your colleague pulls those commits and starts building on top of them. They write three commits of their own, based on your work. Then you decide your commits could be cleaner. You rebase them. Interactive rebase. Squash two together, reword one, reorder another. The result is three clean commits instead of five messy ones. You force-push to the shared branch, replacing the old history with the new one.

Your three new commits have different fingerprints than the five old ones. As far as Git is concerned, they are entirely different commits. They happen to produce similar changes, but Git does not know that. It tracks commits by their fingerprints, and the fingerprints are new.

Now your colleague pulls. Git sees their three commits based on your original five, and it sees your three new commits that replaced those five. It has no idea these are related. It tries to reconcile two divergent histories that share no common recent ancestor. The result is a mess. Duplicated changes. Phantom conflicts. Commits that appear twice with slightly different messages. Hours of untangling.

Linus was characteristically blunt about where the boundary lies.

If you are still in the git rebase phase, you do not push it out. If it is not ready, you send patches around, or use private git trees.

The logic is simple. Pushing to a public branch is a small release. You are telling everyone: this is the state of my work. Base your work on this. Once you have made that promise, you cannot take it back. You cannot say oops, I lied, this is the state you should have used, now it is your mess to sort out.

Preferably you do not pull my tree at all, since nothing in my tree should be relevant to the development work you do.

Torvalds was drawing a boundary, and that last quote reveals something about how the Linux kernel manages this tension in practice. The kernel's mainline repository accepts ten thousand or so changesets every development cycle. It is never rebased. That history is sacred. But the subsystem maintainers who feed into it? They rebase freely in their own private branches before submitting their work upstream. The rule is not "never rebase." The rule is "never rebase what you have shared."

The Disaster Stories

Every developer community has its rebase horror stories. They get passed around like cautionary tales, and they all follow the same pattern: someone rebased a shared branch and the aftermath consumed hours, sometimes days, of everyone's time.

The most common disaster is the force push that eats commits. A developer rebases a shared branch to clean up history, force pushes to replace the old commits, and everyone who had pulled the original commits is now working against a history that no longer exists on the server. When they try to push their own work, Git refuses because the histories have diverged. When they try to pull, they get a cascade of conflicts against commits they thought were already resolved. The fix requires every affected developer to understand what happened and manually reconcile their local state with the rewritten remote. For a team of three, this is annoying. For a team of thirty, it is a lost afternoon.

A subtler disaster is the one Julia Evans documented in her widely-shared blog post on rebase problems. When you are rebasing a branch with many small commits, and one of those commits conflicts with the target, you have to resolve the conflict. But then the next commit might conflict too, against the resolution you just made. And the next. You end up in a loop where you are resolving the same conflict over and over, once per commit, when a single merge would have resolved it once. Developers who have lived through this call it rebase hell. Some of them never rebase again.

Then there is the silent data loss. Rebase creates new commits, which means the new commits do not carry forward metadata from the originals. Co-author credits can vanish. Cryptographic signatures are stripped, because the signature was for the original commit and the rebase created a different one. If your workflow depends on signed commits for auditability, rebasing breaks that chain.

And perhaps the most painful story is the simplest: a developer new to rebase makes a mistake during an interactive rebase, panics, runs git reset dash-dash-hard instead of git rebase dash-dash-abort, and loses a week of work. The commits are technically still in the reflog, that secret diary we will talk about in a future episode, but the developer does not know that. As far as they can tell, the work is gone. Evans specifically noted this pattern in her research: when they were new to rebase, they messed up a rebase and permanently lost a week of work.

The merge camp uses these stories as evidence. Look, they say. Merges are safe. Merges are reversible. You can undo a merge commit in one operation. You can never lose work with a merge because nothing gets rewritten. The history might be ugly, but at least it is honest and intact. Why would you use a tool that can destroy work in the hands of someone who does not fully understand it?

The rebase camp has a counterargument: you do not ban power tools because someone used a table saw without reading the manual. You train people. The golden rule is simple. Private branches: rebase freely. Shared branches: never rebase. Follow the rule and none of these disasters happen.

History as Record Versus History as Narrative

Strip away the technical details and the disaster stories, and the rebase debate comes down to a philosophical disagreement about what version control history is for.

The merge camp says history is a record. It should reflect what actually happened, in the order it happened, with all the mess and parallel development and false starts intact. A merge commit is not noise. It is information. It tells you that two streams of work came together at this point. It tells you who was working in parallel. It tells you when integration happened. If you erase that information, you are lying about the development process, and someday someone will need that information and it will not be there.

The rebase camp says history is a narrative. Its purpose is not archaeological accuracy but communication. Nobody benefits from seeing your fifteen fixup commits and your "oops, forgot a file" and your "actually revert that." Those commits served you during development. They served their purpose. Now clean them up so the next person reading this log can follow the story without wading through your drafts. A novel does not include the author's deleted paragraphs. A rebased history does not include the developer's false starts.

Both positions are defensible. Both have articulate, experienced advocates. And both reflect real values about how software teams should work.

Junio Hamano, who has maintained Git itself for twenty years, uses a workflow that embraces both philosophies in different phases. The Git project has multiple integration branches. New topic branches start in a branch called "seen," which is experimental and gets rebased freely. Topics that prove stable graduate to a branch called "next," where they are tested more seriously. Only when a topic is fully vetted does it merge into the master branch, where history is permanent. The "What's Cooking" status messages Junio sends to the Git mailing list track every topic through these stages.

This is a middle path. Rebase during development, when the work is private and in flux. Merge for integration, when the work is ready and shared. Use rebase to draft. Use merge to publish. The editorial metaphor holds: write rough, revise privately, publish clean.

Linus himself, despite his absolute language about the golden rule, is not against rebasing. He is against rebasing shared history. He explicitly said the rules were guidelines, not commandments.

If you have to occasionally break the rules to solve some odd problem, go ahead and break the rules.

His own workflow demonstrates the nuance. He never rebases the mainline. But he expects subsystem maintainers to clean up their work before submitting it. The Linux kernel's history is remarkably clean for a project with thousands of contributors, and rebasing is part of why.

The Tension That Stays Productive

So who won the rebase wars?

Nobody. And that is the point.

The debate has not been settled because it is not a debate that can be settled. It is a tension between two legitimate values: accuracy and clarity. Record and narrative. What happened versus what matters. Every team negotiates this tension for themselves, based on their size, their release cadence, their tolerance for messy logs, and their willingness to trust developers with sharp tools.

Some teams rebase everything. Every feature branch gets squashed into a single commit before merging. The main branch is a clean, linear story. If you want to know what happened, read the commit messages. If you want to know how it happened, that story is gone.

Some teams merge everything. Every branch merge creates a merge commit. The main branch is a dense graph of forking and rejoining lines. If you want to know how development actually unfolded, it is all there. If you want a simple story, good luck.

Most teams end up somewhere in between. Rebase your local work before pushing. Merge when integrating shared branches. Squash trivial fixups. Keep meaningful commits separate. Use interactive rebase to draft and merge to publish. The best practice, if you can call it that, is the approach Junio uses for Git itself: rebase in private, merge in public.

The command git rebase is one of those rare tools that reveals something about the people who use it. Whether you reach for it instinctively or avoid it on principle says something about how you think about history, about communication, about the purpose of the record you leave behind.

And with that, we have covered the core of how Git works. Branching, merging, rebasing. The cheap pointers, the three-way comparisons, the history rewrites. These three operations, and the debates about how to use them, are the engine of modern software development. Every workflow, every pull request, every continuous integration pipeline is built on these primitives.

The tool was ready. The revolution was underway. But Git was still a command-line tool used mostly by kernel developers and a handful of early adopters. What it needed was a place to live. A home on the web. A social layer on top of the distributed foundation.

In late two thousand seven, two Ruby developers in a San Francisco sports bar, sketching on napkins after a meetup, were about to provide one. That is where we go next.

git rebase dash i. Three words that start an argument. Interactive rebase opens your recent commits like a manuscript draft and lets you edit the story before anyone reads it. Squash, reword, reorder, drop. It is the most opinionated command in Git, because it says something most tools will not say out loud: the first version of history is almost never the one worth keeping. Whether that is wisdom or heresy depends entirely on who you ask.