CI/CD Automation Tutorial 0/46 lessons ~6 min read Lesson 9

    Branching Strategies

    Branching strategies decide how teams parallelize work without losing deployability.

    Course progress0%
    Focus
    20 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Branching strategies decide how teams parallelize work without losing deployability. Staff engineers choose strategies based on release cadence, compliance, team topology, and CI capacity — not because someone blogged about Git Flow in 2010.

    The story

    Two product squads shared one repo. Squad A used month-long feature/payments-v2 branches; Squad B trunk-merged daily. Every Squad A merge rebase conflicted with Squad B's refactors — CI ran 40 minutes and still missed integration bugs. Platform mandated ≤3-day branch age, CODEOWNERS by directory, and a "merge or kill" bot that flagged stale branches. Integration pain dropped 60%.

    Understanding the topic

    Branching strategy is a coordination protocol. It trades isolation (long branches) against integration cost (merge pain, CI duplication).

    • Release branches: stabilize a version line; CI runs heavier suites on release/x.y than on feature PRs.
    • Environment branches: GitLab Flow style — push to staging deploys staging; merge to production deploys prod (branch = env pointer).
    • Feature flags + trunk: short branches or direct trunk commits; flags hide incomplete behavior — CI always integrates real merge graph.
    • Stacked PRs / graphite: dependent PR chains; CI runs on each stack segment with merge-base optimization.
    • Forking workflow: OSS pattern — CI on contributor fork via pull_request; maintainer merge triggers release pipeline.

    Internal architecture

    Branch topology vs CI cost — how branch shape affects pipeline design:

    text
    Long-lived feature branch (anti-pattern at scale)
    feature/big ──(diverges 3 weeks)──> main
    CI: runs on feature only → misses main drift until merge hell
    Short-lived feature branch (GitHub Flow)
    feature/ticket-123 ──(1-2 days)──> main
    CI: PR against main → catches drift early
    Release train branch
    main ──> release/2.4 ──(stabilize)──> tag v2.4.0
    CI: stricter on release/* (no new features, only fixes)
    Trunk + flags
    main ← small commits daily
    CI: every commit; flags gate user-visible behavior
    Stacked changes
    pr/3 ← pr/2 ← pr/1 ← main
    CI: each PR tested against its base (not always main)

    Visual explanation

    Two diagrams show where Branching Strategies lives in the delivery path and how teams implement it in production.

    Branching Strategies — system view
    Work isolation
    Feature branch
    Integration point
    PR to trunk
    CI feedback
    Drift detection
    Release cut
    Tag or branch
    Where this topic sits in the delivery path.
    Branching Strategies — execution flow
    Branch age
    <3 days target
    CODEOWNERS
    Path ownership
    Merge policy
    Squash vs merge
    Deploy ref
    SHA or tag
    Follow this loop when designing or reviewing pipelines.

    Step-by-step explanation

    1. Measure current branch age distribution: git for-each-ref --sort=-committerdate refs/heads/ and histogram days since branch diverged from main.
    2. Choose strategy per repo class: product app (TBD/GitHub Flow), library (semver branches), infra (env branches).
    3. Define merge policy: squash for clean history vs merge commits for audit trail — affects bisect and revert.
    4. Add automation: stale branch bot, branch naming lint in CI, max-age check failing PRs older than N days.
    5. Align CODEOWNERS paths with branch boundaries so cross-cutting changes get early review.

    Production implementation

    Stale branch enforcement in GitHub Actions + branch naming convention:

    • Pair with Graphite or gh stack for stacked PRs when features exceed 400 LOC.
    • Release branches get exception from age check via label release-train.
    • Document merge policy in CONTRIBUTING.md — squash vs merge commit affects revert ergonomics.
    yaml
    # .github/workflows/branch-policy.yml
    name: Branch Policy
    on:
    pull_request:
    types: [opened, synchronize, reopened]
    jobs:
    branch-name:
    runs-on: ubuntu-latest
    steps:
    - name: Validate branch name
    run: |
    BRANCH="${{ github.head_ref }}"
    if ! echo "$BRANCH" | grep -Eq '^(feature|fix|chore|hotfix)/[A-Z]+-[0-9]+'; then
    echo "::error::Branch must match feature|fix|chore|hotfix/TICKET-123"
    exit 1
    fi
    branch-age:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    with: { fetch-depth: 0 }
    - name: Fail if branch diverged > 5 days from main
    run: |
    DIVERGE=$(git merge-base origin/main HEAD)
    AGE=$(( ($(date +%s) - $(git log -1 --format=%ct $DIVERGE)) / 86400 ))
    if [ "$AGE" -gt 5 ]; then
    echo "::error::Branch diverged $AGE days — rebase or split PR"
    exit 1
    fi

    Execution workflow

    1Select and enforce a branching strategy
    1 / 5

    Classify repositories

    Product app, library, infra, mobile — each gets appropriate strategy.

    Document in Backstage/service catalog metadata.

    Real-world use

    Spotify popularized squad autonomy with trunk-based alignment squads. LaunchDarkly and internal flag systems made long branches unnecessary for "hide incomplete work." GitHub's own engineering uses short branches with merge queue; many enterprises still run Git Flow for contractual quarterly releases.

    Enterprise use cases

    Microsoft's OneBranch policy pushes all work through short-lived branches into a shared main with unified CI. Amazon uses team branches sparingly — coordination via service boundaries and separate repos. Monorepos at Uber use path-based ownership so branching strategy is effectively "trunk per directory".

    • Multi-repo: each service owns repo; branching simple; integration via contract tests and consumer-driven CI.
    • Monorepo: trunk mandatory; long branches punish everyone via merge conflicts and full-repo CI cost.
    • Mobile release trains: release branch only for store submission freeze; trunk continues with flags.

    Production case study

    A consumer mobile backend (45 engineers, monorepo) enforced 3-day max branch age and squash merge to main. Prior strategy allowed 2-week feature branches with 35% of merges requiring conflict resolution calls.

    • Policy: branch naming lint, age check in CI, weekly stale branch purge with owner notification.
    • Tooling: internal CLI to split large changes into stacked PRs under 300 LOC each.
    • Exception process: architect approval for release trains only.
    • Result: merge conflict incidents down 70%; p95 time-to-main from 9 days to 1.8 days.

    Trade-offs

    • Long feature branches: developer isolation vs expensive merges and delayed integration feedback.
    • Squash merge: clean history vs loss of granular bisect and per-commit CI evidence.
    • Environment branches: simple deploy mental model vs drift between staging and prod manifests.
    • Many release branches: parallel support for N versions vs multiplied CI and cherry-pick tax.

    Security implications

    Branch strategy affects who can ship code and how hotfixes bypass normal review.

    • Hotfix branches need expedited CI but must not skip security scan — separate workflow, same gates.
    • Environment branch pushes (prod) should require restricted roles and signed commits.
    • Stale branches accumulate unpatched dependencies — Dependabot on active branches only misses dormant refs.
    • Branch deletion after merge reduces attack surface of forgotten feature branches with old secrets in history.

    Scalability analysis

    Branch count and age drive CI spend and merge conflict rates superlinearly in monorepos.

    • 100+ open branches → stale bot and merge queue become mandatory operational tools.
    • Cross-team long branches in monorepos trigger full CI on every push — path filters essential.
    • Release branch proliferation (support 3 major versions) triples patch CI unless cherry-pick automation exists.
    • Stacked PR tooling reduces effective branch count but adds CI orchestration complexity.

    Staff engineer insights

    • Branch age is the leading indicator of delivery risk — measure it weekly, not just DORA after the fact.
    • Squash vs merge is an audit question: SOX teams often require merge commits preserving PR identity in git graph.
    • Don't copy trunk-based from blog posts without feature flags — trunk without flags forces long branches by necessity.
    • Separate repos are a branching strategy: boundaries beat longer branches inside one repo.

    Best practices

    • One main/trunk branch per repo — avoid default branch confusion between master/main/develop.
    • Delete merged branches automatically (GitHub repo setting + local git fetch --prune).
    • Keep PRs small; use stacked PRs instead of week-long mega-branches.
    • Release branches get explicit end-of-life date and CI tier documented in runbook.

    Common mistakes

    • Using develop as default while prod deploys from main — two truths diverge silently.
    • Allowing direct commits to environment branches bypassing PR review.
    • Measuring velocity by branch count — incentivizes never merging.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    5 questions
    1AdvancedQuestionWhen would you choose environment branches over trunk-based deploy?+

    Answer

    Small teams with infrequent deploys and GitOps manifests per env — push to staging branch triggers Argo sync. Breaks at scale due to env drift; prefer trunk + promotion pipeline with same artifact.

    Follow-up

    How do you prevent staging branch from diverging structurally from prod?
    2AdvancedQuestionSquash merge vs merge commit for a regulated audit trail?+

    Answer

    Merge commits preserve PR boundary in graph and map to ticket/approval systems. Squash loses per-commit CI granularity but cleaner history. Hybrid: squash with PR metadata in commit message and retained PR audit log in GitHub.

    Follow-up

    Impact on git bisect?
    3AdvancedQuestionDesign branching for monorepo with 10 teams and daily releases.+

    Answer

    Mandatory trunk-based, ≤2 day branches, path-filtered CI, CODEOWNERS, merge queue, feature flags. No team-specific long-lived branches — use directories and ownership instead.

    Follow-up

    Cross-cutting refactor spanning 8 directories?
    4AdvancedQuestionHow do stacked PRs change CI design?+

    Answer

    Each PR CI runs against its base ref (parent PR branch), not main. Need fetch-depth and merge-base logic; optional batch validation before bottom-of-stack merges to main.

    Follow-up

    Tools: Graphite, gh stack, or custom?
    5AdvancedQuestionHotfix process when using release branches?+

    Answer

    Branch hotfix from release/x.y, CI expedited path, merge to release AND backport to main/develop, tag patch from release branch. Never fix only on main if prod runs release line.

    Follow-up

    Cherry-pick automation?

    Hands-on exercise

    Analyze branch age in a real or sample repo and propose a strategy with enforcement automation.

    • Identify branches >5 days diverged — root cause: size, dependency, or missing policy?
    • Draft CONTRIBUTING.md merge policy and add branch-name workflow.
    • Present histogram to team with recommended SLA.
    bash
    git fetch --all --prune
    git for-each-ref --format='%(refname:short) %(committerdate:iso8601)' refs/remotes/origin \
    | while read branch date; do
    base=$(git merge-base origin/main origin/$branch 2>/dev/null || echo "")
    [ -z "$base" ] && continue
    echo "$branch $(git rev-list --count $base..origin/$branch) commits ahead"
    done | sort -k2 -rn | head -20

    Summary

    You can select branching strategies — release trains, environment branches, trunk+flags, stacked PRs — based on integration cost, compliance, and CI capacity. Enforce with automation and measure branch age, not intentions.

    Ready to mark this lesson complete?Track your journey across the entire course.