My last post got fairly positive feedback, 77% upvotes on Reddit. But in the comments I was asked to add an important practical use-case, how to split commits. So instead of editing my previous post, I decided to make a new post demonstrating how I would do that.

Let’s say you just committed and shortly after thought: “I really should have done that in two seperate commits” so others could cherry-pick that commit later on. Take in note, this should NOT be done to commits that have already been shared (pushed to remote).

This is possible in two different ways. If the commit is the latest one, you can just do a mixed reset. But if the commit is before that, you will have to do a interactive rebase and then a mixed reset while rebasing. But if you’re just adding/removing something, you could just do git commit --amend while you’re located on that commit. Our scenario is a bit more complex because we need to break up changes that have already been committed, into two seperate commits.

There are many scenarios where it would be useful to split up a commit, but let’s just take an example. I have added two modules, sayHello and sayGoodbye. But after I committed I realised that I would probably want to have the modules in seperate commits, so others could cherry-pick the module they needed. Let’s say someone would like to use my sayHello module in their branch, but didn’t need the sayGoodbye module.

You can check out the modules here.

Reset (mixed)

By doing mixed reset you’re moving the HEAD pointer to a certain commit and by definition of mixed you’re leaving the working directory untouched but resetting the index (staging area).

My commit history looks like this:

As you can see the two modules, sayHello and sayGoodbye are in the same commit (8667061). Now I would want to go back to before I committed or added the changes to the index (staging area). I would simply just do: git reset HEAD~1 or git reset --mixed HEAD~1. Those are equivalent. This is a so called mixed reset. You can think of the tilde ~ as a minus, so it’s basically HEAD minus 1 – which would be 2d19dc0

So the result in the terminal would be:

http://i.imgur.com/KKDzQee.png

My commit history now looks like this:

This is before I added or committed the files, that’s why the latest commit is missing and why the HEAD and master pointers now point to 2d19dc0. But the changes are in the working directory.

Now I can re-add the files and commit seperately, like so:

After I’ve done so, the result would be two extra commits (with each module):

Now if someone would like to use my sayHello or sayGoodbye module, they can get either one without being forced to get both modules.

Interactive rebase

The second method uses interactive rebase with the edit option to get to the commit we want to split up, but to change that commit we still need to use mixed reset (method above). If you would like a thorough demonstration of how interactive rebase works, just check out my last post.

This method would be used if the commit you want to split up is further behind the latest commit. This method replays your commits on top of each other, but stops at a certain commit where it allows you to edit it before continuing. While editing the commit, we would use the mixed reset to go back before comitting the changes.

My commit history looks like this:

Now we have a new commit, so what we want to do is go to the 8667061 commit (where both modules have been added), and split that into two seperate commits by using mixed reset, then replay the latest commits on top of that.

Let’s start by doing: git rebase -i HEAD~2. You can think of the tilde ~ as a minus, so it’s basically HEAD minus 2 – which would be 2d19dc0

Then I would get a screen like this:

http://i.imgur.com/BUtmCLo.png

Now change pick infront of 8667061 to edit or just e

If you’re not familiar with vim you have to press i to enter insert mode. To exit insert mode press ESC on your keyboard. To save changes and quit, type :wq

When you’ve done so you should get this message in the terminal:

You can see that it says that you’re located at 8667061, which is the commit we want to split up, but the changes have already been committed. So how would I split the commit up into two? The same as before, I would need to do a mixed reset.

I would simply do: git reset HEAD~1. This is a so called mixed reset. You can think of the tilde ~ as a minus, so it’s basically HEAD minus 1 – which would be 2d19dc0

So the result in the terminal would be:

http://i.imgur.com/atq0ust.png

This is before I added or committed the files. Now I can re-add the files and commit seperately, like so:

After I’ve done so, the result would be two extra commits (with each module):

But we’re not finished! We’re still rebasing. We now need our latest commits, e.g. the one with the main file that uses both modules (f5ac8a9).

To continue replaying the other commits, just type: git rebase --continue. It will continue replaying your new commits on top of the last one.

So after the rebase has finished my commit history looks something like this:

Now if someone would like to use my sayHello or sayGoodbye module, they can do so without having to get both modules at once. Just cherry-pick the commit/module they need.