Use of a source control system (e.g. Git) starts with the relatively simple idea of branching from, and committing back to, a shared codebase. But once you start working with branches, exactly how to leverage source control can quickly become daunting as you consider the (sometimes competing) goals you might have as an organization.
Do I need to support a team of 20 or 200 engineers? Do we need to support several versions of a codebase simultaneously? Can we make changes to our production software whenever we want? And the answer to that last question is, of course, that you cannot do so since you are a mobile developer and your app lives in the App Store or Play Store, making it impossible for you to quickly ship a change to production on a whim. Â
You need a branching strategy. Developers and teams have been dealing with these questions for almost as long as source control systems have been around â and mobile developers have been dealing with even more questions on top of those due to the added complexity that comes with shipping native apps â Â and thereâs a wealth of ideas, experiments and experience from which to draw conclusions.
What is a branching strategy?
A branching strategy (or âdevelopment workflowâ) is essentially a set of rules that a team of developers can follow to govern how they all interact with a shared codebase. Itâs generally standardized for a given repo, and sometimes (but not always) even across the entire engineering organization.
It covers everything from how new features get added into the codebase, to how the latest code is packaged into a binary and ultimately released to users.
Why your branching strategy matters
An effective and efficient mobile development workflow should accomplish the following:
- Helps maintain bug-free production code, keeping your app as crash free as possible
- Doesnât require too much mental overhead, helping ensure mobile developers donât have to pile added stress on what may already be a distracting and complex mobile release management process
- Keeps development nimble and running smoothly, letting you more frequently ship new features and improvements
In short, having a good mobile development workflow will improve developer happiness, while helping your team ship code safely and efficiently.
What to keep in mind when choosing the right branching strategy
Obviously you canât just spend a few minutes googling âBranching strategiesâ (welcome, if thatâs what brought you here), pick one that seems reasonable for your present situation, go with that, and move on. Putting a branching strategy in place without considering its details and implications can have serious negative consequences for your team, especially as you grow.
Not all branching strategies are good branching strategies â at least not for all teams at all stages. What was a great development workflow for your team yesterday may no longer be ideal today. Â
Your goals
Every mobile teamâs goal will be slightly different, and companies at different stages will have different priorities. Shipping new features could be what youâre all about or it could be absolutely critical that you maintain the highest standard for quality on your app to ensure itâs as bug free as it can be.
Maybe your existing process is too complicated and is causing a lot of overhead, distracting developers from building new features while they instead deal with what is essentially administrative work just trying to get their latest release ready to ship to the App Store or Play Store. Or maybe it actually needs a little additional complication so there are more safeguards in place.
A lot of things may be true or false about your current process, which is why you should make sure your head is fully wrapped around your goals before you change something about that process. There are no magic solutions out there. Any branching strategy can work well given the right circumstances, you just need the right one for your circumstances.
Your workflow
What are those circumstances? CI/CD, feature flagging, automated and manual testing, and the number of engineers on your team all directly impact how you build and ship code, and influence how confident you can be that whatâs being shipped to end users is what youâd actually like to be shipping to end users.
Letâs take a closer look.
Number of Engineers
While more contributors can often lead to a more sophisticated and capable product (and/or one that is developed more quickly), it also increases the likelihood of conflicts or other issues in the code. Â Your workflow should be robust enough to handle the volume of work your team produces.
Continuous integration (CI)
Leveraging continuous integration tools within your workflow can protect the team against unexpected integration errors that tend to slow down development. With this greater margin for error, you may be able to consider more sophisticated branching strategies with increased confidence. This is pretty much a prerequisite for any workflow that involves more than one branch.
Continuous delivery (CD)
Continuous delivery goes one step further by demonstrating that the code on your branch is actually stable because it can be compiled into a functional binary that an actual user can test. This provides you with an even greater margin for error.
Unit & Automated Testing
What is the extent of your test coverage? What is the general culture on your team around testing? When new and existing code is covered with unit tests, it reduces the likelihood of introducing regressions into the main branch.
Automation
Even a little automation can go a long way towards reducing friction in your workflows. For example, you could have a script that automatically cherry-picks certain commits from your main branch to a release branch based on a predefined naming convention or keywords, or one that automatically back merges changes during or at the end of releases. (And if you use Runway, you could even have access to these kinds of automations without really needing to think about it.)
Feature flagging
Feature flagging helps keep feature branches short-lived, because it makes it possible to merge even WIP code safely â thereâs no need to keep that code isolated on a branch until itâs ready. You can put more confidence into your working branches because any new work introduced can be reliably hidden and deactivated behind a feature flag until itâs ready.
Striking a balance
The key to selecting a branching strategy is in striking the right balance when it comes to complexity: you want just enough to safeguard the quality of what you ship, but not so much that your team gets slowed down by process and is unable to ship as easily or frequently as a result.
If you donât have high confidence in the code being released, your branching strategy can help mitigate the risk thatâs associated with more unstable code. If you are highly confident in it, then your strategy can help increase the speed at which your team gets new features into production. Itâs another tool you can leverage to achieve your goals.
Which strategy should your own mobile team use?
Common branching strategies
Trunk-based
With trunk-based development, each contributor merges small commits directly into the main branch (or âtrunkâ â think of the widest part of a tree and how it dwarfs any branches in importance and length).
This approach only works reliably when the main branch is âwell protectedâ, typically with a robust CI system in place to integrate and test changes before the corresponding code is merged into the main branch.
Trunk-based development can theoretically work well at scale; Google and Facebook have made use of this approach to allow hundreds or thousands of contributors across multiple time zones to work with new code as quickly as possible (and lessen the likelihood of commensurately awkward merge conflicts from stale branches). But, as youâd expect, these are also organizations that have been able to invest heavily in automation and testing to safeguard workflows at these volumes.
How it stacks up
Maintaining bug-free production code
In pure trunk-based development, all activity is focused on the single working branch. Which means this workflow includes no built in safety nets for catching problems before code is merged. So youâll be able to maintain mostly bug-free production code if (as noted above) your workflow has robust testing and failsafes in place.
Minimizing mental overhead
Engineers directly merge commits to the main branch which is about as low mental overhead as you can get. Release candidates are generated right off the main branch, and because youâre only working with one main branch, any necessary fixes go right into the main branch from which a new release candidate build can be generated. This keeps mental overhead to a minimum, but puts an enormous emphasis on having a main branch that is extremely stable or ârelease-readyâ which requires robust testing and integration practices.
Trunk-based w/ Release Branches
In this very popular variation of the trunk-based approach, you ultimately build your final App Store binary from a release branch that is cut from the working branch. This serves to isolate production-ready work away from code thatâs still in flux, âfreezingâ code in a certain state so it goes through little to no change while it undergoes additional testing. This also allows development to continue on the working branch.
Another way to think of release branches is as stabilization branches. They exist to give pre-release code a place to stabilize and undergo further testing before being released to users, which increases confidence in your release. This makes it a far more reliable approach for most teams compared to purely trunk-based development, and thatâs why itâs the choice of most growing mobile teams and the option weâd recommend if someone were to ask us or if we weâre to write a blog post on this topic. When you donât have the luxury of having direct access to your production code, ensuring stability ahead of each release is pretty important.
Release branches can either be long-lived like in the previous example, or they can be created just-in-time when youâre getting ready to prepare a release. These are known as short-lived release branches. The need to create and then cherry-pick changes to these release branches adds a little extra work to the process, but the tradeoffs are worth it and the additional manual work can be automated away (by, say, using a platform like Runway).
How it stacks up
Maintaining bug-free production code
When there is lots of activity happening on the trunk (which could correlate to relatively less stability), having a stable place from which to build and test release candidates can reduce the risk of new changes introducing new issues. But because this workflow is still for the most part a trunk-based workflow, you donât lose the benefit of maintaining  a higher bar for the general quality of code that you tend to see with trunk-based workflows (because thereâs only one working branch).
Minimizing mental overhead
During development, your workflow is extremely straightforward â long-lived branches discouraged; instead small commits are made and merged directly into main if builds are run successfully.
During releases, the mental overhead is reduced when compared to more complex workflows like GitFlow due to the lack of a âproductionâ branch to manage separately. Any final fixes â or even a hotfix â for a release would ideally be created on main and cherry-picked to the release branch (eliminating issues first on the trunk, which is a core principle of this type of workflow). Though cherry-picks carry their own mental overhead, friction here could, again, be lessened by automation.
GitLab flow (promoted out of the other notables section)
An increasingly popular branching strategy is GitLab flow, which involves two long-running branches: a working branch where active development happens, and a production / release branch, where all new release candidates are built from.
Code from the working branch is merged into the release branch â âpromotedâ â when itâs ready to be cut into a release candidate. Release candidates are built exclusively from the production branch, and any fixes can either be merged directly into the production branch (which is later merged back, or âbackmergedâ, into the main working branch), or cherry-picked from the working branch into the production branch. At the end of the release, the final commit from which the release candidate was built on the production branch is tagged.
Maintaining bug-free production code
Because the production branch serves as a stabilization branch, the GitLab flow branching strategy shares many of the benefits of other branching strategies that involve isolated release branches.
Keeping development nimble and running smoothly
As with other trunk-based branching strategies, having a single development or working branch from which day to day development is happening helps keep things simple â and as a result, more nimble and straight forward. And during releases, work can go on as usual on the working branch while the release candidate is prepared, tested, and bugs are fixed on the isolated production branch.
Minimizing mental overhead
When compared to branching strategies that use short-lived release branches, GitLab flow can come with slightly more overhead under certain circumstances. Over time, merges between the working branch and the production branch can cause the commit histories of the two branches to diverge â most of the time this isnât a problem, but in some cases merge conflicts can occur and can be difficult to grok and to resolve. This tends to be less of a problem with short-lived release branches whose common ancestor with the working branch is typically closer in the commit graph.
GitFlow
GitFlow uses a tagged main branch, a development branch, versioned release branches, feature branches, and hotfix branches.
Work-in-progress code is contained within feature branches that are created off of, and merged back into, the main development working branch. Code getting deployed to production is built from short-lived release branches that are created off of the development branch, then merged into a long-lived production branch when the release is ready (so the production branch always contains the latest production code), and then finally deleted.
Tags mark a snapshot of exactly what code was pushed live for every released version, and these reference points can be used to inspect the source code of older versions that are still out in the wild.
How it stacks up
Referring back to our critical features of an efficient and effective strategy:
Maintaining bug-free production code
GitFlowâs use of release branches ensures that production-ready code will be protected from possibly unstable changes from continuing development on future releases. In addition, any code that has already made its way to production is protected and isolated in the main branch. This is an approach that is well-suited for explicitly versioned software, and works well when it becomes necessary to support multiple versions of live software.
Less nimble development Â
GitFlowâs structure works well for larger teams because it allows for a dedicated development branch that can handle relatively unstable changes. When a release is cut, development can continue normally on the development branch, while code being prepared for production is âquarantinedâ on a release branch until itâs stable and ready for deployment. Finally, your âproductionâ branch is reserved only for mature and stable code that was deemed worthy of release (and the production branchâs head will always represent the most current version thatâs out in the wild).
Minimizing mental overhead
In most situations and for most developers, GitFlow has relatively low overhead. When working on a new feature, developers will create a feature branch off the development branch, and merge it back into the development branch upon completion.
However, there is a bit more overhead when preparing for a release. Teams need to create the release branch, then make sure that any final work going into the release is also merged back into the development branch, and finally that the release branch is merged into the production branch when ready. Often, it will make sense to try to leverage some degree of automation to simplify these steps during the release process.
Other notable approaches
OneFlow - A branching strategy that (via liberal use of rebasing) attempts to preserve GitFlowâs advantages but improves upon a couple of specific weaknesses â by using a simpler conceptual model and preserving an understandable Git project history on your main branch.
Enhanced Git Flow - This method also attempts to improve upon perceived drawbacks of classic Gitflow â the overhead, unreadable Git history, and double merging releases â by force pushing a new main branch during every release.
ThreeFlow - A low-overhead approach that maintains three stable, long-lived branches (main, candidate, release) representing internal, beta, and production builds, and simple rules for what types of code get pushed to which.
GitHub Flow - An extremely simplified approach thatâs more optimized for continuously deployed software (such as web applications), but can be a reasonable choice for small, early-stage mobile development teams that need to iterate and move quickly in non-production environments.
Final thoughts
How much overhead your branching strategy has essentially comes down to how many branches need to be maintained manually. More branches generally means more overhead, unless your team invests in automation.
Moving the team towards fewer branches and a tighter development workflow (short-lived feature branches merged within a couple of days to main) can certainly reduce cognitive load, but itâs a strategy that will only work well with a stable codebase â and that means a team that is willing to buy into a rigorous testing and integration culture.
For most teams, a slightly more complex strategy (and accompanying overhead) is warranted if stability is a priority â release branches provide a greater opportunity to put new code through rigorous validation while allowing for normal development to continue on the main working branch. When combined with the use of git tags, they can also provide the  comfort of knowing you can always just fall back to any of your  âcleanâ production release states whenever needed (like for example, if you need to issue a quick hotfix).  Â
Choosing the right branching strategy for your mobile team comes down to being honest about your team and your goals â and being prepared and willing to make the necessary changes to improve your workflow.
â