This post is a part of a series featuring protips from GitHubbers for improving productivity, efficiency, and more. Continue reading to learn about Sarah Vessels’s protips.
I spend most of my work day in code and pull requests. I like to build things and I like for y’all to use them, so I’m very motivated to get my changes out there! I want to share a process with you that’s worked well for me.
Sometimes my pull requests get too big to easily review because my branch includes too many changes relative to the base branch. Instead of forcing my team to slog through a thousand lines of code, while trying to keep everything in their head and spot any problems, I prefer to open smaller pull requests. Smaller pull requests tend to merge faster, because they’re easier to reason about, so reviewers can breeze through and approve with confidence, and because they have less chance of conflicting with the base branch.
However, my brain doesn’t think in terms of small pull requests! I get an idea, and I want to translate my idea to code before it slips away—get it all out in black-and-green in my editor. Being able to get my feature or bug fix written down in a single branch helps me confirm that it works as I expect and allows me to test the system as a whole. I can verify that the data model I’ve chosen is a good one based on how the UI and endpoints that use that data model come together. This everything-in-one-branch approach, unfortunately, is at odds with my desire to present my teammates with small, easily reviewable pull requests, especially as these branches tend to grow large.
If I try to split my code into separate branches from the beginning, I often struggle with knowing which code makes sense in which branch. I find myself spending too much time thinking about whether I’m putting a commit in the right branch rather than thinking about the feature I’m building. I feel like it hinders the creative process of coding, too, having to worry about neatly dividing code into coherent, easy-to-review branches rather than just going with the flow of where feature development takes me.
I reconcile the two approaches by doing them both, but sequentially: do my original work in a single branch, not worrying about how large it gets, and later open new branches to tease out smaller, atomic bits of work that can land separately. I can use
git cherry-pick to pull bits of my large branch into a very focused smaller branch, then open a pull request from that smaller branch back to the default branch of my repository. As each smaller branch gets merged, when I update my original large branch with the latest code from the base, the diff gets smaller and smaller, because more of its changes are now already in the base branch. I can repeat this process as many times as necessary until the original large branch is no longer so large that it’s a pain to review.
add-categories and get everything out in that branch. I would add just enough tests to confirm my methods are doing what I expect them to do, and verify everything else manually by checking things out in the browser. The branch would be intended as a kind of spike, a test bed to verify that my implementation is the right one. I could open a pull request for this branch, not to solicit reviews and not to be merged yet, but to let my team try things out themselves, and to see the overall approach that I’m thinking about.
Once I’m happy with my
I would open a branch like
category-db and pull all the relevant work from
add-categories into it. Ideally, while I had been working in
add-categories, I would have made atomic commits such that I could run
git cherry-pick commands to pull over just the database changes. That’s not always the case, though, and for those times, I tend to open my
add-categories pull request on GitHub and go to the “Files changed” tab, where I can easily copy chunks of text and files to paste into new commits in
One nice side effect of pulling out changes into branches that are dedicated entirely to that change is that it leads to better test coverage. If I were trying to land my original
I treat my smaller branches as the ones that are going to be reviewed thoroughly by my team, so they get lots of polish. Thorough pull request description, test coverage, as well as consideration for performance and user experience are important. My original
add-categories branch, in this example, would start as a draft pull request and stay that way until I finished landing all the smaller branches that I teased out of it. Once
add-categories is small enough, because enough of its parts have landed in separate branches, I would round out its tests, update the pull request description to reflect the remaining contents of the branch, and finally drop its draft status so my team gets pinged for review.
Daisy-chaining pull requests
Sometimes it can be helpful to “daisy-chain” my pull requests. After creating my
category-db branch, I like to see what changes remain in
add-categories that aren’t already in
category-db. Seeing what’s left in the original big branch will let me decide if it’s sufficiently small to submit it for code review, or if I need to pull out another small branch from it. To view the remaining changes it has, I would edit my
add-categories pull request on GitHub to change its base branch from the default branch of my repository to
category-db instead. Then I can check out the
add-categories branch and
git merge category-db before pushing the result up to GitHub. That updates the diff shown on my pull request such that all the code that’s now in
category-db no longer shows in the
add-categories pull request.
add-categories is still too large for me to be comfortable asking my team for review. The new data validations might be the next part I pull out to their own branch. While on
category-db, I could create a new branch, say
category-validations. I would repeat the process of before, cherry-picking relevant commits where possible, copying code wholesale from the pull request view of
add-categories otherwise. Once
category-validations has been pushed to GitHub, I can open a pull request with
category-db as the base and
category-validations as the head branch. Then I can once again update my
add-categories pull request so that its base branch is now
category-validations instead of
category-db. This results in a daisy-chain effect where my branches are:
% git branch
add-categories // adds the whole categories feature
category-db // adds database table
* category-validations // adds validation logic + tests
Another benefit of chaining pull requests like this is that it unblocks me. Maybe
category-db can’t be merged yet because the database change it describes hasn’t happened, so none of my other branches can be merged either. By opening many pull requests for separate small branches, I can still focus on getting each branch fully tested and reviewed by my team. I can get each set of changes ready to merge by making sure tests pass and they’ve been approved, then when
category-db is ready to merge, I’ll be able to land several branches in short order since the branches depending on
category-db have already gone through the review process.
Once one of my smaller branches gets merged, I can update the pull request(s) that are based on it to instead be based on the default branch of my repository. So when my
category-db branch is merged, I update my
category-validations pull request so its base branch is the primary. Eventually, all the smaller branches have merged and all I have left is a single pull request again, the original
add-categories, based on the default branch. Only this time,
add-categories is a reasonable size for me to ask for review from my team. By this point,
Summarizing my experience
By letting myself focus on expressing my ideas in code, and not focusing on the nitty-gritty details of how to neatly organize my branches, I’m able to develop a feature more quickly. By later splitting a large feature branch into many smaller branches, I help my team ensure we’re merging well-reviewed code, and I’m able to give individual attention to each piece to make sure it’s fully tested. Using git’s branches and GitHub’s pull requests, I’m able to marry the two techniques to hopefully improve the process of building out a feature for both myself and my team.
Do you have a GitHub Protip?
Do you have a tip, trick, or hack that makes your daily life easier on GitHub? Share it with us on social media with #GitHubProtips.