Rebase Branches Cleanly
Rebase branches to keep history clean in this Git Tutorial lesson. Practical steps, comparisons, troubleshooting, and what to learn next.
Focus: rebase branches to keep history clean
Your commit history is a story — and too often it reads like a messy first draft. Merging long-lived feature branches into main dumps Merge branch 'feature/login' into main commits into your log, interleaving your work with your teammates' in a way that makes git log feel like a puzzle. You want a clean, linear history that's easy to review, easy to bisect, and easy to understand months later. The answer is to rebase branches to keep history clean — a technique that rewrites your branch's commits on top of the latest main, giving you a straight line of logical, focused changes. This lesson will show you exactly how to do it safely, when to choose it over merging, and how to recover when something goes sideways.
The problem this lesson solves
Every Git user hits the same wall: you've been working on a feature branch for a few days, and when you're ready to integrate it, your branch has drifted far behind main. Other developers have pushed new commits — maybe a refactor, a new API, a bug fix. Your feature branch is based on an outdated version of the codebase.
If you merge at this point, two things happen:
- Git creates a merge commit that has two parents — one from your branch, one from
main. - Your branch's commits are interleaved with the new commits from
main, but the log shows a non-linear history with branch points and merge bubbles.
Let's see this in action. Imagine this history:
A---B---C feature/login
/
D---E---F---G main
If you run git merge feature/login while on main, you get:
A---B---C feature/login
/ \
D---E---F---G---M main (merge commit)
The problem? git log --oneline now shows commits like M, C, B, A, G, F, E, D in an order that doesn't represent a logical sequence of changes. The history is cluttered and harder to review. A reviewer can't easily see "these three commits are the feature" because they're mixed in with unrelated commits from main.
This is the core pain: merging preserves all the noise of parallel work in your history. Whether you're on a team of two or two hundred, that noise makes git blame, git bisect, and code review slower and more confusing.
Core concept / mental model
Think of your commit history as a story told in chronological order. When you rebase, you're saying: "I want to tell my story as if I had started my work after the latest commit on main."
Technically, git rebase does this:
- It finds the common ancestor of your branch and
main(often called the merge base). - It collects all the commits that exist on your branch but not on
main. - It replays those commits one by one on top of the latest commit of
main.
The result is a linear history: your commits are stacked directly on top of the current main, with no merge commit and no branching in the log.
D---E---F---G---A'---B'---C' main (after rebase and fast-forward)
Note the apostrophes: A', B', C' are new commits — even if the message and diff are identical, each has a new SHA-1 hash because their parent changed. That's the key mental model to internalize: rebase rewrites history. It's not a merge-then-squash; it's a full replay of your changes.
This is why rebasing a branch that's already been shared with others (pushed to a remote and pulled by teammates) is dangerous — you'd be rewriting commits that others depend on.
Here's a simple definition to keep handy:
Rebase (verb): to reapply commits from one branch onto the tip of another branch, creating a linear history. Merge (verb): to combine two branches by creating a new commit that has two parents — preserving the branching structure.
How it works step by step
The rebase process is straightforward once you break it down:
- Switch to your feature branch —
git switch feature/login. You must be on the branch you want to rebase. - Rebase onto the target branch —
git rebase main. Git will: - Identify commits that are infeature/loginbut not inmain. - Reset your branch to point tomain's tip. - Apply each of your commits, one at a time, as new commits on top. - Resolve any conflicts — If a commit's changes conflict with the new base, Git pauses and asks you to fix. You edit the files,
git add, and rungit rebase --continue. - Fast-forward
main(optional) — Once your rebase is done, switch tomainand rungit merge feature/login. Since your branch is now directly ahead ofmain, Git will fast-forward — no merge commit, just a clean pointer move.
The key difference from merge: rebase replays your commits, merge combines them. Both integrate changes, but they produce different history shapes.
Hands-on walkthrough
Let's walk through a concrete example. Assume you're on a feature branch called feature/login, and main has advanced.
Setup
# Starting state: your feature branch has two commits
git log --oneline feature/login
# 2a03f4c Add login form validation
# 8b41c8a Create login form component
git log --oneline main
# 9f6e2a4 Update API client
# c1b55d3 Fix typo in docs
# a1b2c3d Initial commit
Rebase onto main
git switch feature/login
git rebase main
# First, rewinding head to replay your work on top of it...
# Applying: Create login form component
# Applying: Add login form validation
If there are no conflicts, you're done. Check the history:
git log --oneline feature/login
# 3e8f0a1 Add login form validation
# 2faab73 Create login form component
# 9f6e2a4 Update API client
# c1b55d3 Fix typo in docs
# a1b2c3d Initial commit
Notice the new SHA hashes (3e8f0a1, 2faab73) and the linear ordering. Your commits now sit on top of main's latest work.
Fast-forward main
Now integrate into main cleanly:
git switch main
git merge feature/login
# Updating 9f6e2a4..3e8f0a1
# Fast-forward
No merge commit — perfect linear history.
Handling conflicts
Conflicts force you to slow down. If main modified a file you also changed, rebase stops:
git rebase main
# Auto-merging src/login.py
# CONFLICT (content): Merge conflict in src/login.py
# error: could not apply 8b41c8a... Create login form component
# Resolve all conflicts manually, mark them as resolved with "git add", then run "git rebase --continue".
Open the file, find the conflict markers (<<<<<<<, =======, >>>>>>>), edit to keep the right code, then:
git add src/login.py
git rebase --continue
Git opens your editor to confirm the commit message — save and close, and the rebase continues.
Compare options / when to choose what
You now have two main integration strategies: rebase and merge. Here's a practical comparison:
| Feature | Rebase | Merge |
|---|---|---|
| History shape | Linear, clean | Non-linear, complex |
| Commit IDs on feature branch | Rewritten | Preserved |
| Merge commit | No | Yes |
| Ability to bisect bugs | Easier | Harder |
| Safe for shared branches? | No — rewrites public history | Yes |
| When to use | Private feature branches before PR | Long-lived or shared branches |
| Learning curve | Steeper (need conflict resolution) | Gentler |
When to choose rebase:
- Your branch is local or only pushed to your own fork.
- You want a clean, linear history for code review (e.g., open-source contributions).
- You're integrating a short-lived feature (a few commits) that you can safely rewrite.
When to choose merge:
- The branch has been pushed and others are using it.
- You need to preserve the exact commit hashes (e.g., for audit or compliance).
- You're merging a release branch that has been tested widely.
Troubleshooting & edge cases
Even the best rebase can hit snags. Here are the most common ones and how to fix them.
"Cannot rebase: You have unstaged changes"
Git refuses to rebase if your working tree isn't clean. Fix by stashing or committing:
git stash
git rebase main
git stash pop
Conflicts that won't resolve
If you're in a conflict and want to exit the entire rebase:
git rebase --abort
This restores your branch to its pre-rebase state. If you've resolved some conflicts but realize it's not worth it, abort and start over — or use git rebase --quit to stop without restoring.
Rebase onto the wrong branch
You rebased onto main but meant to rebase onto develop. No panic — you can re-rebase:
git rebase --abort # if still in progress
git rebase develop
Or, if you already finished the rebase, use git reflog to find your old branch tip and reset:
git reflog
# 3e8f0a1 HEAD@{0}: rebase finished
# 8b41c8a HEAD@{1}: rebase: checkout feature/login
# ... [old commit]
git reset --hard <old-commit>
Rebased a shared branch by accident
If you already pushed and force-pushed, your teammates will have conflicts. Communicate immediately. If it's a small team, the standard fix is for everyone to run:
git fetch
git rebase origin/feature/login # or use original branch then re-apply
Pro tip: Never rebase a branch that others have based their own work on. If you must, coordinate with your team and use
git push --force(or--force-with-leaseif your remote supports it) with warning.
What you learned & what's next
You've mastered the core idea of how to rebase branches to keep history clean: you can explain why rebasing beats merging for linear history, you can apply it safely in a practical exercise, and you've seen the edge cases that trip up most developers. You now know that rebase replays commits, that it rewrites history, and that it's the right tool for private feature branches.
You've also learned to compare it with merge and choose the right approach based on whether the branch is shared — a critical skill for any team.
Next in this Git Tutorial track, you'll move on to more advanced collaboration techniques. Up next: Interactive rebase — the tool that lets you squash, reorder, and edit commits during a rebase. With a solid foundation in plain rebase, you'll be ready to craft even more intentional, readable history.
Practice recap
In your local test repo, create a feature branch, add a commit, switch to main, add another commit, then rebase the feature branch onto main. Verify the linear history with git log --oneline --graph. Then introduce a conflict on purpose and practice resolving it with git add and git rebase --continue.
Common mistakes
- Rebasing a branch that's already been pushed and used by teammates — this rewrites public commits and causes painful conflicts for others.
- Forgetting to stash or commit local changes before running
git rebase— Git aborts and you lose your bearings. - Running
git rebase --continuewithout adding resolved files — the rebase gets stuck and can be confusing to recover from. - Merging
maininto your feature branch before rebase — this creates a merge commit and defeats the purpose of a linear history. - Force-pushing a rebased branch to a shared remote without coordination — you can permanently lose work if others based branches on the old version.
Variations
- Interactive rebase (
git rebase -i) — lets you squash, reorder, and edit commits for an even more curated history. - Rebase with
--onto— move a subset of commits onto a different base, useful for fixing branching mistakes. - Using
git pull --rebaseinstead of merge while syncing with a remote — keeps your local history linear when fetching new changes.
Real-world use cases
- On a team of 10+, rebase feature branches onto the latest
mainbefore opening a PR, so reviewers see only your changes in a clean, linear sequence. - In open-source contributions, rebase your fork's feature branch atop the upstream
mainto resolve conflicts early and keep the maintainer's log tidy. - During a release branch hotfix, rebase your small fix onto the release branch to apply it without a merge commit, preserving a straight release history.
Key takeaways
- Rebase replays your branch's commits on top of another branch's tip, creating a linear, commit-by-commit history.
- Rebase rewrites commit hashes — never rebase a branch that others have already pulled.
- After rebasing, integrating into
mainbecomes a clean fast-forward with no merge commit. - Conflicts during rebase are resolved one commit at a time — use
git rebase --continueafter staging fixes, or--abortto bail out. - Choose merge over rebase when a branch is shared or when preserving exact commit IDs matters.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.