When I don't (exactly) use Trunk-Based Development

Today I go on record admitting I don't always use TBD, I do sometimes use long-lived branches, and I sometimes even share a branch with another dev.

One topic that comes back constantly is git branching models. It’s no secret that I don’t like GitFlow, and generally advocate using trunk-based development (TBD) instead. But there are a few cases where TBD isn’t enough, and I want to talk about a couple of them.

Both of these examples come from open-source work. Although the first one can apply to a wide number of products, the second one will be extremely rare.

  1. Long-term support releases

    The scenario is that you may use TBD for normal, day-to-day development work, but as a business decision, you choose to also continue to maintain one or more old versions of your software with some subset of changes (such as bug fixes and/or security updates only).

    The approach I use (for example on my Kivik project), is essentially the same as the one promoted at TrunkBasedDevelopment.com as “branch for release”. Whenever I’m ready to create a new major release, I create a new release branch, based on the major version number, i.e. v3. As long as v3 remains under active long-term support (however that’s defined for your project), then any relevant bugfixes or security patches can be cherry-picked from the mainline branch into v3. Or in rare cases, a change may be appropriate for v3 that’s not appropriate for main, in which case a new PR/merge directly into v3 is appropriate.

  2. Following a versioned spec

    Now this one is rare, and I’d be pretty surprised to see this on a commercial team, but it’s an interesting case study just the same, so I’ll mention it. First some background:

    I’m one of the maintainers of the Go-to-JavaScript transpiler called GopherJS. One of our goals is to maintain feature compatibility with the official Go project. This means we have a pretty clearly defined functional spec. It also means that every ~6 months when upstream Go releases a new version, we have a new, updated spec we need to honor, more or less “all at once”.

    Now some of the changes we make fall outside the realm of the Go spec. These can be optimizations, bug fixes, or tooling improvements. These types of changes generally make perfect sense to apply in a TBD manner. That is to say, we can just merge them directly to mainline whenever they’re ready.

    But then the versioned spec changes need much more control. It would never make sense to release a version that, say “supports everything in Go 1.2, and features A, B, and C from Go 1.3”. We need our system to support either Go 1.2 fully or Go 1.3 fully.

    So here’s where we break from the TBD model entirely on this project:

    Whenever we’re ready to begin work on a new Go spec version (usually about a month before it’s finalized), we create a long-running “feature” branch for the new target version. We branc this off of mainline, and usually call it something creative like go1.17. Then we update all of the automated tests to run the tests against the Go 1.17 spec instead of the old 1.16 spec, and then the entire CI pipeline turns read and starts throwing rocks at our faces.

    Then we’ll work on making the CI pipeline happy on that branch by adding any new features required in the new Go version. We’ll also do a careful read over the latest Go release notes as well, to add anything that the failing CI pipeline may not have caught. When that long-lived go1.17 feature branch is finally passing all the tests, we’ll usually run it locally for a week or two, to see if we find any straggling issues, before we finally merge that into mainline, then tag a new 1.17-based release.

    One thing to note: we sometimes do create PRs against our go1.xx branches. That is to say: While we have the go1.xx branch in a broken state, I may work on fixing a specific issue in a go1.xx-foo branch, and create a PR to merge that into the in-progress go1.xx branch. Sometimes we also just work directly on a shared go1.xx branch. Whichever seems easier and cleaner.

So there you have it. I’m on record admitting that I don’t always use TBD, I sometimes use long-lived branches, and I sometimes even share an in-progress branch with other developers. All practices I regularly preach against. Even the best rules have a time and place to be broken, I guess. :)

Share this