Continuous Integration, not Continuous Isolation

Merging can suck.

My first real-world exposure to version control was in the good old Visual SourceSafe days. Back then (and continuing into server-side Team Foundation Version Control), it was common to have exclusive locks on files - i.e. only one person could work on a file at a time. If someone changed a file, you had to "Get Latest" before you could start working on it.

In practice, big changes required many files being locked by one person, and everyone was blocked until it was done.

This was unproductive.

So we worked on branches. We knew that the merge at the end was going to be horrible, but that was a problem for the future. Not worth worrying about now.

Exclusive locks

Then exclusive locks went away, and we started working on the same files, merging as we went. We could even work offline with distributed solutions like mercurial and git, and make changes in tiny branches (rather than branching the whole codebase), which meant smaller merges and less pain.

But big changes and refactorization were still a problem. You still had to merge your changes with everyone else's, and with everyone working on the same files, it became easier to work on your own branch (or a team branch) and worry about merging later. You could even push broken code to the server without bothering everyone else.

So we deferred merging.

Again, we knew the merge at the end was going to be horrible, so for big changes, our situation hadn't improved much.

Distributed version control

Continuous Integration

Continuous Integration means merging all of your changes into everyone else's changes on a continuous basis in order to make sure problems are caught early. You can ensure that the code compiles, all the tests pass, and even run analysis tools to check for code quality or security vulnerabilities.

There are obvious advantages to continuous integration. It's common knowledge that problems are cheaper to fix the earlier they're identified. Whether that's a bug or a merge conflict, if you can resolve it immediately, it will save time in the long run. You want to know straight away whether you've "broken the build". Not a week later when another team member pulls your changes and can't compile, and certainly not a few months later when it's time to push to production1.

If everyone on the team is working on the same code, and they're all pushing their changes regularly, continuous integration will identify problems quickly.

If a team is working with long-lived branches, they're not doing continuous integration. Instead, they're doing continuous isolation.

Continuous Isolation

The term "continuous isolation" came from a tweet by benjiwebber and was expanded upon by Paul Hammant. Thoughtworks later identified the practice as "CI Theatre".

By working on a long-running branch, changes become more and more isolated from the other branches, including trunk (or master). CI builds can still exist against that branch, but they're only integrating changes within that branch, not the code that the other teams are committing. They might make the team feel good, but they're just deferring the pain.

Trunk-based development

The solution seems obvious - don't work on branches! Or if you do, use short-lived branches and merge back into the trunk as soon as you can.

Short-lived branches

I promote this way of working as much as I possibly can, but in practice, it's not always that simple. Not every change can be knocked over in a few days.

trunkbaseddevelopment.com is a fantastic website that goes through this idea in detail, including the practicalities of doing it in real teams. I'd highly recommend reading through it.

The other technique that can help is feature toggles (or any of a bunch of names that mean the same thing). The idea is that even unfinished code can be merged into the trunk, as long as it's hidden behind a flag. As long as it a) compiles, and b) doesn't break anything, you can deploy it all the way to production with the flag turned off. The code won't run, but you can still continuously integrate your code.

Feature toggles deserve their own blog post, so I won't expand further here.

Summary

While long-running branches may feel more productive day to day, realise that you're just deferring the merge pain. That conflict you introduced is going to need attention at some point. The sooner you address it, the easier it will be to resolve.

Integrate continuously, and don't let your branches last long.

Incidentally, there's a recording of an excellent talk on this very topic from GOTO Chicago earlier this year by the always-impressive Sam Newman. Do yourself a favour and watch it!

1 Of course you should be deploying to production more regularly than once a month. Why wait if you've got something meaningful to ship!?

Damian Brady

I'm an Australian developer, speaker, and author specialising in DevOps, MLOps, developer process, and software architecture. I love Azure DevOps, GitHub Actions, and reducing process waste.

--