Every other tool in a developer's life has opinions. The programming language tells you how to structure your logic. The web framework tells you where to put your files. The database tells you how to organize your data. Git tells you almost nothing. Here is branching. Here is merging. Here is a way to record history. Good luck.
This is, by design, a tool that refuses to tell you how to work. You can branch for every feature or never branch at all. You can require code review on every change or let anyone push directly to production. You can organize your code in one repository or a thousand. Git does not care. It gives you primitives and walks away.
And so teams argue. They argue in stand-up meetings and in Slack threads that stretch for days. They argue during onboarding when the new hire asks why the branch is called that. They argue during postmortems when a deployment goes wrong and someone says, "if we had been using trunk-based development this would not have happened." They argue because Git left a vacuum where an opinion should be, and humans cannot tolerate a vacuum.
Episode seven told you about the three major workflows: Git Flow, GitHub Flow, and trunk-based development. What those workflows are, how they work, who invented them. This episode is about what happens when real teams try to adopt them. The arguments, the compromises, the gap between the diagram on the wiki and what people actually do on a Tuesday afternoon when the build is broken and the release is tomorrow.
Before we talk about how other teams use Git, there is a story worth telling about how the Git project uses Git. Because the tool's own maintainer, Junio Hamano, runs one of the most disciplined workflows in open source, and almost nobody outside the kernel community knows about it.
The Git project has four permanent branches, arranged in a hierarchy of stability. At the bottom is a branch called pu, short for proposed updates. This is the wild frontier. Topic branches from contributors get merged here to see how they interact with each other. Nothing in pu is promised. It can be rewound and rebuilt at any time.
You must never base any work on such a branch.
Hamano's warning was clear. People who run pu know they are living dangerously.
Above pu sits next. This is the testing ground. Topics that survive pu get merged into next, where a wider group of developers run them and report problems. Above next sits master, which tracks what will go into the next release. And above master sits maint, which gets only bug fixes for the current stable version. Fixes flow upward: a bug fix committed to maint gets merged into master, then into next, then into pu. Features flow downward: a new feature starts in pu, graduates to next if it looks stable, then to master when the maintainer is confident.
Do not merge to downstream except with a good reason. Your branch no longer merges to upstream cleanly, or upstream API changes affect your branch. Otherwise, leave it alone.
Hamano was adamant about that discipline. He has run this workflow for twenty years. He applies patches from the mailing list as topic branches, merges them through the graduation pipeline, and periodically rewinds the throw-away branches to keep them clean. After every release, he rebuilds next from scratch, re-merging only the topics that survived. It is methodical, patient, and deeply unfashionable. No pull requests. No web interface. Patches arrive by email, get reviewed on the mailing list, and graduate through branches by the maintainer's judgment.
The reason this matters for our story is that Hamano's workflow predates every popular branching model. Git Flow, GitHub Flow, trunk-based development, all of them came later. The tool's own development process is a four-tier graduation system that nobody copied because it requires a single dedicated maintainer with extraordinary discipline. It works brilliantly for Git. It would be a disaster for a team of twenty building a web application.
Episode seven covered Git Flow, GitHub Flow, and trunk-based development in detail. But it skipped one workflow that sits in an interesting middle ground, and that is GitLab Flow.
GitLab Flow was born from a specific frustration. Git Flow had too many long-lived branches. GitHub Flow assumed you could deploy every merge immediately. But what about teams that could not deploy on demand? Teams with staging environments, compliance requirements, release windows? Teams where the code had to pass through three environments before reaching a customer?
GitLab Flow introduced environment branches. Instead of Git Flow's develop and release branches, you have branches that map directly to your deployment environments. Main tracks your staging environment. A pre-production branch tracks your pre-production environment. A production branch tracks what is actually running in production. Code flows downstream: from main to pre-production to production. Never upstream. Every commit on the production branch has already passed through every environment above it.
The elegance is in the mapping. Instead of abstract branch names that represent stages in a release process, your branches are mirrors of real, running systems. When you look at the production branch, you see exactly what your customers are running. When you look at pre-production, you see what is being tested right now. The branches are not a model of your workflow. They are a model of your infrastructure.
This matters because most of the arguments teams have about workflows are really arguments about deployment. When can we ship? How do we know this is safe? What happens if something breaks in production and we need to fix it while new features are in progress? GitLab Flow answers these questions by making the deployment pipeline visible in the branch structure itself.
But GitLab Flow has its own assumption: that your environments are linear. Code flows from staging to pre-production to production, in order, always. If your deployment is more complex than that, if you have regional deployments or canary releases or environments that do not form a neat line, the model starts to strain.
The workflow argument is about branches. But there is an even bigger argument about something more fundamental: should all your code live in one repository, or should each project get its own?
Google operates this way at an extraordinary scale, storing billions of lines of code in a single repository using custom infrastructure that is not Git. We will explore exactly how they do that, and how Facebook and Microsoft bent their tools to breaking point, in a later episode. For now, the philosophy is what matters.
The monorepo, as this approach is called, has real advantages. When all your code is in one place, refactoring across project boundaries is a single commit. If you change an API, you can update every caller at the same time. There is no versioning hell where team A depends on version two of a library while team B needs version three. Everyone sees the same code at the same time. Dependencies are visible, not hidden in lock files across fifty repositories.
The multirepo camp argues the opposite. Each team owns its own repository. Each service has its own release cycle. Teams can choose their own tools, their own CI pipeline, their own deployment cadence. Autonomy. Independence. Clear boundaries.
But multirepos have their own nightmare: the diamond dependency problem. Project A depends on libraries B and C. Libraries B and C both depend on library D. But B needs version one of D, and C needs version two. Now project A cannot build. In a monorepo, this cannot happen because there is only one version of everything. In a multirepo world, it happens constantly, and the fix usually involves someone spending a week updating dependencies across six repositories in the right order.
The monorepo versus multirepo debate is really a debate about trust and autonomy. Monorepos say: we are one team, we share everything, we move together. Multirepos say: we are many teams, we own our domains, we move independently. Neither is wrong. The choice reveals what the organization values.
Here is something the workflow blog posts never mention: team size changes everything.
A team of three does not need a branching model. They talk to each other constantly. They know what everyone is working on. Merge conflicts are rare because three people rarely touch the same file at the same time. They push to main. They deploy when it looks good. Any process beyond that is overhead.
A team of thirty needs structure. Not because the individuals are less capable, but because communication does not scale linearly. Three people have three communication paths. Thirty people have four hundred and thirty-five. You cannot know what everyone is working on. You cannot coordinate merges by shouting across the room. You need branches. You need pull requests. You need someone, or something, standing between a developer's work and the shared codebase, checking that the change will not break what someone else is building.
A team of three hundred needs a workflow enforced by tooling, not by convention. At that scale, the workflow is not a suggestion on a wiki page. It is encoded in CI pipelines, branch protection rules, automated test gates, and deployment scripts. The humans involved cannot keep the process in their heads. The machines have to enforce it.
This is why the "which workflow is best" arguments are mostly pointless. A workflow that works beautifully for a startup of eight will collapse at a company of eight hundred. Git Flow's overhead is absurd for three developers but sensible for three hundred working on software with quarterly releases and long-term support branches. GitHub Flow's simplicity is perfect for a team that deploys continuously but incomplete for a team that cannot deploy until a compliance review is finished.
The workflow follows the team, not the other way around. And when a team grows, or shrinks, or changes how it ships software, the workflow has to change with it. The teams that suffer most are the ones that picked a workflow when they were small and never revisited it as they grew, or the ones that imposed a heavyweight process on a five-person team because "that is how we did it at my last company."
Even the person who started the biggest workflow argument in Git history came around to this view. In two thousand twenty, a full decade after his blog post launched a thousand team debates, Vincent Driessen added a note of reflection to the top of the original Git Flow article.
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.
The person who defined the most widely adopted branching model in history told people to stop using it if it did not fit. That takes a rare kind of intellectual honesty. And it reinforces the point: there is no universal workflow. There is only the one that matches how your team actually ships software.
Every team has two workflows. The one written on the wiki, and the one people actually use.
The wiki says: create a feature branch from develop, name it with the ticket number, open a pull request when ready, get two approvals, squash merge to develop. Clean. Orderly. The diagram looks beautiful.
The terminal tells a different story. Someone pushed directly to develop because the fix was one line and it seemed silly to open a pull request for it. Someone has a feature branch that has been open for six weeks because the feature got deprioritized but never formally abandoned. Someone merged without the required approvals because it was Friday at five and the other reviewers had already left. Someone rebased when the team convention is merge, or merged when the convention is rebase, and now the history looks like a plate of spaghetti.
This gap exists on every team. The interesting question is not whether it exists, but how wide it is. On healthy teams, the gap is narrow. The workflow is simple enough that following it is easier than working around it. Exceptions happen but they are rare and acknowledged. On unhealthy teams, the gap is a canyon. The official workflow is so cumbersome that people routinely circumvent it, and the resulting mess is worse than having no workflow at all.
Code review is where this tension shows up most visibly. Research consistently finds that smaller pull requests get reviewed faster and more thoroughly. Two hundred lines of changes get meaningful feedback. Two thousand lines get a rubber stamp. The wiki might say "all changes require review." In practice, a three-thousand-line pull request gets an approval with a one-word comment because the reviewer does not have two hours to actually read it. The process was followed. The purpose of the process was not.
The teams that navigate this well are the ones that treat their workflow as a living thing. They retrospect on it. They notice when people are working around the process and ask why. They adjust. They accept that the workflow they chose six months ago might not be the right one today. They understand that the point of the workflow is not the workflow. The point is shipping good software without making each other miserable.
Git ships with exactly three commands for understanding where you stand in whatever workflow your team has chosen. The command git branch with the flag dash-dash-merged shows you which branches have already been folded back into your current branch. Everything listed there is finished work, safe to clean up. The opposite, git branch dash-dash-no-merged, shows what is still in flight. Unfinished features. Abandoned experiments. That branch from six weeks ago that nobody remembers starting.
And git branch with the flag dash v dash v shows every branch alongside the last commit and, crucially, whether it is tracking a remote branch and how far ahead or behind it has drifted. This is the command that tells you the truth about your workflow. Not the truth the wiki describes, but the truth about what is actually happening across the team right now. Eight branches. Three of them weeks ahead of the remote. Two with no remote tracking at all. One that has diverged from its upstream in both directions.
These are housekeeping commands. Workflow hygiene. Nobody writes blog posts about them. But they are the commands that keep a team's workflow from slowly decaying into chaos. The branches that pile up, the stale pull requests, the forgotten experiments, these are the entropy that every workflow battles constantly.
And that is really the story of how teams use Git. Not the clean diagrams. Not the blog posts with color-coded branch flows. The real story is messier and more human than that. It is a team of people trying to coordinate their work using a tool that gives them absolute freedom and no guidance. The workflows they adopt are not technical solutions. They are social contracts. Agreements about how to treat each other's code, how much review is enough, when to ask permission and when to just push.
Git provides the primitives. Branches, merges, remotes, history. Everything else, the conventions, the ceremonies, the arguments about rebase versus merge that turn into arguments about what kind of team you want to be, all of that comes from the humans. The tool is neutral. Perfectly, maddeningly, productively neutral.
Junio Hamano runs a four-tier graduation system for the Git project because the project has one maintainer who reviews everything personally. Google commits to a single branch tens of thousands of times a day because they built infrastructure to make that safe. A five-person startup pushes to main with no pull requests because everyone trusts each other and talks constantly. A bank with two hundred developers uses Git Flow with mandatory approvals because the regulator requires an audit trail. All of them are using Git correctly. None of them are using it the same way.
The workflow that works is the one the team actually follows. Not the one they aspire to follow. Not the one they read about on a blog. The one they do, on a Tuesday afternoon, when the build is broken and the release is tomorrow and someone needs to get a fix into production right now. That is when you find out what your workflow really is.
Next episode, we discover what happens when things go wrong, and why Git almost never actually loses your work. Even when you think it has.
git branch dash dash merged. The command that tells you what is finished. Everything it lists has already been folded back into your current branch, safe to delete. Its opposite, git branch dash dash no-merged, is more revealing. That is the list of everything still in flight, everything abandoned, everything someone started six weeks ago and never came back to. Together, they are the closest Git comes to having an opinion about your workflow: clean up after yourself.