Distributed Workflows
Git's distributed model does not prescribe how a team should collaborate. It supports several shapes of workflow, each tuned to a different team size, release cadence, and tolerance for ceremony. The question is not which one is right. The question is which one fits the team and the product you actually have.
Why workflows have shapes
A workflow is the social contract sitting on top of Git's plumbing. Git itself does not know whether your team has three people or three thousand, whether you ship to production every hour or every quarter, or whether contributors have direct push access to the main repository. Those facts shape how branches are created, who is allowed to merge what, and how often code is integrated.
You can break the choices down along a handful of axes: how many long-lived branches there are, how trust flows between contributors and maintainers, how short feature branches stay before integration, and what role tags and release branches play. The workflows that follow are recognizable named patterns, but they are points on a continuum, not laws. Most real teams adopt a workflow as a starting point and then bend it.
A team-level agreement about which repositories are canonical, who pushes to them, how branches are organized, and how changes move from a contributor's working tree into shipped code. The pattern lives in conventions and tooling, not in Git's command surface.
Centralized workflow
Everyone clones a single central repository and pushes to it directly. The hub is the project; the clones are nodes that consume and contribute to it. There is usually one long-lived branch, main, and people coordinate by pulling before they push.
Pro Git describes the first-push, second-fetch-then-merge cycle as the everyday rhythm. The first developer to push wins; the second developer's push is rejected as a non-fast-forward and they must fetch and merge before pushing again. That single rule keeps history coherent without ceremony.
git clone https://example.com/project.git
cd project
# edit, stage, commit locally
git pull --rebase origin main
git push origin main
This shape works well for small teams, internal tools, and groups arriving from Subversion who want to keep their habits while gaining Git's local power. It scales further than it looks: Pro Git points out that Git's branching is strong enough that hundreds of developers can still work in a centralized shape if they use branches well. The limit is social, not technical.
Integration-manager workflow
Contributors do not have push access to the canonical repository. Each one pushes to their own public clone (a fork) and asks a maintainer, the integration manager, to pull their work in. The maintainer reviews, tests, and merges. This is the shape that hosted services like GitHub and GitLab built their pull-request and merge-request flows around.
The forking step is what makes this scale. Anyone in the world can copy the repository, work on it on their own schedule, and propose changes without the maintainer pre-trusting them. The maintainer pulls only the work they want, in the order they want, and never gives up the right to say no. Most large open-source projects on hosted platforms are running some flavor of this pattern.
git clone https://github.com/me/project.git
cd project
git remote add upstream https://github.com/maintainer/project.git
git checkout -b fix-typo
# edit, commit
git push -u origin fix-typo
# then open a pull request from me:fix-typo to maintainer:main
This is the same shape as forks and pull requests, generalized. The pull request is the integration manager's review and merge interface; the workflow is the policy around it.
Dictator and lieutenants
For projects that are too large for one integration manager, Pro Git describes a pyramid. Developers work on topic branches and rebase them onto the reference repository's master. Lieutenants own subsystems and merge developers' work into their own subsystem branch. A benevolent dictator (the project lead, or a small core team) merges from lieutenants into the official master and pushes to the reference repository, which everyone rebases against. The Linux kernel is the canonical example; subsystem maintainers play the lieutenant role, and Linus Torvalds plays the dictator role.
Two things make this work. First, every developer rebases onto the reference repository's master before publishing, so the dictator and lieutenants never have to rewrite anyone else's history. Second, the lieutenant layer absorbs the review and integration work that would otherwise overload one person. Pro Git's framing is direct: this scales to projects with hundreds or thousands of collaborators precisely because the integration cost is delegated.
Trunk-based development
All developers integrate to a single trunk (often main) on short cycles. Feature branches, if they exist at all, live for hours to a day or two, not weeks. Unfinished features are merged anyway, hidden behind feature flags, and turned on only when ready. Martin Fowler's branching-patterns essay calls this out as the modern reading of continuous integration: small, frequent merges to one shared branch, not long-lived feature branches that integrate once.
The cost of this discipline is the feature-flag machinery and the cultural commitment to keeping main releasable at all times. The payoff is what every continuous-delivery shop wants: integration pain is paid in small, daily installments instead of in massive merge marathons. Google, Meta, and similar large web-scale companies run versions of this model on top of monorepos.
git switch main
git pull --rebase
git switch -c quick-fix
# small, focused change
git commit -am "Fix off-by-one in pagination"
git push -u origin quick-fix
# open PR; merge same day; delete branch
GitFlow
Vincent Driessen's 2010 essay A successful Git branching model codified what came to be called GitFlow. Two long-lived branches: main for production-ready code and develop for integration. Three families of short-lived supporting branches: feature/* branch from develop, release/* branch from develop when a release is about to ship, and hotfix/* branch directly from main when something on production is broken.
It is the heaviest of the popular models. Driessen himself, in a 2020 update appended to the original post, says that GitFlow was designed for a world of explicit versioned releases and "is hardly the kind of software that is being developed these days." He recommends a simpler GitHub-Flow-style approach for continuous-delivery web apps and reserves GitFlow for software with multiple supported versions in production. Treat that note as part of the canonical source: even the author no longer recommends GitFlow as a default.
GitFlow's reputation lingers, but its author has explicitly stepped back from recommending it for continuous delivery. If a team picks GitFlow today, they should be able to name the multi-version reason they need it.
GitHub Flow
The lightweight cousin of trunk-based development, popularized by GitHub. There is one long-lived branch, main, and it is always deployable. Work happens on short feature branches, opens as a pull request, gets reviewed, and merges back to main. Deployment happens from main. There is no develop branch, no release branches, no separate hotfix family.
git switch main
git pull
git switch -c add-search-filter
# work, commit, push
git push -u origin add-search-filter
# open PR; review; merge; deploy from main
The implicit contract is that main is good enough to ship at any commit. Continuous integration on every pull request enforces it. The simplicity is the point: there is less to remember, less to teach a new hire, and less ceremony between "I have an idea" and "it is in production."
GitLab Flow
GitLab's documented variant takes GitHub Flow and adds environment-tracking branches. Code lands on main from short feature branches as usual. From there, it can be promoted to a pre-production branch (or staging) and then to a production branch, each branch corresponding to a deployment target. Merges flow downstream only: never from production back into main. For open-source bridges, GitLab also describes an upstream-first variant where fixes go to the upstream project first and then back down to your own fork.
The shape sits between GitHub Flow's single-branch simplicity and GitFlow's heavier release branches. The cost is one extra branch per environment. The benefit is that "what is in production right now?" is answerable by looking at one branch, not by hunting for a tag.
Release-branch workflow
Some products ship versioned releases that must be supported in parallel. A bug found in v2.1 may need a patch on v2.1, not just on the latest main. The release-branch workflow handles this by cutting a long-lived release/v2.1 branch at the v2.1 release commit and cherry-picking hotfixes onto it (see cherry-pick and patch flow).
git switch main
git switch -c release/v2.1
git push -u origin release/v2.1
# later, a fix lands on main as commit abc1234
git switch release/v2.1
git cherry-pick abc1234
git tag v2.1.4
git push origin release/v2.1 v2.1.4
This is mandatory for shipped software with paying customers on older versions: operating systems, databases, programming languages, libraries with long support windows. It is overkill for an app that only ever has one live version in production.
Choosing one
There is no universal answer, but there are useful defaults. The table below maps team and product shape to a starting workflow. Treat it as a recommendation, not a verdict.
| Team / product shape | Default workflow | Why |
|---|---|---|
| Small internal team, single deployable web app, continuous delivery | GitHub Flow or trunk-based | Minimum ceremony; main is always shippable; integration is daily. |
| Large web-scale company, monorepo, frequent releases | Trunk-based with feature flags | Short-lived branches scale to thousands of engineers; flags decouple merge from release. |
| Versioned product with multiple supported releases in production | Release-branch workflow (often layered on GitHub Flow for trunk) | Long-lived release branches let you patch old versions without disturbing main. |
| Open-source project, public contributors, hosted on GitHub or GitLab | Integration-manager (fork-and-PR) | Contributors do not need push access; maintainers stay in control of merges. |
| Massive project with subsystem maintainers (Linux-kernel scale) | Dictator and lieutenants | Hierarchy distributes integration cost across many trusted maintainers. |
| Small team migrating from Subversion | Centralized | Familiar shape; introduces local commits and branches without changing the social structure. |
| Single deployable app, but with staging and production environments to manage | GitLab Flow | Environment branches make "what is in each environment?" trivially answerable. |
If you compare workflows by branch lifetime, scale, and complexity, the spread is clearer: trunk-based and GitHub Flow have the shortest branch lifetimes and lowest complexity; GitFlow has the longest and highest; dictator-and-lieutenants scales the furthest in headcount; release-branch is the only one that comfortably maintains multiple shipped versions at once.
Common pitfalls
Mixing workflows without naming the mix. A team that says "we do GitHub Flow" but actually has a stale develop branch and several long-lived feature/* branches is doing something else, and the mismatch between the name and the practice is where confusion grows. Pick one shape, write it down in a short CONTRIBUTING file, and let exceptions be explicit.
Long-lived feature branches. Every day a branch sits unintegrated, the merge cost grows roughly with the rate of change on main. By the time a two-month branch lands, it can take longer to merge than it took to write. Cap branch age; split large work behind feature flags.
Infrequent integration disguised as CI. Running tests on a branch is not the same as integrating with what teammates are doing. Fowler points out that the term "trunk-based development" was coined partly because so many teams were using "continuous integration" to mean "we run a build on every push to my feature branch." Tests are necessary, integration is non-negotiable.
Picking GitFlow by inertia. GitFlow is well known because it was the first widely shared model with a memorable diagram. Its own author has since said it is not the right default for continuous-delivery web apps. If a team adopts it today, the choice should be deliberate: multiple supported versions in production, or another concrete reason that needs the extra branches.
Sources & further reading
-
Canonical treatment of the centralized, integration-manager, and dictator-and-lieutenants shapes. The naming of these three patterns in this page follows Pro Git's terminology.
-
Walks through centralized, feature-branch, GitFlow, and forking workflows from a team-onboarding angle. Useful when you want a side-by-side comparison written for practitioners, not theorists.
-
The Atlassian write-up of GitFlow with concrete branch-creation commands. Read alongside Driessen's 2020 update before adopting GitFlow today.
-
The original GitFlow essay. The 2020 update appended to the top is the part most teams miss: the author no longer recommends GitFlow as a default for continuous-delivery web apps.
-
GitLab's documented variant with environment-tracking branches and the upstream-first model for open-source bridges. The clearest articulation of why a single deployable workflow may still want one branch per environment.
-
A pattern-language treatment of mainline, feature branching, continuous integration, release branches, and release trains. The clearest essay on why trunk-based development is the modern reading of continuous integration.