The traditional Dev → Test → Staging → Production model is flawed, leading to unnecessary complexity and reinforcing outdated software delivery patterns. This breakdown explains why branch promotion is a failure mode, why GitHub Flow and Release Flow are reasonable alternatives, and why Git Flow belongs in the bin.
If teams still promote code through a Dev → Test → Staging → Production model, they are doing it wrong. This model inevitably leads to a branch promotion strategy, adding friction, increasing risk, and delaying value delivery.
Branching should be a tool to support flow, not an administrative overhead that slows everything down. If a model requires multiple merges to get code into production, it is already behind. Reverse integration, which involves pulling changes from downstream branches back into upstream branches, is fraught with danger and should be avoided.
This model was meant to provide structure and control, but in practice, it leads to teams confusing environments with branches.
The typical pattern looks like this:
What started as an environment management strategy becomes a branch promotion model, where:
This is a linear, gated approach that kills agility. Instead of focusing on delivering value, teams get stuck in a cycle of merging, resolving conflicts, and firefighting. Reverse integration only amplifies the chaos, introducing instability at the worst possible moments.
If teams are passing code between branches like a baton in a relay race, they are reinforcing a broken process. This is just waterfall with more Git commands.
Instead of treating branches as milestones, teams should focus on continuous integration and delivery. That means:
main
as soon as it is ready.Reverse integration breaks this model by introducing last-minute, untested changes into upstream branches, increasing risk and eroding confidence in deployments. Instead of integrating forward with stability, teams are forced into reactive fixes that create further instability.
This eliminates the bottlenecks of branch promotion. Instead of waiting weeks for a merge to move through environments, code is always deployable.
Branch promotion models often significantly increase cognitive load as engineers may be forced to support multiple versions in production. This excessive complexity increases the chances of reverse integration, where engineers must back-port features and fixes to different production versions, introducing further instability.
In these scenarios, Developers face constant challenges in tracking which code changes apply to which versions, leading to a higher risk of errors and regressions. Maintaining multiple live versions not only complicates testing, debugging, and feature rollouts but also makes it nearly impossible to ensure consistency across environments.
An extreme version of this is branch-by-customer, where separate branches are maintained for different clients. This is one of the most unmanageable and expensive practices, requiring extensive manual effort to maintain, patch, and update. Merging changes across multiple customer-specific branches is error-prone and time-consuming, leading to unpredictable behaviour and instability. Avoid at all costs.
Git Flow was an attempt to support many of the old branching models, but it is a bloated relic that needs to die! If teams are still using Git Flow, it is time to stop.
It introduces:
release/\*
branches that delay deployment.hotfix/\*
branches that signal a broken process.Teams that adopt Git Flow reinvent branch promotion, creating an overcomplicated merge-heavy workflow that belongs in the past.
The alternative is to use trunk or mainline development where all code is integrated continuously into the main, and there are only ever short-lived topic branches for a few developers to work together on something small.
The two main options relevant here are GitHub Flow and Release Flow.
One of the easiest to understand, implement, and do well is GitHub Flow. For most teams its the only branching model they will need as it provides that speed and simplicity that enable fast turnarounds and low cognitive load. It looks like:
main
.One would expect the pull request to be as automated as possible within the context of modern software engineering practices:
Reverse integration is completely unnecessary in GitHub Flow because all changes integrate forward, reducing complexity and risk.
For teams that need to support multiple versions in production, Microsoft’s Release Flow extends GitHub Flow without unnecessary complexity for the specific purpose of having a release version that you need to help until the next release is ready. Microsoft developed this because it took longer than their 3 weeks for Sprint to deploy new versions of Azure DevOps to the thousands of databases that they used.
main
.release/1.2
) is created.This keeps development fast while maintaining stability where needed. It avoids regression by always fixing into `main`. Critically, Release Flow continues to avoid the pitfalls of reverse integration, ensuring that all changes move forward in a controlled, predictable manner.
Branching should enable fast delivery, not slow it down.
If teams still promote branches through environments, it is time to rethink the strategy. Reverse integration is a dangerous practice that adds unnecessary risk and complexity. The best branching model is the one that gets in the way the least. Stop promoting branches. Start delivering value.
If you've made it this far, it's worth connecting with our principal consultant and coach, Martin Hinshelwood, for a 30-minute 'ask me anything' call.
We partner with businesses across diverse industries, including finance, insurance, healthcare, pharmaceuticals, technology, engineering, transportation, hospitality, entertainment, legal, government, and military sectors.
NIT A/S