Git Rebase: A Quick Tutorial on Git Rebasing

2 Comments

Join the Conversation

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. It helps keep your git history cleaner and more understandable.

Rebasing a feature branch onto master
Rebasing a feature branch onto master

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 and history nicely organized.

When to use git rebase — before you merge.

It’s simple — rebase your branch before you merge it. This technique does have some weaknesses however. In this article, I will explain what a rebase and a merge really do and what are the implications of this technique.

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:

  1. 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.
  2. Determines what changed for each of those commits, and puts those changes aside.
  3. Sets the current head to point to [base].
  4. For each of the changes set aside, replays that change onto the current head and creates a new commit.

Once completed, 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:

  • Branch you: the branch you are rebasing
  • Branch johnny: the branch from which you get the new commits

Johnny made some commits and you want to pull those into you branch while keeping the commits and history clean. To do so:

git checkout you
git rebase johnny

Git Rebase

When you rebase you on 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 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”).

The Rules

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

By 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

Comments

  1. Putra
    at 10:21 am

    Can you please give example with step by step picture regarding Rule One: Never Rebase Public Branches, I don’t get it.

    Maybe in real example of Centralized Workflow for 2/3 Developer.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

All comments are held for moderation. I'll publish all comments that are on topic and not rude. You'll even get little stars if you do an extra good job.

You may write comments in Markdown. This is the best way to post any code, inline like `<div>this</div>` or multiline blocks within triple backtick fences (```) with double new lines before and after.

Want to tell me something privately, like pointing out a typo or stuff like that? Contact Me.

%d bloggers like this: