How many times have you asked someone which branching model (or branching strategy) they are using on a project, and the answer was something like: “Oh, we have a development branch; everything is merged there. Oh, and also the main branch. There are feature branches as well.”
Sometimes the answer is: “We have the main branch, and we merge everything there. Yes - features, bug fixes, everything.”
There are a couple of mainstream branching models that could fit into these descriptions.
In this article, we are going to talk about git branching models (strategies, workflows), which one to choose for your team depending on the setup, what are the pros and cons, and how to decide which one is for you or why would you even need it in the first place.
Most commonly used branching strategies
There are a couple of options when selecting a branching model. Let’s list them out and take a look at their main characteristics, along with the pros and cons:
- GitHub flow
- GitLab flow
We will go over each listed here, see their pros and cons, and what characterizes them. After that, we’ll examine another approach that supports continuous integration and delivery.
GitFlow is a branching model defined back in 2010. The idea behind it was to enable parallel development where developers can work on features separately from each other without directly affecting the main branch's codebase.
This model contains the following types of branches:
- main (master) - contains production-ready code
- develop - contains the latest integrated code from the merged feature, release, and hotfix branches
- feature - created from develop branch, used for developing new functionalities, gets merged back into develop
- release - created from develop branch when a specific set of features is ready for production, considered as a “release candidate” for the next production release
- hotfix - created from the main branch and used to perform immediate fixes on the latest production version of the software
Some later implementations of this model added bugfix branches, but those can be considered the same as feature branches with a more specific purpose of resolving issues instead of adding new features.
This model works well for larger teams and software that needs version tracking and an option to revert to the previous version if and when required. As the author of this model states - “Software development shifted towards web applications in the last decade, and web apps are typically continuously delivered, not rolled back, so you don't have to support multiple versions of the software running in the wild.” The author also suggests GitHub flow as a starting point for most teams today. GihHub flow will be described shortly.
Some main benefits of this strategy are:
- It allows parallel development isolated from the production code. Multiple features can be developed simultaneously, but none of them will directly affect the production until a release branch is created and merged into the main branch
- Provides support for multiple versions of the software. This can be useful when you need to roll back to a previous version
- Having specific types of branches makes planning and executing deployments in different environments easier, allowing more systematic testing
- It is supported on both CLI and most GUI Git tools. Developers can use simple commands to perform a group of actions needed to create or merge specific types of branches
On the other hand, some challenges come with GitFlow:
- Git history can become unreadable unless each feature branch is rebased on the develop branch before merging. Even if this does not guarantee that the history will remain clean
- Can slow down the release cycle as each feature needs to be merged into the develop branch. Only when a set of features are complete, the team decides on creating a release branch, and once the release candidate is ready, you can promote the new version to production. This is far from having continuous integration and continuous delivery
GitHub flow is a simpler branching model than GitFlow. It is a feature branching model with one main branch where production-ready code resides. All other branches are considered feature branches, and adding new features or fixing issues should be done on them. Those should be merged back into the main branch once the work is finished and changes are reviewed.
This branching strategy follows six simple principles that, when followed, ensure you have good and maintainable code. Some of those principles should be followed even when not working with GitHub flow, like using descriptive branch names or often committing to your local branch and regularly pushing changes to the remote.
Advantages of this model are:
- Due to its simplicity, it works well for small teams and web applications or when you need a single version in production
- Provides support for continuous integration and continuous delivery to some degree
Some of the drawbacks are:
- Unable to support multiple versions at the same time
- The lack of dedicated development branches makes it prone to more production bugs
- It does not fully support CI/CD since there is no regular integration with the main branch (you can still have long-living branches)
GitLab flow tries to find a balance between the simplicity of GitHub flow and the ability to control deployments with environment-specific (e.g., staging, pre-production, and production) branches or have versioned releases with release branches.
Those are two possible approaches with the GitLab flow branching model:
- Versioned releases with each release having its dedicated release branch. When bugs are fixed, a fix should be applied to the main branch first and cherry-picked into the release branch (that way, we ensure that there is no regression in the upcoming releases).
- Continuous releases using production branches that should contain production-ready code. Feature branches get merged into the main branch, and changes are merged into the production branch when the code is ready to be released. This can be extended by using, for example, the staging branch to control deployment to a staging environment. This way, you can set up your CI tool to automatically deploy from specific branches to a specific environment.
Some pros of this strategy are:
- It’s more simple than GitFlow yet better organized and structured than GitHub flow
- It can support some form of continuous delivery
- Supports versioned releases if needed
Then again, there are some cons not to be ignored:
- It can lead to messy collaboration if no strict rules are applied
- With multiple versions in production, it can become quite complex
Moving away from feature branches and slow release cycles - welcome continuous delivery
None of the branching models mentioned above supports continuous integration to the full extent. But continuous integration and delivery are imperative in today’s development practice, and it has become important to find a corresponding branching strategy to support them. There is one approach fully compatible with these, and it’s called trunk-based development.
Trunk-based development is a branching strategy where changes from all developers get integrated regularly (at least every day) to a shared “trunk” branch (the name comes from the trunk of a tree). These changes are integrated with the trunk using short-lived (often local-only) feature branches. The desired state of the main branch (we are going to use main interchangeably with trunk) is that it always contains releasable code.
An important concept to keep in mind here is that changes should be kept as small as possible, and integrations should happen as early as possible. This creates an ecosystem where merge conflicts can easily be avoided but, on the other hand, requires a vast amount of automated tests in place.
In order to reduce the risk of breaking your production code, you should probably consider combining trunk-based development with feature flags since these two practices fit nicely together. With feature flags in place, you can more easily control releasing your changes. This way, developers can deploy partially finished features to production while keeping them hidden from end-users.
Some pros of trunk-based development are:
- Changes are deployed to production much faster than with traditional branching models
- It enables developers to move fast. The number of steps to merge your code to the trunk is significantly reduced. This results in having production-ready code closer to the next release early
- Reduces the number of merge conflicts, at least in theory, since there are no long-lived branches.
Same as everything else, there are also some challenges to be considered:
If too many developers try to integrate their changes simultaneously, they can end up toppling each other and constantly breaking buildsIt requires highly skilled and experienced developers otherwise, you risk having “immature code” integrated that could potentially destabilize the project
Which one should you use?
As with most things, there is no simple answer to this question. It mostly depends on the complexity of the project, your team setup, and distribution, or your specific development needs. Same as everything else in software development, there is no silver-bullet solution; everyone should find one for themselves. Once you start, you are probably going to be able to optimize it when the need arises.
If I were starting a new project right now, I would probably choose Github flow for its simplicity but slowly transition either towards GitFlow or some hybrid between the two in case there is the need for tracking release versions and the team/project setup requires it, or in the direction of trunk-based development and fully support continuous integration and delivery.
About the author
Milivoj Kovacevic is a full-stack software developer with over 8 years of experience as a professional software engineer working at our Novi Sad engineering hub. Meet Milivoj here.
Milivoj is a hardworking and passionate developer whose dedication and goal-oriented work style are a tremendous benefit for the team he is a part of. In his team lead role, he has been leading a number of software developers so he had the opportunity to develop essential mentorship skills.
Recently, Milivoj worked mostly on Java, Spring Boot, micro-services, and React engagements. His main interests are backend-oriented technologies, following software development best practices and principles, implementing design patterns, and focusing on delivering quality.