Imagine you are reading a high-quality technical book. Each chapter follows the next in a logical sequence, building upon the previous concepts. Now, imagine if that book were written like a typical unmaintained Git repository: chapters are out of order, there are random pages of “fixed typo” or “checkpoint” inserted every few pages, and two different storylines merge into each other without warning. You would put that book down immediately.
In software development, your Git history is the story of your project. A messy history filled with “wip,” “fix,” and dozens of merge commits makes it nearly impossible for teammates (or your future self) to understand why a change was made. This is where Git Rebase comes in. While often feared by beginners, rebasing is one of the most powerful tools in a developer’s arsenal for creating a clean, linear, and readable project history.
In this comprehensive guide, we will demystify git rebase. We will explore how it differs from merging, how to use interactive rebasing to “squash” your commits, and the “Golden Rule” that will keep you from breaking your team’s workflow. Whether you are a beginner or looking to polish your intermediate skills, this guide will provide everything you need to master the art of the rebase.
What is Git Rebase?
At its core, rebasing is the process of moving or combining a sequence of commits to a new base commit. Internally, Git accomplishes this by creating new commits and applying them to the specified base. It is essentially saying: “I want to pretend that I started my work from this specific point in time, even though I actually started it earlier.”
Think of it like this: You are painting a mural on a wall (the main branch). You decide to work on a specific detail, like a tree, on a separate piece of transparent glass (your feature branch). While you were painting your tree, someone else added a sun and clouds to the wall. If you just slap your glass onto the wall, the tree might overlap the clouds in a weird way. By rebasing, you are effectively taking your tree painting, looking at the updated wall, and choosing the perfect new spot to apply your paint so it looks like the sun and clouds were always there before you started.
# The conceptual view of a rebase
# Before:
# A---B---C (main)
# \
# D---E (feature)
# After 'git rebase main':
# A---B---C (main)
# \
# D'---E' (feature)
Notice that in the “After” example, the commits D and E have become D' and E'. This is because rebasing rewrites history. These are brand new commits with different hash IDs, even if the code changes are the same.
Git Rebase vs. Git Merge: The Key Differences
To understand why rebasing is useful, we must compare it to its sibling: git merge. Both commands serve the same purpose—incorporating changes from one branch into another—but they do so in fundamentally different ways.
The Merge Approach
Merging is non-destructive. It creates a new “merge commit” that ties the two histories together. It acts as a historical record of exactly when the branches were joined.
- Pros: Preserves the complete history and chronological order. Easy to understand and safe.
- Cons: Can lead to a “polluted” history with many unnecessary merge commits, making the
git loghard to read.
The Rebase Approach
Rebasing moves the entire feature branch to begin on the tip of the main branch. It effectively incorporates all of the new commits in main by “replaying” the feature branch commits on top of them.
- Pros: Results in a perfectly linear history. Eliminates unnecessary merge commits. Makes
git logmuch cleaner. - Cons: Rewrites history, which can be dangerous on shared branches. If not done carefully, it can lead to complex conflict resolution scenarios.
When Should You Rebase?
Choosing between merge and rebase usually depends on your team’s workflow and the specific situation. Here are the most common scenarios where rebasing shines:
- Updating a Local Feature Branch: If you’ve been working on a feature for a few days and the
mainbranch has moved forward, rebasing your feature branch ontomainkeeps your work up-to-date without adding a “Merge main into feature” commit. - Pre-Pull Request Cleanup: Before submitting your code for review, you can use interactive rebase to clean up your commit messages, combine small “fix typo” commits into meaningful ones, and ensure the reviewer sees a logical progression of work.
- Maintaining a Linear History: Some organizations enforce a “No Merge Commit” policy to keep their master branch history as clean as possible.
How to Perform a Standard Rebase
Let’s look at the step-by-step workflow for a standard rebase. Suppose you are working on a branch named feature-login and you want to update it with the latest changes from main.
Step 1: Update your local main branch
First, ensure your local main branch is synchronized with the remote repository.
# Switch to main branch
git checkout main
# Pull the latest changes
git pull origin main
Step 2: Start the rebase
Switch back to your feature branch and tell Git to rebase it onto main.
# Switch to feature branch
git checkout feature-login
# Rebase onto main
git rebase main
Step 3: Handle the results
If there are no conflicts, Git will finish the process automatically. If there are conflicts, Git will pause and ask you to resolve them (see the “Handling Conflicts” section below).
Step 4: Push your changes
Because rebasing rewrites history, if you have previously pushed your feature-login branch to a remote server, a standard git push will be rejected. You must use a “force” push.
# WARNING: Use --force-with-lease instead of --force for better safety
git push origin feature-login --force-with-lease
Pro-tip: --force-with-lease is safer than --force because it will fail if someone else has pushed new commits to the remote branch that you haven’t pulled yet.
Interactive Rebase: The Developer’s Swiss Army Knife
While standard rebase moves your branch, Interactive Rebase (git rebase -i) allows you to edit the commits themselves as they are being moved. This is the ultimate tool for commit hygiene.
To start an interactive rebase for your last 3 commits, run:
git rebase -i HEAD~3
Your default text editor will open with a list of commits and a list of commands. It will look something like this:
pick a1b2c3d Add login form validation
pick e5f6g7h Fix typo in validation
pick i9j0k1l Add unit tests for login
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# d, drop <commit> = remove commit
Common Interactive Rebase Tasks
- Squashing: Change
picktosquash(ors) to combine a commit with the one above it. This is great for combining small bug fixes into the main feature commit. - Rewording: Change
picktoreword(orr) if you just want to fix a typo in a commit message. - Dropping: Change
picktodrop(ord) to completely delete a commit from the history. - Reordering: Simply move the lines in the text editor to change the order in which commits are applied.
Once you save and close the file, Git will execute the commands from top to bottom.
Handling Conflicts During a Rebase
Conflicts happen when Git can’t figure out how to merge changes automatically. During a rebase, conflicts are handled one commit at a time. This can feel tedious if you have many commits, but it ensures each “layer” of your work is applied correctly.
When a conflict occurs, Git will stop and show you a message. Here is how to fix it:
- Locate the conflict: Run
git statusto see which files are “both modified.” - Fix the files: Open the files in your editor, look for the
<<<<<<<and>>>>>>>markers, and decide which code to keep. - Stage the fix: Once the file is fixed, run
git add <filename>. - Continue: Instead of committing, run
git rebase --continue.
If things go horribly wrong and you get overwhelmed, you can always go back to the way things were before you started with:
git rebase --abort
The Golden Rule of Rebasing
There is one rule you must follow to avoid becoming the most hated person on your team: Never rebase a branch that has been pushed to a shared repository and is being used by others.
Because rebasing rewrites history (changing commit IDs), if you rebase a shared branch like main or a shared develop branch, your teammates’ local histories will no longer match the server’s history. This causes a nightmare of merge conflicts and duplicate commits when they try to pull the changes.
Only rebase branches that you own and that no one else is working on.
Safety First: Using Git Reflog to Recover
New developers often fear rebasing because they think they might “lose” their work if they make a mistake. However, Git almost never actually deletes data immediately. It keeps a log of every movement your HEAD makes in a tool called reflog.
If you finish a rebase and realize your code is broken or you’ve lost a commit, run:
git reflog
You will see a list of recent actions. Find the state of the repository before you started the rebase (it will look something like HEAD@{5}), and run:
git reset --hard HEAD@{5}
Boom! You’ve successfully time-traveled back to safety. This is why Git is so hard to truly break.
Summary & Key Takeaways
Mastering git rebase transforms you from a developer who just “gets code into the repo” to one who crafts a professional, maintainable project history. Here is what we’ve covered:
- Rebase vs Merge: Merge preserves history with a merge commit; Rebase rewrites history for a linear path.
- Linear History: Clean histories make debugging (using
git bisect) and code reviews significantly easier. - Interactive Rebase: Use it to squash “fixup” commits and polish your messages before pushing.
- The Golden Rule: Only rebase private, local branches. Never rewrite shared history.
- Reflog: Your safety net for when things go wrong during a rebase.
Frequently Asked Questions (FAQ)
1. Is rebase better than merge?
Neither is “better”—they serve different purposes. Merging is safer for shared history and preserves the chronological context of when work was integrated. Rebasing is better for personal branch management and keeping a clean, readable project log. Most professional teams use a combination of both.
2. What happens if I forget the “Golden Rule”?
If you rebase a shared branch, your colleagues will see errors when they try to pull. They will likely have to force-pull or manually rebase their work onto your new history. It creates a lot of manual work and confusion, so it’s best avoided through good communication.
3. How often should I rebase?
A good habit is to rebase your feature branch against the main branch once or twice a day. This keeps your branch up-to-date and ensures that when it’s time to merge your PR, the conflicts are small and manageable rather than a massive headache at the end of the week.
4. Can I undo a rebase after I’ve force-pushed?
Technically, yes. If you haven’t performed a garbage collection on your local machine, you can use git reflog to find your old commits, reset your local branch to that state, and then force-push that back to the remote. However, this should be done with extreme caution.
5. What is the difference between squash and fixup?
In an interactive rebase, both squash and fixup combine the commit into the one above it. The difference is that squash prompts you to edit the combined commit message, whereas fixup simply discards the message of the commit being merged, keeping only the message of the parent commit.
