Skip to content

Git Workflows & Branching Strategies — Primer

Why This Matters

Every team that uses Git eventually answers the same question: "How do we organize branches, releases, and collaboration?" The answer is a workflow — a set of conventions about when you branch, how you merge, and what triggers a deploy. Choose the wrong workflow and your team drowns in merge conflicts, broken builds, and release delays. Choose the right one and your CI/CD pipeline hums along while developers stay productive.

The existing git topic covers commands and fundamentals. The git-advanced topic covers internals, rebase mechanics, and recovery. This topic sits on top: it is the strategy layer — which workflow fits your team, why, and how to operate it day-to-day.

Etymology note: The concept of a "branching model" barely existed before distributed VCS. In SVN/CVS, branching was expensive and merging was painful, so teams avoided branches. Git made branching cheap (a branch is just a 41-byte pointer file), which unlocked the explosion of workflow strategies starting around 2008-2010.

Core Concepts

1. Trunk-Based Development (TBD)

Origin: Google and Facebook have practiced trunk-based development since the mid-2000s. Google's monorepo (with 86 TB of history as of 2015) runs on a single trunk. Facebook's mobile team moved to trunk-based in 2013 after GitFlow-style branching caused week-long merge freezes.

Mental model: Everyone commits to main (the trunk). Feature branches, if they exist, live less than one day. Long-lived branches are forbidden. Features that are not ready for users are hidden behind feature flags rather than kept on separate branches.

main: ──A──B──C──D──E──F──G──H──  (continuous)
              │       │
              └─fix─┘  └─feat─┘    (short-lived, <1 day)

Key properties: - Branches live hours, not days - Feature flags decouple deploy from release - CI runs on every push to main - Deployments are continuous or near-continuous - Merge conflicts are rare because branches are short

When to use: - High CI/CD maturity (automated tests, fast builds) - Teams deploying multiple times per day - SaaS/web services where you control the runtime - Teams with strong test culture and feature flag infrastructure

When NOT to use: - You ship versioned software (desktop apps, SDKs, firmware) - Your CI pipeline takes >30 minutes (branches pile up) - You have compliance requirements that mandate release branches - Your team lacks automated testing discipline (broken trunk = everyone blocked)

Mnemonic: "TBD = Tiny Branch Duration." If your branch survives overnight, it is not trunk-based development.

2. GitHub Flow

Origin: Scott Chacon (GitHub co-founder) described GitHub Flow in 2011 as a deliberate simplification. GitHub's own team found GitFlow too heavy — they deployed 5-10 times per day and needed something lighter.

Mental model: There is one eternal branch: main. Every change starts as a feature branch off main, gets a pull request, passes CI, gets reviewed, and merges back to main. Deployments happen from main (or directly from the PR branch for preview environments).

main:    ──A──────B──────C──────D──────
              \              /
feature:       └──E──F──G──┘
                  PR + CI + review

Key properties: - main is always deployable - Feature branches are short-lived (hours to a few days) - Pull requests are the integration gate - CI runs on the PR branch before merge - No release branches, no develop branch, no hotfix branches

When to use: - Teams of 2-20 developers - Continuous delivery (you deploy what is on main) - Web applications and SaaS products - Teams that want simplicity without the extreme discipline of TBD

When NOT to use: - You need to support multiple released versions simultaneously - You have a formal release/QA cycle that takes days - Your team lets feature branches live for weeks (this is GitHub Flow in name only)

Mnemonic: "One branch to rule them all." If you have more than main + short feature branches, you have outgrown GitHub Flow.

3. GitFlow

Origin: Vincent Driessen published "A Successful Git Branching Model" on January 5, 2010. It became the most-linked Git workflow article in history. In 2020, Driessen added a "note of reflection" to the post, acknowledging that GitFlow is not suitable for teams practicing continuous delivery — it was designed for software that ships named versions (1.0, 2.0, etc.).

Mental model: Two long-lived branches: main (production releases) and develop (integration). Feature branches come off develop. When you are ready to release, you cut a release/X.Y branch from develop, stabilize it, then merge it to both main (tagged) and back to develop. Hotfixes branch from main and merge to both main and develop.

main:      ──v1.0─────────────v1.1──────────v1.2──
                \               /               /
release:         \          ──R1.1──           /
                  \        /        \         /
