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,
I then decided to start up a new feature and add a new file called... you guessed it... third.js
.
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.
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
.
Now create all of the branches you'd like by the end of the modifications,
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,
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.
When you're done, save the file and close it.
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.
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,
Sweet so that's the feature branch done! The log now looks as follows,
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
.
That was a success :)
Modifying the fix/firstjs-defaults
Branch
Finally, I need to set up the fix/firstjs-defaults
branch. This is how my rebase output looks,
And again, it was a successful rebase :)
Final Git Log
So now we have all of our branches with the correct commits,
I think it's time to delete the backup branch :O Run git branch -D feature/backup
and check out the 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,
Now my commit message doesn't have any typos!
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