Branching Strategies¶
What You'll Learn¶
- How trunk-based development, GitHub flow, GitFlow, and release branches work
- How to choose a strategy based on how you deliver software
- How merge commits, squash merges, and rebase merges affect history
- Why feature flags make short-lived branches possible, and how GitOps repositories are laid out
Why the Strategy Matters¶
A branching strategy decides how long changes live apart before they're integrated. The longer branches live, the bigger and riskier every merge becomes. Research on software delivery performance, including the DORA reports, consistently associates short-lived branches and frequent integration with higher delivery performance.
Trunk-Based Development¶
Everyone integrates into one main branch (the trunk) at least daily. Branches, if used, live for hours to a day or two.
gitGraph
commit id: "a"
branch fix-timeout
commit id: "b"
checkout main
merge fix-timeout
branch add-metric
commit id: "c"
checkout main
merge add-metric
commit id: "d" tag: "v1.8.0"
branch retry-flag
commit id: "e"
checkout main
merge retry-flag
mainis always releasable, protected by CI.- Unfinished features are merged behind feature flags, not held back on branches.
- Releases are tags on
main, or deploys of every commit.
Best for: continuous delivery, web services, teams with solid automated tests.
GitHub Flow¶
A lightweight version of the same idea:
- Branch from
main. - Commit and push.
- Open a pull request; CI runs; reviewers approve.
- Merge to
main. - Deploy
main.
It's trunk-based development with pull requests as the integration step, and it's the default for most teams on GitHub and GitLab.
GitFlow¶
A structured model with long-lived branches:
gitGraph
commit id: "1.0" tag: "v1.0"
branch develop
commit id: "d1"
branch feature/search
commit id: "f1"
commit id: "f2"
checkout develop
merge feature/search
branch release/1.1
commit id: "rc fix"
checkout main
merge release/1.1 tag: "v1.1"
checkout develop
merge release/1.1
checkout main
branch hotfix/1.1.1
commit id: "hotfix"
checkout main
merge hotfix/1.1.1 tag: "v1.1.1"
checkout develop
merge hotfix/1.1.1
| Branch | Lifetime | Purpose |
|---|---|---|
main |
Permanent | Released code only |
develop |
Permanent | Integration of finished features |
feature/* |
Days to weeks | One feature |
release/* |
Until release | Stabilization and version bumps |
hotfix/* |
Hours | Urgent fixes to production, merged to both main and develop |
GitFlow fits software shipped in versioned releases on a schedule — desktop apps, on-premises products, libraries. For continuously deployed services, it adds overhead and delays integration; even its original author has since recommended simpler workflows for web applications delivered continuously.
Release Branches¶
When you must support several versions at once (for example 2.x and 3.x of a library or product), keep trunk-based development on main and cut release branches for maintenance:
git switch -c release/2.4 v2.4.0
# Fix on main first, then bring the fix back to the release branch
git switch release/2.4
git cherry-pick -x 9f3c2d1 # -x records the original commit in the message
git tag -a v2.4.1 -m "Release 2.4.1"
Fixing on main first and cherry-picking back ensures the fix isn't lost in the next major version.
Choosing a Strategy¶
| Your situation | Choose |
|---|---|
| Web services deployed many times a day with good automated tests | Trunk-based development or GitHub flow |
| A small team starting out | GitHub flow |
| Versioned releases customers install, several supported versions | Trunk-based with release branches |
| Scheduled releases with a formal stabilization phase | GitFlow, knowing it slows integration |
| Regulated environments needing approvals per environment | GitHub flow plus deployment environments and approvals in CI/CD, not per-environment branches |
Merge Methods¶
Pull requests can be merged three ways:
| Method | Result on main |
Keep when |
|---|---|---|
| Merge commit | All branch commits plus a merge commit | Individual commits matter and are well curated |
| Squash merge | One commit per pull request | Branch commits are noisy; you want one revertible unit per change |
| Rebase merge | Branch commits replayed linearly, no merge commit | Commits are curated and you want linear history |
Many teams allow squash only: one pull request becomes one commit, git revert undoes a whole change, and git log on main reads like a changelog. The pull request title becomes the commit message, so write good titles.
Feature Flags¶
Short-lived branches need a way to merge incomplete work safely:
if flags.is_enabled("order-retries", user=current_user):
submit_with_retries(order)
else:
submit(order)
- Code is merged and deployed dark, then released by flipping the flag — for internal users first, then a percentage, then everyone.
- A bad feature is turned off in seconds without a rollback.
- Remove flags after launch. Stale flags become permanent branches in your code.
Branches and Environments in GitOps¶
A common mistake is one Git branch per environment (dev, staging, prod), promoted by merging between them. Branches drift, merges carry unrelated changes, and it's hard to see what differs between environments.
Prefer one branch, one folder per environment:
platform-config/
├── base/
│ ├── deployment.yaml
│ └── kustomization.yaml
└── overlays/
├── dev/
│ └── kustomization.yaml # image tag abc1234, 1 replica
├── staging/
│ └── kustomization.yaml # image tag abc1234, 2 replicas
└── prod/
└── kustomization.yaml # image tag 9e8d7c6, 6 replicas
Promotion is a pull request that changes the image tag in the next environment's folder — reviewable, auditable, and trivially revertible. See GitOps with ArgoCD and Flux.
Common Mistakes¶
- Adopting GitFlow for a continuously deployed web service and wondering why integration is painful.
- Feature branches that live for weeks because there's no feature-flag mechanism.
- One long-lived branch per environment, merged forward, with environments silently diverging.
- Fixing a bug on a release branch and forgetting to fix
main. - Allowing every merge method, so
main's history is an unreadable mix.
Interview Questions¶
- Compare trunk-based development and GitFlow. When would you choose each?
- How do feature flags enable trunk-based development?
- What are the trade-offs between squash merges and merge commits?
- How would you structure branches for a product that supports versions 2.x and 3.x?
- Why is a branch per environment an anti-pattern for GitOps, and what do you use instead?
Next¶
Continue to Undo and Recovery.