Git Good
Git Good
Git Good
The Workflow Wars
S2 E2618m · Apr 05, 2026
Vincent Driessen's 2010 blog post "A Successful Git Branching Model" sparked a decade-long debate: is Git Flow's rigid five-branch structure a blueprint for success or an overcomplicated relic that stifles modern development teams?

The Workflow Wars

The Blog Post That Started a War

This is episode twenty-six of Git Good. In the last few episodes, we watched developers hit the wall, drown in error messages, and endure the pain of migrating to Git from tools they already understood. All of that assumed something that turns out not to be obvious at all. Once you have Git, once your team has survived the migration and your developers have memorized the sacred incantation of add, commit, push, a question appears that Git itself refuses to answer. How should you use it?

In January of two thousand ten, a Dutch software engineer named Vincent Driessen published a blog post on his personal site. Driessen was based in the Netherlands, had a master's degree in computer science from Radboud University in Nijmegen, and described himself as an API designer. He built developer tools. He thought carefully about workflows. And he had been working on a branching model for his own projects, a set of rules about which branches to create, when to create them, and how they should flow into each other.

The blog post was titled "A Successful Git Branching Model." It came with a diagram. The diagram was beautiful. It showed five types of branches flowing into each other like tributaries of a river, color-coded, with arrows showing the direction of merges. At the top sat the master branch, holding only production-ready code. Below it ran a parallel branch called develop, the trunk of daily work. Feature branches sprouted off develop and merged back when finished. Release branches split off develop when a version was nearly ready, received only bug fixes, and merged into both master and develop when done. Hotfix branches came off master for emergencies and merged back into both master and develop so the fix reached everywhere.

It was meticulous. It was complete. It answered a question that thousands of teams were asking, because Git had given them the power to branch freely but no guidance on how to organize those branches. Driessen even built a command-line tool called git-flow that automated the whole thing. Type git flow feature start and it would create the branch, name it correctly, base it off develop. Type git flow release start and it would create the release branch with the right prefix. The tool enforced the model so you did not have to remember the rules.

Within a year, the blog post had been read millions of times. Teams adopted it wholesale. Atlassian documented it. Stack Overflow questions referenced it. Hiring managers listed it as a requirement. The diagram became so famous that people who had never read the original post could describe the model from memory. Git Flow was not just a branching strategy. It was the branching strategy. For a few years, suggesting anything different felt like heresy.

The Beautiful Machine That Grinds

The trouble started when teams tried to live inside the diagram.

Git Flow was designed for a specific kind of software. Software that ships versioned releases. Software where version one point three is in production while version one point four is being developed and version one point two still needs the occasional patch. Desktop applications. Mobile apps. Libraries. Software with a release schedule, where you plan what goes into each version and ship it on a date.

But by two thousand twelve, a different kind of software was eating the world. Web applications. Services that deploy continuously, where there is no version one point three because the code in production right now is the only version that matters, and it will be different in an hour. Teams building web apps adopted Git Flow because it was the only branching model anyone had written down in a way that felt authoritative. They adopted the ceremony, the develop branch, the release branches, the hotfix branches, the merge rituals. And then they discovered what happens when you impose a release-oriented workflow on software that does not have releases.

Feature branches lived too long. A developer would start a feature, work on it for two weeks, and when they tried to merge it back into develop, the branch had diverged so far that the merge conflicts spanned dozens of files. Other features had landed in the meantime. The longer the branch lived, the worse the eventual merge. Teams found themselves spending more time resolving conflicts than writing code.

The develop branch became a bottleneck. Every feature had to merge there, and every merge was a potential source of breakage. Release branches added another layer. Fixes applied to a release branch had to be merged back into develop, and sometimes those merges conflicted with features that had landed while the release was being prepared. Hotfix branches made it worse. A production emergency created a hotfix branch off master, and that fix had to reach both master and develop, and sometimes develop had moved so far ahead that the hotfix did not apply cleanly.

Adam Ruka, a software developer, wrote a blog post in two thousand fifteen with the title "GitFlow Considered Harmful." He was blunt.

The history of a project managed using GitFlow for some time invariably starts to resemble a giant ball of spaghetti.

Ruka's complaint was specific. The no-fast-forward flag that Git Flow recommended, which preserved merge commits for every feature branch, created a tangled history that was nearly impossible to read. Developers pushed to the wrong branches. Tagging went wrong. The same fix got applied in different places. And the mistakes were not rare. They happened over and over, across different teams, in different companies, on different continents. If the same class of error keeps recurring, Ruka argued, the problem is not the developers. The problem is the system.

George Stocker, writing in March of two thousand twenty, was even more direct in a post titled "Please Stop Recommending Git Flow."

