Git Branching Strategies
Git makes branches cheap, which means a team has to decide how to use them. How long do branches live? Where does release-ready code sit? How do hotfixes reach production? A branching strategy answers these questions, and it shapes how often you integrate, how painful merges are, and how fast changes reach users.
The industry has largely converged on short-lived branches merged frequently into one main branch, backed by strong CI and feature flags. Heavier models like GitFlow still fit some products, such as versioned software and mobile apps with store review. The key is choosing deliberately rather than inheriting a process.
TL;DR
- Trunk-based development: everyone integrates into
mainat least daily through tiny branches or direct commits, with incomplete work hidden behind feature flags. Best for continuous delivery. - GitHub Flow: branch from
main, open a PR, review, merge, deploy. Simple and the most common for web apps. - GitFlow: long-lived
developandmainplus feature, release, and hotfix branches. Suits scheduled, versioned releases, but it's heavy for SaaS. - Release branches (
release/2.4) stabilize and patch shipped versions whilemainmoves on. - Short-lived branches (hours to a couple of days) are the biggest predictor of painless merges.
- Pick a merge policy (merge commit, squash, or rebase) and enforce it with branch protection.
Quick Example
GitHub Flow in practice:
With branch protection on main (required reviews, required status checks, linear history), every change reaching production has been reviewed and tested.
Core Concepts
Trunk-Based Development
Developers integrate small changes into the trunk (main) continuously, at least once a day. Branches, if used, live hours rather than weeks. Unfinished features ship dark behind feature flags, and releases are cut from trunk, often automatically on every merge.
- Pros: minimal merge conflicts, fast feedback, and it enables continuous deployment. DORA research links trunk-based development to high delivery performance.
- Cons: requires strong automated testing, fast CI, and feature-flag discipline; broken trunk blocks everyone.
GitHub Flow
One long-lived branch (main, always deployable) plus short-lived topic branches merged through pull requests. It's effectively trunk-based development with mandatory PR review. It's simple enough to explain in a sentence and fits most web services.
GitFlow
GitFlow gives structure for planned, versioned releases, but its long-lived branches cause merge debt, and double-merging hotfixes is error-prone. Even its author has noted it's a poor fit for continuously delivered web apps.
Release Branches
For products that support several shipped versions (libraries, on-prem software, mobile apps), cut release/x.y from main when stabilizing a release. Fixes land on main first and are cherry-picked to supported release branches. This keeps main moving while shipped versions get patches.
Merge Policies
Squash-merge is the most common default for application repos: one PR equals one commit, which pairs well with Conventional Commits for changelogs and makes git revert of a whole feature trivial. See Git rebase for keeping branches current.
Choosing a Strategy
- Continuously deployed SaaS → trunk-based or GitHub Flow, feature flags, squash merges.
- Mobile apps → GitHub Flow plus release branches per store submission.
- Libraries and versioned products with LTS → trunk plus release branches for maintained versions, semver tags.
- Heavily regulated, scheduled releases → release branches with stabilization windows; GitFlow only if you truly need a separate
develop. - Monorepos → trunk-based with path-based CI and code owners. See monorepos.
Best Practices
Keep Branches Short-Lived
The longer a branch lives, the more it diverges and the more painful the merge. Break large features into small, independently mergeable steps. Use flags to hide incomplete work, and stacked PRs when changes depend on each other.
Protect the Main Branch
Require pull requests, passing status checks, and at least one approval, and block force-pushes. Enable merge queues on busy repos so each merge is tested against the latest main.
Automate Releases From Tags or Merges
Releases shouldn't depend on someone remembering the steps. Tag releases (v2.4.0) and let CI build and publish, or deploy every merge to main behind progressive rollout. See CI/CD.
Name Branches Consistently
Prefixes like feat/, fix/, chore/, plus a ticket ID (feat/PAY-142-refund-api), make branches searchable and let automation link them to issues.
Common Mistakes
Long-Lived Feature Branches
A branch open for three weeks against a busy main ends in a merge that's risky to resolve and hard to review. Integrate at least daily, or rebase regularly and split the work.
GitFlow for a Continuously Deployed Web App
Maintaining develop, release branches, and back-merges adds ceremony without benefit when every merge could ship. Simplify to GitHub Flow.
Environment Branches
Branches named staging and production that code is "promoted" through by merging drift apart quickly and create confusing conflicts. Promote the same build artifact through environments instead, and keep environment configuration outside the branch model. See deployment strategies.
FAQ
What's the difference between trunk-based development and GitHub Flow?
They're closely related. Both have one main branch and short-lived branches. Strict trunk-based development emphasizes integrating at least daily, sometimes committing directly to trunk, with feature flags for incomplete work. GitHub Flow always goes through a pull request but doesn't prescribe how long branches live. In practice, GitHub Flow with small, fast PRs is trunk-based development.
Is GitFlow outdated?
For continuously delivered web applications, largely yes: its long-lived branches slow integration. It still fits products with scheduled, versioned releases and several supported versions, though many such teams now prefer trunk plus release branches.
How do feature flags relate to branching?
Flags decouple deploying code from releasing features. You can merge unfinished work to main while it's hidden, which removes the need for long-lived feature branches. See feature flags.
Should I squash-merge or rebase-merge?
Squash if individual PR commits are messy or you want one revertible commit per change. Rebase-merge if your team writes clean, meaningful commits and values the granular history. Whichever you pick, enforce it in repository settings so history stays consistent.
Related Topics
- Git — The version control system overview
- Git Rebase — Keeping branches current and history clean
- Feature Flags — Shipping incomplete work safely
- Code Review — Pull request practices
- CI/CD — Automating tests and releases per branch
- DORA Metrics — Measuring delivery performance