Introduction to trunk-based development

Trunk-based development is a practice in which all developers work on a shared branch, called the trunk or mainline using a version control system of their choice. Instead of creating long-lived feature branches, developers make changes directly to the main branch, which is continuously integrated and tested.

Trunk-based development is a prerequisite for continuous integration (CI). Each time changes are pushed to the trunk branch (which often happens several times a day), a suite of automated tests run before and after the merge to determine whether or not the change introduces regressions.

Every change to trunk triggers a build and a series of automated tests. A change that breaks your trunk branch must be fixed immediately before making any other changes—this may sound time-consuming at first, but since tests are run at each step of the development process, bugs and regressions are encountered far less frequently in a team that practices CI.

Gitflow is a branching model that creates multiple long-lived branches for different stages of the development process (for example, feature branch, develop branch, release branch, and master branch). Different developers use different techniques to merge/commit between these branches, adding increased complexity to the system.

Additionally, Gitflow developers work on large, long-lived feature branches for collaboration. These feature branches are often maintained for days or even months, making the merge into the main branch a tedious and risky task. These feature branches are usually so large that "code freeze" periods are required to ensure that the main branch is still in a working state, since these merge events are more prone to introducing regressions and bugs.

In contrast, trunk-based development uses a single shared branch (trunk) where all developers work and continuously integrate their changes. The model is relatively simple and agile, operating under the assumption that the trunk branch is always stable to work off of and commit to. Small batches or "stacks" of branches are extremely short-lived, and changes are merged into trunk every couple of hours.

Here's a visual distinction between trunk-based development, and a Gitflow-style of development:

Image from Google Cloud

Image from Google Cloud

There are many benefits of trunk-based development, but these are the most notable ones:

  • Fast feature delivery: In trunk-based development, changes are continuously integrated and tested (the trunk must always be green), so new features can be delivered faster than in a feature-branching model where features are developed in isolation and then integrated later.

  • "Green" collaboration: Team members frequently update and sync their work with the main branch in trunk-based development. With this approach, peers integrate each other's changes on an hourly or minute-by-minute basis, ensuring the base they're working off of is never stale.

  • Granular code reviews: Trunk-based development encourages smaller, "stacked" changes off of trunk, making code reviews more manageable and easier to complete. Additionally, since changes are continuously merged and tested, review feedback cycles tend to be much shorter.

First and foremost, developers must understand how to break their changes up into small, dependent branches—those coming from a feature-branch oriented workflow may find this difficult at first.

The goal of trunk-based development is to merge changes as quickly as possible, while still keeping trunk error-free. For this to happen, batched/stacked changes should be reviewed as quickly as possible, so branches exist for less than a day. The longer a branch exists, the higher the chance of introducing bugs and merge conflicts when merging the changes into trunk. Breaking up changes into smaller, dependent branches allows developers to have their code reviewed and merged while simultaneously working on new changes.

Many teams have three or more active branches on a given repository—such as a develop branch, a release branch, several feature branches, and a main branch. In trunk-based development, it's strongly recommended to keep the number of active branches to a minimum.

Since a team's agility depends on the working status of the trunk branch, having multiple open branches makes repairing or reverting bad changes to trunk unnecessarily complicated. In situations where developers are inclined to create a feature or release branch as a gate or to develop risky features separately from the main branch, using feature flags is suggested. Feature flags wrap specific changes in an "inactive" code path and can be conditionally enabled/disabled—eliminating the need for creating another branch and instead introducing the changes directly into trunk in a non-destructive way.

To achieve CI, each commit to a repository must undergo testing before, during, and after it merges into trunk, and subsequently trigger an automated build process. As a result, this automated test suite should consist of only short-running integration or unit or acceptance tests, and the automated build process should be quick and repeatable. Automated tests should be reliable (not flaky), and should assess the high-level functionality of the code system—and more comprehensive end-to-end tests can be run later on in development.