We should start from somewhere, so let me begin from an overview of the most popular branching strategies or models. This will help us define a basic vocabulary in order to draw a context around the opening question.
Stable Trunk
Trunk must be The Stability. No unverified code can get there. Just very done and well baked. Real work usually goes in the version-next branch. When your acceptance criteria have been met, you're able to merge updates into the mainline. Depending on a type of a software that you are working on (web application, desktop, embedded or distributed systems) and your release plans, you may publish or deploy this new version for the world.
Integration Branch
A buffer branch between a bunch of feature branches and the destination branch (e.g. master
or rel2.0
version). As name implies, this kind of strategies stimulates any sort of branching because at the end those isolated changes have to be working together. But this model locates furthest from Continuous Integration ideas (and as some of you noticed Continuous Delivery/Deployment too). I don't say that it is not possible to have a good CI solution with many branches. I'm saying that here you just postpone real integration procedure by working on a separate branch and delaying your feedback loop. Integration is hard.
Unstable Trunk or Trunk-based development
Day to day commits on a trunk. When you're ready to release just cut a branch. Simple. Straightforward. Very CI friendly. Homepage.
Promiscuous Integration
This name was proposed by Martin Fowler:
With this approach they only push to the mainline at the end, as before. But they merge frequently with each other, so this avoids the Big Scary Merge. So is this more ad-hoc integration a form of CI or a different animal entirely? I think it is a different animal, again a key point of CI is everyone integrates to the mainline every day. Integrating across feature branches, which I shall call promiscuous integration (PI), doesn’t involve or even need a mainline.
GitFlow
Often people tend to believe that they are using something like GitFlow or A successful Git branching model. But in the real world of extreme situations, they might violate the rules implied by this strategy, and in the end, they could have something ugly like PI. GitFlow has many nuances and asks from a team the Spartan’s discipline. Really complex.
Let’s take one step back
Why do we need branching at all? Karl Bielefeldt says:
Unless you are all working out of the same working tree, you are using branches, whether you call them that or not.
So see in the branches a potential releasable version of your program. While any team works on a project, each member have been working on its own version. And the longer the work goes, the more different versions they all will have. "It works on my machine!"—have you heard that before?
The essence of Continuous Integration is a single version integrating all the changes from all the sources that can be verified for any requirements compliance and be ready for release. One of the key characteristics of a good CI solution is a short feedback loop. You've made a commit, build server makes his routines and viola—you are receiving report about what was done well and where something felled off.
Even if you try to make the world better making a super-refactoring in a feature branch, that brave new world do not stand still and your chances of meeting the Integration Hell are hugely increased.
There are several other reasons why teams may choose to branch their code.
- Physical branching of the system's physical configuration—branches are created for files, components, and subsystems.
- Functional branching of the system's functional configuration—branches are created for features, logical changes, both bugfixes and enhancements, and other significant units of deliverable functionality (e.g., patches, releases, and products).
- Environmental branching of the system's operating environment—branches are created for various aspects of the build and runtime platforms (compilers, windowing systems, libraries, hardware, operating systems, etc.) and/or for the entire platform.
- Organizational branching of the team's work efforts—branches are created for activities/tasks, subprojects, roles, and groups.
- Procedural branching of the team's work behaviors—branches are created to support various policies, processes, and states.
Bit earlier, the same authors of Continuous Delivery book—Jez Humble and David Farley—wrote that only three of above are worth it:
There are three good reasons to branch your code.
- First, a branch can be created for releasing a new version of your application. This allows developers to continue working on new features without affecting the stable public release. When bugs are found, they are first fixed in the relevant public release branch, and then the changes are applied to the mainline. Release branches are never merged back to mainline.
- Second, when you need to spike out a new feature or a refactoring; the spike branch gets thrown away and is never merged.
- Finally, it is acceptable to create a short-lived branch when you need to make a large change to the application that is not possible with any of the methods described in the last chapter—an extremely rare scenario if your codebase is well structured. The sole aim of this branch is to get the codebase to a state where further change can be made either incrementally or through branch by abstraction.
Avoid Continuous Disintegration
We have different flavors of continuous integration practices. Starting from CI itself than going through Continuous Delivery and ending by Continuous Deployment.
All of them are united by idea of multi-level feedback loop that provide to a human, in a relatively short time, observations about work that they have just done. TDD (or sometimes Design by Testing) allows you to see how your programming language can express your design ideas. Unit test gives you understanding that your basic algorithm or happy and sad paths are valid. Integration testing brings you ability to see how groups of classes, modules, or components interact with each other, or how well 3rd party services are integrated with your application.
On a higher level, you could check the code quality by using various static code analysis tools. Functional application quality can be verified by going through packs of automatic regression and acceptance tests. Another level—performance, load and stress tests which also provide you good information about different sides and slices of your project.
Put this model simple: You've made a modification of your software (no matter application code, database scheme, environment configuration) and publish your update to the real working directory that automatically insures that all the things are still stable by using CI/CD mechanism.
Each time you create a branch you are making a step away from CI. Moreover, the longer the branch lives, the greater the likelihood of subsequent integration errors. Until of course you know what you're doing. Good questions to think about: Do you need a feature branch? How long is it going to live? What is the cost of merging? Do all the team members know what your branch are created for? Do you have all the tests that reassure your in that all existing functionality still working fine and as expected? Did you and all of yours coworkers really understand yours branching strategy?
Life without branching
Reasonable question: If branching is a potential step back from a continuous stability and integrity, how can one deal with releases when epic-like task or a huge refactoring took place on a road? Continuous Integration book says:
There are four strategies to employ in order to keep your application releasable in the face of change:
- Hide new functionality until it is finished.
- Make all changes incrementally as a series of small changes, each of which is releasable.
- Use branch by abstraction to make large-scale changes to the codebase.
- Use components to decouple parts of your application that change at different rates.
The first one is clear: Just use application settings and condition variables in your code or feature toggling frameworks. Second option is about more careful release planning, backlog grooming, or deep understanding of how to split unsplittable. Third, is a pattern proposed by Paul Hammant. And the last one bullet means a good knowledge of your domain, organisation structure, and change rates of individual modules of designed system.