Sometimes (maybe even all the time) you need to make changes to a release branch after that branch has been cut and frozen: you found a bug or there’s a tweak you need to make or you have additions to a feature that can’t wait for the next release. When this happens, you can do one of two things: a cherry-pick or a backmerge.
Both approaches enable you to get work out into a release branch while ensuring your team’s development branch never gets (too far) out of date, but they do so in what are essentially exact opposite ways. With a cherry-pick, fixes are first merged into the development branch and then cherry-picked into the release branch. With a backmerge, fixes are merged directly into the release branch, which is eventually merged back to the development branch to ensure you’re not working with out-of-date code.
Which one should you use? It depends.
A brief guide to cherry-picking mobile release work
As I mentioned around four sentences ago, when you cherry-pick updates you do so by grabbing select commits from your working branch and bringing them over to the release branch via the <code>git cherry-pick<code> command. Simple enough, right?
Let’s look at what this means in practice:
First, you create a PR against your team’s working branch that includes either one or multiple commit fixes. Once this PR is approved, you merge it into the working branch. The fix is now available on your team’s main working branch, so any future branches will include the merged fixes.
Then, you’ll want to bring over the fixes to the release branch by doing the following:
- Create a new branch off the release branch
- Cherry-pick the fix commits onto that branch using <code>git cherry-pick<code>
- Fixing any resulting conflicts
- Open a PR against the release branch
- Once the PR is approved and checks have passed, merging the PR in
You could also <code>git cherry-pick<code> and push right into the release branch like a maniac, but you shouldn’t do that (and also probably couldn’t do that since release branches are usually protected).
The advantage of cherry picking is that you’re making changes to your team’s more active, working branch and pulling them over to the release as needed. This leaves fewer chances for conflicts down the road, and maintains the integrity of your working branch as the true development “trunk” and source of truth. Cherry-picks by their very nature encourage a discrete and targeted approach for choosing which fixes make it over to the release branch, since the cherry-pick action is performed on a per-commit level.
But there are some downsides. If your working branch has diverged significantly from the point at which the release branch was cut, it might prove difficult to untangle fixes from surrounding work so they can be cherry-picked over discretely without also bringing over chunks of additional work. For this reason, it’s a good rule of thumb to try and only cherry-pick fixes that are as self-contained as possible, and don’t touch too many code paths or files. You can also try to avoid merging big chunks of new feature work or complicated refactors until you’re confident all necessary fixes have made their way into the release. Some teams choose to formalize this in the form of “code freeze” or “code slush” during a release cycle, in an effort to streamline the process of getting fixes into the release.
Cherry-picking may sound simple, but it does involve a number of manual (and tedious) steps. The git cherry-pick command isn’t one that’s often used during every day development, so developers may find themselves needing to re-learn the right commands every time they want to get a fix into the release. And don’t forget that the <code>git cherry-pick<code> command has a very different mechanism for bringing over changes than a traditional merge — cherry-picked work is copied over to the target branch and a completely new commit is recorded. If your team cares about keeping the commit histories of the two branches — the working branch, and the release branch — as similar as possible, then cherry-picking might not be the way to go.
To make getting fixes into your release branch with cherry-picks as simple as it sounds, Runway provides an automation that manages the process of cherry-picking changes into active releases, so it happens automagically every time without a hitch. All you have to do is tag any PR you merge into the working branch with a special token that flags it as needing to be cherry-picked (so our automation catches it) and then we take care of the rest.
Behind the scenes, Runway branches off your release branch, cherry-picks over the necessary commits, and opens a PR against the release branch. You can even set it up to merge automatically once all required checks have passed, and Runway will notify your team via Slack when all is said and done.
That all sounds reasonable, but is it better than backmerging? It depends.
A brief guide to backmerging mobile release work
As above, you find yourself in a position where you need to get changes out to a release branch. In this case, however, instead of making changes to the working branch, you make them to the release branch. You check out the release branch, do the fix there, push it up, and the resulting PR is merged right into the release branch. This approach is a lot more streamlined and familiar than cherry-picking for most developers. The only issue being that you now have this fix that exists in the release branch, but not in your working branch, leaving them out of sync.
From here, you have a couple of options:
Traditionally, you’d backmerge a release branch just once: at the end of the release. You take the latest state of the release branch and merge into the working branch, ensuring any work that was done directly in the release branch isn’t lost. This is happening just one time per release, which means that changed code is not going to be on the working branch for some period of time. This is ok if your release cycles are short or you don’t need the fix in place for whatever else you happen to be working on. But the longer you wait, the more you risk encountering merge conflicts. And keep in mind that any time you merge one branch into another, a merge commit is recorded — this mucks up your working branch’s commit history, which some teams may prefer to avoid.
Another approach is to continuously backmerge: each time you apply a fix or change to the release branch, that change is merged to the working branch, ensuring the two branches spend little to no time out of sync. As above, each merge is recorded in the working branch’s history with a merge commit, which can get noisy pretty fast.
If you choose to take a backmerging approach, Runway caters to both styles: it can automatically backmerge your release branch once the release is complete (released to the App Store or Google Play Store), or its “continuous” mode watches for changes to the release branch and initiates a backmerge every time a change is introduced.
What else does our automation do?
- The working branch isn’t the only place fixes need to land; we’ll propagate backmerges to release branches that were cut after the “source” branch as well.
- If there are no conflicts we notify you via Slack or Teams that the backmerge is done. If there are conflicts, we pause our backmerge attempts and let you know there’s an issue.
- We then suggest you tackle the backmerge manually, ideally by cutting a temporary branch, bringing in all the changes, and then opening a new PR.
Now that we’ve discussed both options, do we have an answer to the question we asked at the beginning of this blog post?
Are cherry-picks or backmerges better? It depends.
To steal a joke from @thepracticaldev, every single engineering question ever conceived can be answered with two words: it depends.
With cherry-picks, you grab select commits from your working branch and bring them over to the release branch via the <code>git cherry-pick<code> command (or via an automation like Runway’s). But issues can occur if your working branch has diverged significantly from the point at which the release branch was cut, making it hard to untangle fixes from surrounding work so they can be cherry-picked over discreetly without bringing over large chunks of other work. And if you’re not relying on automation to help you out with cherry-picking fixes, it can be hard to remember all the commands for bringing fixes over, quickly becoming tedious and repetitive.
With backmerges, you work directly with the release branch, ensuring the change itself will likely be conflict free and work without issue. For individual developers, this approach also involves less manual work: you fix the issue on the release branch, merge it into the release branch, and in all likelihood forget about it — the engineers fixing issues during the release are often not the same ones in charge of backmerging release branches. Problems arise when you don’t backmerge for a while, or when fixes that involve a lot of work across multiple files are merged into the release branch, as you then run the risk of running into a large number of conflicts the next time you do so. And, if you don’t have a good process in place for backmerging release branches, it’s easy to forget to backmerge — a mistake that can be catastrophic for downstream releases if impotant fixes aren’t merged back into the right place.
Regardless of which option you choose, getting fixes into the release branch is an important part of the mobile release process — and critical towards making sure releases move smoothly and stress-free.
There are some good rules of thumb to consider:
- Choose one approach and stick to it. If you backmerge, avoid cherry-picking, and if you cherry-pick make sure nothing ever makes it into the release branch that hasn't first gone into the working branch.
- Automate the process if you can. This will reduce a lot of frustration associated with repetitive manual tasks that are error prone — this is especially relevant for cherry picking, which tends to involve more manual steps each time a fix needs to be pulled into the release.
- Keep your fixes discrete and as contained as possible, regardless of which approach you take. The more targeted your fixes are and the fewer files they touch, the easier your life will be.
- Avoid cutting the release branch until you're really ready. Too often, release branches are cut before features are stable. This leads to huge volumes of "fixes" needing to be made before release, which drastically increases the likelihood of merge conflicts, regressions, and other general forms of release pain.
- Be disciplined about the kinds of fixes that are allowed to make it into a release branch. Some teams go as far as implementing "fix request" flows, where the release manager or a panel of people in charge of reviewing release fixes must explicitly approve a fix request before it can be merged into the release branch. This keeps release candidates stable, and helps protect against late-arriving work that has the potential to cause a regression close to submit time.
But which one is really and truly better? As you now know: it depends!