Every merge you've done so far ended with a merge commit and a diamond shape in the graph. There's another way to combine work from two branches that produces a straight line instead. It's called rebase, and it's one of the most powerful (and most misused) commands in Git.
What rebase actually does
Say you're on feature, which has one commit on top of a common
ancestor with main. Meanwhile main has its own new commit.
You have a diverged shape.
Instead of merging, you can rebase:
git rebase main
Git does this:
- Sets aside your
featurecommits temporarily. - Fast-forwards
featureto point atmain's tip. - Re-applies your feature commits, one at a time, on top of that.
The result is a straight line: main's commits followed by your
feature's commits, no diamond, no merge commit. It looks like you
did all your feature work AFTER main had already been updated.
The commits are new commits
The re-applied commits have the same author, message, and file changes as the originals. But they have different parent commits, and therefore different SHA hashes. Git treats them as brand new commits.
The original commits still exist in Git's storage for a while
(recoverable via git reflog), but they become unreachable and
will be garbage-collected eventually. As far as your branch is
concerned, the old commits are gone and new ones have taken their
place.
This is why rebase is sometimes described as "rewriting history". It literally is.
Merge vs rebase: pick your style
Both merge and rebase get feature's work onto main. They produce different histories:
- Merge preserves the fact that development happened in parallel. The graph forks and rejoins. Merge commits act as markers.
- Rebase hides the parallelism. The graph is a straight line. Every commit looks like it built on the one before it, in order.
Neither is objectively better. Some teams love merge because it
records the actual history of who worked on what and when. Some
teams love rebase because the log is cleaner and easier to read.
Most teams pick one style and stick to it. Some do both:
git pull --rebase to keep your local branch clean, git merge
when landing features.
The one rule you cannot break
Never rebase commits that other people have based work on.
Because rebase creates new commits, anyone who had a copy of the old commits now has stale ones. Their work is anchored to commits that no longer exist on your branch. When they try to pull, Git gets confused, they get confused, and someone spends an afternoon untangling it.
Safe places to rebase:
- Your own local feature branch, before you push it anywhere.
- A remote branch that only you have touched.
Unsafe places to rebase:
- Any branch other people have pulled from and built on.
main,develop, or any long-lived shared branch. Ever.
If you follow that one rule, rebase is a great tool. If you break it, you'll learn why the rule exists the hard way.
What if there are conflicts?
Rebase re-applies your commits one at a time. If any of them
conflicts with what's now on main, Git pauses, hands you the
conflicted file, and waits for you to resolve. Same conflict
markers as a merge. Once you've fixed the file:
git add <file>
git rebase --continue
Git moves on to the next commit in the sequence. If you want to bail out completely:
git rebase --abort
Puts everything back the way it was before you started.