Trunk-based development

What is 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 on the main branch, which is continuously integrated and tested.

What is continuous integration?

Trunk-based development is a pre-requisite for a team to practice continuous integration (CI). Each time changes are pushed to the trunk branch (which happens several times a day), a suite of automated tests are run before and after the merge to determine whether or not the change introduces bugs/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 any other changes can be made - this may sound time-consuming at first, but since tests are being run at each step of the development process, bugs/regressions are encountered far less frequently in a team that practices CI.

Gitflow vs. trunk-based development

Gitflow is a branching model that creates multiple long-lived branches for different stages of the development process (e.g., 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 can be maintained for days or even months, making the merge into the main branch a tedious and high-risk 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/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 will always be stable to work off of/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

Why use trunk-based development?

There are several benefits of trunk-based development, but these are some notable ones:

  1. 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.

  2. "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 even minute-by-minute basis, ensuring the base they're working off of is never stale.

  3. 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.

How to start using trunk-based development

Small, batched or stacked changes

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.

Speedy, frequent code review

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, such that these branches exist for less than a day. The longer a branch exists, the higher the chance of introducing bugs/encountering merge conflicts is 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.

Limited number of active branches/feature flags
Many teams have 3+ active branches on a given repository - this could be a develop branch, a release branch, several feature branches, a main branch, etc. In trunk-based development, keeping the number of active branches to a minimum is strongly recommended. Since the agility of the team is dependent on the working status of the trunk branch, having multiple open branches can make the process of repairing/reverting bad changes to trunk unnecessarily complicated. In situations where developers are inclined to create yet another feature/release branch to gate or develop risky features separately from the main branch of their repo, 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.

Quick automated test suite and build process

To achieve CI, each commit to a repo must undergo testing before, during and after it has been merged into trunk, and subsequently trigger an automated build process. As a result, this automated test suite should consist of only short-running integration/unit /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 - more comprehensive end-to-end tests can be run later on in development.

References and further reading