Splitting commits using git rebase

We have all been there before: you’re working on a big feature branch, committing all over the place and then at some point you need to extract something out to merge it back to the main (or develop) branch. My usual to method is to split off a new branch and then use git rebase to keep only the commits of the parts that I want to extract out and merge that back after which I then rebase my feature branch on the target branch again.

But what if you find that some commit halfway through the changeset needs to be split up? That’s what I will be diving into in this article.

The example

If you want to follow along with this article you can get a copy of the example repository that I am using here from GitHub. The repository has a main branch called trunk that has a README.txt file and a branch called feature-a that contains two additional text files: FILE1.txt and FILE2.txt.

If you take a look at the history of the feature-a branch you will see that FILE1.txt and FILE2.txt were added in one commit and that a line was added to FILE1.txt in another commit:

Output of the “git log” command.

In this article I will show your how to take FILE1.txt out of the feature-a branch.

Splitting up the commit

Rather than creating a new branch like I said in the intro we first need to split up the commit that has FILE1.txt and FILE2.txt, to do this you need to start an interactive rebase on the main branch with git rebase -i trunk (you will need to switch to the feature-a branch first if you haven’t already using git checkout feature-a).

The rebase command opens your shell’s default text editor with a buffer where you can orchestrate the rebase. Change the word “pick” before the commit named “Adds FILE1 and FILE2” to “edit” to instruct the rebase command to stop for editing at that commit, then quit the editor writing the buffer in the same way that you would when committing changes from the command-line:

The instructions shown by git allow you to amend the commit message, this is not what we want however. Instead we want to unstage the changes of this commit and then perform two commits committing FILE1.txt and FILE2.txt separately.

Looking at the above screenshot you can see the use of the command git reset HEAD~ which has the effect of undoing the last commit, now we can commit the files in two commits:

Now the rebase process can be continued using git rebase --continue:

The git log command now shows the updated commit history:

Splitting the changes into a new branch

Now that FILE1.txt is committed in separate commits we can create a new branch extracting it out and then rebasing on trunk interactively once more:

To remove commits from the branch simply delete the lines referencing them in the buffer:

The feature-b branch can now be merged into trunk safely to add FILE1.txt:

Updating the original feature branch

All that remains now is to update the original feature-a branch by rebasing it on trunk using the non-interactive version of the git rebase command:

If you take a look at the output of the git log command after this you will see something like the below:

If you have a remote repository you should now (as a final step) force-push the feature-a branch to update it using git push -f origin feature-a to ensure it mirrors the branch you have locally.

In conclusion

Git rebase is an invaluable tool in managing your changes, splitting up commits is just only one of the many things that you can do with it.

If you want to apply this technique (or others using git rebase) on branches that you are working on with multiple people you should coordinate with them before rebasing as force-pushing changes (which will be required after rebasing) will overwrite the remote branch and will create conflicts for your colleagues (or they might inadvertently merge the old version of the branch into the new one creating an unwanted mess). The basic flow would be that everyone pushes their changes, you update your copy by pulling said changes in, perform the rebase, force-pushing your version and then have everyone discard their copy of the branch, fetch the changes and then switch to the updated branch to continue their work on it.

If you have feedback on this article or have questions based on its contents then feel free to reach out to me on Twitter or through e-mail.