We ❤ Git and GitHub at Sparkbox. It’s our go-to version control system. We encourage clients who need a version control system to use Git and GitHub if possible. Just about everyone at Sparkbox touches GitHub at some point during a normal work day, whether that be through issues or commits. Our typical Git and Github workflow is to keep the master
branch in a “deployable state,” which means nothing goes into master
without a peer review and master
is usually auto deployed to a staging or QA server. Work is added to master
by creating feature branches and then pull requesting those branches back into master
. This approach works really well when the work for each feature branch can be accomplished in a week or two and only one or two developers are working in that branch at a time. Merging to master
in a regular cadence like this does not work well when a feature will take months and or involve many developers. In that case, one solution is to use a long-lived feature branch strategy. For the last couple of months, I’ve been working on a project that uses this strategy and I’d like to share how I prefer to work in it.
What is a Long-lived Feature Branch?
A long-lived feature branch (LLFB) can be useful when working on a feature that: shouldn’t be in the master
branch until it’s ready, will take a significant amount of time (e.g. a month or more) to complete, and involve several developers. This approach allows master
to avoid all the churn that happens in the feature branch. Remember, we typically consider master
to be “deployable” so that means master
can’t afford to have half-baked features in it. One necessity when working with a LLFB is keeping it up-to-date with master
. While we don’t want to muck up master
, we do want all the great changes and fixes that are coming in from other teams into master
in our feature branch. This also makes the final merge of the LLFB much easier.
Get the Latest
How do we update the LLFB with fixes and other new features from master
? The typical approach is to git merge master
in the LLFB. However, that creates a pretty gnarly looking log with many “merge commits” cluttering the history. The alternative is to git rebase master
in the LLFB. Rebase works by going back to the point where the LLFB split from master
and updating that point with master
and then “replaying” the LLFB commits on top of that. The LLFB branch work is now always happening on top of the latest work from master
. Now, the log shows a linear progression of work without any “merge commits.”
We Have A (New) Problem
The LLFB is now synced with master
. However, individual developers typically create personal feature branches off the LLFB and now need to create pull requests for their work. When a developer pushes up their personal feature branch, GitHub will interpret their old “pre-rebase” LFFB commits as new commits and report that there are conflicts between the personal feature branch and the LLFB. What happened? git rebase
happened. When git rebase
runs it “rewrites” the history, creating identical but different commits. Therefore, the LLFB technically no longer has the commits it had when the developer created the personal branch.
Rebase Uphill Both Ways
One option would be to git rebase
the personal feature branch off the LLFB and slog through all the conflicts and commits just like the LLFB did with master
. This hurts more and more as the LLFB lives longer. Remember as the LLFB gets periodically rebased off master
its commits get rewritten (Git uses different commit SHAs). This means when the personal feature branch is rebased off LLFB Git resolves—and finds conflicts in—all the commits since the start of the LLFB.
Cherry Pick to the Rescue!
Instead of fighting through all those commits, we can use git cherry pick
. Cherry-pick works by copying a commit or a series of commits from another branch onto the current branch. Instead of working bottom-up (or past to the present) like rebase
, cherry-pick
works top-down (or present back to the past). Remember, git rebase
resets the personal feature branch to the branch point off LLFB and then replays the personal feature branch commits one at a time. With git reset
we can set our personal feature branch to current state of the LLFB and then drop the personal feature branch commits on top via cherry-pick
. So, the only conflicts will be in the personal feature branch commits. Sounds great, right? We can’t do this blindly. If we reset our personal feature branch to the LLFB we would lose the new commits. We need to take measures to avoid losing the personal feature branch commits and have them available to cherry-pick
.
Backups Are Good
Before jumping into any potentially hairy Git situation, it’s smart to create a copy of the personal feature branch first, whether that be a literal duplicate branch or pushing the branch up to origin. The goal is to have the new commits in two places. Once backed up, it’s time to start on the personal feature branch. It’s important to get back to the latest LLFB state (be sure to do this when the personal feature branch is checked out), git reset LLFB --hard
. This will coerce the personal feature branch (e.g. discard commits) to be identical to the LLFB. Scary right? Not really. Remember the commits are in two places (three if you count the reflog
).
In the Right Place
Now that the personal feature branch is identical to the latest and greatest from LLFB, it’s time to bring back the commits that got blown away by the git reset. Let’s get the refs to those commits we backed up earlier. You can do a few different log commands to get those refs: git reflog
git log origin/personal-branch
Find your commits? Great! Now pass them to cherry-pick
with oldest to newest going left to right like this:
git cherry-pick --ff commit1sha commit2sha
cherry-pick
applies the commits you specify one-by-one on top of the branch. You’re in! Now, your personal feature branch has the latest from the LLFB and your awesome feature applied on top! Go ahead and test things to make sure it’s all good. You will have to git push --force
now since your remote branch and your local branch commits are completely different. The above method is great if you only have a few commits to cherry-pick
, but what if you have a bunch? Well, you can pass a “range” to git cherry-pick
like so:
git cherry-pick --ff commit1sha~1..commit10sha
This will grab commits commit1sha through commit10sha
.
Choose the Right Tool
cherry-pick
isn’t for every situation but then again neither is merge
. Git offers multiple methods to manage code, knowing when to use one or the other is a vital skill for any developer.
Sparkbox’s Development Capabilities Assessment
Struggle to deliver quality software sustainably for the business? Give your development organization research-backed direction on improving practices. Simply answer a few questions to generate a customized, confidential report addressing your challenges.