Lumen Night Reader
~/posts/git-branching-strategies

Git Branching Strategies

16 min read Markdown

Comparison Matrix

StrategyBranchesRelease CadenceComplexityCI/CD FitMulti-versionTeam SizeBest For
Trunk-Basedmain only, short-lived branches (<1 day)ContinuousLowAnyHigh-velocity SaaS
GitHub Flowmain + feature/*ContinuousLowSmall–MidSaaS, startups, OSS
GitLab Flowmain + feature/* + env branchesContinuous with gatesMediumMidTeams needing staging gates
Ship / Show / Askmain + feature/*ContinuousLowSmall–MidBalancing speed with review
Stacked Diffsmain + dependent chainContinuousMediumMid–LargeLarge changes, atomic reviews
Feature Flag Flowmain (trunk-based + flags)Deploy ≠ ReleaseMediumMid–LargeDecoupling deploy from release
Release Flowmain + release/*ScheduledMediumLargeDiscrete releases, Microsoft-style
Gitflowmain, develop, feature/*, release/*, hotfix/*ScheduledHighMid–LargeFormal QA, multiple versions

Legend: ✓ strong fit, ◐ partial, ✗ weak fit


Decision Guide

Deciding factor is deployment model:

If you…Use
Deploy continuously, single versionTrunk-Based or GitHub Flow
Need a staging gate before productionGitLab Flow
Want review flexibility based on riskShip / Show / Ask
Make large changes that need atomic reviewsStacked Diffs
Need to decouple deploy from releaseFeature Flag Flow
Have scheduled releases but continuous devRelease Flow
Maintain multiple supported versions, formal QAGitflow

For solo developers and small personal projects — GitHub Flow or straight trunk-based. Gitflow adds overhead with no payoff at that scale.


Strategies

Workflows use two running examples:

  • Feature: add user avatar upload
  • Bugfix: fix: avatar upload crashes on files > 5MB

1. Trunk-Based Development

Used by Google, Meta, and most high-velocity teams.

Everyone commits directly to main or merges very short-lived branches (under 1 day). Relies heavily on feature flags, CI, and automated testing.

Key branches: main

Pros:

  • Fastest integration cycle
  • Minimal merge conflicts
  • Forces small, incremental commits
  • Pairs naturally with feature flags

Cons:

  • Requires strong CI/CD pipeline
  • Broken main blocks everyone
  • Needs feature flags for work-in-progress
  • High discipline required across the team

Tooling: Feature flags (LaunchDarkly, Unleash), strong CI gates, automated rollback

Feature Workflow

# no feature branch — commit directly to main
git checkout main
git pull

# implement (small, complete change)
# ...

git add .
git commit -m "feat: add user avatar upload"
git push origin main
# CI runs, auto-deploys to production

If change is bigger than a few hours — short-lived branch:

git checkout -b avatar-upload    # lives < 1 day
# ...
git add .
git commit -m "feat: add user avatar upload"
git push origin avatar-upload
# open PR, merge same day, delete branch
gh pr create --fill && gh pr merge --squash

Bugfix Workflow

No distinction between feature and bugfix — same flow, same speed.

git checkout main
git pull

# fix directly on main (small fix)
# ...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin main
# CI runs, auto-deploys

If fix needs review:

git checkout -b fix-avatar-size     # short-lived, < 1 day
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix-avatar-size
gh pr create --fill && gh pr merge --squash
# branch deleted, deployed

2. GitHub Flow

The default for most SaaS and web teams deploying continuously.

Single main branch with short-lived feature branches. Every change goes through a PR, gets reviewed, and merges back to main. Deploy on merge.

Key branches: main, feature/*

Pros:

  • Dead simple to learn and operate
  • PR-based code review built in
  • Minimal process overhead
  • Native GitHub integration

Cons:

  • No staging gate
  • No release management
  • Single version only
  • Hotfix is just a regular PR (no dedicated path)

Tooling: GitHub PRs, CI on every push, auto-deploy on merge to main

Feature Workflow

git checkout main
git pull

# feature branch
git checkout -b feat/avatar-upload

# work...
git add .
git commit -m "feat: add user avatar upload"
git push origin feat/avatar-upload

# open PR against main
gh pr create --base main --fill

# review, CI passes, merge
gh pr merge --squash

# deploy happens automatically on merge to main

Hotfix — same flow, no special branch:

git checkout -b fix/avatar-crash
# fix...
git push origin fix/avatar-crash
gh pr create --base main --fill
gh pr merge --squash

Bugfix Workflow

Identical to feature flow. fix/ prefix is convention only.

git checkout main
git pull

git checkout -b fix/avatar-upload-crash

# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix/avatar-upload-crash

gh pr create --base main --fill
# review, CI passes
gh pr merge --squash
# auto-deploys on merge

3. GitLab Flow

GitHub Flow extended with environment branches for staging/production gates.

Feature branches merge to main, then changes promote through environment branches (staging, production). Adds just enough structure for teams that need a gate before production.

Key branches: main, feature/*, staging, production

Pros:

  • Environment branches add safety
  • Flexible promotion model
  • Supports staging/prod split
  • Better audit trail than GitHub Flow

Cons:

  • More branches to manage than GitHub Flow
  • Environment drift possible if not automated
  • Documentation is GitLab-centric
  • Merge direction matters and can confuse

Tooling: GitLab CI/CD, environment branches, merge request approvals

Feature Workflow

git checkout main
git pull

git checkout -b feat/avatar-upload
# work...
git add .
git commit -m "feat: add user avatar upload"
git push origin feat/avatar-upload

# MR against main
# CI runs, review, merge to main

# promote to staging (merge main → staging)
git checkout staging
git merge main
git push origin staging
# staging CI deploys, QA verifies

# promote to production (merge staging → production)
git checkout production
git merge staging
git push origin production
# production deploy

Bugfix Workflow

git checkout main
git pull

git checkout -b fix/avatar-upload-crash
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix/avatar-upload-crash

# MR against main, review, merge
gh pr create --base main --fill
gh pr merge --squash

Regular bug — promote through environments like any change:

git checkout staging
git merge main
git push origin staging
# QA verifies on staging

git checkout production
git merge staging
git push origin production

Critical production bug — skip staging, cherry-pick to production:

# after merging to main:
git checkout production
git cherry-pick <commit-sha>
git push origin production
# then sync staging
git checkout staging
git merge main
git push origin staging

4. Ship / Show / Ask

Coined by Rouan Wilsenach. Developers categorize each change into one of three paths:

  • Ship — merge directly, no review (trivial/low-risk changes)
  • Show — open a PR for visibility, no blocking review required
  • Ask — full PR with blocking review (high-risk, architectural, uncertain)

Key branches: main, feature/*

Pros:

  • Developers choose the appropriate review level
  • Reduces review bottlenecks for trivial changes
  • Show path keeps team informed without blocking
  • Scales trust with developer maturity

Cons:

  • Requires high trust and team maturity
  • No formal release process
  • Risk of under-reviewing
  • Cultural convention, not tooling-enforced

Tooling: PR labels or naming conventions, CI on all paths, documented team agreements

Feature Workflow

Same git mechanics as GitHub Flow — the difference is the PR category:

git checkout -b feat/avatar-upload
# work...
git add .
git commit -m "feat: add user avatar upload"
git push origin feat/avatar-upload

Ship (trivial, low-risk — merge directly):

git checkout main
git merge feat/avatar-upload
git push origin main

Show (PR for visibility, no blocking review):

gh pr create --base main --fill --label "show"
# team sees it, comments optional
gh pr merge --squash

Ask (needs review — blocking PR):

gh pr create --base main --fill --label "ask" --reviewer teammate
# wait for approval
gh pr merge --squash

Bugfix Workflow

Category depends on severity. Production fire → Ship. Subtle race condition → Ask.

git checkout -b fix/avatar-upload-crash
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix/avatar-upload-crash

Ship (obvious fix, well-tested, production burning):

git checkout main
git merge fix/avatar-upload-crash
git push origin main
# no review, straight to deploy

Show (clear fix, team should know):

gh pr create --base main --fill --label "show"
gh pr merge --squash

Ask (complex root cause, uncertain side effects):

gh pr create --base main --fill --label "ask" --reviewer teammate
# wait for approval
gh pr merge --squash

5. Stacked Diffs

Used at Meta and Google (via Phabricator/Gerrit). Large changes are broken into a chain of dependent, reviewable diffs that can land independently.

Key branches: main, stacked dependent branches

Pros:

  • Each diff is small and reviewable
  • Diffs can land independently
  • Better review quality than monolithic PRs
  • Unblocks parallel work on dependent features

Cons:

  • Requires specialized tooling (painful without it)
  • Rebase conflicts without tool support
  • Steeper learning curve
  • CI per-diff can be complex to configure

Tooling: Graphite, ghstack, spr, git-branchless, Gerrit

Feature Workflow

Large feature broken into reviewable chain:

# diff 1: model + migration
git checkout main
git pull
git checkout -b avatar/01-model
# add model, migration...
git add .
git commit -m "feat(avatar): add Avatar model and migration"
git push origin avatar/01-model
gh pr create --base main --fill

# diff 2: storage service (depends on diff 1)
git checkout -b avatar/02-storage   # branches from 01-model
# add storage logic...
git add .
git commit -m "feat(avatar): add S3 storage service"
git push origin avatar/02-storage
gh pr create --base avatar/01-model --fill

# diff 3: API endpoint (depends on diff 2)
git checkout -b avatar/03-api       # branches from 02-storage
# add endpoint...
git add .
git commit -m "feat(avatar): add upload endpoint"
git push origin avatar/03-api
gh pr create --base avatar/02-storage --fill

# each PR is small and reviewable
# land in order: 01 → 02 → 03
# after 01 merges, rebase 02 onto main, etc.

With tooling (Graphite):

gt create avatar/01-model -m "feat(avatar): add Avatar model"
gt create avatar/02-storage -m "feat(avatar): add S3 storage"
gt create avatar/03-api -m "feat(avatar): add upload endpoint"
gt submit    # creates all PRs with correct base branches
# Graphite handles rebase cascade on merge

Bugfix Workflow

Rarely needed for bugs — most fixes are single-diff.

Simple bug (single diff):

git checkout main
git pull
git checkout -b fix/avatar-size
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix/avatar-size
gh pr create --base main --fill
# review, merge

Complex bug (root cause + fix + regression test):

# diff 1: add regression test (proves the bug)
git checkout main
git checkout -b fix/avatar/01-test
git add .
git commit -m "test: add regression test for >5MB avatar upload"
git push origin fix/avatar/01-test
gh pr create --base main --fill

# diff 2: actual fix
git checkout -b fix/avatar/02-fix
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix/avatar/02-fix
gh pr create --base fix/avatar/01-test --fill

# land in order

6. Feature Flag Flow

Not a formal git strategy — it’s trunk-based development with a feature flag platform layered on top. The key insight: deploy and release become independent operations.

Key branches: main

Pros:

  • Deploy != release (deploy daily, release when ready)
  • Instant rollback by toggling a flag
  • A/B testing and gradual rollouts built in
  • Dark launches for internal testing in production

Cons:

  • Flag debt accumulates (stale flags left in code)
  • Testing matrix grows exponentially with flag combinations
  • Runtime complexity increases
  • Needs a dedicated flag management platform

Tooling: LaunchDarkly, Unleash, Flagsmith, Split.io, OpenFeature

Feature Workflow

# trunk-based — commit to main behind a flag
git checkout main
git pull
git checkout -b avatar-upload      # short-lived

# code is wrapped in a feature flag
# if flag_enabled("avatar-upload"):
#     show_upload_widget()

git add .
git commit -m "feat: add avatar upload behind feature flag"
git push origin avatar-upload
gh pr create --base main --fill
gh pr merge --squash

# deployed to production — but invisible to users
# flag is OFF by default

# QA: enable flag for internal team
# gradual rollout: 5% → 25% → 100%
# if broken: toggle flag OFF (instant rollback, no deploy)

# after full rollout — cleanup:
git checkout -b chore/remove-avatar-flag
# remove flag checks, dead code path
git commit -m "chore: remove avatar-upload feature flag"
gh pr create --base main --fill
gh pr merge --squash

Bugfix Workflow

Non-critical bug — fix behind flag for safe rollout:

git checkout main
git pull
git checkout -b fix/avatar-size

# new code path behind flag
# if flag_enabled("avatar-upload-v2"):
#     upload_with_size_check()    # fixed path
# else:
#     upload_legacy()             # current broken path

git add .
git commit -m "fix: avatar size handling behind flag"
git push origin fix/avatar-size
gh pr create --base main --fill
gh pr merge --squash

# deploy (flag OFF) → enable for internal → 10% → 100%
# confirm fix works, then cleanup:
git checkout -b chore/remove-avatar-v2-flag
# remove flag, delete legacy path
git commit -m "chore: remove avatar-upload-v2 flag"
gh pr merge --squash

Critical bug — flag gives instant rollback:

# if the broken code was already behind a flag:
# toggle flag OFF → immediate fix, zero deploy
# then fix the code at normal pace

7. Release Flow

Microsoft’s variant of trunk-based development. Development happens on main, and release branches are cut at ship time. Hotfixes are cherry-picked from main to the release branch.

Key branches: main, release/*

Pros:

  • Trunk development velocity
  • Clean release branch cuts
  • Cherry-pick hotfixes to specific releases
  • Supports LTS branches

Cons:

  • Release branch maintenance overhead
  • Cherry-pick conflicts
  • More process than GitHub Flow
  • Stale release branches if not cleaned up

Tooling: CI/CD with branch policies, cherry-pick automation, release calendars

Feature Workflow

git checkout main
git pull

git checkout -b feat/avatar-upload
# work...
git add .
git commit -m "feat: add user avatar upload"
git push origin feat/avatar-upload
gh pr create --base main --fill
gh pr merge --squash
# feature is on main, not yet released

# release time — cut a release branch
git checkout main
git pull
git checkout -b release/2.4
git push origin release/2.4
# build + deploy from release/2.4

# hotfix after release:
git checkout main
git checkout -b fix/avatar-crash
# fix...
git commit -m "fix: handle missing avatar gracefully"
gh pr create --base main --fill
gh pr merge --squash

# cherry-pick to release branch
git checkout release/2.4
git cherry-pick <commit-sha>
git push origin release/2.4
# rebuild + redeploy

Bugfix Workflow

Bug found before release ships:

git checkout main
git pull
git checkout -b fix/avatar-upload-crash
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix/avatar-upload-crash
gh pr create --base main --fill
gh pr merge --squash
# fix is on main, included in next release cut

Bug in released version (hotfix):

# fix on main first
git checkout main
git pull
git checkout -b fix/avatar-upload-crash
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin fix/avatar-upload-crash
gh pr create --base main --fill
gh pr merge --squash

# cherry-pick to affected release branch
git checkout release/2.4
git cherry-pick <commit-sha>
git push origin release/2.4
# rebuild + redeploy release/2.4

# if multiple releases affected:
git checkout release/2.3
git cherry-pick <commit-sha>
git push origin release/2.3

8. Gitflow

Created by Vincent Driessen in 2010. The most structured model — uses long-lived develop and main branches with dedicated feature/*, release/*, and hotfix/* branches.

Still alive in enterprises and projects with formal release cycles: mobile apps, embedded software, on-prem products that need to maintain multiple supported versions.

Key branches: main, develop, feature/*, release/*, hotfix/*

Pros:

  • Clear, formal release process
  • Parallel version support
  • Structured hotfix path
  • Well-documented and widely understood

Cons:

  • Significant branch overhead
  • Merge hell between long-lived branches
  • Slow for CI/CD-oriented teams
  • develop branch often drifts from main
  • Overkill for most SaaS/web projects

Tooling: git-flow CLI extension, branch protection rules, manual QA gates

Feature Workflow

# start feature from develop
git checkout develop
git pull
git checkout -b feature/avatar-upload

# work...
git add .
git commit -m "feat: add user avatar upload"
git push origin feature/avatar-upload

# PR against develop
gh pr create --base develop --fill
gh pr merge --no-ff    # merge commit preserves history

# release time — cut release branch from develop
git checkout develop
git checkout -b release/2.4
# bump version, final QA fixes only
git commit -m "chore: bump version to 2.4.0"
git push origin release/2.4

# after QA approval — merge to main AND develop
git checkout main
git merge --no-ff release/2.4
git tag -a v2.4.0 -m "Release 2.4.0"
git push origin main --tags

git checkout develop
git merge --no-ff release/2.4
git push origin develop
git branch -d release/2.4

# hotfix (branches from main)
git checkout main
git checkout -b hotfix/avatar-crash
# fix...
git commit -m "fix: handle missing avatar gracefully"

# merge to main AND develop
git checkout main
git merge --no-ff hotfix/avatar-crash
git tag -a v2.4.1 -m "Hotfix 2.4.1"
git push origin main --tags

git checkout develop
git merge --no-ff hotfix/avatar-crash
git push origin develop
git branch -d hotfix/avatar-crash

With git-flow CLI:

git flow feature start avatar-upload
# work...
git flow feature finish avatar-upload   # merges to develop

git flow release start 2.4
# version bump, QA...
git flow release finish 2.4             # merges to main + develop, tags

git flow hotfix start avatar-crash
# fix...
git flow hotfix finish avatar-crash     # merges to main + develop, tags

Bugfix Workflow

Regular bug (found during development):

git checkout develop
git pull
git checkout -b bugfix/avatar-upload-crash

# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin bugfix/avatar-upload-crash

# PR against develop
gh pr create --base develop --fill
gh pr merge --no-ff
# included in next release cycle

Bug found during release QA:

# fix directly on release branch
git checkout release/2.4
git checkout -b bugfix/avatar-upload-crash

# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin bugfix/avatar-upload-crash

gh pr create --base release/2.4 --fill
gh pr merge --no-ff
# continue QA on release/2.4

Production hotfix (critical):

git checkout main
git pull
git checkout -b hotfix/avatar-upload-crash

# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git push origin hotfix/avatar-upload-crash

# merge to main
git checkout main
git merge --no-ff hotfix/avatar-upload-crash
git tag -a v2.4.1 -m "Hotfix 2.4.1"
git push origin main --tags

# merge to develop (so fix isn't lost)
git checkout develop
git merge --no-ff hotfix/avatar-upload-crash
git push origin develop

git branch -d hotfix/avatar-upload-crash

With git-flow CLI:

git flow hotfix start avatar-upload-crash
# fix...
git add .
git commit -m "fix: handle avatar files exceeding 5MB limit"
git flow hotfix finish avatar-upload-crash
# auto-merges to main + develop, auto-tags
git push origin main develop --tags

Quick Reference

Feature Paths

StrategyBranch fromPR targetDeploy triggerHotfix path
Trunk-Basedmainmainmerge to mainsame as feature
GitHub Flowmainmainmerge to mainsame as feature
GitLab Flowmainmainpromote staging → prodPR to main → promote
Ship / Show / Askmainmainmerge to mainShip (direct merge)
Stacked Diffsmainparent diffmerge chain to mainsingle-diff PR to main
Feature Flag Flowmainmainmerge to main (flag off)toggle flag off
Release Flowmainmainrelease branch cutcherry-pick main → release/*
Gitflowdevelopdeveloprelease/* → main mergehotfix/* from main → main + dev

Bug Paths

StrategyRegular bugCritical production bugBranch fromMerges to
Trunk-Basedcommit to maincommit to mainmainmain
GitHub FlowPR to mainPR to main (prioritized)mainmain
GitLab FlowPR to main → promotecherry-pick to productionmainmain → env branches
Ship / Show / AskShow or AskShip (direct merge)mainmain
Stacked Diffssingle-diff PRsingle-diff PR (prioritized)mainmain
Feature Flag Flowfix behind flagtoggle flag OFF (instant)mainmain
Release FlowPR to maincherry-pick main → release/*mainmain + release/*
Gitflowbugfix/*develophotfix/*main + developdevelop / maindevelop / main + develop

Key difference: Gitflow is the only strategy with a structurally different path for bugs vs features (bugfix/* and hotfix/* branches with distinct merge targets). All other strategies treat bugs as regular changes with priority/urgency adjustments.