Organizing a mess with cherry-picking
Yesterday was the first time I used git's cherry-pick
functionality. I've known of it but had never had really good use cases. Until yesterday.
Working on a new product and codebase is always challenging at the beginning. There's so much unknown unknowns and complexity behind simple-sounding requests. I dove a bit too deep into a rabbit hole in my current work before I realized I was there.
Story time
I picked up a ticket that sounded relatively simple: add a field to one of the models (and build the plumbing around it to make it functional). I started working on that, made it functional and then took on to build the next piece: there was a comment in the ticket saying that as part of this work, another closely related piece should be implemented.
To implement that, I needed to do a bit of fundamental refactoring of other models and bit by bit, I ended up in a situation that I had a massive pull request that wasn't quite finished and had bits and pieces of all of these things.
It was exactly what the books about pull requests warned against: it did too many things, it had too many only vaguely related changes and it was huge. It was over 1k LOC net change across nearly a 100 files. To be fair, quite a bit of those changes were in tests and test fixtures but still it was bit too much.
Complex mess slows me down
I had a really slow start to this week due to the grown complexity. I felt like I had gotten so deep to the hole that it was hard to navigate and maintain a sense of direction. I felt like I was running around in circles and by lunch time Tuesday, the negative feelings had overtaken my productivity and I wasn't getting anywhere.
After I had finished the process, I realized how much this problem was pulling down my productivity, my mood and me wanting to be a developer.
I decided to split the pull request into three pieces. Only thing was, I didn't know how. That's when I decided to finally learn how to use git cherry-pick
Pick 'em cherries
I started my splitting process by starting a new fresh branch from dev and cherry picked a couple of commits from the old branch that implemented the model refactoring bit. With git cherry-pick [commit-hash]
, you can pick commits from other branches by providing the command the hashes you needed. Since I had an open pull request draft, it was easy to find and pick things from it.
This also provided me a great opportunity to clean up the commits a bit and combine commits with git rebase -i HEAD~N
where N
is the amount of commits I want to rebase and squashing commits into one and improving their commit messages. I sometimes notice I had forgotten a console.log, a commented out part or a .only()
in tests after pushing the branch to remote which leads to occasional "Oops, didn't mean to do that"
type of commits. I want to become better at cleaning those up as I go but since I'm not there yet, this fresh start offered a good opportunity for that.
Here's an example of the commit log at the time (newest first):
da39a3 Add more tests for A
4b0d32 Add validation for A
55bfef Refactor frontend to work with C
38749b Forgot a test runner comment for A
956018 Refactor model for feature C
ee5e6b Pure refactor of B
90afd8 Add field to form for feature A
070fg9 Create routes for feature A
I added A/B/C markers to them to make these imaginary examples easier to follow.
I decided to start with B
as it was a pure refactor with no effect on functionality:
git checkout -b new-branch-for-B
git cherry-pick ee5e6b
A new branch with just that one commit picked to its own branch, ready to be reviewed! The new PR is so much nicer to review and can be merged in before my other ones as it's a pure refactoring of internals and doesn't introduce any new functionality.
I'd then continue with A and C respectively:
git checkout -b new-branch-for-A
git cherry-pick 070fg9^..90afd8
git cherry-pick 38749b
git cherry-pick da39a3^..4b0d32
git checkout -b new-branch-for-C
git cherry-pick 956018 55bfef
You can cherry pick multiple commits by providing it multiple commit hashes or a range of commits with ..
. The ^
at the end of a commit means "include this commit".
Now, instead of one monster PR that was hard to mentally manage, I had three smaller ones and if I needed to change something on one of them, I could switch to that branch and make changes.
Oh the mental clarity
The benefits were so good. Not only did I create more manageable pull requests for the team to review, I also got myself out of the rabbit hole and back into much more manageable situation so I can finish the work for this feature. And I learned a new tool/technique along the way so yay me!