Back to Software Engineering & Delivery Mastery Series

Part 11: Advanced Git Workflows — GitFlow, Trunk-Based & Monorepos

May 13, 2026 Wasil Zafar 42 min read

Your branching strategy determines your deployment frequency. This article gives you the complete decision framework for choosing between GitFlow, GitHub Flow, Trunk-Based Development, and monorepo strategies — with real-world tradeoffs and team-size guidance.

Table of Contents

  1. Introduction
  2. GitFlow
  3. GitHub Flow
  4. GitLab Flow
  5. Trunk-Based Development
  6. Comparison Table
  7. Monorepos vs Polyrepos
  8. Branch Protection & Policies
  9. Case Studies
  10. Exercises
  11. Conclusion & Next Steps

Introduction — Why Branching Strategies Matter

If you can only change one thing about your engineering organisation's delivery speed, change the branching strategy. Research from the DORA team (Accelerate, 2018) consistently shows that deployment frequency correlates strongly with team performance — and your branching model is the single biggest constraint on how often you can deploy.

A branching strategy is your team's agreement about when code splits from the main line, how long it lives in isolation, and what conditions must be met before it merges back. Get this wrong and you'll face merge conflicts measured in days, releases delayed by weeks, and hotfixes that break other features.

Key Insight: There is no universally "best" branching strategy. The right choice depends on your deployment model, team size, compliance requirements, and release cadence. This article gives you the framework to choose correctly.

The Relationship Between Branching and Deployment Frequency

Consider this spectrum: at one extreme, a team using full GitFlow with release branches might deploy every 2-4 weeks. At the other extreme, a team using trunk-based development deploys dozens of times per day. Both can be correct — for different contexts.

Branching Model vs Deployment Frequency Spectrum
flowchart LR
    A[GitFlow] --> B[GitLab Flow]
    B --> C[GitHub Flow]
    C --> D[Trunk-Based Dev]

    A --- E["Deploy: 2-4 weeks"]
    B --- F["Deploy: 1-2 weeks"]
    C --- G["Deploy: Daily"]
    D --- H["Deploy: Multiple/day"]

    style A fill:#132440,color:#fff
    style B fill:#16476A,color:#fff
    style C fill:#3B9797,color:#fff
    style D fill:#BF092F,color:#fff
                            

The critical question isn't "which strategy is best?" but rather "what does your business need?" A medical device company with FDA compliance needs GitFlow's rigour. A startup shipping a SaaS product needs trunk-based development's speed.

GitFlow — The Classic Enterprise Model

Vincent Driessen published "A successful Git branching model" in 2010, and it became the default for enterprise teams. GitFlow defines five branch types with strict rules about how code flows between them.

The Five Branch Types