It is predicated off a predictable, long-term release cycle, not off releasing new code every few minutes or hours.

Stocker's argument cut to the heart of it. Git Flow was not wrong. It was designed for a world that most teams no longer inhabited. The mismatch was not between Git Flow and Git. It was between Git Flow and how software had changed.

Six Rules and a Chat Bot

Scott Chacon was watching all of this from inside the company that had, more than any other, shaped how developers used Git. Chacon had co-founded GitHub. He had written Pro Git, the free textbook that became the closest thing Git had to an official manual. He had spent years teaching Git to confused audiences around the world. And at GitHub, they did not use Git Flow. They used something much simpler.

In August of two thousand eleven, Chacon published a blog post describing what he called GitHub Flow. Where Driessen's post came with a five-color diagram and a command-line tool, Chacon's came with six rules. One, anything in the master branch is deployable. Two, create descriptively named branches off master for new work. Three, commit locally and regularly push to the same branch on the server. Four, open a pull request when you need feedback or think the work is ready. Five, after someone reviews and approves, merge into master. Six, deploy immediately after merging.

That was it. No develop branch. No release branches. No hotfix branches. No special naming conventions. No command-line tool to enforce the workflow. Just master, feature branches, pull requests, and deployment.

Git itself is fairly complex to understand. Making the workflow that you use with it more complex than necessary is simply adding more mental overhead to everybody's day.

Chacon backed this up with evidence from GitHub's own practice. At the time, GitHub deployed to production multiple times a day. Not weekly. Not biweekly. Multiple times daily. The deployment logs showed roughly twenty-four deployments in a single day, performed by six different employees, including people from the support and design teams, not just engineers. They deployed through a chat bot called Hubot that lived in their Campfire chat room. A designer could finish a change, open a pull request, get it reviewed, merge it, and deploy it to production in the time it would take a Git Flow team to figure out which branch to start from.

The simplicity was the point. GitHub Flow removed the ceremony. There was no git flow init, no choosing between feature and release and hotfix branch types. You just created a branch, did the work, opened a pull request, and merged it. The pull request was the workflow. It was where code review happened, where discussion happened, where the decision to ship was made. Everything else was overhead that GitHub had stripped away because they did not need it.

GitHub Flow won the web. It became the default workflow for most web development teams, for open source projects, for startups. Not because it was theoretically superior. Because it was simple enough that a new hire could learn it in an afternoon and start contributing the next morning.

Everyone Commits to Main

But if GitHub Flow was the rebellion against Git Flow's complexity, trunk-based development was the revolution that questioned whether you needed long-lived branches at all.

The idea was not new. It predated Git entirely, going back to the days when teams worked on a single mainline in CVS or Subversion. But in the two thousand tens, a paper and a set of practices from Google made it impossible to ignore. In two thousand sixteen, Google engineers Rachel Potvin and Josh Levenberg published a paper with a title that sounded like a provocation: "Why Google Stores Billions of Lines of Code in a Single Repository."

The numbers were staggering. Twenty-five thousand software engineers working in a single repository. Sixteen thousand human changes per day, plus another twenty-four thousand automated changes. Not sixteen thousand changes across many repositories. Sixteen thousand changes landing on one trunk, every day.

Google did not use Git for this. They had built their own version control system called Piper, running on Spanner, distributed across ten data centers. Developers could subset the repository, checking out only the parts they needed. Code review happened through a tool called Critique. And the key practice that made the whole thing work was that almost everyone committed directly to the trunk. Not to feature branches that lived for days or weeks. To the trunk. To the main line. The single source of truth.

The trick was feature flags. Instead of isolating new work on a branch, Google developers wrote the new code behind a toggle. Old behavior and new behavior existed side by side in the same codebase, and a configuration flag determined which one ran. When the new feature was ready, you flipped the flag. When you were confident it worked, you removed the old code. No merge. No branch. No divergence.

Facebook did something similar, though they used Mercurial instead of a custom tool, and they deployed the main web application twice a day on weekdays. Their engineers worked on the same trunk, reviewed code before it landed, and relied on automated testing and feature flags rather than branches to manage work in progress.

Trunk-based development terrified small teams. The idea of everyone committing to main, with no feature branches to isolate half-finished work, felt like driving without a seatbelt. What if someone pushed broken code? What if two people edited the same file at the same time? What if a feature took three weeks and you could not hide it on a branch?

The advocates had answers. You push broken code less often when you integrate frequently, because your changes are small. Two people editing the same file resolve the conflict immediately, while the context is fresh, instead of three weeks later when neither of them remembers what they were doing. A three-week feature gets broken into small increments behind a flag, each one reviewed and tested, each one landing on the trunk as a working, shippable change.

