Book cover
All rights reserved. Version for personal use only.
This web version is subjected to minor edits. To report errors or typos, use this form.

Home | Dark Mode | Cite

Software Engineering: A Modern Approach

Marco Tulio Valente

1 Managing Branches with Git-flow, GitHub Flow, and TBD

In this article, we will present three strategies for working with branches in software projects. It’s an important and practical subject, as these strategies define the workflow that teams employ to implement new features or fix bugs, for instance.

1.1 Git-flow

Git-flow is a commonly used branch strategy proposed by Vincent Driessen in 2010 (link). Essentially, the strategy uses two permanent branches:

  • main—also known as master or trunk—is used to store code that is production-ready.

  • develop stores code with features that have been implemented, but haven’t undergone a final test, which is typically performed by a Quality Analyst (QA).

Moreover, Git-flow proposes three temporary branches:

  • Feature branches
  • Release branches
  • Hotfix branches

These branches are described next. They can be created using Git’s commands (see some examples here) or by tools and plugins that provide macros to facilitate Git-flow use.

Feature Branches

These branches are created from develop before developers start a new feature implementation. Once the implementation is finished, they are merged back into develop and then removed. Thus, feature branches often exist only in a developer’s local repository.

Example: In the next figure, we present three feature branches. The full circles represent commits and the empty circles are merges. Notice how feature branches derive from and return to develop.

Three feature branches created from develop using Git-flow

Release Branches

These branches also originate from develop. They are used to prepare a new release, which must be approved by the customers. Once the customer gives the green light, the release branches are integrated into main since a new system version is ready for deployment.

If changes occurred during the approval process, the release branch should also be merged back into develop.

Example: After implementing the three features from the previous figure, the team leader decided to generate a release 1.0. For this purpose, they created a release branch (see figure) which was used to perform the last changes to attend the customer. After these changes, the code was finally approved and deployed—i.e., integrated into main. Lastly, the changes made on the release branch were also applied to develop.

Release branch (last branch in the figure) using Git-flow

Hotfix Branches

These branches are used to fix a critical error detected in production—that is, in code that’s in the main branch. Therefore, they spawn from main, receive commits to fix the critical bug, and are finally re-integrated into main and develop.

Example: After releasing the new version, users reported a critical bug. Thus, a branch was created to correct this critical bug (see following figure), which derived from main. After the bug fix, the branch was re-integrated into main and a new release was generated with the tag 1.0.1. Finally, the branch was also integrated into develop.

Hotfix branch (last branch in the figure) using Git-flow


Summarizing, the most common flow when using Git-flow is as follows:

Feature ⇒ develop ⇒ release ⇒ main

A feature is always implemented in a specific branch. Then, this branch is merged into develop, where the feature undergoes integration tests. Periodically, a release branch is created to show a new system version to customers. Once approved, this version is integrated into main and made available to all users.

Git-flow should primarily be used when there are manual tests and QA teams, as well as when customers need to approve and validate any new version of the code before it goes into production.

However, when using Git-flow, feature branches may take a long time to be integrated into develop, which may result in many integration conflicts (merge hell). Moreover, if the integration of release branches takes time, developers will have to wait longer to receive feedback on the new features they’ve implemented.

1.2 GitHub Flow

GitHub Flow is a common branch model when using GitHub. It’s simpler than Git-flow as it only consists of the main branch and feature branches. It also provides support for code review before integration, through GitHub’s Pull Requests (PR) mechanism.

When using GitHub Flow, the main steps are as follows:

  1. A developer creates a branch in their local repository.
  2. Implements a feature or fixes a bug.
  3. Pushes the branch to GitHub.
  4. Goes to GitHub and opens a Pull Request (PR), i.e., a request for someone to review their implementation.
  5. A reviewer checks the new code and possibly merges the PR into main.

An example of a PR request is shown in the next figure, which was extracted from GitHub’s documentation. In this figure, a PR is opened to review the my-patch-1 branch. After the review is complete, this branch will be integrated into main.

Pull Request creation interface (Source: GitHub)

In the appendix about git, we discuss and explain the concept of Pull Request better.

GitHub Flow is primarily used in systems with only one production version, as is typically the case with web systems. A disadvantage of the model is that PRs can take a long time to be reviewed.

Despite the name, the same flow can be used with other version control services, like GitLab.

1.3 Trunk-Based Development (TBD)

TBD is even simpler than GitHub Flow as it uses only one branch—the main branch, also known as master or trunk.

In reality, even in TBD, developers may create feature branches, but these should have a limited duration, of at most two days, as suggested by Paul Hammant in his TBD book (and also in this post):

One key rule is the length of life of the branch before it gets merged and deleted. Simply put, the branch should only last a couple of days. Any longer than two days, and there is a risk of the branch becoming a long-lived feature branch (the antithesis of trunk-based development).

To use TBD, there should be a comprehensive suite of unit and integration tests to avoid introducing bugs and regressions in the main branch. Moreover, by using TBD, it becomes easier to adopt practices such as Continuous Integration (CI) and Continuous Deployment (CD).

In Chapter 10 of the book, we provide a more detailed description of TBD and also explain the feature flags mechanism, which is used to prevent incomplete feature implementations from going into production.

Follow us on LinkedIn or Twitter.