Breaking up a Single Git Branch into Multiple Features

Intro

Recently, I've been working using the Git Flow approach. While implementing a feature, I found some code that I had written poorly before. There's that great quote by Robert C. Martin,

Always leave code cleaner than you found it.

So I decided to follow his advice and make the changes. But I was silly and didn't change my branch. Now my feature had all the new stuff... and all my fixes to stuff that wasn't really related. The problem was that I didn't work on the feature and the fixes linearly. There were interwoven commits (even though they were almost completely independent - apart from the package installations). So instead of creating a pull request with my feature and a bunch of fixes, I decided I wanted to break up the branch into multiple branches and create a pull request for each. But what is the easiest way to do this?

The Setup

Before we jump into the (easiest) way that I found, let me show you a little demo. I have a project that has two .js files (first.js and second.js). Imagine that these are completely unrelated,

My initial repository

I then decided to start up a new feature and add a new file called... you guessed it... third.js.

Adding my new feature

But while I was working on it, I realised a couple of flaws in first.js and second.js and made changes to them both. I then continued to add code to third.js to finish up the feature.

My new feature branch all messed up :(

Mistakes were made, but how do we fix them?

The Fix

There are a few ways to fix this. The two that I'll point out are git rebase and git cherry. We could use git rebase to remove some of the commits and keep others and we could use git cherry to create new branches (off develop) and pull certain commits in. Both are good options, but in this tutorial I'm going to run through using git rebase. I'd like to give willoller some credit for his answer in stackoverflow, this is pretty much a visual representation of what he said.

I'd also like to give credit to Dan Gitschooldude for the git cherry video which was super insightful and you should check out if you haven't used this before.

So first we need to copy the current branch, in my case feature/new-amazing-idea into multiple branches (1 backup and then the number of branches we'd like to end on). For me that's 3 branches in total. First I'll rename this branch, git branch -m feature/backup.

Rename the branch

Now create all of the branches you'd like by the end of the modifications,

Create all of the new branches

Sweet! We're ready to go, let's start with the feature/new-amazing-idea branch. First we need to check it out, git checkout feature/new-amazing-idea.

Modifying the feature/new-amazing-idea Branch

We're going to use git rebase which means that we're going to rewrite the history of this branch. The first step is to specify how many commits to look back during this rewrite process. In my case, it's 8 commits between the feature/new-amazing-idea and develop. So I started up an interactive rebase by running git rebase -i HEAD~8. I use vim as my editor so the output may look a bit different to yours but this is what I see,

The rebase output

So now we can see all of the commits that we're editing and the action that should be applied to each commit on the far left. I then changed "pick" to "drop" for all of the commits that did not form a part of my new feature.

Pick which commits to keep in this branch

When you're done, save the file and close it.

Closing the rebase sceen

In my case, I had a merge conflict :/... This is because package.json was updated in multiple commits. I removed the conflict and committed the changes.

Removed merge conflict

Once I'd fixed the conflicts, I ran git rebase --continue to see if there were anymore conflicts that needed to be resolved. In my case, there weren't anymore,

Continue the rebase

Sweet so that's the feature branch done! The log now looks as follows,

The git log after the first rebase

Modifying the feature/secondjs-async-workflow Branch

Now I'm going to extract the data for the second feature I had going on here. It's pretty similar to the previous section. First check it out, git checkout feature/secondjs-async-workflow and then run rebase, git rebase -i HEAD~8.

My rebase output for the second feature

That was a success :)

Successful rebase

Modifying the fix/firstjs-defaults Branch

Finally, I need to set up the fix/firstjs-defaults branch. This is how my rebase output looks,

Rebase output for the fix branch

And again, it was a successful rebase :)

Successful rebase for the fix branch

Final Git Log

So now we have all of our branches with the correct commits,

New Git log

I think it's time to delete the backup branch :O Run git branch -D feature/backup and check out the logs,

The final logs

The merge conflict commit isn't ideal, but I'd much rather have that than a branch that has multiple features that kills my code reviewer. PS. You may have noticed that I spelt moment-timezone incorrectly in my commit. The git rebase command lets you modify your commit messages as well using the reword option,

Reword a commit

Now my commit message doesn't have any typos!

Fixed commit message

Conclusion

We're now ready to create 3 pull requests and our code reviewers will have a much easier time reviewing the code, since there are fewer lines and they all share the same responsibility. I hope this helps in future projects when you get lost like me and decide to make changes before switching branches :P