The discipline required was real. Trunk-based development needed fast builds, comprehensive automated tests, and a culture where breaking the build was treated as serious. Google invested enormously in build infrastructure. Not every team could do that. But the teams that could reported faster delivery, fewer integration problems, and the complete elimination of merge hell.

The Middle Ground Nobody Talks About

GitLab, watching this war from the sidelines, proposed its own workflow. GitLab Flow was not as famous as Git Flow or GitHub Flow. It did not have a founding blog post or a charismatic author. But it addressed a real gap.

GitHub Flow assumed that merging to master meant deploying to production. For teams with staging environments, pre-production testing requirements, or regulatory constraints, that assumption did not hold. You could not deploy every merge immediately. But you also did not need Git Flow's five branch types and merge rituals.

GitLab Flow kept GitHub Flow's simplicity. One main branch. Feature branches. Pull requests, which GitLab calls merge requests. But it added environment branches. A staging branch that code flowed through before production. A pre-production branch for final validation. Each environment got its own branch, and code promoted from one to the next in order. You could add as many stages as your deployment process required.

It was pragmatic. It was boring. It solved a real problem for teams whose deployment process had more than one step but who did not want to maintain five branch types. It never became a movement because it never had a villain. Git Flow was the thing to rebel against. GitLab Flow was the thing that quietly worked.

The Creator Walks It Back

On March fifth, two thousand twenty, ten years and two months after the original post, Vincent Driessen added a note to the top of "A Successful Git Branching Model." He called it a note of reflection.

Web apps are typically continuously delivered, not rolled back, and you do not have to support multiple versions of the software running in the wild. This is not the class of software that I had in mind when I wrote the blog post ten years ago. If your team is doing continuous delivery of software, I would suggest to adopt a much simpler workflow, like GitHub Flow, instead of trying to shoehorn git-flow into your team.

There it was. The creator of Git Flow, the man whose diagram hung on whiteboards in offices around the world, telling people to stop using his model if it did not fit. Not because it was wrong. Because the world had changed.

Driessen was careful. He did not disown Git Flow entirely. He added that for software with explicit version numbers, for mobile apps and desktop applications and libraries, the model still made sense. And he closed with a line that should be printed on every team's wall: "Always remember that panaceas do not exist. Consider your own context."

That last sentence is the one that matters. Not because it is profound, but because it is the thing that every workflow war forgets. The right workflow depends on what you are building, how often you ship, how large your team is, and how much you trust each other. A team of three building a web app needs something different from a team of thirty maintaining a desktop application with five supported versions. A team with a mature test suite and fast builds can afford trunk-based development. A team without those things will break production every other day.

The workflow wars were never about branches. They were about culture. How much ceremony does your team need? How much do you trust your colleagues to ship without breaking things? How much do you value clean history versus fast delivery? These are human questions, not technical ones. Git provides the mechanism. The arguments are about values.

The Accelerant

There is one more thread to pull. In the last few years, something has changed the equation. AI-generated code is landing in repositories at an accelerating rate. Developers are committing more often, in smaller increments, sometimes without fully understanding what the code does. A developer using an AI assistant might open five pull requests in a day where they used to open one.

This has implications for every workflow. If commits are smaller and more frequent, trunk-based development's core assumption, that small changes integrating often is safer than big changes integrating rarely, gets stronger. The argument for feature branches, that they isolate work for careful review, also gets stronger, because code you did not fully write deserves more scrutiny, not less.

The workflow wars are not over. They have just gained a new dimension. The teams that figure out how to review AI-assisted code quickly and confidently will ship faster. The teams that treat AI-generated code the same as human-written code will discover, eventually, that trust is not the same as speed. But that is a story for later in this season, when we look directly at what AI is doing to the Git workflow.

For now, the lesson from the workflow wars is the same lesson Vincent Driessen arrived at after a decade. There is no single right answer. There is only your team, your software, your release cadence, and the honest question of how much complexity you can sustain without drowning. Driessen drew a beautiful diagram. Chacon wrote six simple rules. Google built an empire on a single trunk. And somewhere right now, a team is arguing about which approach is correct, as if the tool cares.

That was episode twenty-six of Git Good. Next time, we step from branching into the ritual that branching made possible, the thing that happens after you push your branch and before it lands on main. The code review. The pull request. The place where workflows become personal.

Git flow init versus git switch with the create flag and a branch name. That is the ceremony gap in one line. Git flow init asks you a series of questions. What should the production branch be called? What should the development branch be called? What prefixes do you want for feature branches, release branches, hotfix branches? It sets up a structure before you have written a single line of code. Git switch with the create flag and a branch name just creates a branch. No questions. No ceremony. No opinions about how your branches should be organized. Both are valid. One assumes you know your process. The other assumes you will figure it out as you go. The choice says more about your team than about your code.