Git Rebase is not nearly as complicated as it sounds — or as confusing as the documentation or other explanations make it out to be. It’s a powerful tool that’ll make your git history easier to follow.
What is Git Rebasing?
git rebase is the process of moving or combining a sequence of commits to a new base commit. Rebasing changes the base of your branch from one commit to another making it appear as if you’d created your branch from a different commit.
Git accomplishes this by creating new commits and applying them to the specified base. It’s very important to understand that even though the branch looks the same, it’s composed of entirely new commits.
Git Rebasing vs. Merging
The first thing to understand about
git rebase is that it solves the same problem as
git merge Both are designed to integrate changes from one branch into another — just in different ways.
- When you do rebase a feature branch onto master, you move the base of the feature branch to master branch’s ending point.
- Merging takes the contents of the feature branch and integrates it with the master branch. As a result, only the master branch is changed. The feature branch history remains same.
- Merging adds a new commit to your history.
There’s also a special kind of merge called fast-forward, done when a branch being merged is just a continuation of the branch you’re merging into—so the new commits are just pasted on top of the target branch (ie. it is “fast-forwarded”).
Let’s say you start working on a new feature in a dedicated branch, then another developer updates the
master branch with new commits resulting in a forked history.
The new commits in
master are needed to the feature you’re working on so needs to be incorporated into your
feature branch — there’s two options, merge or rebase.
Option 1 — Merge the Branch
The simplest option would be to merge the
master branch into the feature branch using
git checkout feature git merge master
or as a one-liner:
git merge feature master
This will create a new “merge commit” in the
feature branch that ties the histories of both branches which looks like this:
This option is non-destructive, the existing branches are not changed in any way which avoids all the potential pitfalls of rebasing (see below).
Option 2 — Rebase the Branch
The other option is to rebase the
feature branch onto the
git checkout feature git rebase master
This operation moves the entire
feature branch to begin on the tip of the
master branch which incorporates all new commits in
master. Instead of merging, git rebasing re-writes the project history by creating brand new commits for each commit in the original branch.
Benefits to Rebasing — two trade-offs.
- Streamlines a potentially complex history
- Avoids merge commit “noise” in busy repos with busy branches
- Cleans intermediate commits by making them a single commit
The major benefit to git rebasing is a much cleaner project history. It eliminates unnecessary merge commits required by
git merge and results in a perfectly linear project history — shown in the diagram above. You’ll be able to follow the top of
feature all the way to the beginning of the project with
git bisect and
The two-trade offs are safety & traceability. If you don’t follow the golden rule of rebasing, never use it on
public branches, re-writing project history can be catastrophic for collaboration. Rebasing loses the context provided by a merge commit so unable to see when upstream changes were incorporated into the feature.
How to Git Rebase
git rebase [base]
The Git command above will rebase the current branch onto [base], which can be any kind of commit reference (an ID, a branch name, a tag, or a relative reference to
HEAD). When ran, Git performs the following steps:
- Identifies each commit that is an ancestor of the current commit but not of [base]. This can be thought of as a two-step process: first, find the common ancestor of [base] and the current commit; call this the ancestor commit. Second, collect all the commits between the ancestor commit and the current commit.
- Determines what changed for each of those commits, and puts those changes aside.
- Sets the current head to point to [base].
- For each of the changes set aside, replays that change onto the current head and creates a new commit.
HEAD (the current commit), is a descendant of [base], but it contains all the changes as if it had been merged with [base].
Git Rebase — behind the scenes.
Let’s take 2 branches for example:
you: the branch you are rebasing
johnny: the branch from which you get the new commits
Johnny made some commits and you want to pull those into your branch while keeping the commits and history clean. To do so:
git checkout you git rebase johnny
When you rebase
johnny, git creates a temporary branch that is a copy of branch
johnny, and tries to apply the new commits of
you on it one by one.
For each commit to apply, if there are conflicts, they will be resolved inside of the commit.
After a rebase, the new commits from
you (in blue) are not exactly the same as they were:
- If there were conflicts, those conflicts are integrated in each commit
- They have a new hash
But they keep their original date which might be confusing since in the final branch, commits in blue were created before the two last commits in purple.
Git Rebase Golden Rule
Never rebase while you’re on a public branch — only rebase a branch if you are the only one using it.
Rebase has the advantage that there is no merge commit created, but
HEAD is not a descendant of the pre-rebase
HEAD commit which means rebasing can be problematic.
This means that a rebased head cannot be pushed to a remote server, because it does not result in a fast-forward merge. Moreover, it results in a loss of information. It is no longer known that the branch your rebasing was once on its current
changes history and could confuse someone who already knows about the commit.
When should you rebase vs. merge?
- Use merge in cases where you want a set of commits to be clearly grouped together in history
- Use rebase when you want to keep a linear commit history
- Don’t use rebase on a public/shared branch
If the feature branch you are getting changes from is shared with other developers, rebasing is not recommended, because the rebasing process will create inconsistent repositories. For individuals, rebasing makes a lot of sense.
If you want to see the history completely same as it happened, you should use merge. Merge preserves history whereas rebase rewrites it.
Rebasing is better to streamline a complex history, you are able to change the commit history by interactive rebase. You can remove undesired commits, squash two or more commits into one or edit the commit message.
Rebase will present conflicts one commit at a time whereas merge will present them all at once. It is better and much easier to handle the conflicts but you shouldn’t forget that reverting a rebase is much more difficult than reverting a merge if there are many conflicts.
Here’s some examples of when you should use
Situation 1: You’re using a private branch.
public branches are branches that other people might have checked out. If you’re developing a branch on your own and not sharing it with anyone, you could rebase it to keep the branch up to date with respect to the main branch. Then, when you finally merge your developed branch into the main branch, it will be free of merge commits, because it will appear that your development branch was a descendant of the main head. Moreover, the main branch can move forward with a fast-forward merge rather than a regular merge commit.
Rebasing rewrites history, and anyone having branches that were checked out of the history you just unmade will be sad, angry or worse. That’s one reason you can’t just push rebased branch on GitHub (unless you force it and sacrifice a kitten). So just say no.
Rebasing private branches is perfectly fine, and in fact often done when squashing or rearranging commits, cleaning up a branch before going public with it, or just updating long-running feature branch (go easy on the last one, though).
Situation 2: A remote branch changes at the same time.
If you commit to a branch, but that branch changes at the same time on a remote machine, you can use rebase to shift your own commits, allowing you to push your commits to the remote repository.
Git Rebasing Examples
The example below combines git rebase with git merge to maintain a linear project history. This is a quick and easy way to ensure that your merges will be fast-forwarded.
# Start a new feature git checkout -b new-feature master # Edit files git commit -a -m "Start developing a feature"
In the middle of our feature, we realize there’s a security hole in our project:
# Create a hotfix branch based off of master git checkout -b hotfix master # Edit files git commit -a -m "Fix security hole" # Merge back into master git checkout master git merge hotfix git branch -d hotfix
After merging the hotfix into master, we have a forked project history. Instead of a plain git merge, we’ll integrate the feature branch with a rebase to maintain a linear history:
git checkout new-feature git rebase master
This moves new-feature to the tip of master, which lets us do a standard fast-forward merge from master:
git checkout master git merge new-feature