A git branching strategy is a small decision that compounds into a cultural one. It quietly decides how often you release, how long code sits unmerged, how risky a hotfix is, and whether “long-lived feature branch” is a normal phrase or an alarm bell. It’s also a decision teams make once, then never revisit — even when the team, product, or deployment pace changes out from under it.
This post is the short version of that decision. Four common strategies — GitFlow, GitHub Flow, GitLab Flow, trunk-based development — what each actually is, what it implies, what it costs, and how to pick one now without committing to it forever.
TL;DR
- GitFlow = versioned releases + long-lived
develop+ release/hotfix branches. Right for shrink-wrapped software with explicit releases. Overkill for most web services.- GitHub Flow =
main+ short-lived feature branches + PR-triggered CI/CD. Simple, fits 80% of web teams.- GitLab Flow = GitHub Flow + per-environment branches (
pre-prod,production). Good when deploy ≠ merge.- Trunk-based development = everyone commits to
main(ormain+ ≤ 24h feature branches); features hidden behind feature flags. Highest velocity, highest feature-flag discipline.- The strategy must match how often you ship. Releasing twice a day on GitFlow is suffering; quarterly point releases on trunk-based is chaos.
Why Branching Strategy Matters
Branches are free. What isn’t free is what each branch implies — the merge it demands, the CI it triggers, the release-notes chapter, the regression surface. A long-lived branch accumulates conflicts exponentially. A “release” branch with no automation costs a whole person’s Thursday. A feature branch that skips develop and lands in main during a release freeze is a production incident waiting for a name.
The strategy is really four questions answered at once:
| Question | Possible answers |
|---|---|
| Where does main “live”? | Always-deployable head (trunk/GitHub Flow) vs an integration branch separate from releases (GitFlow). |
| How are releases cut? | Continuous (every merge) vs explicit version (tag on main) vs release branch promoted over time (GitFlow). |
| How do hotfixes work? | From main/trunk (trunk-based) vs dedicated hotfix branches (GitFlow) vs revert-and-reship (GitHub Flow). |
| How do we prevent WIP in prod? | Short branches + tight PRs (GitHub Flow) vs feature flags (trunk-based) vs release gating (GitFlow). |
Every strategy below is a coherent set of answers to those four. The wrong thing is mixing answers — calling your strategy “GitFlow” but shipping out of develop, or “trunk-based” with six-month feature branches.
Strategy 1 — GitFlow
The original (Vincent Driessen, 2010). Aimed at software with explicit numbered releases — think boxed software, mobile apps that go through store review, firmware. Structured, ceremonial, and a little over-engineered for most web services today.
Branches
| Branch | Purpose | Lifetime |
|---|---|---|
main (or master) | Production. Tagged releases only. Never committed to directly. | Permanent. |
develop | Integration branch. All completed features merge here. | Permanent. |
feature/* | One feature; branches off develop, merges back into develop. | Short-to-medium. |
release/* | Preparing a release; branched off develop, merged into both main and develop. | Short (days). |
hotfix/* | Urgent fix; branched off main, merged into both main and develop. | Very short. |
Lifecycle
- Engineer branches
feature/payment-2faoffdevelop, commits, opens PR. - PR merges back into
developwhen reviewed. - When enough features accumulate, PM declares “we’ll ship v2.3” — branch
release/2.3offdevelop. - Only bug fixes go to
release/2.3(no new features).developis free to receive features for v2.4. - When ready, merge
release/2.3→main, tagv2.3, and merge back intodevelop(to pick up bug fixes). - If prod breaks:
hotfix/2.3.1offmain→ fix → merge to bothmain(tagv2.3.1) anddevelop.
Fits when
- You ship explicit versions (mobile apps, libraries, SDKs, desktop software).
- Releases are infrequent (every few weeks or slower).
- You must support multiple versions in parallel (
v2.xandv3.xboth live). - “Release notes” is a real artifact the business cares about.
Doesn’t fit when
- You ship more than once a day.
- You have fewer than 5 engineers (the ceremony overhead isn’t worth it).
- Your product is a web service where every merge goes to production anyway.
The common mistake
Teams adopt GitFlow because they read the canonical blog post, then discover they actually ship continuously. The develop → release/* → main dance becomes pure overhead — develop ends up identical to main, and the release branch is a zero-commit ceremony. If that describes you, you want GitHub Flow, not GitFlow.
Strategy 2 — GitHub Flow
Introduced by GitHub alongside Pull Requests. Simple and the default for most web teams. Released as a short guide by Scott Chacon in 2011.
Branches
| Branch | Purpose | Lifetime |
|---|---|---|
main | Always-deployable. Every merge goes to production. | Permanent. |
feature/* (or just username/feature) | One change. Branches off main, merges back via PR. | Short (hours to days). |
That’s it. No develop, no release/*, no separate hotfix branch — a hotfix is just a small PR with fast review.
Lifecycle
- Branch off
main. - Commit.
- Open PR → CI runs → review → merge.
- Merge to
maintriggers deploy to production (via CI/CD). - Delete branch.
Fits when
- You deploy continuously (at least daily).
- Your product is a web service or API where “version” means “current state of prod”.
- Small-to-mid team (2–30 engineers).
- Feature flags (or short branches) keep WIP out of production.
Doesn’t fit when
- You release versioned software that customers install.
- You have a distinct QA stage that takes days before anything reaches prod.
- You need to maintain and patch older versions in parallel.
The common mistake
Merging to main but not automating deploy. If main is meant to be always-deployable but in practice it sits a week before being released, you’ve bought the overhead of GitHub Flow without the benefit. Either automate the deploy, or adopt a strategy with an explicit release step.
Strategy 3 — GitLab Flow
GitHub Flow + explicit environment branches. Pragmatic middle ground for teams that want continuous merging but not continuous deployment.
Branches
| Branch | Purpose | Promotion rule |
|---|---|---|
main | Integration. Merged features land here. | From PR. |
pre-production | Auto-deployed to staging. | Fast-forward from main. |
production | Auto-deployed to prod. | Fast-forward from pre-production (after QA sign-off). |
Each environment is a branch that is strictly behind the previous. To promote a change, you merge (or fast-forward) one branch into the next.
Lifecycle
- Branch off
main, commit, PR, merge tomain. mainauto-deploys to dev environment.- When QA is ready:
git merge main→pre-production→ deploys to staging. - When release manager is ready:
git merge pre-production→production→ deploys to prod.
Fits when
- You want every change integrated continuously but released on a cadence.
- You have a staging environment where code needs to “bake” before prod.
- You have auditors who want to see “what’s in pre-prod” vs “what’s in prod”.
Doesn’t fit when
- Your deploy is truly continuous; the extra branches just add overhead.
- You change environments often; each change needs the branch pipeline updated.
Strategy 4 — Trunk-Based Development
Everyone commits to a single branch — main or trunk — directly or via branches that live for less than 24 hours. Incomplete features are hidden behind feature flags. Releases are cut by tagging a commit on main.
This is how Google, Meta, and most very-high-velocity teams work. It demands the highest discipline — but delivers the fastest integration and the shortest merge conflicts.
Branches
| Branch | Purpose | Lifetime |
|---|---|---|
main (trunk) | The single source of truth. Every commit must pass CI. | Permanent. |
| Optional: short-lived feature branches | For work > 1 day, merged back fast. | ≤ 24 hours. |
release/* | Optional: cut per release for long-term support, but never worked on. | As long as the release is supported. |
Lifecycle
- Start work on
main(or branch and merge within 24h). - Hide the unfinished feature behind a flag (
if (features.newCheckout)). - Continuously push small commits to
mainthat pass CI. - When feature is done: turn the flag on — either for everyone, or progressively (canary).
- Hotfix = revert the bad commit from
mainand roll forward (not a separate branch).
Fits when
- High-velocity teams (hundreds of deploys per day).
- Feature-flag infrastructure is robust.
- CI is reliable and fast (under 10 min to green).
- Pair programming or short-turnaround review culture.
Doesn’t fit when
- You release on a schedule customers install (versioned software).
- Feature-flag discipline is weak (flags become dead code or bugs leak through).
- CI is slow or flaky — devs will start bypassing it.
The common mistake
“Trunk-based” is not “no branches” — it’s “no long branches”. Teams read “commit to main” literally, skip review, and flaky code lands in prod. The discipline that makes trunk-based work is short branches + mandatory review + reliable CI. Remove any of those and you have chaos.
Which Strategy Should You Pick?
A decision matrix using the four questions at the top of this post.
| Team characteristic | GitFlow | GitHub Flow | GitLab Flow | Trunk-based |
|---|---|---|---|---|
| Ships versioned software | ✅ Ideal | ❌ | ❌ | ⚠️ Only with release branches |
| Ships web service continuously | ❌ | ✅ Ideal | ⚠️ OK | ✅ Ideal |
| Multiple live versions supported | ✅ | ❌ | ❌ | ⚠️ Hard |
| Distinct QA stage before prod | ✅ | ⚠️ Needs staging | ✅ Natural fit | ⚠️ Via feature flag |
| Team ≥ 50 engineers | ⚠️ Heavy | ✅ | ✅ | ✅ (with discipline) |
| Team ≤ 10 engineers | ❌ Over-engineered | ✅ Ideal | ⚠️ Maybe | ⚠️ Overhead of flags |
| Feature flags in place | n/a | Nice to have | Nice to have | ✅ Required |
| Deploy automation mature | n/a | Required | Required | Required |
The 80% answer in 2026: GitHub Flow. The 15% answer: trunk-based, if the team has the feature-flag discipline. The 4%: GitLab Flow, if you really need environment branches. The 1%: GitFlow, if you ship versioned software that customers install.
Concrete Policies You Still Need, Regardless
Strategy only gets you so far. These policies do the rest of the work.
| Policy | Why it matters |
|---|---|
Branch protection on main | No force-push, no direct commits. PR required. |
| Required status checks | CI must pass; code coverage can’t drop. |
| Required reviews | At least one approval; code-owners for sensitive paths. |
| Conventional commits | feat:, fix:, chore: — enables automatic changelog + semver bump. |
| Squash-merge PRs | One commit per feature in main history — easier to revert, read, bisect. |
| PRs under ~400 lines | Reviewer attention drops fast past this. Split work into smaller PRs. |
| Linear history on main | Either squash-merge or rebase-merge. No merge commits from PRs. |
| Delete branch on merge | Keeps the branch list manageable. |
These apply to all four strategies above. They are what makes any strategy actually work.
Feature Flags Are the Missing Piece
If you’ve chosen trunk-based or GitHub Flow with fast deploys, feature flags aren’t optional — they’re the safety net that makes “merge to main” not terrifying.
At minimum you want:
- Off-by-default boolean flags — new code paths guarded; feature ships only when flag is on.
- Percentage rollouts — 1% → 10% → 50% → 100%, with ability to roll back fast.
- Kill switches — dangerous features with a flag you can flip off in 30 seconds.
- Flag lifecycle discipline — flag created with an owner and a kill date. Expired flags get removed as tech debt.
Tools: LaunchDarkly, Unleash, PostHog, GrowthBook, Flagsmith, or (for small scale) a config table in your own DB. The platform matters less than the discipline.
See the companion post: Feature Flags and Progressive Delivery in Production.
Migrating Between Strategies
You probably won’t pick the perfect strategy first try. Here’s how to move.
GitFlow → GitHub Flow
- Merge
developintomain; deletedevelop. - Wire up a CI/CD that deploys on merge to
main. - Convert existing
feature/*branches — rebase them ontomain, merge. - Delete old
release/*branches; keep tags onmaininstead.
The friction is usually cultural, not technical: engineers used to “commit to develop” hesitate to commit to a branch that ships immediately. Feature flags and small PRs solve this.
GitHub Flow → Trunk-Based
- Introduce feature flags first (spend a sprint on infrastructure).
- Shorten branch lifetimes — target 1 day maximum.
- Once branches are genuinely short, let engineers push directly (still via CI) for small changes.
Don’t skip step 1. “Trunk-based without feature flags” is just “shipping WIP”.
GitHub Flow → GitLab Flow
- Create
pre-productionandproductionbranches frommain. - Wire deploy pipelines to the two new branches instead of to
main. - Adopt the promotion ritual —
main→pre-production→production, each via fast-forward merge.
Worked Example: What Each Looks Like on the Commit Graph
Imagine the same week of work — two features, one hotfix, one release — rendered in each strategy:
| Strategy | What the graph shows |
|---|---|
| GitFlow | Feature branches off develop, merged back. release/1.2 branch cut, bug fixes on it, merged to main (tag v1.2) and back to develop. Hotfix branch from main, merged to both. Busy. |
| GitHub Flow | Several short-lived feature branches off main, all merged back via PR. No release ceremony; main is always deployable. One extra PR for the hotfix. Clean. |
| GitLab Flow | Like GitHub Flow for main. Plus two additional branches (pre-production, production) fast-forwarded from main on a schedule. Three parallel rails. |
| Trunk-based | Mostly a single straight line on main. Optional short twigs for ≤1-day work. Hotfix is just another commit on main, preceded by a revert if needed. Sparsest graph. |
The busier the graph, the more ceremony. Pick the least busy graph your release cadence can support.
Closing Checklist
Before committing to a strategy:
- Match the strategy to your release cadence — continuous deploys ≠ versioned releases.
- Branch protection on
main/production: no force-push, required reviews, required CI. - Automation wired: PR merge → CI → (auto)deploy or promotion step.
- Feature flags if you’re on trunk-based or fast GitHub Flow.
- Conventional-commit style so changelogs and bumps can be automated.
- Squash-merge or rebase-merge; no merge commits from PRs into main history.
- Branch deletion on merge.
- One documented “how we release” page so new hires don’t have to ask.
Further Reading
- Vincent Driessen — A successful Git branching model (the original GitFlow post) at nvie.com. Read it even if you won’t use GitFlow — it coined the vocabulary.
- Scott Chacon — GitHub Flow and the GitHub docs GitHub Flow guide.
- GitLab Docs — GitLab Flow. The canonical write-up of the environment-branch variant.
- trunkbaseddevelopment.com — Paul Hammant’s deep-dive site on trunk-based; includes short-lived-branch vs committed-directly patterns.
- Continuous Delivery — Humble & Farley (2010). Pre-dates most of these posts but the principles underlie all four strategies.
The best strategy is the one your team actually follows. Pick the lightest approach your release cadence can support, agree on it in writing, and revisit it in six months — because if the team has grown, the strategy probably needs to grow with it.