By Zhang Liaoyuan (Yunyue)
Code programming is the starting point of the software development and delivery process, and code release is the end of this process. The code branching mode runs through the entire process from development to integration and then release. It is the closest partner of engineers. But how do we choose the most appropriate branch mode based on our own business characteristics and team size?
In this article, we will be addressing this issue by introducing the processes and characteristics of several mainstream Git branch modes, and providing selection suggestions.
Overview of Branch Modes
The purpose of branches is to isolate one branch from another. However, additional branches add up to maintenance costs. We can define the following combinations from the development and release branches:
- Trunk development and trunk release
- Branch development and trunk release
- Trunk development and branch release
- Branch development and branch release
Imagine two different scenarios:
- If a software program corresponds to one developer and requires only one release version, which branch mode is appropriate for this case?
- If a software program corresponds to 10 developers and requires multiple release versions, which branch mode is appropriate?
An appropriate branch mode can greatly improve the efficiency of software development, integration, and release. When each development team starts working, they must first determine the branching strategy. So, which branch mode would be the appropriate one? Before answering this question, let’s first look at the common branch modes.
Mainstream Branch Modes
Common branch modes include trunk-based development (TBD), Git-Flow, Github-Flow, and Gitlab-Flow.
TBD means that all developers collaboratively work on one develop branch, that is, the master branch. In this mode, no additional long-lived develop branches are allowed, and only the master branch is used for development collaboration.
As no other develop branches that have been separated for a long time exist, code changes are continuously updated to the master branch, avoiding problems caused by code merging. Meanwhile, in this development mode, we recommend that you create a release branch from the master branch based on the cadence of releasing software versions. In TBD mode, all modifications are made on the master branch, including the correction of defects. After all the modifications are completed, they are cherry-picked to the release branch.
The characteristics of this case can be summarized into the following items:
- Only one develop branch, the master branch, is available.
- All modifications are made on the master branch.
- A release branch can be created from the master branch.
- Modifications made on the master branch are cherry-picked to the release branch of the corresponding version based on the defect correction strategy.
In TBD mode, a team shares a develop branch and performs integration verification on the develop branch. Each code submission triggers integration verification. This requires that each batch of submitted code changes be quickly verified on the master branch to accommodate the next batch of code changes. Here, each batch of code changes must be based on the previous stable version. To ensure that the master branch is always available for code changes, the following requirements must be met:
- Each batch of changes must be small in scale to ensure a controllable verification scope.
- A complete automated verification mechanism is required for quick verification.
Therefore, the TBD mode is a key driver of continuous integration. The TBD mode is conducive to continuous integration and helps build a stable and reliable baseline for the master branch. This baseline supports anytime release, implementing continuous delivery. However, the building of this baseline depend on the ability to quickly compile, check, and verify changes to the master branch based on the team’s mature collaboration capabilities and engineering support. In addition, as release branches are involved, we must sort out the relationships among product versions, branches, and deployment scenarios to prevent release branch confusion, and to determine the defect correction strategies for each branch.
In TBD mode, each batch of submitted code changes must be small in scale and can be quickly verified to ensure releasability on the master branch. For some features in the development process, each change submission does not mean the completion of the entire features. To remove the impact of “semi-finished features” from the master branch, a feature toggle is often used. With the introduction of the feature toggle, frequent code changes are collected and verified first, but the relevant features are hidden by the feature toggle until they are fully ready.
However, the introduction of the feature toggle incurs cost, because the feature toggle is a configuration, which is essentially the same as a common macro definition, such as #if #else. From this sense, it produces a code branch. On the other hand, the use of the feature toggle makes your code vulnerable. Therefore, a good code design is required for the proper use of the feature toggle.
To address needs such as the feature switch in the development of a certain feature, and to suit scenarios where more and more teams work together to develop a feature, feature-oriented branch modes have emerged. Among the modes, Git-Flow is the most representative one.
Git-Flow provides a way of addressing the need for parallel development between different features. When you start developing a feature, create a feature branch from the master branch. Then, all the development work on the feature is done on this feature branch. After you complete the work on the feature, merge the feature branch back to the main code path for release.
Git-Flow has the following branches:
- Feature branch: is a branch for developers to develop features.
- Develop branch: is a branch that collects the developed features.
- Release branch: is a branch that is responsible for version release.
- Hotfix branch: is a branch that corrects online defects.
- Master branch: is a branch that stores the baseline of the latest released version.
Each feature has its own develop branch, that is, the feature branch. When a developer needs to work on two features, the developer only needs to switch between the branches by running the “check out” command. This is to prevent the mutual interference between the development of the two features during the development process.
When you develop a feature, you need to verify it separately. After the feature is verified, merge it to an integration branch called develop branch to verify the entire software program. In this process, the develop branch is similar to the master branch most of the time. The develop branch always stores the most recent unreleased version. After the code on the develop branch is verified and can be released, you can create a release branch from the develop branch for release.
If a defect is found on the release branch during the release process, correct the defect on the release branch and synchronize the correction to the develop branch. After the version on the release branch is released, the finalized code is synchronized to the develop and master branches again. In this way, the master branch always keeps the baseline of the working version. Meanwhile, the develop branch keeps the latest version for development integration.
Git-Flow introduces a branch called hotfix, which is dedicated to correcting online defects. After the defects are corrected, they are collected to the develop branch and then synchronized to the master branch. In fact, we can consider hotfix as a special feature branch, but code changes submitted by it need to be synchronized to the master branch while being integrated to the develop branch.
Do you think Git-Flow is complicated? Then, let’s see how it work by going through the feature development process.
- Developers receive a development request and create a feature branch from the develop branch.
- The developers complete local development and local verification, and then submit their code to the feature branch.
- The code is verified on the feature branch, which continuously merges newly created code.
- The developers complete feature development and the feature branch detects no errors during the verification. Then, the code on the feature branch is merged to the develop branch.
- The develop branch performs integration verification, which may involve the code from other feature branches. After the integration verification is completed, the feature branch will be deleted.
- When the develop branch keeps a mature release version, for example, the develop branch has performed full testing and corrected all defects, you can create a release branch for release.
- After the release is completed, the code on the release branch is merged to the develop and master branches, and then the release branch is deleted. Here, note that the master branch always keeps the most recent released code.
The hotfix process is as follows:
- If a defect is found after a release, a hotfix branch is created from the master branch.
- The defect is corrected and then verified on the hotfix branch.
- The correction is merged to the develop and master branches.
- The hotfix branch is deleted.
The Git-Flow branch mode provides a relatively complete set of branches to cover most scenarios in the software development process. For a long time, developers take this mode as the standard Git branch mode, as its name implies in some sense. However, Git-Flow also has its problems. For example:
- It involves lots of branches and each type of branches has a unique usage, making it difficult for developers to differentiate different branches.
- The overall branch structure is too complicated and is redundant for most teams and projects.
- The long lifecycle of feature branches leads to merge conflict. The longer the lifecycle of a feature branch, the greater the difference between this feature branch and the develop branch. As a result, integrating the code on this feature branch to the develop branch can result in potential code conflict.
- Since the develop branch is somewhat redundant, the integration role played by the develop branch can be fully replaced by the master branch. However, the additional introduction of the develop branch for the integration of code changes increases the complexity of the Git-Flow mode. Similarly, if the develop branch is removed, the hotfix branch becomes unnecessary, because the hotfix and feature branches are essentially “do-the-same” branches in this case.
So, is there any branch mode that not only allows development tasks to be isolated from the mainline, but also is more lightweight than Git-Flow? In my opinion, the appropriate solution should be extremely simple in nature. Then, let’s take a look at another branch mode called GitHub-Flow.
Unlike Git-Flow, GitHub-Flow involves no release branches. In the ideas of GitHub-Flow, once a version is ready to go, it can be deployed. Similarly, GitHub-Flow believes that hotfixes are identical to minor feature changes, and their processing methods should be similar.
GitHub-Flow has the following overall requirements:
- All code on the master branch is the latest working version that can be deployed.
- To perform a new task, a new branch is created from the master branch and is explicitly named to reveal its purpose, for example, new-scheduling-strategy.
- Code changes must be committed to the local branch as frequent as possible. Meanwhile, the changes must be synchronized to the branch with the same branch name on the server as frequent as possible.
- If you want to merge new code to the master branch, you must initiate a pull request to request code review.
- After the code review is passed or when the code review is ongoing, the branch must be deployed to the test environment for verification.
- If the review and verification are passed, the code must be merged to the master branch and can be immediately deployed to the production environment.
Compared with Git-Flow, GitHub-Flow features greater simplicity. On the other hand, GitHub-Flow meets the need for continuous deployment. In GitHub-Flow, defects on the master branch can be quickly detected and the defective code can be quickly restored through mechanisms such as rollback. By merging everything into the master branch and deploying the master branch frequently, you can minimize the amount of unreleased code, which is also the best practice advocated by lean development and continuous delivery. Deployment was originally a tedious task, but implementing it frequently helps simplify this task to achieve continuous delivery.
Although GitHub-Flow simplifies the Git-Flow branch mode, it leaves us with unanswered questions about deployment, environment, and release. Therefore, we hope to provide more clues for these questions through GitLab-Flow.
Compared with GitHub-Flow, GitLab-Flow adopts a similar development mode except replacing pull requests with merge requests. Despite of this, the usage of the merge request is similar to that of the pull request, because both of them can be used a communication method for code review and feedback.
The biggest difference between GitHub-Flow and GitLab-Flow lies in their release modes. Unlike GitHub-Flow, GitLab-Flow introduces the production branch for the production environment and the pre-production branch for the pre-release environment (if any). In this way, the master branch hosts the code deployed in the integration environment, the pre-production branch hosts the code deployed in the pre-release environment, and the production branch hosts the latest code deployed in the production environment.
After a feature is developed, you must submit a merge request to merge the feature development code to the master branch and deploy the master branch to the integration environment for verification. After the verification is passed, you must submit a merge request to merge the master branch into the pre-production branch and deploy the pre-production branch to the pre-release environment for verification. After the verification is passed, you must submit a merge request to merge the code from the pre-production branch to the production branch.
As described in the preceding paragraph, based on the actual environment, the trunk release is merged downstream and deployed in sequence. In addition, GitLab-Flow supports different versions of the release branch. That is, different versions create different release branches from the master branch, and different release branches release code through the pre-production and production branches.
As you can see, GitLab-Flow does more than GitHub-Flow on the release side. While GitLab-Flow is strongly dependent on GitLab tools, GitLab-Flow can be seamlessly integrated with the Issue system in GitLab. In the working mode recommended by GitLab-Flow, each time a new feature branch is created, this request is actually initiated from an issue, that is, creating a mapping between the issue and the feature branch.
Pros and Cons of Different Branch Modes
By classifying the preceding branch modes based on the number of development and release branches, we can determine the following modes:
- TBD adopts trunk development and supports branch or trunk release.
- Git-Flow adopts branch development and supports branch release.
- GitHub-Flow adopts branch development and supports trunk release.
- GitLab-Flow adopts branch development and supports trunk or branch release.
After we understand the common branch modes, we can choose appropriate practices based on our own business characteristics and team size. In other words, there is no perfect mode, but an appropriate mode.
Then, what must we consider when choosing the most suitable branch mode based on the characteristics of the team and project?
Choosing an Appropriate Practice
First of all, we must consider the version release cycle of the project. If the release cycle is long, Git-Flow is the best choice. Git-Flow can address issues such as new feature development, version release, and production system maintenance. If the release cycle is short, TBD and GitHub-Flow are both good choices. GitHub-flow features the integration of pull requests and code review. If your project is already using GitHub, GitHub-Flow is the best choice. GitHub-Flow and TBD are demanding for infrastructure such as continuous integration and automated testing. If relevant infrastructure is incomplete, we recommend that you do not use GitHub-Flow and TBD.
In addition, we recommend that you exercise the following practices from the perspective of the number of required release versions:
- If a product needs to support multiple release versions, use Git-Flow.
- If a simple product needs to support a single release version, use GitHub-Flow or TBD.
- If a complex product needs to support a single release version, use GitLab-Flow.
If you find that existing mainstream branch modes cannot meet your requirements, you can define your own branch modes. For example, if you have a team that adopts trunk development, you can define the spring, summer, fall, and winter branches. That is, use the “spring branch” in spring and the “summer branch” in summer. This branch mode has two benefits. First, it achieves trunk development and continuous release. Second, its name is very interesting and makes development work funny.