Git Flow: The Original Branching Strategy Explained

Master the Git Flow branching model with master, develop, feature, release, and hotfix branches. Learn when to use it, common pitfalls, and production best practices.

published: reading time: 13 min read updated: March 31, 2026

Git Flow: The Original Branching Strategy Explained

Git Flow is the branching model that started it all. Created by Vincent Driessen in 2010, it introduced a structured approach to managing parallel development streams that became the default choice for teams worldwide for over a decade.

The model defines five distinct branch types, each with a specific purpose and lifecycle. It assumes a project with scheduled releases, where features are developed independently, tested together, and shipped on a predictable cadence. Understanding Git Flow is essential — even if you never use it, every modern branching strategy exists as a reaction to it.

This post covers the complete Git Flow model, when it makes sense, when it doesn’t, and how to avoid the traps that have derailed countless teams.

When to Use / When Not to Use

Use Git Flow When

  • Scheduled releases — Your team ships on a calendar cadence (monthly, quarterly) rather than continuously
  • Multiple versions in production — You need to maintain v1.x while developing v2.x simultaneously
  • Large teams with defined roles — Separate developers, QA, and release managers benefit from the structure
  • Enterprise or regulated environments — Audit trails, change management, and release gates are mandatory
  • Desktop or mobile applications — Where each release requires a build, sign, and distribution process

Do Not Use Git Flow When

  • Continuous deployment — If you deploy on every merge, the release branch overhead is wasted
  • Small teams (1-5 people) — The ceremony outweighs the coordination benefit
  • Web services and SaaS — Where “release” means pushing to production, not packaging a binary
  • Fast-moving startups — Where the time from feature-complete to production should be minutes, not days

Core Concepts

Git Flow defines five permanent and temporary branch types:

