Intro

This article is for those who are comfortable with basic git operations and concepts like staging, committing, pushing, pulling, and merging, but who still find themselves struggling through the occasional merge conflict, or creating embarrassingly long commit histories (“fix typo”, “actually fix typo”, “remove unintentionally committed changes”), or feeling unsure about what their branch history looks like after a merge, or two, or three. Of course, those who have it all under control and just want to learn more about what git offers are welcome too!

In this article we’ll be talking about rebasing and squashing. Rebasing can be used instead of merging to bring in changes from master or another branch and results in a much cleaner and more intuitive branch history. Squashing is the process of taking two or more existing commits and combining them into one commit. This ultimately allows you to change the way you think and talk about commits — you can talk about “the” commit where a bug was fixed or “the” commit where a feature was introduced, but perhaps more importantly for right now it’ll let you clean up those embarrassing commit histories of trying a million times to get the build to pass on the server.

Rebasing theory

What is rebasing? The git documentation states that it is “reapplying commits on top of another base tip”. What does that mean? Let’s bring in some visuals. Let’s say you have a branch/repo that looks like this:

Current state of our repo

You branched off of master some time ago and made a few commits, and there have been a couple commits to master since you created your branch. If you rebase your branch onto master (and note that with rebasing we usually say “onto”, as opposed to merging in which we usually merge “from”), your branch/repo will now look like this:

After rebasing hotfix onto master

So the 3 commits from the hotfix branch were “reapplied” on top of “another base tip” which in this case is the head of the master branch, as opposed to the previous “base tip” which was 2 commits behind the head of the master branch. Frankly “base tip” is a bit of a strange word, and it might make more sense if you think of rebasing as “re-parenting”, because you’re changing the parent of either one commit or a series of commits. “Reapplying” in this case means that we take the diff between the first commit of hotfix and its original “base tip” and apply it to the new “base tip”.

Another way to think about rebasing is that when you’ve rebased a branch, it’s as if you had just now created your branch off of this latest master and done all of your work on top of it. I personally find it much easier to think about my branches when they’re in this state, as opposed when I’ve merged changes from master and created a sort of “railroad track” in my commit history:

Merging master into hotfix as opposed to rebasing

I find rebasing especially helpful when there are merge conflicts — when you merge, you have to solve the merge conflicts in the merge commit, and so when you look through your history to find the sum total of changes needed to implement your feature or fix your bug, you need to look through both the original commits and the merge commit. When you rebase, you still have to solve the same merge conflicts, but instead of solving them in a merge commit, you solve them in the commits as they’re being re-applied (if the rebase process detects a merge conflict, it’ll stop and let you resolve it before continuing), and so when you go back to look at the diffs of the individual commits, they now make sense when you’re comparing them to master, as opposed to having some lines missing because they’re added in a future merge commit.

Rebasing practice

OK enough theory, let’s bust out our terminals and crank out a few commands. For these exercises, feel free to either make a test repo or go ahead and do them within a repo you frequently work with. We won’t be making any destructive changes.

Let’s start with creating a branch a couple of commits behind master. You can do this with one command with

git checkout --branch mytestbranch master~2

The ~2 tells git to create the branch not at master, but 2 commits behind.

Now let’s make some changes to the repo and make a commit.

touch new_file

git add new_file

git commit --message "Added a new file"

Any changes are fine. You might want to avoid changing a file that has a high chance of having a merge conflict so as to avoid dealing with merge conflicts for your first rebase, but it’s up to you. Doing something straightforward like adding a file or editing the README is fine.

Now that you’ve got some changes in your branch, let’s rebase it onto master, but first let’s run git log --oneline -2 and note the output. On my machine it looks like this:

$ git log --oneline -2

00e444061 (HEAD -> mytestbranch) Added a new file

bb25634af Merge pull request #2398 from jsmith/master

So we’ve got the commit I just created, 00e444061 , in which I added a new file, and its parent commit, bb25634af , which is 2 commits behind master. Now to rebase onto the latest master we’ll run:

git rebase master

And that’s it. Congratulations on your first rebase! Let’s look at the new output of git log:

$ git log --oneline -2

1bb3ef7c1 (HEAD -> mytestbranch) Added a new file

3a29b1c32 (master) Merge pull request #2421 from jsmith/master

You’ll notice that both commits have changed. The second commit is 3a29b1c32 , which is the tip of my master branch, and the first commit is 1bb3ef7c1 . When rebasing your commit the hash will change because you are changing the parent of the commit, which is part of the information used to compute the commit hash.

Go ahead and practice a bit more. Maybe try to create a merge conflict on purpose so that you can see how to deal with merge conflicts when doing a rebase.

Rebasing pro tips

If you’ve followed along so far, you might be thinking, ‘OK, so if I want to get the latest changes from master into my branch, I update my local copy of master and rebase onto that’, which would look like this:

git checkout master

git pull origin master

git checkout branch_i_want_to_rebase

git rebase master

And this will work, but There’s A Better Way ™!

git fetch origin master

git rebase origin/master

Both ways work. I prefer the second one since it’s quicker and means I don’t have to switch branches (I often find that switching branches switches my mental context, even if I switch right back. Worst case scenario, I get interrupted after switching to master, but before switching back to my branch, what a nightmare!), but I’ve included both to demonstrate that there are multiple approaches and hopefully seeing the same thing accomplished in two different ways will help the reader’s understanding of the topic.

EDIT: Readers write in that There’s An Even Better Way ™

git pull --rebase origin master

Just one command! I hadn’t used this one before, but I’m slowly trying to rebuild my habits to take more advantage of this. One less opportunity to be interrupted.

Rebasing gotchas

When you try to push a rebased branch to your server, you may get an error like this:



To

! [rejected] mytestbranch -> mytestbranch (non-fast-forward)

error: failed to push some refs to '

hint: Updates were rejected because the tip of your current branch is behind

hint: its remote counterpart. Integrate the remote changes (e.g.

hint: 'git pull ...') before pushing again.

hint: See the 'Note about fast-forwards' in 'git push --help' for details. $ git push origin mytestbranchTo https://github.com/githubuser/repo.git ! [rejected] mytestbranch -> mytestbranch (non-fast-forward)error: failed to push some refs to ' https://github.com/githubuser/repo.git hint: Updates were rejected because the tip of your current branch is behindhint: its remote counterpart. Integrate the remote changes (e.g.hint: 'git pull ...') before pushing again.hint: See the 'Note about fast-forwards' in 'git push --help' for details.

This happens when you’ve pushed the branch ahead of rebasing, and are now trying to push the rebased branch. Git sees that the commits you’re trying to push this time are different from the commits associated with this branch on the server and stops you from pushing, fearing that you will overwrite data. In this case though, we know what we’re doing and we know there won’t be any data loss, so we can override git’s judgement by saying git push --force origin mytestbranch . Some might consider this a tradeoff, i.e. to take up rebasing you have to give up some protections that git provides, but I consider this simply as a cost of doing business. The benefits of rebase far outweigh losing this protection.

I was told you should never rebase a shared branch

This comes up a lot in discussions about rebasing so I wanted to address it. Some people say you should never rebase a branch to which multiple people are contributing. Never is a strong word, but in general they are right. When you rebase a branch we’re sharing without telling me, and afterwards I go to update it on my machine, git pull might have issues, and I’m gonna be like “what the hell?”. If you tell me ahead of time, and I’m familiar enough with rebasing to rebase any branches of mine onto your rebased branch, we can make it work. For larger teams with varied skill sets this coordination gets more difficult, and so it’s easier for shared branches to simply merge in changes from other branches as opposed to rebasing onto them.

Rebasing review