Trunk-based Git branching
13 Dec 2023
A “trunk-based” strategy for creating and deleting “branches” of a Git repository can drastically reduce conflicts when teams collaborate on editing a shared file.
Consider trunk-based branching
Allowing multiple long-lived copies of a Git repository (“branches”), such as “dev
,” “qa
,” and “main
,” to all exist at the same time fell out of favor in many circles over the course of the 2010’s because keeping them synchronized with each other often became unmanageable.
If you’re a Git beginner overwhelmed by the idea of keeping multiple long-lived branches of a codebase synchronized, you’re in luck. You’re actually quite fashionable in Silicon Valley!
What is trunk-based development?
- Maintaining only 1 main “branch” per Git repository over a long lifespan.
- Limiting the lifespan of all other branches to be extremely short.
(Some people call this concept “feature-based”. There’s a lot of overlapping and conflicting jargon around Git branching strategies. I like to say “trunk-based” because it appears in the DevOps Handbook and Accelerate, which means managers are more likely to have heard it before.)
How do I do it?
The best practices below can help reduce the complexity of resolving conflicts when multiple coders collaborate on a single codebase.
1: Work quickly
Bite off only what you can chew.
Whenever you need to edit the contents of files tracked with Git, plan out an amount of work that you can do within a few hours.
(Or, at worst, a few days.)
2: Use ephemeral scratch-paper-like branches
Never directly edit files in a long-lived shared copy (“branch”) of your repository.
- With a few button clicks, you can “branch” off a new copy of the files and edit them at your leisure.
- Once you’re confident about the quality and stability of your changes, you can click a few buttons to merge your work back into the long-lived shared copy of the files with a “pull request” and delete the branch you used like throwing away scratch paper.
Protect long-lived branches
You can force contributors to use scratch-paper-like branches.
Branch protection refers to settings available in cloud hosts for Git repositories that, among other things, let you forbid specific branches (copies) of your repository’s files from being edited directly.
This forces authors of files in your Git repository to use branching and pull requests as described above.
Enabling these restrictions is a highly recommended best practice on all branches of the codebase that are meant to exist for a long time (rather than be created and destroyed ephemerally).
In trunk-based development, there will be just 1 such branch.
3: Enjoy resolving simpler conflicts
If someone else is working on the same files as you in their own scratch-paper branch, the second person to finish might notice that they can’t complete the pull request process until they’ve clicked a few buttons to sort out differences (“resolved merge conflicts”).
If everyone on your team has followed the “work quickly” best practice, making and destroying short-lived branches every few hours, then the busywork of resolving conflicts (e.g. deciding which version to use when one person typed “false
” and another person typed “FALSE
“) should be quick and easy.
Git repository cloud hosting providers offer web-browser-based and desktop-software-based user interfaces to facilitate this work.
How can I trust a single branch?
You might wonder how on earth a single codebase can power multiple levels of nonproduction running software and the production version of your software.
- What about all of the variations in configuration, like making sure the application connects to equivalent nonproduction/production databases?
- What about the fact that features often need to be manually reviewed in lower environments before they are introduced into higher environments?
Refactor deployment automations
You may need to reconfigure your software build, test, and deployment automations.
If you have separate deployment automations triggered by updates to each of many long-lived branches, you would need to refactor them into a single automation triggered off of the main (only!) branch of your code.
Don’t worry – all of the major “CI/CD” build/test/deploy automation providers allow you to create multi-stage automations that require human approval before proceeding through important stages, like deployment to higher environments.
The more you can parameterize and modularize (caveat) not only your CI/CD automation scripts, but also the code it’s building (so that a single codebase won’t know what environment on which it’ll be running until the CI/CD automation actually builds/deploys/runs it), the better.
Dynamic configuration
Trusting trunk-based development may also require thinking of:
- feature “deployment” (putting new theoretically-runnable code onto a server) separately from …
- …feature “release” (bringing about the “if-then-else” conditions under which the new code would actually have the opportunity to run).
Software may need to be rewritten to leverage dynamic configuration mechanisms like feature flags (the aforementioned “if-then-else”) when nonproduction and production environments will both be running a single shared codebase.
You are already likely familiar with separating “deployment” from “release” through dynamic configuration if you have ever developed an application that needs to automatically appear to change on a schedule.
For example, Healthcare.gov needs to be able to reveal the next year’s plans in the middle of the night every November 1.
The code that supports the new plans exists on production servers long before November 1 – the general public simply can’t see it in action until November 1.
If you’re thinking this sounds an awful lot like a database, you’re right.
You’re definitely moving application behavior configuration out of “version-controlled source code” and into a more conventional data-storage tool.
That said, reserve your transactional databases for data that changes frequently, like multiple times an hour.
Use dedicated application configuration data stores like LaunchDarkly, AWS AppConfig, Azure App Configuration, etc. for application-version-behavior configuration settings that change more slowly – and that often no longer even need to exist some time after a feature has been safely released.
What if I enjoy multiple long-lived branches?
Despite Silicon Valley fashions, there is nothing wrong with maintaining a Git repository that has multiple long-lived “branches” if your team is happy and productive.
You’ll write the best software when you choose a standard that works well for your team, regardless of its resemblance to the standard that works well for someone else’s team.
Furthermore, complex branching strategies were once far more widespread in the larger software development industry. Even teams who prefer trunk-based branching for new projects may have legacy codebases that are simplest to maintain in their current form.
Try short-lived branching anyway
You can still keep a different long-running branch of your codebase, like “dev
,” cleaner by applying all the same principles that trunk-based teams apply to “main
.”
If you haven’t already, try protecting dev
from direct edits. Then ask contributors to bite off only what they can chew within a few hours, and to use short-lived branches off of dev
when editing the codebase.
Additional resources
- Wikipedia - Feature Toggle
- Screaming In The Cloud podcast episode #399: Feature Flags & Dynamic Configuration
- The 5 Stages of Feature Flag Adoption
- Martin Fowler on feature flags
- Pete Hodgson on feature flags
- Example: Rebranding in secret with feature flags
- Feature Toggle vs. Feature Flag: The Rise of the Flag
- Podcast episode: Feature Flags and Toggle Management Reduce Developer Stress and Increase Productivity
- Code With Jason podcast episode #166: Feature Flags and Duplication