BranchPrefixPurposeLifetime
mainProduction-ready code onlyPermanent
developIntegration branch for featuresPermanent
feature/*feature/New features and enhancementsTemporary
release/*release/Preparation for a production releaseTemporary
hotfix/*hotfix/Emergency fixes to productionTemporary

The two permanent branches form the backbone: main holds only production-ready code, while develop serves as the integration branch where completed features merge before release.


graph LR
    A[main] --> B[develop]
    B --> C[feature/login]
    B --> D[feature/search]
    C --> B
    D --> B
    B --> E[release/1.0]
    E --> A
    E --> B
    A --> F[hotfix/1.0.1]
    F --> A
    F --> B

Architecture and Flow Diagram

The complete Git Flow lifecycle from feature inception through production release:


graph TD
    A[main - Production] -->|fork| B[develop - Integration]
    B -->|fork| C[feature/user-auth]
    B -->|fork| D[feature/api-v2]
    C -->|merge| B
    D -->|merge| B
    B -->|fork| E[release/2.0]
    E -->|bug fixes| E
    E -->|merge + tag| A
    E -->|merge| B
    A -->|fork| F[hotfix/security-patch]
    F -->|merge + tag| A
    F -->|merge| B

Step-by-Step Guide

1. Initialize Git Flow

Most teams use the git-flow CLI extension or follow the manual workflow:


# Initialize with defaults
git flow init

# This creates develop from main and sets up branch prefixes
# Default prefixes: feature/, release/, hotfix/, support/

2. Develop a Feature

Features branch from develop and merge back into develop:


# Start a new feature
git flow feature start user-authentication

# This creates and checks out feature/user-authentication from develop

# Work on the feature...
git add .
git commit -m "feat: implement OAuth2 login flow"

# Finish the feature (merges to develop and deletes the branch)
git flow feature finish user-authentication

Manual equivalent:


git checkout develop
git checkout -b feature/user-authentication
# ... work ...
git commit -am "feat: implement OAuth2 login flow"
git checkout develop
git merge --no-ff feature/user-authentication
git branch -d feature/user-authentication

The --no-ff flag is critical — it preserves the feature branch topology in history even after the merge.

3. Prepare a Release

When develop has enough features for a release:


# Start a release branch
git flow release start 2.0.0

# This creates release/2.0.0 from develop

# Bump version numbers, fix last-minute bugs
git commit -am "chore: bump version to 2.0.0"

# Finish the release (merges to main AND develop, tags main)
git flow release finish 2.0.0
git push origin main develop --tags

The release branch is the only branch that merges to both main and develop. This ensures:

  • main gets the tagged release
  • develop gets any release-only bug fixes

4. Handle a Hotfix

When production breaks, you don’t wait for the next release:


# Start a hotfix from main
git flow hotfix start 2.0.1

# Fix the bug
git commit -am "fix: resolve null pointer in payment processing"

# Finish (merges to main AND develop, tags main)
git flow hotfix finish 2.0.1
git push origin main develop --tags

Hotfixes bypass develop entirely because the fix must go to production immediately. The merge back to develop prevents the bug from reappearing in the next release.

Production Failure Scenarios + Mitigations

ScenarioWhat HappensMitigation
Release branch driftsrelease/* lives for weeks, accumulating fixes that never reach developTime-box release branches to 1-2 weeks max; merge develop into release daily
Hotfix merge conflictsHotfix merges cleanly to main but conflicts with developKeep develop close to main; run integration tests on hotfix before merging to develop
Feature branch rotLong-lived feature branches become impossible to merge back to developRebase features on develop at least every 2-3 days; set a 2-week max lifetime
Tag collisionTwo releases get the same version tagEnforce semantic versioning in CI; reject duplicate tags in pre-receive hooks
Develop is brokenSomeone merges broken code to develop, blocking all featuresRequire CI to pass before merge; use protected branches

Trade-offs

AspectAdvantageDisadvantage
StructureClear roles for every branch typeOverhead for small teams
Release managementDedicated release branch for stabilizationSlows down time-to-production
HotfixesFast path from production fix to releaseMerge conflicts between hotfix and develop
HistoryClean, readable topology with —no-ff mergesMore merge commits than linear history
Parallel workMultiple features and releases simultaneouslyContext switching between branches
Learning curveWell-documented, widely understoodNew developers must learn five branch types

Implementation Snippets

Git Flow CLI Cheat Sheet


# Initialize
git flow init

# Features
git flow feature start <name>
git flow feature finish <name>
git flow feature publish <name>
git flow feature pull <name>

# Releases
git flow release start <version>
git flow release finish <version>

# Hotfixes
git flow hotfix start <version>
git flow hotfix finish <version>

# Support (long-term support branches)
git flow support start <version>
git flow support finish <version>

CI Integration — Protecting Branches

# .github/workflows/git-flow.yml
name: Git Flow Validation
on:
  pull_request:
    branches: [main, develop, "release/**"]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Verify --no-ff merge
        run: |
          git log --oneline --graph -20
          # Verify merge commits exist for feature branches
      - name: Version tag check
        run: |
          if [[ $GITHUB_REF == refs/tags/* ]]; then
            TAG=${GITHUB_REF#refs/tags/}
            if ! echo "$TAG" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
              echo "Tag must follow semver: $TAG"
              exit 1
            fi
          fi

Pre-commit Hook — Prevent Direct Commits to main


#!/bin/bash
# .git/hooks/pre-commit
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)

if [ "$BRANCH" = "main" ]; then
  echo "ERROR: Direct commits to main are not allowed."
  echo "Use hotfix branches for production fixes."
  exit 1
fi

if [ "$BRANCH" = "develop" ]; then
  echo "WARNING: Direct commits to develop are discouraged."
  echo "Use feature branches instead."
fi

Observability Checklist

  • Logs: Track branch creation and merge events in your CI/CD pipeline logs
  • Metrics: Measure average feature branch lifetime, release branch duration, and hotfix frequency
  • Traces: Correlate release tags with deployment events and incident reports
  • Alerts: Alert when release branches exceed 2 weeks, when develop has failing CI, or when hotfix rate exceeds 2 per month
  • Dashboards: Track branches-per-developer, merge conflict rate, and time-from-feature-start-to-production

Security and Compliance Notes

  • Branch protection: Enable required reviews and status checks on main and develop in GitHub/GitLab settings
  • Signed tags: Use git tag -s for release tags to cryptographically sign version markers
  • Access control: Restrict who can merge to main (release managers only) and develop (senior developers)
  • Audit trail: The --no-ff merge strategy provides a clear audit trail of which features shipped in which release
  • Compliance: For regulated industries, release branches serve as change management artifacts — document what was tested and approved

Common Pitfalls and Anti-Patterns

  1. The Forever Release — A release branch that lives for months becomes a second develop. Time-box releases strictly.
  2. Skipping develop — Committing directly to main for “small changes” breaks the model. Use hotfix branches for everything.
  3. Feature branch hoarding — Developers who keep features local for weeks create merge nightmares. Push and open PRs early.
  4. Release branch feature creep — Adding new features to a release branch defeats its purpose. Only bug fixes belong there.
  5. Ignoring develop sync — Not merging develop into active release branches causes painful integration at the end.
  6. Tag-only releases — Tagging develop instead of main means your production tag points to untested code.
  7. No rebase policy — Feature branches that never rebase on develop accumulate divergence. Rebase regularly.

Quick Recap Checklist

  • main branch contains only production-ready, tagged code
  • develop branch is the integration point for all features
  • Features branch from and merge back to develop with --no-ff
  • Release branches are created from develop, merged to both main and develop
  • Hotfixes branch from main, merge to both main and develop
  • All releases are tagged with semantic versions
  • Branch protection rules prevent direct commits to main
  • CI validates all merges to develop and release/*
  • Release branches are time-boxed to 1-2 weeks
  • Feature branches are rebased on develop regularly

Branching Strategy Comparison

AspectGit FlowGitHub FlowTrunk-Based Development
Best team size10-1001-2020-10000+
Release cadenceScheduled (weeks)Continuous (minutes)Continuous (multiple/day)
Branch count5 types2 types1 permanent + short-lived
ComplexityHighLowMedium
CI requirementModerateStrongVery strong
Feature flagsOptionalOptionalRequired
Hotfix pathDirect from mainSame as any PRFlag toggle or quick PR
Learning curveSteep (5 branch types)Gentle (1 rule)Moderate (discipline-heavy)
Merge frequencyLow (per release)Medium (per feature)Very high (multiple/day)
Multi-versionYes (support branches)NoNo

Interview Q&A

What is the difference between a release branch and a hotfix branch in Git Flow?

A release branch is created from develop to prepare for an upcoming production release. It allows final bug fixes, version bumps, and documentation updates without blocking new feature development on develop. It merges to both main and develop.

A hotfix branch is created from main to address a critical production issue that cannot wait for the next scheduled release. It bypasses develop entirely for speed, then merges back to both main and develop to prevent the bug from reappearing.

Why does Git Flow use --no-ff merges for feature branches?

The --no-ff (no fast-forward) flag forces Git to create a merge commit even when a fast-forward merge would be possible. This preserves the topology of the feature branch in the commit history, making it clear which commits belonged to which feature.

Without --no-ff, the feature branch's commits would be linearly applied to develop, losing the visual grouping that shows what was developed together. This is especially valuable for auditing and understanding release contents.

What happens if you forget to merge a hotfix back to develop?

The bug fix exists in main and is deployed to production, but develop still contains the buggy code. When the next release is created from develop, the bug reappears because the hotfix was never integrated into the development stream.

This is one of the most common Git Flow mistakes. The git flow hotfix finish command handles both merges automatically, which is why using the CLI extension is recommended over manual branch management.

Can Git Flow work with continuous deployment?

Technically yes, but it is not a good fit. Git Flow assumes a separation between "integration" (develop) and "release" (release branch), which contradicts the continuous deployment principle of deploying every passing merge to main.

Teams practicing continuous deployment typically use GitHub Flow or Trunk-Based Development instead, where every merge to main is deployable and feature flags control rollout rather than branch management.

How do you handle multiple major versions simultaneously in Git Flow?

Git Flow supports this through support branches. When you need to maintain v1.x while developing v2.x, you create a support branch from the last v1.x release tag:

git flow support start 1.x

Hotfixes for v1.x branch from the support branch, not from main. This keeps the maintenance stream isolated from active development. However, managing multiple support branches adds significant complexity, which is why many teams prefer to minimize the number of actively maintained versions.

Resources

Category

Related Posts

Choosing a Git Team Workflow: Decision Framework for Branching Strategies

Decision framework for selecting the right Git branching strategy based on team size, release cadence, project type, and organizational maturity. Compare Git Flow, GitHub Flow, and more.

#git #version-control #branching-strategy

GitLab Flow: Environment and Release-Based Branching

Master GitLab Flow — the branching strategy that combines Git Flow simplicity with deployment pipelines. Learn environment-based and release-based branching patterns.

#git #version-control #branching-strategy

Automated Release Pipeline: From Git Commit to Production Deployment

Build a complete automated release pipeline with Git, CI/CD, semantic versioning, changelog generation, and zero-touch deployment. Hands-on tutorial for production-ready releases.

#git #version-control #ci-cd