In modern software development, speed and agility are crucial when it comes to developing and releasing software. However, when you have a large team of developers working simultaneously, branching and merging code can become messy fast.
Therefore, teams need to have a process in place to implement multiple changes at once. This is where having an efficient branching strategy becomes a priority for these teams.
What is a branching strategy?
Branches are primarily used as a means for teams to develop features giving them a separate workspace for their code. These branches are usually merged back to a master branch upon completion of work. In this way, features (and any bug and bug fixes) are kept apart from each other allowing you to fix mistakes more easily.
This means that branches protect the mainline of code and any changes made to any given branch don’t affect other developers.
A branching strategy, therefore, is the strategy that software development teams adopt when writing, merging and deploying code when using a version control system.
It is essentially a set of rules that developers can follow to stipulate how they interact with a shared codebase.
Such a strategy is necessary as it helps keep repositories organized to avoid errors in the application and the dreaded merge hell when multiple developers are working simultaneously and are all adding their changes at the same time.
Such merge conflicts would eventually deter shipping code quickly and thus hindering from creating and maintaining an efficient DevOps process as the whole purpose of DevOps is creating a fast workflow that would allow for the release of small batches of code.
Thus, adhering to a branching strategy will help solve this issue so that developers can work together without stepping on each other’s toes. In other words, it enables teams to work in parallel to achieve faster releases and fewer conflicts by creating a clear process when making changes to source control.
When we talk about branches, we are referring to independent lines of code that branch off the master branch, allowing developers to work independently before merging their changes back to the code base.
In this post, we will outline some of the branching strategies that teams use in order to organize their workflow where we will look at their pros and cons and which strategy you should choose based on your needs, objectives and your team’s capabilities.
Why you need a branching strategy
As mentioned above, having a branching strategy is necessary to avoid conflicts when merging and to allow for the easier integration of changes into the master trunk.
- Enhance productivity by ensuring proper coordination among developers
- Enable parallel development
- Help organize a series of planned, structured releases
- Map a clear path when making changes to software through to production
- Maintain a bug-free code where developers can quickly fix issues and get these changes back to production without disrupting the development workflow
Branches are not just exclusive to Git. However, in this article we focus on Git due to the many advantages this model of branching offers.
Consequently, before we delve into the various branching strategies out there, including Git branching strategies, we will take a look at how Git actually handles branches and why it’s a standout among other VCS tools.
Git branches allow developers to diverge from the main branch by creating separate branches to isolate code changes. The default branch in Git is the master branch.
The biggest advantage of a Git branch is that it’s ‘lightweight’, meaning that data consists of a series of snapshots so with every commit you make, Git takes a picture of what your files look like at that moment and stores a reference to that snapshot. This means that these branches aren’t just copies of the file system but simply a pointer to the latest commit.
Meanwhile, other VCS tools store information as a list of file-based changes which may slow things down and use up significant space.
In Git, a branch is essentially a reference or a pointer to the latest commit in a given context; it’s not a container for commits. As you create new commits in the new branch, Git creates new pointers to track the changes. Git branches, then, can be seen as a pointer to a snapshot of your changes.
The images below illustrate this concept, where the top image shows the master branch and a pointer pointing to the last commit and the image right below it shows what happens when you create a new branch called ‘dev’- a new pointer now points to the latest commit.
To sum up, the Git branching model is lightweight compared to other version control systems; this is why it’s so easy and cheap to create branches in Git, as the whole code doesn’t need to be copied to the branch creating a large amount of duplicate files, as opposed to other VCS tools.
What are some common Git branching strategies?
Considered to be a bit complicated and advanced for many of today’s projects, GitFlow enables parallel development where developers can work separately from the master branch on features where a feature branch is created from the master branch.
Afterwards, when changes are complete, the developer merges these changes back to the master branch for release.
This branching strategy consists of the following branches:
- Feature- to develop new features that branches off the develop branch
- Release- help prepare a new production release; usually branched from the develop branch and must be merged back to both develop and master
- Hotfix- also helps prepare for a release but unlike release branches, hotfix branches arise from a bug that has been discovered and must be resolved; it enables developers to keep working on their own changes on the develop branch while the bug is being fixed.
The main and develop branches are considered to be the main branches, with an infinite lifetime, while the rest are supporting branches that are meant to aid parallel development among developers, usually short-lived.
GitFlow pros and cons
Perhaps the most obvious benefit of this model is that it allows for parallel development to protect the production code so the main branch remains stable for release while developers work on separate branches.
Moreover, the various types of branches make it easier for developers to organize their work. This strategy contains separate and straightforward branches for specific purposes though for that reason it may become complicated for many use cases.
It is also ideal when handling multiple versions of the production code.
However, as more branches are added, they may become difficult to manage as developers merge their changes from the development branch to the main. Developers will first need to create the release branch then make sure any final work is also merged back into the development branch and then that release branch will need to be merged into the main branch.
In the event that changes are tested and the test fails, it would become increasingly difficult to figure out where the issue is exactly as developers are lost in a sea of commits.
Indeed, due to GitFlow’s complexity, it could slow down the development process and release cycle. In that sense, GitFlow is not an efficient approach for teams wanting to implement continuous integration and continuous delivery.
Thus, in that case a much simpler workflow such as GitHub Flow is recommended.
GitHub Flow is a simpler alternative to GitFlow ideal for smaller teams as they don’t need to manage multiple versions.
Unlike GitFlow, this model doesn’t have release branches. You start off with the main branch then developers create branches, feature branches that stem directly from the master, to isolate their work which are then merged back into main. The feature branch is then deleted.
The main idea behind this model is keeping the master code in a constant deployable state and hence can support continuous integration and continuous delivery processes.
GitHub Flow pros and cons
Github Flow focuses on Agile principles and so it is a fast and streamlined branching strategy with short production cycles and frequent releases.
This strategy also allows for fast feedback loops so that teams can quickly identify issues and resolve them.
Since there is no development branch as you are testing and automating changes to one branch which allows for quick and continuous deployment.
This strategy is particularly suited for small teams and web applications and it is ideal when you need to maintain a single production version.
Thus, this strategy is not suitable for handling multiple versions of the code.
Furthermore, the lack of development branches makes this strategy more susceptible to bugs and so can lead to an unstable production code if branches are not properly tested before merging with the master-release preparation and bug fixes happen in this branch. The master branch, as a result, can become cluttered more easily as it serves as both a production and development branch.
A further disadvantage is as this model is more suited to small teams and hence, as teams grow merge conflicts can occur as everyone is merging to the same branch and there is a lack of transparency meaning developers cannot see what other developers are working on.
GitLab Flow is a simpler alternative to GitFlow that combines feature-driven development and feature branching with issue tracking.
With GitFlow, developers create a develop branch and make that the default while GitLab Flow works with the main branch right away.
GitLab Flow is great when you want to maintain multiple environments and when you prefer to have a staging environment separate from the production environment. Then, whenever the main branch is ready to be deployed, you can merge back into the production branch and release it.
Thus, this strategy offers propers isolation between environments allowing developers to maintain several versions of software in different environments.
While GitHub Flow assumes that you can deploy into production whenever you merge a feature branch into the master, GitLab Flow seeks to resolve that issue by allowing the code to pass through internal environments before it reaches production, as seen in the image below.
Therefore, this method is suited for situations where you don’t control the timing of the release, such as an iOS app that needs to go through the App store validation first or when you have specific deployment windows.
Trunk-based development is a branching strategy that in fact requires no branches but instead, developers integrate their changes into a shared trunk at least once a day. This shared trunk should be ready for release anytime.
The main idea behind this strategy is that developers make smaller changes more frequently and thus the goal is to limit long-lasting branches and avoid merge conflicts as all developers work on the same branch. In other words, developers commit directly into the trunk without the use of branches.
Consequently, trunk-based development is a key enabler of continuous integration (CI) and continuous delivery (CD) since changes are done more frequently to the trunk, often multiple times a day (CI) which allows features to be released much faster (CD).
This strategy is often combined with feature flags. As the trunk is always kept ready for release, feature flags help decouple deployment from release so any changes that are not ready can be wrapped in a feature flag and kept hidden while features that are complete can be released to end-users without delay.
Trunk-based development pros and cons
As we’ve seen, trunk-based development paves the way for continuous integration as the trunk is kept constantly updated.
It also enhances collaboration as developers have better visibility over what changes other developers are making as commits are made directly into the trunk without the need for branches. This is unlike other branching methods where each developer works independently in their own branch and any changes that occur in that branch can only be seen after merging into the main branch.
Because trunk-based development does not require branches, this eliminates the stress of long-lived branches and hence, merge conflicts or the so-called ‘merge hell’ as developers are pushing small changes much more often. This also makes it easier to resolve any conflicts that may arise.
Finally, this strategy allows for quicker releases as the shared trunk is kept in a constant releasable state with a continuous stream of work being integrated into the trunk which results in a more stable release.
However, this strategy is suited to more senior developers as this strategy offers a great amount of autonomy which non-experienced developers might find daunting as they are interacting directly with the shared trunk. Thus, for a more junior team whose work you may need to monitor closely, you may opt for a Git branching strategy.
How to choose the best branching strategy for your team
When first starting out, it’s best to keep things simple and so initially GitHub Flow or Trunk-based development may work best. They are also ideal for smaller teams requiring only a single version of a release to be maintained.
GitFlow is great for open-source projects that require strict access control to changes. This is especially important as open-source projects allow anyone to contribute and so with Git Flow, you can check what is being introduced into the source code.
However, GitFlow, as previously mentioned, is not suitable when wanting to implement a DevOps environment. In this case, the other strategies discussed are a better fit for an Agile DevOps process and to support your CI and CD pipeline.
The following table summarizes the strategies discussed in this article and which strategy is appropriate in which context:
|Product type and its release method||Team size||Collaboration maturity||Applicable mainstream branch mode|
|All||Small team||High||Trunk-Based Development (TBD)|
|Products that support continuous deployment and release, such as SaaS products||Middle||Moderate||GitHub-Flow and TBD|
|Products with a definite release window and a periodic version release cadence, such as iOS apps||Middle||Moderate||Git-Flow and GitLab-Flow with release branch|
|Products that are demanding for product quality and support continuous deployment and release, such as basic platform products||Middle||Moderate||GitLab-Flow|
|Products that are demanding for product quality and have a long maintenance cycle for released versions, such as 2B basic platform products||Large||Moderate||Git-Flow|
To sum up, there is no such thing as the perfect strategy. The strategy you choose will depend on your team and the nature and complexity of your project and so this should be evaluated on a case-by-case basis.
It’s also fine to start off with one strategy and adapt it over time according to your needs. Needless to say, whatever strategy you end up choosing should aim to increase your team’s productivity by giving them a clear and consistent strategy to organize their work.