develop:    ──────A──B──C──D──E──F──G──H──I──J──
                   \    /        \      /
feature:            └──┘          └────┘

Key properties: - main only receives merges from release and hotfix branches (every commit on main is a release) - develop is the integration branch - Feature branches come from and merge to develop - Release branches stabilize the code (bugfixes only, no new features) - Hotfix branches enable emergency patches to production

When to use: - Versioned software products (desktop apps, mobile apps, SDKs, libraries) - Teams that ship named releases on a schedule (quarterly, monthly) - Multiple versions supported simultaneously in production - Compliance environments requiring release artifacts and audit trails

When NOT to use: - SaaS teams deploying multiple times per day (way too much ceremony) - Small teams (<5 developers) — the overhead is not justified - Teams with high CI/CD maturity — GitFlow adds friction for little benefit

Mnemonic: "DFRHM" — Develop, Feature, Release, Hotfix, Main. Five branch types. If you cannot name all five, you are doing GitFlow wrong.

Memory aid: Think of GitFlow as a train schedule — releases depart on a schedule, features board during development, and hotfixes are emergency stops.

4. GitLab Flow

Origin: GitLab proposed this in 2014 as a middle ground between GitHub Flow (too simple for some) and GitFlow (too complex for most). It introduces environment branches that model your deployment pipeline.

Mental model: main is the development trunk. Downstream branches represent environments: staging, production, or version branches like 1.0-stable. Code flows one direction: main -> staging -> production. You never commit directly to environment branches — you merge from upstream.

main:        ──A──B──C──D──E──F──
                    \        \
staging:             └──B──C──D──E──
                           \
production:                 └──C──D──

Key properties: - Environment branches model the promotion pipeline - Merges flow downstream only (main -> staging -> production) - Each environment branch is always deployable to its environment - Cherry-picks are discouraged; full merges are preferred - Issue tracking integration (branches link to issues)

When to use: - Teams with distinct staging/production environments - Organizations that need a gated promotion model - Teams that want GitHub Flow simplicity but with environment awareness - Projects using GitLab CI/CD with environment-specific deploy jobs

When NOT to use: - You have a single deploy target (just use GitHub Flow) - You need to support multiple released versions (use release branching) - Your promotion model is more complex than linear (e.g., regional deploys)

Mnemonic: "Water flows downhill." Code always merges downstream through environments — never upstream.

5. Release Branching

Origin: This is the oldest pattern, predating Git itself. Linux kernel development used release branches (2.6.x stable series). It is still the standard for projects that must support multiple released versions: databases, operating systems, frameworks, SDKs.

Mental model: Development happens on main. When a release is ready, you cut a release/X.Y branch. That branch receives only bugfixes and security patches (cherry-picked or targeted). main continues forward development. Multiple release branches can coexist.

main:          ──A──B──C──D──E──F──G──H──I──
                    \           \
release/1.0:         └──B'──P1──P2──P3  (maintenance)
                                \
release/2.0:                     └──E'──P4  (maintenance)

Key properties: - Multiple release branches coexist, each receiving patches - Cherry-picks carry fixes from main to release branches (or vice versa) - Each release branch has its own CI pipeline and artifact build - Version tags mark specific releases (v1.0.0, v1.0.1, v1.0.2) - EOL (end-of-life) dates govern when branches stop receiving patches

When to use: - Multiple versions in the field simultaneously (PostgreSQL 14/15/16/17) - Enterprise software with LTS commitments - Open-source libraries with semver and backport policies - Any project where users cannot be forced to upgrade

When NOT to use: - SaaS products (you only run one version) - Small projects with a single deploy target - Teams without the discipline to cherry-pick consistently

Mnemonic: "Branch per promise." Every release branch is a promise to your users: "we will patch this version until date X."


Decision Framework

Choosing a workflow is not a technical decision — it is an organizational one. The right workflow depends on how your team works, not which workflow has the coolest diagram.

The Four Questions

Question If the answer is... Consider...
How often do you deploy? Multiple times/day TBD or GitHub Flow
Weekly/biweekly GitHub Flow or GitLab Flow
Monthly/quarterly GitFlow or Release Branching
How many versions do you support? One (SaaS) TBD, GitHub Flow, or GitLab Flow
2-3 concurrent Release Branching
Many (LTS model) Release Branching
What is your CI/CD maturity? Full automation, fast builds TBD
Automated tests, moderate builds GitHub Flow
Manual QA stages GitFlow or GitLab Flow
What is your team size? 1-5 TBD or GitHub Flow
5-20 GitHub Flow or GitLab Flow
20+ with sub-teams GitLab Flow or GitFlow