Branch Lifetime Purpose Created From Merges Into
main Permanent Production-ready code
develop Permanent Integration branch main release, main
feature/* Temporary New feature work develop develop
release/* Temporary Release preparation develop main + develop
hotfix/* Temporary Production bug fixes main main + develop
GitFlow Branch Lifecycle
gitGraph
    commit id: "init"
    branch develop
    commit id: "dev-1"
    branch feature/login
    commit id: "feat-1"
    commit id: "feat-2"
    checkout develop
    merge feature/login id: "merge-feat"
    branch release/1.0
    commit id: "bump-version"
    commit id: "fix-typo"
    checkout main
    merge release/1.0 id: "v1.0" tag: "v1.0.0"
    checkout develop
    merge release/1.0 id: "back-merge"
    checkout main
    branch hotfix/1.0.1
    commit id: "critical-fix"
    checkout main
    merge hotfix/1.0.1 id: "v1.0.1" tag: "v1.0.1"
    checkout develop
    merge hotfix/1.0.1 id: "hotfix-merge"
                            

When GitFlow Works

GitFlow excels in specific contexts:

  • Multiple supported versions — If you maintain v2.x and v3.x simultaneously (desktop software, mobile SDKs, embedded firmware), GitFlow's release branches are essential
  • Scheduled releases — If your organisation releases on fixed cadences (quarterly, monthly), the release branch gives a stabilisation period
  • Regulatory compliance — When each release requires sign-off, audit trails, and traceability, GitFlow's explicit branching makes compliance documentation straightforward
  • Large teams with siloed features — When 50+ developers work on independent features that must be batched into releases

Common Pitfalls

Warning: GitFlow is not suitable for continuous deployment. If you deploy from main multiple times per day, the develop branch becomes an unnecessary bottleneck. Vincent Driessen himself added a note to his original post recommending simpler models for web applications.

The most common GitFlow failure is long-lived feature branches. When a feature branch lives for 3+ weeks, it diverges so far from develop that merging becomes a multi-day project. The solution — if you must use GitFlow — is to keep feature branches under 5 days and rebase frequently.

# GitFlow: Creating a feature branch
git checkout develop
git pull origin develop
git checkout -b feature/user-authentication

# Work on the feature...
git add .
git commit -m "feat: add JWT token validation"

# Keep up-to-date with develop (do this daily)
git fetch origin
git rebase origin/develop

# When ready, merge back to develop
git checkout develop
git merge --no-ff feature/user-authentication
git push origin develop
git branch -d feature/user-authentication
# GitFlow: Creating a release branch
git checkout develop
git checkout -b release/2.1.0

# Bump version numbers
echo "2.1.0" > VERSION
git add VERSION
git commit -m "chore: bump version to 2.1.0"

# Fix release-critical bugs (no new features!)
git commit -m "fix: correct pagination off-by-one"

# Merge to main AND develop when ready
git checkout main
git merge --no-ff release/2.1.0
git tag -a v2.1.0 -m "Release 2.1.0"
git push origin main --tags

git checkout develop
git merge --no-ff release/2.1.0
git push origin develop
git branch -d release/2.1.0

GitHub Flow — The Simplified Model

GitHub Flow strips branching down to its essence: there is main (always deployable) and feature branches (short-lived work). That's it. No develop branch. No release branches. No hotfix branches.

GitHub Flow — Simple and Direct
gitGraph
    commit id: "init"
    branch feature/search
    commit id: "search-1"
    commit id: "search-2"
    checkout main
    merge feature/search id: "PR #42"
    commit id: "deploy-1" tag: "deployed"
    branch feature/dark-mode
    commit id: "dark-1"
    checkout main
    branch fix/typo
    commit id: "typo-fix"
    checkout main
    merge fix/typo id: "PR #43"
    commit id: "deploy-2" tag: "deployed"
    merge feature/dark-mode id: "PR #44"
    commit id: "deploy-3" tag: "deployed"
                            

The GitHub Flow Process

  1. Create a branch from main with a descriptive name
  2. Add commits — small, atomic, well-described
  3. Open a Pull Request — start discussion, request review
  4. Review and discuss — CI runs tests automatically
  5. Merge — squash or merge commit into main
  6. Deploy — main is always deployable; deploy immediately
# GitHub Flow: The complete workflow
# Step 1: Create branch from main
git checkout main
git pull origin main
git checkout -b feature/add-search-api

# Step 2: Make commits
git add src/search.ts
git commit -m "feat: implement search endpoint with Elasticsearch"

git add tests/search.test.ts
git commit -m "test: add search endpoint integration tests"

# Step 3: Push and open PR
git push origin feature/add-search-api
# Open PR via GitHub CLI:
gh pr create --title "feat: add search API" \
    --body "Implements full-text search using Elasticsearch" \
    --reviewer @team-lead

# Step 4-5: After review approval, merge via GitHub UI or CLI
gh pr merge --squash

# Step 6: main is deployed automatically via CI/CD pipeline

GitHub Flow works best for:

  • Web applications and SaaS — where there's only one version in production
  • Small to medium teams (2-20 developers)
  • Continuous deployment — deploying multiple times per day
  • Products without multiple supported versions
Industry Example GitHub Engineering Team

GitHub's Own Deployment Practice

GitHub itself uses GitHub Flow (naturally). Their engineering team deploys to production dozens of times per day. Every merged PR triggers a deployment pipeline that includes automated tests, canary deployment, and progressive rollout. The average time from merge to production is under 15 minutes.

Key enabler: comprehensive automated testing (>50,000 tests) combined with feature flags for incomplete work. Engineers can merge code that isn't customer-visible yet.

continuous deployment feature flags progressive rollout

GitLab Flow — The Middle Ground

GitLab Flow sits between GitFlow's complexity and GitHub Flow's simplicity. It introduces environment branches that map to deployment environments, solving a problem GitHub Flow ignores: what if you can't deploy every merge immediately?

Environment Branches

In GitLab Flow, code flows downstream through environment branches:

# GitLab Flow: Environment branch promotion
# Developers merge to main (like GitHub Flow)
git checkout main
git merge --no-ff feature/new-dashboard

# Promote main → staging (manual or automated trigger)
git checkout staging
git merge main
git push origin staging
# → triggers staging deployment

# After staging validation, promote to production
git checkout production
git merge staging
git push origin production
# → triggers production deployment

Comparison with Other Flows

Aspect GitFlow GitHub Flow GitLab Flow
Long-lived branches main + develop main only main + environment branches
Release branches Yes (mandatory) No Optional
Deploy trigger Merge to main Merge to main Merge to environment branch
Staging environment Ad-hoc Feature branch deploys Dedicated branch
Best for Versioned releases Continuous deployment Regulated environments

Trunk-Based Development

Trunk-Based Development (TBD) is the most extreme simplification: everyone commits directly to main (the "trunk"), or uses feature branches that live for less than one day. There is no develop branch, no release branches, no integration branches. Main is always deployable because it's always being deployed.

Key Insight: Trunk-Based Development isn't reckless — it's disciplined. It requires the highest level of engineering maturity: comprehensive automated tests, feature flags, pair programming or rapid code review, and CI that runs in under 10 minutes.

Feature Flags for Incomplete Work

The apparent contradiction — "how do you merge unfinished features into main?" — is solved by feature flags. Code exists in main but is invisible to users until the flag is enabled:

// Feature flag pattern for trunk-based development
// All code merges to main immediately, but is gated behind flags

const featureFlags = {
    newCheckoutFlow: process.env.FF_NEW_CHECKOUT === 'true',
    darkMode: process.env.FF_DARK_MODE === 'true',
    aiRecommendations: process.env.FF_AI_RECS === 'true'
};

function renderCheckout(cart) {
    if (featureFlags.newCheckoutFlow) {
        // New checkout — still in development
        return renderNewCheckout(cart);
    }
    // Existing checkout — shown to all users
    return renderLegacyCheckout(cart);
}

// Feature flags can be:
// 1. Boolean (on/off)
// 2. Percentage-based (10% of users)
// 3. User-targeted (internal employees only)
// 4. Environment-specific (staging: on, production: off)
console.log("Feature flags loaded:", featureFlags);
# Trunk-Based Development: Typical developer workflow
# Start of day — pull latest trunk
git checkout main
git pull origin main

# Create a very short-lived branch (optional, max 1 day)
git checkout -b wz/add-search-index

# Make focused changes
git add src/search/indexer.ts
git commit -m "feat: add search indexer behind FF_SEARCH flag"

# Push and create PR (reviewed within hours, not days)
git push origin wz/add-search-index
gh pr create --title "feat: search indexer (flagged)" --reviewer @pair-partner

# PR is reviewed immediately (often within 30 minutes)
# After approval, merge and delete branch
gh pr merge --squash
git checkout main
git pull origin main
git branch -d wz/add-search-index

# The CI/CD pipeline deploys to production automatically
# Feature is invisible to users because FF_SEARCH=false in production

Trunk-Based Development at Scale

The world's largest engineering organisations use trunk-based development:

  • Google — 25,000+ engineers commit to a single monorepo trunk. Over 80,000 commits per day. Automated testing gates every change.
  • Meta — Engineers commit to trunk with "landcastle" (a pre-merge test system that validates changes before they land)
  • Netflix — Trunk-based with feature flags. Their system handles thousands of deployments per day across microservices
  • Spotify — Moved from GitFlow to trunk-based development as they scaled. Deployment frequency increased from weekly to many-times-daily
Research Finding DORA State of DevOps Report

Trunk-Based Development and Elite Performance

The DORA research programme (spanning 7 years and 36,000+ respondents) found that elite-performing teams are 2x more likely to practice trunk-based development than low performers. They also found that teams with branches living longer than one day had significantly lower deployment frequency and higher change failure rates.

The correlation is clear: short-lived branches (or no branches at all) correlate with higher throughput and better stability — contradicting the intuition that more branching equals more safety.

DORA metrics deployment frequency change failure rate

Workflow Comparison Matrix

Use this table to choose the right workflow for your team:

Dimension GitFlow GitHub Flow GitLab Flow Trunk-Based
Deploy frequency 2-4 weeks Daily 1-2 weeks Multiple/day
Team size 10-100+ 2-20 5-50 Any (with maturity)
Complexity High Low Medium Low (ops: High)
Release model Versioned Continuous Staged Continuous
CI requirement Moderate High High Very high
Feature flags needed No Optional Optional Essential
Multiple versions Yes No Yes No
Merge conflicts Frequent (large) Rare (small) Occasional Minimal

Monorepos vs Polyrepos

Orthogonal to branching strategy is repository structure. A monorepo contains all of an organisation's code in a single repository. A polyrepo (or multi-repo) approach uses one repository per service, library, or team.

Monorepo Advantages

  • Atomic changes — A single commit can update an API, its client library, and the documentation simultaneously
  • Code sharing — Shared libraries live alongside consuming code, eliminating versioning overhead
  • Unified CI — One pipeline, one set of tools, one configuration
  • Visibility — Every developer can see, search, and learn from all code
  • Dependency management — One version of each dependency across the entire organisation

Polyrepo Advantages

  • Team autonomy — Teams own their repo, their CI, their release cadence
  • Access control — Trivial to restrict access per repository
  • Tooling simplicity — Standard Git works without custom infrastructure
  • Smaller clone sizes — Developers only clone what they need
  • Independent deployability — Each service has its own lifecycle

Monorepo Tooling

Tool Language Key Feature Used By
Bazel Any Hermetic builds, remote caching Google, Stripe
Nx JS/TS Affected detection, computation caching Many OSS projects
Turborepo JS/TS Incremental builds, remote caching Vercel, many startups
Pants Python/Go/Java Fine-grained invalidation Toolchain
Lerna JS/TS Package publishing Babel, Jest (legacy)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "scripts": {
    "build": "turbo run build",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "dev": "turbo run dev --parallel"
  },
  "devDependencies": {
    "turbo": "^2.0.0",
    "typescript": "^5.4.0"
  }
}
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Branch Protection & Policies

Regardless of which branching strategy you choose, protecting your main branch is non-negotiable for professional teams. Branch protection rules enforce quality gates before code reaches production.

Essential Protection Rules

  • Required reviews — At least 1-2 approved reviews before merge
  • Status checks — CI must pass (build, test, lint) before merge is allowed
  • Signed commits — Verify author identity with GPG keys
  • Linear history — Require rebase or squash merge (no merge commits)
  • Up-to-date branches — Branch must be current with main before merging
  • No force push — Prevent history rewriting on protected branches
# GitHub branch protection via API (or configure in Settings > Branches)
# .github/settings.yml (using probot/settings app)
branches:
  - name: main
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 2
        dismiss_stale_reviews: true
        require_code_owner_reviews: true
      required_status_checks:
        strict: true
        contexts:
          - "ci/build"
          - "ci/test"
          - "ci/lint"
          - "security/snyk"
      enforce_admins: true
      required_linear_history: true
      restrictions:
        users: []
        teams: ["release-managers"]
# Setting up commit signing (required for signed commits policy)
# Generate GPG key
gpg --full-generate-key

# List keys and get the key ID
gpg --list-secret-keys --keyid-format=long
# Output: sec   rsa4096/ABC123DEF456 2024-01-01

# Configure Git to use the key
git config --global user.signingkey ABC123DEF456
git config --global commit.gpgsign true

# Export public key for GitHub
gpg --armor --export ABC123DEF456
# Paste output into GitHub > Settings > SSH and GPG keys

# Verify signed commits
git log --show-signature -1

Case Studies

Case Study Google Engineering

Google's Monorepo and Trunk-Based Development

Google stores virtually all of its code — billions of lines across thousands of projects — in a single monorepo called "google3". Over 25,000 engineers commit to the same trunk daily, producing 80,000+ commits per day.

How it works:

  • Custom VCS called Piper (Perforce-derived) handles the scale Git cannot
  • TAP (Test Automation Platform) runs affected tests for every change
  • Changes go through mandatory code review before submission
  • Feature flags gate all incomplete work
  • Automated code health tools refactor code globally

Result: Despite enormous scale, Google ships hundreds of production changes per hour with low failure rates.

monorepo trunk-based 25k+ engineers
Case Study Atlassian

Atlassian's GitFlow to Trunk-Based Migration

Atlassian (makers of Jira and Bitbucket) initially promoted and used GitFlow extensively. As their products moved to cloud-first SaaS delivery, they found GitFlow's overhead unsustainable:

  • Merge conflicts consumed 15-20% of developer time
  • Release branches delayed features by 2-3 weeks
  • Hotfix branches created divergence nightmares

Migration: They moved to trunk-based development with feature flags over 6 months. Deployment frequency increased from bi-weekly to daily. Their documentation (atlassian.com/git) now recommends trunk-based for SaaS products.

migration SaaS delivery speed

Exercises

Exercise 1 — Strategy Selection: Your company has 40 developers building an on-premises enterprise application with quarterly releases. Customers run versions 3.x, 4.x, and 5.x simultaneously. Which branching strategy do you recommend and why? What would change if the product moved to SaaS?
Exercise 2 — Migration Plan: You've been hired to migrate a team from GitFlow to trunk-based development. The team currently has feature branches that live for 2-3 weeks. Write a phased migration plan (4-6 phases) that reduces branch lifetime incrementally without disrupting delivery.
Exercise 3 — Monorepo Evaluation: Your organisation has 12 microservices, each in its own repository. Teams spend significant time keeping shared libraries in sync. Evaluate whether migrating to a monorepo (using Nx or Turborepo) would improve or harm delivery speed. List 5 criteria for your decision.
Exercise 4 — Branch Protection Design: Design a branch protection policy for a fintech startup (10 developers, deploying 3x/day, SOC 2 compliant). Specify: required reviewers, status checks, merge strategy, and commit signing requirements. Justify each choice.

Conclusion & Next Steps

Your branching strategy is not a religious choice — it's an engineering decision driven by your deployment model, team size, compliance needs, and product lifecycle. GitFlow serves versioned software with regulatory requirements. GitHub Flow serves SaaS teams deploying continuously. Trunk-based development serves organisations that invest in engineering maturity to achieve maximum delivery speed.

The trend across the industry is clear: shorter-lived branches, faster merges, and more automation. If your branches live longer than 2 days, you're accumulating integration risk. If they live longer than a week, you're almost certainly creating unnecessary merge conflicts.

Next in the Series

In Part 12: Build Systems & Dependency Management, we'll explore how code transforms into deployable artifacts — npm, Maven, Gradle, pip, lock files, reproducible builds, and the dependency management practices that prevent "works on my machine" failures.