19. August 2025 By Lane Westlund
Rethinking branching: how to bring stability and predictability to your releases
Too frequently, releases are delayed by bugs, or worse, bugs are found in the field and require another round of release testing and deployment. This ends up costing the company time, money, and reputation as deadlines are missed and the entire test/deploy cycle must be started again. This branching model is designed as a solution to this, since its primary focus is the stability of current and upcoming releases. This is most important in environments where both deployments and testing for release are costly and time consuming (as is frequently the case with embedded systems).
Stability first: a model for reliable and predictable releases
I’ve often joined software development groups and heard the phrase “we use Git Flow”. While that might have been true at the very start, various pressures and experiences can cause organizations to slowly mutate this into their own system. One such system I’ve frequently encountered is something I like to call the “release based branching model”.
In this model, there is no ongoing develop branch, but rather a series of cascading release branches. These release branches are created as soon as a feature is in development which is planned for them. Features are then merged into the releases, and releases are merged “upward” into later versioned releases immediately. The immediate separation of release branches serves to isolate features, refactoring, and bugfixes (and therefore new bugs) from each other, so instead of “merging to develop when it’s done”, these things are pushed into the future and merged into something other than the next release.
The net effect of this strategy is the overall increase in software quality and stability due to the earlier isolation of planned release branches. This effect does come at a slight cost in developer effort due to the greater number of merges (and possibly merge conflicts), but generally the stability increase is seen to be worth it.
Planning ahead: creating release branches early for better control
At first glance, it might seem that the primary difference between this model and Git Flow would be the fact that there is no develop branch. In a practical sense, this is not true: There will always be a “highest” release branch and seen from a certain perspective this is just a “develop” branch which is being continuously renamed.
The key difference with this branching model is the time at which new release branches are created. Whereas with Git Flow a decision is made to “stabilize develop” by creating a release branch, this branching model focuses on creating a release branch as soon as any feature is planned for a specific release. This allows the next release to only get specific planned features, fixes, or refactoring.
Release branches should be named to indicate the general release (for example release/20.x), and specific releases should be tagged (for example release/20.1.0). Space should be left in the versioning scheme for hotfixes, which can then branch off from specific releases, tagged appropriately, then merged into the lowest active current release. Hotfix branch naming can have its own designator (e.g. hotfix/20.1.0.1) but in practice, the naming is somewhat less important as hotfix branches (like all other branches) should be deleted after being tagged as a release and merged upwards.
The “main” or “master” branches are also no longer needed. Sometimes it is useful for a project to indicate its current release, in which case the main branch can be used and manually placed on specific tagged releases instead of having a release merged into it. This keeps all release hashes identical, which is important.
Model description with examples
It is perhaps easier to understand this system using concrete examples instead of abstract descriptions.
In this example, the first step would be to rename an existing “develop” branch to align with the next upcoming release version, say “release/4.5.x”. At this point, development continues as usual, and feature “A” is merged into release/4.5.x.

As soon as a new feature or bugfix is planned for a later version, a new branch should be created, for example “release/4.6.x” with planned features “B” and “C” branching off from 4.5.x.

It is likely that development continues on 4.5.x, so it will receive two example bugfixes “D” and “E”. Bugfix “D” is merged first into 4.5.x and then branch release/4.5.x is immediately merged into release/4.6.x.

Subsequently Bugfix “E” is then merged into 4.5.x. This completes version 4.5.x, so this branch receives the tag “release/4.5.0”. In this example, there is unfortunately a conflict between 4.5.x (having just received Bugfix “E”) and 4.6.x. To solve this a merge branch: “merge/4.5-4.6” is created from release/4.6.x. Here the 4.5.x branch is merged, conflicts, sorted, then the entire branch is merged back into 4.6.x, where later planned feature C is also merged.

Proactive merging: preventing small issues from becoming big problems
With this branching system, one of the best rules to follow is “merge up as soon as possible”. This prevents smaller issues from snowballing into larger ones. So as soon as any merge happens into a lower numbered release branch, these branches should be merged upward immediately. Ideally an automated system would be used to do this.
Planning also is a key element of this system but frequently runs afoul of the desire to have new features earlier. In these cases, the only option is to cherry-pick features merged into later versions back into new feature branches targeting earlier branches. From a Git perspective this is somewhat ugly but at least should not be a problem when the subsequent upward merge occurs.
Refactoring in newer versions does become an issue when merging up earlier versions. Often extreme care must be taken to ensure that the feature being merged up is correctly integrated into the new code. This effort is part of a trade-off however: the goal of this branching strategy is the stability of the earlier release versions.
Another possible issue comes up when “one off” bugfixes or features are implemented in earlier versions but are not desired in later ones. In this case, the “-sours” parameter for git merge is very useful, allowing the connection between the release branches to be maintained, but the earlier feature is not merged up. This merge connection is always important to do, rather than leaving a “dangling” release branch unmerged. It is important to signify to subsequent developers that the feature was not simply “forgotten”, which would risk including it in any subsequent upward merge.
Conclusion
This approach contrasts with more popular modern branching strategies, which often favor single main branches for continuous merging and deployment. However, this model arises as a practical solution to specific constraints. Frequently this method is met with resistance from developers, who primarily see the increased cost of their effort with regards to merging and solving merge conflicts. However, when viewed from the overall organizational perspective, the gains in stability of releases often makes up for this up front investment.
Do you have questions about branching strategies, or would you like to optimize your development processes? Our experts at adesso will be happy to help you.