Git Branching Strategies: From Git Flow to Trunk-Based
A comparison of Git Flow, GitHub Flow, Trunk-Based Development and how to choose between them
Contents
A branching strategy follows from team size, release cadence, and continuous integration and delivery (CI/CD) maturity, and there is no single right answer.
Overview
A branching strategy is the convention that defines how a team separates, integrates and releases code. What fits depends on team size, release cadence and the nature of the product. Organizations built around continuous integration and delivery (CI/CD) have widely adopted Trunk-Based Development.
Git Flow
The traditional branching model Vincent Driessen proposed in 2010. It suits projects with periodic releases.
Branch structure
master (production)
│
├── hotfix/payment-bug ────→ merged into master + develop
│
├── release/1.2.0 ─────────→ merged into master + develop
│
develop (integration branch)
│
├── feature/user-auth ──→ merged into develop
├── feature/payment ────→ merged into develop
└── feature/notification → merged into developWhat each branch is for
| Branch | Lifetime | Purpose | Merge target |
|---|---|---|---|
master (main) | Permanent | Code deployed to production | - |
develop | Permanent | Integration for the next release | - |
feature/* | Temporary | New feature development | develop |
release/* | Temporary | Release preparation (quality assurance (QA), bug fixes) | master + develop |
hotfix/* | Temporary | Urgent production fixes | master + develop |
The Git Flow workflow
# 1. start work on a feature
git checkout develop
git checkout -b feature/user-auth
# 2. feature is done, merge into develop
git checkout develop
git merge --no-ff feature/user-auth
git branch -d feature/user-auth
# 3. prepare the release
git checkout develop
git checkout -b release/1.2.0
# 4. release is done, merge into master and develop
git checkout master
git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release 1.2.0"
git checkout develop
git merge --no-ff release/1.2.0
git branch -d release/1.2.0
# 5. hotfix
git checkout master
git checkout -b hotfix/payment-bug
# after the fix
git checkout master
git merge --no-ff hotfix/payment-bug
git tag -a v1.2.1 -m "Hotfix: payment bug"
git checkout develop
git merge --no-ff hotfix/payment-bug--no-ff blocks a fast-forward merge so a merge commit is always recorded. The work for one feature stays grouped under a single commit, which makes it easy to trace later which branch was integrated and when.
Strengths and weaknesses
| Strengths | Weaknesses |
|---|---|
| Release management is systematic | Many branches, high complexity |
| Version history is explicit | Frequent merge conflicts |
| Production stays stable | Does not sit well with CI/CD |
| Suits large teams | Release cycles can stretch out |
When it fits
- Mobile apps (app store review cycles)
- On-premise software
- Periodic releases (every two or four weeks)
- Products that maintain several versions at once
Trunk-Based Development (TBD)
A strategy centered on a single main branch (trunk/main), with short-lived branches merged frequently. Google, Facebook and Netflix use it, and it is common in CI/CD-driven organizations.
Core principles
- Short-lived branches: two days at most, ideally a few hours
- Frequent integration: merge into main several times a day
- Always deployable: main can go to production at any moment
- Feature flags: unfinished features live in the code but stay switched off
Feature flags
// Feature flag gating an unfinished feature
if (featureFlags.isEnabled("new-checkout-flow")) {
return newCheckoutService.process(order);
} else {
return legacyCheckoutService.process(order);
}| Feature flag tool | Characteristics |
|---|---|
| LaunchDarkly | SaaS, real-time toggles, A/B testing |
| Unleash | Open source, self-hosted |
| Flagsmith | Available both open source and as SaaS |
| AWS AppConfig | Native AWS integration |
| OpenFeature | Vendor-neutral standard (under the Cloud Native Computing Foundation (CNCF)) |
TBD and CI/CD
Strengths and weaknesses
| Strengths | Weaknesses |
|---|---|
| Merge conflicts stay small | Feature flags are their own management burden |
| Fast feedback loop | Requires strong CI/CD |
| Highest possible deployment frequency | Requires automated tests |
| Lighter review load (small PRs) | An inexperienced team destabilizes main |
GitHub Flow
The simplified branching model GitHub proposed. It uses only main and feature branches.
Workflow
main ─── A ─── B ─── C ─── D ─── E ─── F
\ /
└── feat ──┘
(PR + Review)- Branch off main
- Add commits
- Open a pull request
- Review the code
- Merge into main
- Deploy immediately
Compared to Git Flow
| Aspect | Git Flow | GitHub Flow |
|---|---|---|
| Number of branch types | 5 | 2 (main + feature) |
| Complexity | High | Low |
| Deployment frequency | Periodic | Immediately on merge |
| Team size it fits | Large teams | Small to mid-size teams |
GitLab Flow
GitHub Flow with environment branches added.
main ──→ staging ──→ productionEnvironment branches
feature → main (development)
│
▼
staging (test/QA)
│
▼
production- Provides a deployment gate per environment
- Hotfixes can be cherry-picked straight onto production
- Environment branches take the place of release branches
Release Flow (Microsoft)
The strategy Microsoft uses on large teams such as Azure DevOps.
Core structure
- main: like Trunk-Based, always deployable
- release branches: cut from main at release time, hotfixes only, applied by cherry-pick
- Suits products that must support several releases simultaneously
Monorepo strategy
What a monorepo is
Managing several projects or services in a single Git repository. Google, Meta and Microsoft all do this.
The monorepo tooling ecosystem
| Tool | Characteristics | Users |
|---|---|---|
| Nx | Smart build cache, dependency graph analysis | Nrwl and others |
| Turborepo | Remote caching, pipeline-based task execution | Vercel |
| Bazel | Multi-language, hermetic builds, remote execution | Google, Uber, Airbnb |
| Lerna | npm package management, versioning | Open source community |
| Rush | Large-scale monorepo management from Microsoft | Microsoft |
| Moon | Rust-based, fast task execution | Community |
Monorepo plus Trunk-Based Development
The hard parts:
- Affected analysis: automatically determining which services a change touches
- Selective build and test: building and testing only the services that changed
- CODEOWNERS: assigning reviewers automatically by directory ownership
# Nx: test only the affected projects
npx nx affected --target=test
# Turborepo: reuse cached results
turbo run build --filter=auth-service...nx affected picks out only the projects that depend on the changed files and tests those. In Turborepo, the trailing ... in --filter=auth-service... means the service and its dependent packages are built together.
Merge queue
The problem: the rebase race
When several PRs are approved at the same time, this happens:
PR #1: approved → merge attempt → success
PR #2: approved → main has changed → rebase needed → re-test → merge attempt
PR #3: approved → main has changed again → rebase needed → re-test...What a merge queue does
PR #1 ─┐
PR #2 ─┤──→ [Merge Queue] ──→ auto rebase → test → merge
PR #3 ─┘ │
main always stays green- An approved PR enters the merge queue
- The queue rebases it automatically
- Tests run on a temporary merge branch
- On success it merges automatically; on failure it is dropped from the queue
Main implementations
| Tool | Characteristics |
|---|---|
| GitHub Merge Queue (native) | Integrated with GitHub Actions |
| Mergify | SaaS, advanced rule engine |
| Trunk Merge | Integrated with the trunk.io ecosystem |
| Aviator MergeQueue | Optimized for monorepos |
| Bors | Open source, started in the Rust community |
Stacked PRs
A workflow that splits a large feature into a chain of small PRs so review and merging can run in parallel. It started as a Meta internal tool and Graphite brought it to a wider audience.
The old way versus stacked PRs
[Traditional: one big PR]
feature/big-change ──────────────────────→ main
(1000 lines, hard to review, takes forever)
[Stacked PRs: a chain of small PRs]
stack/part-1 (DB schema) ──→ main
└── stack/part-2 (API) ──→ stacked on top of stack/part-1
└── stack/part-3 (UI) ──→ stacked on top of stack/part-2Benefits
- Review efficiency: a small PR gets a focused, fast review
- Parallel development: part-2 and part-3 can be built while part-1 is in review
- Fewer merge conflicts: small changes integrate often
- Easy rollback: when something breaks, only one step has to be reverted
Main tools
| Tool | Characteristics |
|---|---|
| Graphite | Purpose-built for stacked PRs, CLI plus web dashboard |
| ghstack (Meta) | Stacked PRs on GitHub |
| git-branchless | Mercurial-like UX, stack management |
| Aviator | Stacked PRs plus merge queue in one |
# Graphite usage example
gt branch create part-1
# after making changes
gt commit create -m "feat: add db schema"
gt branch create part-2 # automatically stacked on top of part-1
# after making changes
gt commit create -m "feat: add API endpoints"
gt stack submit # submit the whole stack as PRsgt branch create part-2 automatically stacks on the previous branch (part-1), and gt stack submit pushes the whole stack up as individual PRs in one go.
Branch protection rules
GitHub branch protection settings
| Rule | Description |
|---|---|
| Require pull request reviews | PR review required (at least N reviewers) |
| Require status checks to pass | CI checks must pass |
| Require branches to be up to date | Must be current before merging |
| Require signed commits | GnuPG (GPG) or SSH signature required |
| Require linear history | No merge commits (rebase or squash only) |
| Include administrators | Rules apply to admins too |
| Restrict pushes | Direct pushes limited |
| Allow auto-merge | Merges automatically once conditions are met |
# Example status check in GitHub Actions
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: npm run lintChoosing a branching strategy
| Criterion | Git Flow | Trunk-Based | GitHub Flow | Release Flow |
|---|---|---|---|---|
| Team size | Large | Any size | Small to mid-size | Large |
| Release cadence | Periodic (2-4 weeks) | Continuous | Continuous | Periodic |
| Deployment automation | Optional | Required | Recommended | Recommended |
| Maintaining parallel versions | Possible | Difficult | Difficult | Possible |
| Complexity | High | Low | Very low | Medium |
| Dependence on CI/CD | Low | High | Medium | Medium |
| Feature flags | Optional | Required | Optional | Optional |
| Fit for monorepos | Low | High | Medium | High |
Decision flowchart
Summary
There is no absolute right answer in branching, and the choice is driven by team size, release cadence and CI/CD maturity. Periodic releases or several versions maintained in parallel point to Git Flow or Release Flow. Solid automation and frequent deployment make Trunk-Based Development cheaper in merge conflicts and integration effort. A small or mid-size team that wants simplicity is well served by GitHub Flow.
Whichever strategy you pick, merge queues, stacked PRs and branch protection rules are what hold integration quality up. The next post continues with code review and collaboration conventions.