A often overlooked feature of Git is it’s
git rebase command. It’s the process of moving a branch to a new base commit. Confused? Don’t be. Rebasing is really just moving a branch from one commit to another.
Rebasing is a common way to integrate upstream changes into your local repository. Pulling in upstream changes with Git merge results in a superfluous merge commit every time you want to see how the project has progressed. On the other hand, rebasing is like saying,
I want to base my changes on what everybody has already done. It’s pretty simple once you understand it and will help keep your commits nicely organized.
The Git Rebase Syntax
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 vs. Merge
Git rebasing versus merging is a common question that get’s asked. Not surprising since it can indeed be quite confusing. When to rebase and when not to rebase is the question, here’s the answer.
The primary reason for Git rebasing is to maintain a linear project history. In rebase, you change your branch that’s being rebased so it looks like it was branched off a new base instead of the original. The process rewrites the commits, so you’ll end up with different commit IDs.
In merge, you combine two divergent branches back into one. 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”).
Rebase has the advantage that there is no merge commit created. However, because HEAD is not a descendant of the pre-rebase HEAD commit, rebasing can be problematic. For one thing, it 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 it’s current
HEAD. This results in a
changing of history that could confuse someone who already knows about the commit.
Because of this danger of rebasing, it is best reserved for two situations.
Rule One: Never Rebase Public Branches
public branches here I mean 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).
Rule Two: Committing When Branch Change on Remote
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.
Updating Public Feature Branches
What to do if your public branch needs to be updated from upstream? You can’t rebase it since it’s public, and you can’t merge into it since you’ll want to merge it back some day.
Turns out it’s really simple: Create a fresh one off of upstream, merge your branch into it, and continue working on a new one.
(This was originally a comment on the How To GitHub: A Complete Guide to Forking, Branching, Squashing and Pulls article Hacker News discussion. The article itself and the ensuing discussions are full of useful advice—if you haven’t already, go read them!).
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