Quick Decision Tree

Do you ship versioned software with LTS support?
├── YES → Release Branching (possibly with GitFlow for the development phase)
└── NO → Do you deploy multiple times per day?
         ├── YES → Do you have feature flags and fast CI?
         │         ├── YES → Trunk-Based Development
         │         └── NO  → GitHub Flow
         └── NO  → Do you have staging/production promotion gates?
                   ├── YES → GitLab Flow
                   └── NO  → GitHub Flow

Mnemonic: "FAST" — Frequency, Active versions, Stage gates, Team size. Walk through these four factors and the workflow picks itself.


Merge vs Rebase vs Squash

Every workflow requires merging code. The how matters as much as the when.

Merge Commit (git merge --no-ff)

Creates a merge commit that preserves the complete branch history.

main:    ──A──────M──
              \  /
feature:       B──C

Pros: - Full history preserved — every individual commit is visible - git bisect works perfectly across the branch - Merge commits serve as documentation ("Feature X was integrated here") - Easy to revert an entire feature: git revert -m 1 <merge-SHA>

Cons: - History can become noisy with many small branches - git log --oneline on main shows merge commits between feature commits - GitFlow's develop branch becomes particularly hard to read

Best for: GitFlow, Release Branching, teams that value audit trails.

Rebase (git rebase main then fast-forward merge)

Replays feature commits on top of main, creating a linear history.

Before:  main: ──A──B         After:  main: ──A──B──C'──D'
              \                    (linear, no merge commit)
feature:       C──D

Pros: - Clean, linear history - git log --oneline reads like a story - No merge commits cluttering the log - git bisect works on a single line

