Git Branching Strategies
Comparison Matrix
| Strategy | Branches | Release Cadence | Complexity | CI/CD Fit | Multi-version | Team Size | Best For |
|---|---|---|---|---|---|---|---|
| Trunk-Based | main only, short-lived branches (<1 day) | Continuous | Low | ✓ | ✗ | Any | High-velocity SaaS |
| GitHub Flow | main + feature/* | Continuous | Low | ✓ | ✗ | Small–Mid | SaaS, startups, OSS |
| GitLab Flow | main + feature/* + env branches | Continuous with gates | Medium | ✓ | ◐ | Mid | Teams needing staging gates |
| Ship / Show / Ask | main + feature/* | Continuous | Low | ✓ | ✗ | Small–Mid | Balancing speed with review |
| Stacked Diffs | main + dependent chain | Continuous | Medium | ◐ | ✗ | Mid–Large | Large changes, atomic reviews |
| Feature Flag Flow | main (trunk-based + flags) | Deploy ≠ Release | Medium | ✓ | ◐ | Mid–Large | Decoupling deploy from release |
| Release Flow | main + release/* | Scheduled | Medium | ◐ | ✓ | Large | Discrete releases, Microsoft-style |
| Gitflow | main, develop, feature/*, release/*, hotfix/* | Scheduled | High | ◐ | ✓ | Mid–Large | Formal QA, multiple versions |
Legend: ✓ strong fit, ◐ partial, ✗ weak fit
Decision Guide
Deciding factor is deployment model:
| If you… | Use |
|---|---|
| Deploy continuously, single version | Trunk-Based or GitHub Flow |
| Need a staging gate before production | GitLab Flow |
| Want review flexibility based on risk | Ship / Show / Ask |
| Make large changes that need atomic reviews | Stacked Diffs |
| Need to decouple deploy from release | Feature Flag Flow |
| Have scheduled releases but continuous dev | Release Flow |
| Maintain multiple supported versions, formal QA | Gitflow |
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 productionIf 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 --squashBugfix 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-deploysIf 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, deployed2. 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 mainHotfix — 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 --squashBugfix 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 merge3. 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 deployBugfix 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 --squashRegular 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 productionCritical 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 staging4. 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-uploadShip (trivial, low-risk — merge directly):
git checkout main
git merge feat/avatar-upload
git push origin mainShow (PR for visibility, no blocking review):
gh pr create --base main --fill --label "show"
# team sees it, comments optional
gh pr merge --squashAsk (needs review — blocking PR):
gh pr create --base main --fill --label "ask" --reviewer teammate
# wait for approval
gh pr merge --squashBugfix 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-crashShip (obvious fix, well-tested, production burning):
git checkout main
git merge fix/avatar-upload-crash
git push origin main
# no review, straight to deployShow (clear fix, team should know):
gh pr create --base main --fill --label "show"
gh pr merge --squashAsk (complex root cause, uncertain side effects):
gh pr create --base main --fill --label "ask" --reviewer teammate
# wait for approval
gh pr merge --squash5. 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 mergeBugfix 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, mergeComplex 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 order6. 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 --squashBugfix 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 --squashCritical 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 pace7. 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 + redeployBugfix 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 cutBug 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.38. 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
developbranch often drifts frommain- 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-crashWith 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, tagsBugfix 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 cycleBug 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.4Production 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-crashWith 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 --tagsQuick Reference
Feature Paths
| Strategy | Branch from | PR target | Deploy trigger | Hotfix path |
|---|---|---|---|---|
| Trunk-Based | main | main | merge to main | same as feature |
| GitHub Flow | main | main | merge to main | same as feature |
| GitLab Flow | main | main | promote staging → prod | PR to main → promote |
| Ship / Show / Ask | main | main | merge to main | Ship (direct merge) |
| Stacked Diffs | main | parent diff | merge chain to main | single-diff PR to main |
| Feature Flag Flow | main | main | merge to main (flag off) | toggle flag off |
| Release Flow | main | main | release branch cut | cherry-pick main → release/* |
| Gitflow | develop | develop | release/* → main merge | hotfix/* from main → main + dev |
Bug Paths
| Strategy | Regular bug | Critical production bug | Branch from | Merges to |
|---|---|---|---|---|
| Trunk-Based | commit to main | commit to main | main | main |
| GitHub Flow | PR to main | PR to main (prioritized) | main | main |
| GitLab Flow | PR to main → promote | cherry-pick to production | main | main → env branches |
| Ship / Show / Ask | Show or Ask | Ship (direct merge) | main | main |
| Stacked Diffs | single-diff PR | single-diff PR (prioritized) | main | main |
| Feature Flag Flow | fix behind flag | toggle flag OFF (instant) | main | main |
| Release Flow | PR to main | cherry-pick main → release/* | main | main + release/* |
| Gitflow | bugfix/* → develop | hotfix/* → main + develop | develop / main | develop / 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.