“Hey, why don’t we just commit our code to the main branch?”
That simple statement can get you a lot of half-confused, half-horrified glares from software developers. Loads of developers will look at you like you’ve lost your mind for suggesting such a thing. It’s like a reflexive fear that’s been instilled by countless tales of horrific mistakes making their way into production code. But how dangerous is it really?
Well, before we get to that, let’s cover a bit of background here. Most modern source code repositories follow one of three types of branching strategy: GitFlow, GitHub Flow, or Trunk Based Development. Of the three, GitFlow is overwhelmingly the most common strategy. GitFlow is a branching strategy built around the concept of isolating new development into “feature branches” that organize sets of related features together. Typically this leads to situations where a feature branch ends up being primarily authored by one or perhaps a few developers working on related features of the application. Since complex features can take a long time to develop, these features branches can remain separate from the main branch for a long time–several days or even weeks is not uncommon. In government codebases, these branches can live even longer than that (several weeks or perhaps months) if they’re blocked by external dependencies. This helps keep the code that developers are working on from incorporating mistakes made by other developers… but it also means that different features aren’t being merged back together and tested very frequently. This results in a complex process of resolving merge conflicts that can take a significant amount of developer effort all on its own. As you might imagine, procrastination also tends to result in these merges occurring shortly before a release deadline. This is encumbered further by a common practice referred to as a pull (or merge) request and a formal code review process, which involves getting a different developer who didn’t work on the story to take time away from their story to review the code before approving a merge into the main branch. This can be quite a time consuming process since the other developer has to go over all the work done on the feature branch… which can contain weeks of developer effort. Needless to say this general practice tends to result in complex merge conflicts and a lot of developer time being spent on code reviews and waiting on other developers to do code reviews.
Trunk Based Development takes an almost diametrically opposed approach to branching. Instead of isolating new code onto separate feature branches, in Trunk Based Development developers… commit their code directly to the main branch. The branch that gets automatically integrated, built, and deployed to a staging environment. Developers are usually committing their code to the main branch at least once a day, often more than once a day. The intent behind Trunk Based Development is to keep the amount of “work in progress” very low by organizing development into small batches of changes that are easy to merge, test, and gather feedback about. If you’ve ever asked yourself what the “Continuous Integration” part of “Continuous Deployment/Continuous Integration” means, it refers to this practice of merging everyone’s work together all the time. This approach to development is very dependent on a high-quality CI/CD setup that can quickly merge, build, and test changes to the codebase.
But surely if all the developers are committing all their work to main all the time, it’s going to introduce a lot of bugs into the codebase? After all, developers are notoriously incapable of writing perfect code and tend to just write a lot of bugs. Plus, loads of features just can’t be completed in a day, and what good is a half-completed feature? How do teams practicing this type of development this avoid producing a broken jumbled mess of half-completed features? Glad you asked!
Teams using Trunk Based Development avoid the above issues by following a set of practices I’m going to refer to as the 7 Commandments Of Trunk Based Development.
- Main Is Also Release. There isn’t a separate release branch. We’re not keeping old branches with stale code. What’s in main is the application we are willing to release today.
- The Authority To Write Code Is A Failed Test. No new code gets written without first having a test fail. If you want a new feature, you start with a new test.
- Incomplete Features Are Flagged. This means that new feature development gets hidden behind feature flags. You run tests for each enabled feature before deploying a build.
- Nothing Deploys If The Tests Fail. Since all new code requires tests in advance, and all features are gated by flags, there is never an excuse for a deployment to end users that fails a test.
- Nobody Commits Code Alone. Development is done in pairs, always. Two developers approving the code is enough code review to get released.
- All Bug Fixes Start With A Test. If you want the authority to change the code to fix a bug, you have to start by describing the bug with a test that fails.
- Bugs Come First. Bugs should always jump to the top of the backlog. Even if they’re very small, that just means they’re very fast to fix.
These 7 practices serve as guardrails that make sure the codebase stays coherent, releasable, and extensively tested. They each serve complementary purposes that help to improve overall software quality. For example, requiring developers to commit back to main daily forces them to validate the code they are writing against the full test suite on a regular basis. This helps protect against regressions. Having bugs pop directly to the top of the backlog discourages developers from taking shortcuts. Requiring developers to write software in pairs means there’s continuous code review and joint ownership over the codebase. It’s both a code review practice and a practice that almost completely eliminates “bus factor” problems on a team by sharing context across more members of the team. Requiring new features to be gated behind feature flags makes it much easier to reconfigure the application for different environments, improving future flexibility and reducing operational headaches.
The end result of this development approach tends to be a very maintainable codebase that teams can quickly add features onto but are never afraid to deploy. “Cutting a release” is as simple as building the application and packing the artifacts into an appropriate installation package. Operational testing mostly becomes an opportunity to gather new user requirements because the team is continually testing everything they’re trying to accomplish. The team drastically reduces the risk that comes from making changes by sharply limiting the scope of changes and forcing developers to continually check their work against the work everyone else is doing.
So that brings us back to the original question. Why not just commit code directly to the main branch? It’s not as crazy as it sounds, and if you’re disciplined about it, it can drastically improve code quality over traditional branching strategies. But that’s not to say it’s a perfect strategy for every situation. It works best for small teams that can strictly follow the 7 rules listed above, especially teams that are developing greenfield cloud-hosted software directly from user requirements. This method lets them develop software quickly, get it into the hands of users, and prove the value stream makes sense as quickly as sustainably feasible. But over time it is likely that a team will develop additional needs that might prompt them to adapt this model for larger teams.
One common adaptation that teams adopt is so-called “scaled trunk-based development”. This reintroduces very short-lived feature branches–software is still reintegrated multiple times per week (feature branches should still remain short-lived)–but it helps keep teams from stepping on each other’s toes when there are more than a handful of workstreams working on the same codebase.
Want to chat about your favorite git strategy? Looking for hard problems to solve? Checkout Raft’s open positions at Raft and come work with me and tons of other very talented individuals solving interesting problems.