Cons: - Rewrites commit SHAs (C becomes C') — dangerous on shared branches - Requires force-push if the branch was already pushed - Conflict resolution happens per-commit during rebase (can be tedious) - Loses the "this was a branch" information

Best for: Trunk-based development, small teams, personal branches before merging.

Squash Merge (git merge --squash)

Combines all feature branch commits into a single commit on main.

Before:  main: ──A──B         After:  main: ──A──B──S
              \                    (S = squashed A+B+C from feature)
feature:       C──D──E

Pros: - Extremely clean history — one commit per feature/PR - Easy to revert a feature: git revert <squash-SHA> - Hides messy "WIP" and "fix typo" commits from main

Cons: - Loses individual commit granularity — git bisect cannot find which part of a feature caused a bug - Large features become one enormous diff - Original commit messages and authorship are lost (only the squash message remains) - The feature branch cannot be deleted safely until you verify the squash

Best for: GitHub Flow with strict PR hygiene, teams that want one-commit-per-PR on main.

Comparison Table

Factor Merge Rebase Squash
History readability Noisy but complete Clean and linear Minimal and clean
git bisect granularity Full Full Per-PR only
Feature revert ease revert -m 1 Revert each commit revert <SHA>
Shared branch safety Safe Dangerous Safe
Commit authorship Preserved Preserved Lost (squash author only)
git blame detail Per-commit Per-commit Per-PR

Mnemonic: "MRS" — Merge (complete history), Rebase (linear beauty), Squash (one commit per story). Each sacrifices something the others preserve.


CI/CD Mapping

Each workflow implies a different CI/CD pipeline design.

Trunk-Based Development CI/CD

Push to main → Run full test suite → Auto-deploy to production
                                      (feature flags control visibility)
  • CI runs on every push to main (the only branch that matters)
  • Deployments are automatic — if tests pass, code ships
  • Feature flags are the release mechanism, not branches
  • Rollback = turn off the flag (or deploy previous commit)

GitHub Flow CI/CD

Push to feature branch → Run tests on PR → Review + Approve
→ Merge to main → Run tests → Deploy to production
  • CI runs on PR branches (catch failures before merge)
  • Main is always deployable — deploy on merge or on schedule
  • Preview environments per PR are a common enhancement
  • Rollback = revert the merge commit

GitFlow CI/CD

Push to develop → Run tests → Nightly build
Cut release/X.Y → Run full QA suite → Fix bugs on release branch
Merge to main → Tag vX.Y.Z → Build release artifacts → Deploy
  • CI runs on develop (integration tests), release branches (QA), and main (release builds)
  • Deploy artifacts come from tagged commits on main
  • Hotfix branches trigger emergency CI + deploy pipeline
  • Multiple CI pipeline definitions for different branch patterns

GitLab Flow CI/CD

Push to main → Run tests → Merge to staging → Deploy to staging
→ Verify in staging → Merge to production → Deploy to production
  • CI runs on main (unit tests) and environment branches (integration/deployment tests)
  • Each environment branch triggers deployment to its target
  • Promotion is a merge, not a deploy command
  • Rollback = revert the merge on the environment branch

Release Branching CI/CD

Push to main → Run tests → Continue development
Cut release/X.Y → Run release QA → Tag vX.Y.0 → Build artifacts
Cherry-pick fix → Tag vX.Y.1 → Build patch artifacts
  • Each release branch has its own CI configuration (may target different OS versions, SDKs)
  • Artifact builds are triggered by version tags
  • Backport CI validates cherry-picks on older branches
  • Matrix builds for multiple supported versions

Etymology & History

Understanding where these workflows came from helps you understand their design assumptions.

Workflow Year Creator Context
Release Branching ~2000 Linux kernel community Supporting multiple kernel series (2.4.x, 2.6.x)
GitFlow 2010 Vincent Driessen Blog post aimed at teams shipping versioned software
GitHub Flow 2011 Scott Chacon (GitHub) Reaction to GitFlow's complexity for web apps
GitLab Flow 2014 GitLab team Middle ground for teams with environment promotion
Trunk-Based Dev ~2005 (formalized ~2017) Google, Facebook, Paul Hammant Scaled engineering at Google; Hammant's trunkbaseddevelopment.com

Driessen's 2010 blog post ("A Successful Git Branching Model") was the watershed moment. Before it, most teams either committed to trunk or invented ad-hoc branching. Driessen gave teams a vocabulary: develop, feature, release, hotfix. The model spread virally — and was adopted by many teams for whom it was a poor fit (SaaS teams shipping daily do not need release branches).

Google's monorepo and trunk-based development showed that even at massive scale (25,000+ engineers), a single trunk works — if you have the tooling (Piper, CitC, TAP) and cultural discipline. Most teams do not have Google-scale tooling, which is why GitHub Flow is the most commonly adopted workflow in practice.

The 2020 reckoning: Driessen updated his original blog post with a note:

"If your team is doing continuous delivery of software, I would suggest to adopt a much simpler workflow (like GitHub flow) instead of trying to shoehorn git-flow into your team."

This was significant — the creator of GitFlow explicitly said it is not for everyone.


Quick Reference

Workflow Cheatsheet

Workflow Long-lived branches Feature branches Release mechanism Best for
Trunk-Based main only Hours (<1 day) Feature flags + continuous deploy SaaS, high CI/CD maturity
GitHub Flow main only Days (1-3) Deploy from main Most web teams
GitFlow main + develop Days to weeks Release branches + tags Versioned software
GitLab Flow main + env branches Days (1-3) Merge to env branch Teams with staging gates
Release Branching main + release/* Varies Cherry-pick to release branch Multi-version support

Merge Strategy Quick Pick

Your situation Use
You want full history and easy feature reverts Merge commits
You want clean linear history on main Rebase
You want one commit per PR on main Squash merge
You are on a shared branch others have pulled Never rebase
You need git bisect granularity inside a feature Do NOT squash

Memory Aids Summary

  • TBD = Tiny Branch Duration — branches die in hours
  • GitHub Flow = "One branch to rule them all" — just main + feature branches
  • GitFlow = DFRHM — Develop, Feature, Release, Hotfix, Main
  • GitLab Flow = "Water flows downhill" — always merge downstream
  • Release Branching = "Branch per promise" — each branch is a support commitment
  • FAST decision framework — Frequency, Active versions, Stage gates, Team size
  • MRS merge strategy — Merge (complete), Rebase (linear), Squash (minimal)

Wiki Navigation

Prerequisites

  • Git Workflows Flashcards (CLI) (flashcard_deck, L1) — Git Workflows & Branching Strategies