Back to blog

Git Flow Rethink: When Process Stops Paying Rent

July 28, 20239 min read

After months of solo-maintaining a corporate API, the ceremony of Git Flow became visible as ceremony — steps without payoff. Vincent Driessen retracted his own methodology in 2020. This is what it took for me to admit he was right.

Development Series — 23 articles
  1. Mastering Git Repository Organization
  2. CancellationToken for Async Programming
  3. Git Flow Rethink: When Process Stops Paying Rent
  4. Understanding System Cache: A Comprehensive Guide
  5. Guide to Redis Local Instance Setup
  6. Fire and Forget for Enhanced Performance
  7. Building Resilient .NET Applications with Polly
  8. The Singleton Advantage: Managing Configurations in .NET
  9. Troubleshooting and Rebuilding My JS-Dev-Env Project
  10. Decorator Design Pattern - Adding Telemetry to HttpClient
  11. Generate Wiki Documentation from Your Code Repository
  12. TaskListProcessor - Enterprise Async Orchestration for .NET
  13. Architecting Agentic Services in .NET 9: Semantic Kernel
  14. NuGet Packages: Benefits and Challenges
  15. My Journey as a NuGet Gallery Developer and Educator
  16. Harnessing the Power of Caching in ASP.NET
  17. The Building of React-native-web-start
  18. TailwindSpark: Ignite Your Web Development
  19. Creating a PHP Website with ChatGPT
  20. Evolving PHP Development
  21. Modernizing Client Libraries in a .NET 4.8 Framework Application
  22. Building Git Spark: My First npm Package Journey
  23. Dave's Top Ten: Git Stats You Should Never Track

The Habit That Stopped Paying Rent

For a stretch of months I was the only person committing to a particular repository — a corporate API with two consuming applications and a release cadence measured in weeks, not days. I followed Git Flow on it the way I had followed Git Flow on every project for years. Cut a feature branch from develop. Open a PR into develop. Merge. Eventually cut a release branch. Tag. Merge release back into main. Merge main back into develop to keep them in sync. Close the loop.

Somewhere around month four, I caught myself doing the merge-back from main into develop and realized I could not name a single thing it had bought me that day. It was just a step. I did it because that is what the methodology said to do. There were no parallel feature branches racing toward the same release. There was no other developer whose work needed to be reconciled. There was no one I was protecting from anyone, including myself. The ceremony was still happening. The reason for the ceremony had quietly left the room.

That is the moment this article is actually about. Not branching strategy. The much smaller, more uncomfortable moment when a habit you championed becomes visible as a habit.

Driessen Already Said It

The thing that made the moment land harder is that Vincent Driessen — who wrote "A successful Git branching model" in 2010 and gave the industry the diagram half of us spent a decade implementing — had already added a note to the top of his original post:

"If your team is doing continuous delivery of software, I would suggest adopting a much simpler workflow instead of trying to shoehorn git-flow into your team."

The author of the most influential branching article in the history of Git, walking back his own methodology in his own house. That is not a footnote. That is a small public reckoning, and it had been sitting at the top of the original post for years before I let it apply to me.

I had read that note. I had probably linked to that note. And I was still, on a Friday afternoon, alone in a repo, dutifully merging main back into develop because that was step seven.

Why Git Flow Worked Before

I am not here to bury Git Flow. It worked. On the right team, on the right kind of project, it still works.

The team it was designed for is doing real work in parallel. Multiple developers. Overlapping features. Hotfixes that cannot wait for the current release. A QA process that needs a stable target while the next thing is already underway. Released software in the hands of customers who paid money and expect upgrade paths and rollback options. In that world, the develop branch is not ceremony — it is a real coordination surface. The release branch is not bureaucracy — it is a frozen target everyone can stabilize against. The merge-back from main is not busywork — it is the only honest way to make sure the hotfix that went to production is also reflected in what everyone is building next.

Git Flow is a good answer to those problems. The trouble starts when you carry the answer into a room where those problems are not present.

A Different Room

The repo I was maintaining alone was not that room.

It was a corporate API. Two internal applications consumed it. The team was me. The release model was: when a feature is ready and tested, ship it. There was no commercial customer waiting on a downloadable artifact. There was no version of the software running in twelve different production environments at twelve different patch levels. There was no parallel feature stream from other developers that the develop branch needed to reconcile.

And the API itself had a hard rule that quietly removed the last argument for ceremony: every published version was immutable. New behavior went into a new version. Breaking changes were impossible by construction, because the old version kept answering the old way for as long as anyone was calling it. There was no scenario in which I needed to roll back a hotfix across multiple historical versions, because there was no scenario in which a hotfix could change a published version at all.

The thing Git Flow was protecting me from could not happen.

When I looked up from my own repo and watched what other teams were doing — single permanent branch, short-lived feature branches, branch policies and pipelines doing the discipline that the ceremony used to do — I was not looking at a fad. I was looking at the answer to a different problem than the one Git Flow was originally built for. And it was, much more honestly, the problem I actually had.

What Tooling Quietly Replaced

The other thing that had changed under the surface, over years of me not noticing, is that the platform itself started doing the work the branches used to do.

Branch policies on a single trunk now enforce code review, required reviewers, and required status checks before anything merges. Build pipelines run on every PR and refuse to let a red build land. Deploy pipelines automate the path from a green merge to a tagged build to a deployed environment. Versioning increments on every successful build, so the question "what is in production?" answers itself by reading a tag, not by walking a branch graph.

None of this is exotic. It is the default behavior of GitHub, Azure DevOps, and GitLab now. A lot of what Git Flow used branches to express — this code is reviewed, this code is the release candidate, this code is what shipped — is now expressed by the pipeline, the policy, and the tag. The branches were a way to make those facts visible when there was no other place to put them. There is now another place to put them.

So when I removed the develop branch and the release branch from my workflow, I was not removing discipline. I was removing the artifact I had been using to enact the discipline, because the discipline now lived somewhere more reliable.

Where I Landed

For the API repo, I now work off a single permanent branch. Short-lived feature branches when I am working on something non-trivial, direct commits when the change is small and obvious, branch policies that require a green build, and a pipeline that tags and deploys on merge. New API behavior gets a new versioned route. Old versions stay frozen until telemetry says no one is calling them anymore.

The whole thing fits in my head. I can tell what is in production by reading a tag. I no longer have a develop branch silently drifting from main between release cycles. I no longer have temporary branches with weaker policies than the permanent ones, which was always one of Git Flow's quieter failures in practice.

For other repos — the ones with three other developers, a real release cadence, customers on multiple versions, a QA team that needs a stable target — I would not necessarily make the same call. Those repos may genuinely need the structure Git Flow gives. The point is not that Git Flow is wrong. The point is that I am not on those projects right now, and pretending I am has been costing me time for years.

What This Was Actually About

The branching strategy is the surface. The thing underneath is harder, and it is the thing I want to be honest about.

It is uncomfortable to back away from a process you have advocated for. There are people I taught Git Flow to. There are diagrams I drew on whiteboards. There is a version of professional identity that gets quietly bound up in "this is how I do version control," and unbinding it feels like admitting something — about the years before, about the projects where the ceremony was already not paying off and I did not look. Driessen's note at the top of his own post is, I now suspect, the same admission written down by someone with much more on the line.

The worth of a process is not what it was worth on the project where you learned it. It is what it is worth on the project in front of you, this week, this team, this codebase. Git Flow was a good answer to a real problem. It is still a good answer to that problem. It is not, anymore, an answer to mine.

The next process I am quietly defending right now will probably need the same honest look in a few years. I would like to be faster, next time, about giving it.

Explore More