Learning Objectives

By the end of this module, you will be able to:

  • Explain the core philosophy behind Git Flow, GitHub Flow, GitLab Flow, and trunk-based development
  • Design a branching strategy that matches your team's size, release cadence, and deployment model
  • Implement Git Flow's multi-branch model with feature, develop, release, hotfix, and main branches
  • Apply GitHub Flow's lightweight pull-request-driven workflow
  • Use environment branches and release branches in GitLab Flow
  • Manage releases with branches and tags across different strategies
  • Evaluate trade-offs between long-lived branches and short-lived feature branches

1. Why Branching Strategies Matter

A branching strategy is the contract your team follows about when branches are created, how they're named, where they merge, and who approves them. Without one, teams quickly devolve into merge conflict hell, unclear release states, and "works on my branch" syndrome.

The Core Tension

Every branching strategy navigates a fundamental tension:

Isolation ◄──────────────────────► Integration

Long-lived branches                Short-lived branches
More divergence                    Less divergence
Bigger merges                      Smaller merges
More control                       More speed

More isolation means developers work undisturbed but drift apart. More integration means developers stay in sync but face more frequent coordination.

Key Factors in Choosing a Strategy

FactorFavors Long-Lived BranchesFavors Short-Lived Branches
Team sizeLarge (10+)Small (2–8)
Release cadenceScheduled (monthly/quarterly)Continuous (daily/hourly)
Deployment modelOn-premise / versioned softwareSaaS / web apps
Regulatory requirementsHigh (audit trails required)Low (move fast)
QA processManual / multi-stageAutomated CI/CD
Customer supportMultiple versions in productionSingle production version

2. Git Flow

Git Flow was introduced by Vincent Driessen in 2010 and became the dominant branching model for years. It's designed for projects with scheduled releases and multiple versions in production.

Branch Structure

                        ┌─────────────────────────────────────────────┐
                        │                  main                       │
                        │  (production-ready, tagged releases only)   │
                        └──────┬──────────────┬───────────────────────┘
                               │              │
                          tag v1.0        tag v1.1
                               │              │
┌──────────────────────────────┴──────────────┴───────────────────────┐
│                          develop                                     │
│  (integration branch — always the latest delivered development)     │
└───┬──────────┬──────────┬───────────┬──────────┬────────────────────┘
    │          │          │           │          │
    ▼          ▼          ▼           ▼          ▼
 feature/   feature/   release/    hotfix/   feature/
 login      search     1.1         1.0.1     dashboard

The Five Branch Types

1. main (or master)

  • Contains only production-ready code
  • Every commit on main is a release, tagged with a version number
  • Never commit directly to main

2. develop

  • The integration branch where features come together
  • Always reflects the latest completed development work
  • Nightly builds or staging deployments typically run from develop

3. feature/* branches

  • Branch from: develop
  • Merge back into: develop
  • Naming: feature/user-authentication, feature/JIRA-1234-search
  • Lifetime: days to weeks (not months!)
# Start a feature
git checkout develop
git checkout -b feature/user-authentication
 
# Work on the feature...
git add .
git commit -m "feat: add user authentication flow"
 
# Finish the feature
git checkout develop
git merge --no-ff feature/user-authentication
git branch -d feature/user-authentication

The --no-ff flag is critical in Git Flow — it ensures a merge commit is always created, preserving the fact that a feature existed as a logical unit:

Without --no-ff (fast-forward):          With --no-ff:

A ── B ── C ── D ── E                   A ── B ──────── M ── ...
                                                       ╱
                                              C ── D ──
                                              (feature visible in history)

4. release/* branches

  • Branch from: develop
  • Merge back into: develop AND main
  • Naming: release/1.1.0
  • Purpose: freeze features, allow only bug fixes, update version numbers, finalize docs
# Start a release
git checkout develop
git checkout -b release/1.1.0
 
# Bump version number
# Fix last-minute bugs only — NO new features
 
# Finish the release
git checkout main
git merge --no-ff release/1.1.0
git tag -a v1.1.0 -m "Release 1.1.0"
 
# Merge back into develop (to capture release bug fixes)
git checkout develop
git merge --no-ff release/1.1.0
 
git branch -d release/1.1.0

5. hotfix/* branches

  • Branch from: main
  • Merge back into: develop AND main
  • Naming: hotfix/1.0.1
  • Purpose: emergency production fixes that can't wait for the next release
# Start a hotfix
git checkout main
git checkout -b hotfix/1.0.1
 
# Fix the critical bug
git commit -m "fix: patch SQL injection vulnerability"
 
# Finish the hotfix
git checkout main
git merge --no-ff hotfix/1.0.1
git tag -a v1.0.1 -m "Hotfix 1.0.1"
 
git checkout develop
git merge --no-ff hotfix/1.0.1
 
git branch -d hotfix/1.0.1

Git Flow with the git-flow Extension

The git-flow CLI tool automates these branch operations:

# Install
brew install git-flow-avh    # macOS
apt install git-flow          # Ubuntu/Debian
 
# Initialize in a repo
git flow init
 
# Feature workflow
git flow feature start user-authentication
# ... work ...
git flow feature finish user-authentication
 
# Release workflow
git flow release start 1.1.0
# ... finalize ...
git flow release finish 1.1.0
 
# Hotfix workflow
git flow hotfix start 1.0.1
# ... fix ...
git flow hotfix finish 1.0.1

When Git Flow Works

  • Packaged software with version numbers (libraries, frameworks, mobile apps)
  • Teams with a dedicated QA phase before each release
  • Products with multiple versions supported simultaneously
  • Regulated environments requiring clear release audit trails

When Git Flow Doesn't Work

  • Web apps with continuous deployment (too much ceremony)
  • Small teams (1–3 developers — too much overhead)
  • Rapid iteration environments where developmain most of the time

3. GitHub Flow

GitHub Flow was created by Scott Chacon at GitHub in 2011 as a reaction to Git Flow's complexity. It's designed for teams that deploy continuously.

The Rules

  1. main is always deployable
  2. Branch off main for any change (features, fixes, experiments)
  3. Commit to your branch locally, push regularly to the remote
  4. Open a pull request when you want feedback or are ready to merge
  5. After PR review and CI passes, merge to main
  6. Deploy immediately after merging

Branch Structure

main ── A ── B ──────── M1 ──────── M2 ── C ──────── M3 ── ...
                       ╱            ╱                ╱
              feature-1            fix-bug       feature-2

That's it. One long-lived branch (main) and short-lived topic branches.

The Workflow

# 1. Create a branch from main
git checkout main
git pull origin main
git checkout -b add-user-search
 
# 2. Make commits
git add .
git commit -m "feat: add user search endpoint"
git commit -m "feat: add search UI component"
git commit -m "test: add search integration tests"
 
# 3. Push and open a PR
git push -u origin add-user-search
gh pr create --title "Add user search" --body "Implements user search..."
 
# 4. After review and CI, merge via GitHub UI or CLI
gh pr merge --squash
 
# 5. Deploy (triggered automatically via CI/CD)

Feature Flags Instead of Feature Branches

In GitHub Flow, long-running features aren't kept on long-lived branches. Instead, use feature flags:

# Feature flag pattern
if feature_flags.is_enabled("new_search", user):
    return new_search_results(query)
else:
    return legacy_search(query)

This lets you merge incomplete features to main without exposing them to users. When ready, flip the flag — no branch merge required.

When GitHub Flow Works

  • Web applications with continuous deployment
  • Small to medium teams (2–15 developers)
  • SaaS products with a single production version
  • Teams with strong CI/CD and automated testing

When GitHub Flow Doesn't Work

  • Multiple release versions in production simultaneously
  • Environments requiring a formal release/QA gate
  • Projects where "always deployable main" is impractical

4. GitLab Flow

GitLab Flow, proposed by GitLab, sits between Git Flow's complexity and GitHub Flow's simplicity. It adds environment branches or release branches on top of GitHub Flow.

Variant 1: Environment Branches

For teams deploying to multiple environments (staging, pre-production, production):

main ── A ── B ── C ── D ── E ── F ── ...
               │              │
               ▼              ▼
         pre-production ── C ── E ── ...
                           │
                           ▼
                    production ── C ── ...

Changes flow downstream only: mainpre-productionproduction. You cherry-pick or merge from upstream to downstream — never the reverse.

# Feature developed and merged to main via MR
# When ready for staging:
git checkout pre-production
git merge main
 
# After staging validation, promote to production:
git checkout production
git merge pre-production

Variant 2: Release Branches

For projects that ship versioned releases while continuing development:

main ── A ── B ── C ── D ── E ── F ── G ── ...
               │                   │
               ▼                   ▼
           release/1.0         release/2.0
             │   │                │
          v1.0.0 v1.0.1        v2.0.0
  • Development continues on main
  • When ready for a release, create a release/X.Y branch
  • Bug fixes go to main first, then cherry-pick to the release branch (upstream first policy)
# Bug found in release/1.0
# Fix on main first
git checkout main
git commit -m "fix: correct date parsing for leap years"
 
# Then cherry-pick to release branch
git checkout release/1.0
git cherry-pick <commit-hash>
git tag v1.0.1

The "Upstream First" Rule

GitLab Flow mandates that fixes always land on main first, then get cherry-picked to release or environment branches. This prevents the common problem of fixes being made on a release branch but never making it back to main.

When GitLab Flow Works

  • Teams with multiple deployment environments
  • Projects needing both continuous development and versioned releases
  • Organizations that want more structure than GitHub Flow but less than Git Flow

5. Trunk-Based Development

Trunk-based development (TBD) is the most integration-heavy strategy. Developers commit directly to main (the "trunk") or use extremely short-lived branches (< 1 day).

Core Principles

  1. One branch: main (the trunk) is the single source of truth
  2. Small commits: multiple times per day
  3. No long-lived branches: feature branches live hours, not days
  4. Feature flags: hide incomplete work behind flags
  5. CI is mandatory: every commit to trunk must pass all tests

How It Works

Trunk (main):
A ── B ── C ── D ── E ── F ── G ── H ── I ── J ── ...
     ╲       ╱          ╲   ╱
      b1 ── b2           e1
   (< 1 day branch)   (< 1 day)

Branches, if used at all, are merged within hours. Some teams skip branches entirely:

# Direct-to-trunk workflow
git checkout main
git pull --rebase origin main
 
# Make a small, focused change
git add .
git commit -m "feat: add email validation"
git push origin main

Scaled Trunk-Based Development

For larger teams, short-lived feature branches are used with these constraints:

# Branch lives for hours, not days
git checkout -b feat/validate-email
git commit -m "feat: add email validation to signup"
git push -u origin feat/validate-email
 
# Open PR, get quick review, merge same day
gh pr create --title "Add email validation"
# After approval:
gh pr merge --rebase

Release from Trunk

Releases are cut from the trunk using tags or short-lived release branches:

main ── A ── B ── C ── D ── E ── F ── G ── ...
               │              │
            tag v1.0       tag v1.1

   OR

main ── A ── B ── C ── D ── E ── F ── G ── ...
               │
          release/1.0 ── cherry-pick ── tag v1.0.1
        (created at release time, only for hotfixes)

When Trunk-Based Works

  • Teams with mature CI/CD and comprehensive test suites
  • Google, Facebook, Microsoft (Windows) — proven at massive scale
  • Products with continuous deployment
  • Teams that prioritize integration speed over isolation

When Trunk-Based Doesn't Work

  • Open-source projects with external contributors (need PR review)
  • Teams without strong automated testing
  • Regulated environments requiring pre-release approval gates
  • Junior-heavy teams that need the safety net of code review before merge

6. Strategy Comparison

Side-by-Side Overview

AspectGit FlowGitHub FlowGitLab FlowTrunk-Based
Long-lived branchesmain + developmain onlymain + env/releasemain only
Feature branchesfeature/*topic branchestopic branchesvery short / none
Release processrelease/* branchtag from mainrelease/* or env promotiontag from trunk
Hotfix processhotfix/* branchbranch + PRfix on main, cherry-pickfix on trunk
Merge strategy--no-ff alwayssquash or mergemergerebase or direct push
Best forVersioned softwareSaaS / web appsMulti-env deploymentsHigh-velocity teams
ComplexityHighLowMediumLow (process), High (discipline)
CI/CD dependencyLowMediumMediumCritical

Decision Flowchart

Do you support multiple release versions simultaneously?
├── YES → Git Flow or GitLab Flow (release branches)
└── NO
    │
    Do you have multiple deployment environments (staging, prod)?
    ├── YES → GitLab Flow (environment branches)
    └── NO
        │
        How often do you deploy?
        ├── Multiple times per day → Trunk-Based Development
        └── Daily to weekly → GitHub Flow

Merging Strategies Within Branching Models

Different branching models pair naturally with different merge strategies:

Git Flow:        merge --no-ff    (preserve feature history)
GitHub Flow:     squash merge     (clean main history)
GitLab Flow:     merge commit     (traceability across environments)
Trunk-Based:     rebase           (linear trunk history)

7. Release Management Across Strategies

Tagging Releases

Regardless of strategy, tags mark release points:

# Annotated tag (recommended for releases)
git tag -a v2.1.0 -m "Release 2.1.0: search feature, bug fixes"
 
# Push tags
git push origin v2.1.0
# or push all tags
git push --follow-tags

Version Branches vs. Tags

Tags (lightweight releases):
main ── A ── B ── C ── D ── E ── F ── ...
            v1.0       v1.1       v2.0

Version branches (when you need to patch old releases):
main ── A ── B ── C ── D ── E ── F ── ...
            │              │
        release/1.x    release/2.x
          │    │           │
        v1.0  v1.0.1    v2.0

Use tags alone when you only support the latest version. Use release branches when you need to backport fixes to older versions.

Backporting Fixes

When a bug is found in an older release:

# Fix on main first (upstream first)
git checkout main
git commit -m "fix: handle null user in permissions check"
 
# Cherry-pick to the release branch
git checkout release/1.x
git cherry-pick <commit-hash>
git tag -a v1.0.1 -m "Patch: null user fix"
git push origin release/1.x --follow-tags

8. Branch Naming Conventions

Consistent naming makes branch purpose immediately clear:

Common Patterns

PatternExampleUse Case
feature/<description>feature/user-searchNew functionality
fix/<description>fix/login-redirectBug fixes
hotfix/<description>hotfix/security-patchUrgent production fixes
release/<version>release/2.1.0Release preparation
chore/<description>chore/update-depsMaintenance tasks
docs/<description>docs/api-referenceDocumentation changes
refactor/<description>refactor/auth-moduleCode restructuring

Including Ticket Numbers

# Ticket prefix helps trace branches to issues
git checkout -b feature/PROJ-1234-user-search
git checkout -b fix/GH-567-login-redirect

Enforcing Conventions

Use a pre-push hook or CI check to validate branch names:

#!/bin/bash
# .githooks/pre-push
branch=$(git rev-parse --abbrev-ref HEAD)
pattern="^(feature|fix|hotfix|release|chore|docs|refactor)/"
 
if [[ ! "$branch" =~ $pattern ]] && [[ "$branch" != "main" ]] && [[ "$branch" != "develop" ]]; then
    echo "ERROR: Branch name '$branch' doesn't follow naming convention."
    echo "Use: feature/, fix/, hotfix/, release/, chore/, docs/, or refactor/"
    exit 1
fi

Command Reference

CommandDescription
git checkout -b <branch>Create and switch to a new branch
git merge --no-ff <branch>Merge with a merge commit (no fast-forward)
git merge --squash <branch>Squash all branch commits into one, stage result
git cherry-pick <hash>Apply a specific commit to the current branch
git tag -a <tag> -m "<msg>"Create an annotated tag
git push --follow-tagsPush commits and annotated tags together
git branch -d <branch>Delete a merged branch
git branch -D <branch>Force-delete an unmerged branch
git flow initInitialize git-flow in a repository
git flow feature start <name>Start a git-flow feature branch
git flow feature finish <name>Finish a git-flow feature branch
git flow release start <ver>Start a git-flow release branch
git flow release finish <ver>Finish a git-flow release (merge + tag)
git flow hotfix start <ver>Start a git-flow hotfix branch
git flow hotfix finish <ver>Finish a git-flow hotfix branch

Hands-On Lab: Practicing Branching Strategies

Setup

mkdir branching-lab && cd branching-lab
git init
echo "# My Project" > README.md
echo "version=1.0.0" > version.txt
git add .
git commit -m "initial commit"

Part 1: Git Flow Simulation

Goal: Walk through a complete Git Flow cycle — feature, release, and hotfix.

# 1. Create develop branch
git checkout -b develop
 
# 2. Start a feature
git checkout -b feature/add-login
 
echo "function login() { return true; }" > auth.js
git add auth.js
git commit -m "feat: add login function"
 
echo "function logout() { return true; }" >> auth.js
git add auth.js
git commit -m "feat: add logout function"
 
# 3. Finish the feature (merge into develop with --no-ff)
git checkout develop
git merge --no-ff feature/add-login -m "Merge feature/add-login into develop"
git branch -d feature/add-login
 
# 4. Start a release
git checkout -b release/1.1.0
echo "version=1.1.0" > version.txt
git add version.txt
git commit -m "chore: bump version to 1.1.0"
 
# 5. Finish the release
git checkout main
git merge --no-ff release/1.1.0 -m "Merge release/1.1.0"
git tag -a v1.1.0 -m "Release 1.1.0"
 
git checkout develop
git merge --no-ff release/1.1.0 -m "Merge release/1.1.0 back into develop"
git branch -d release/1.1.0
 
# 6. Simulate a production bug → hotfix
git checkout main
git checkout -b hotfix/1.1.1
 
echo "function login() { /* fixed */ return validate(); }" > auth.js
git add auth.js
git commit -m "fix: validate credentials in login"
 
echo "version=1.1.1" > version.txt
git add version.txt
git commit -m "chore: bump version to 1.1.1"
 
# 7. Finish the hotfix
git checkout main
git merge --no-ff hotfix/1.1.1 -m "Merge hotfix/1.1.1"
git tag -a v1.1.1 -m "Hotfix 1.1.1"
 
git checkout develop
git merge --no-ff hotfix/1.1.1 -m "Merge hotfix/1.1.1 into develop"
git branch -d hotfix/1.1.1

Checkpoint: Run git log --all --oneline --graph --decorate and verify you see:

  • The merge commits on main for the release and hotfix
  • Tags v1.1.0 and v1.1.1 on main
  • Feature history preserved via --no-ff on develop

Part 2: GitHub Flow Simulation

Goal: Practice the lightweight branch-and-PR workflow.

# Start fresh
mkdir github-flow-lab && cd github-flow-lab
git init
echo "# App" > README.md
git add . && git commit -m "initial commit"
 
# 1. Feature branch from main
git checkout -b add-search
 
echo "function search(q) { return results; }" > search.js
git add search.js
git commit -m "feat: add search function"
 
echo "test('search works', () => expect(search('hi')).toBeDefined());" > search.test.js
git add search.test.js
git commit -m "test: add search tests"
 
# 2. Simulate squash merge (like GitHub's "Squash and merge" button)
git checkout main
git merge --squash add-search
git commit -m "feat: add search feature (#42)"
git branch -d add-search
 
# 3. Tag a release
git tag -a v1.0.0 -m "Release 1.0.0"

Checkpoint: Run git log --oneline. You should see a clean, linear history with a single squashed commit for the entire feature.

Part 3: GitLab Flow with Environment Branches

Goal: Simulate promotion of changes through environments.

# Start fresh
mkdir gitlab-flow-lab && cd gitlab-flow-lab
git init
echo "# App" > README.md
git add . && git commit -m "initial commit"
 
# 1. Create environment branches
git branch pre-production
git branch production
 
# 2. Develop a feature on main
git checkout -b add-api
echo "app.get('/api/users', handler);" > api.js
git add api.js
git commit -m "feat: add users API endpoint"
 
git checkout main
git merge --no-ff add-api -m "Merge add-api"
git branch -d add-api
 
# 3. Promote to pre-production
git checkout pre-production
git merge main -m "Promote to pre-production"
 
# Verify pre-production looks good...
 
# 4. Promote to production
git checkout production
git merge pre-production -m "Promote to production"
git tag -a v1.0.0 -m "Release 1.0.0"
 
# 5. Return to main for next development
git checkout main

Checkpoint: Run git log --all --oneline --graph --decorate and verify the same commit appears on all three branches, with the production branch tagged.

Part 4: Trunk-Based Development

Goal: Practice the commit-to-trunk workflow with short-lived branches.

# Start fresh
mkdir tbd-lab && cd tbd-lab
git init
echo "# App" > README.md
git add . && git commit -m "initial commit"
 
# 1. Direct commit to trunk (for small changes)
echo "const config = { debug: false };" > config.js
git add config.js
git commit -m "chore: add production config"
 
# 2. Short-lived branch (for slightly larger changes)
git checkout -b feat/add-logger
 
echo "function log(msg) { console.log('[LOG]', msg); }" > logger.js
git add logger.js
git commit -m "feat: add logging utility"
 
# Merge back immediately (within hours)
git checkout main
git rebase feat/add-logger   # or git merge --ff-only feat/add-logger
git branch -d feat/add-logger
 
# 3. Another direct commit with a feature flag
cat > features.js << 'EOF'
const FEATURES = {
  NEW_DASHBOARD: false,  // flip to true when ready
  DARK_MODE: true,
};
module.exports = FEATURES;
EOF
git add features.js
git commit -m "feat: add feature flag for new dashboard"
 
# 4. Tag a release from trunk
git tag -a v1.0.0 -m "Release 1.0.0"

Checkpoint: Run git log --oneline --graph. The history should be completely linear — no merge commits.

Part 5: Comparing Histories

Goal: Visually compare the histories produced by each strategy.

# Go to each lab directory and run:
echo "=== Git Flow ==="
cd ../branching-lab
git log --all --oneline --graph --decorate | head -20
 
echo ""
echo "=== GitHub Flow ==="
cd ../github-flow-lab
git log --all --oneline --graph --decorate | head -20
 
echo ""
echo "=== GitLab Flow ==="
cd ../gitlab-flow-lab
git log --all --oneline --graph --decorate | head -20
 
echo ""
echo "=== Trunk-Based ==="
cd ../tbd-lab
git log --all --oneline --graph --decorate | head -20

Notice the differences:

  • Git Flow has the most complex graph with multiple merge points
  • GitHub Flow has a clean linear history (due to squash merge)
  • GitLab Flow shows the promotion path through environments
  • Trunk-Based is completely linear

Challenge: Design Your Strategy

Create a branching strategy for this scenario:

You're leading a team of 6 developers building a SaaS application. You deploy to staging automatically on every merge to main, and promote to production manually once per week. You occasionally need to hotfix production between weekly releases. Design your branching model, document the branch naming convention, and implement it in a test repository.

Hint: This scenario calls for a blend — GitHub Flow's simplicity with GitLab Flow's environment promotion.


Common Pitfalls

PitfallWhy It HappensHow to Avoid It
Long-lived feature branchesFeatures grow beyond original scopeTime-box branches (< 1 week), split large features
Forgetting to merge hotfix to developRush to fix productionUse checklists or git-flow tool which automates this
Direct commits to main/developBypassing the workflow under pressureBranch protection rules on the remote
Divergent develop and mainInfrequent releasesRelease regularly, even small batches
Merge conflicts from stale branchesBranch existed too longPull/rebase from target branch daily
No branch protectionTrust-based workflow fails at scaleEnable required reviews + CI checks on main
Over-engineering the strategyApplying Git Flow to a 2-person projectMatch strategy complexity to team size
Inconsistent branch namingNo documented conventionEnforce naming via hooks or CI
Not deleting merged branchesClutter accumulatesgit fetch --prune, auto-delete on merge in GitHub/GitLab
Cherry-pick without upstream-firstFix lands on release but not mainAlways fix on main first, then cherry-pick down

Pro Tips

  1. Start simple, add complexity when needed. Most teams should begin with GitHub Flow and only graduate to Git Flow or GitLab Flow when they hit a real limitation — not a hypothetical one.

  2. Branch protection is non-negotiable. Regardless of strategy, protect main with required PR reviews and CI checks. This is the single most impactful guardrail.

  3. Automate branch cleanup. Enable "delete branch after merge" in GitHub/GitLab settings, and periodically run git fetch --prune locally to remove stale tracking branches.

  4. Document your strategy. Put your branching model in a CONTRIBUTING.md file. Include a diagram, naming conventions, and merge policies. New team members should be able to understand the workflow from this document alone.

  5. Measure your branch lifetime. Track how long feature branches live. If the average exceeds a week, your branches are too long-lived. This is a leading indicator of painful merges.

  6. The best strategy is one your team actually follows. A perfectly designed workflow that nobody understands is worse than a simple one that everyone follows consistently.


Quiz / Self-Assessment

1. In Git Flow, which branch do hotfix branches branch from?

Show Answer

main (not develop). Hotfixes address production issues, so they branch from the production-ready branch. They merge back into both main and develop.

2. What does the --no-ff flag do in Git Flow, and why is it important?

Show Answer

--no-ff (no fast-forward) forces Git to create a merge commit even when a fast-forward is possible. This is important in Git Flow because it preserves the history of a feature branch as a logical unit — you can see where the feature started and ended in the graph, making it easy to revert an entire feature if needed.

3. How does GitHub Flow differ from Git Flow?

Show Answer

GitHub Flow uses only one long-lived branch (main) while Git Flow uses two (main and develop) plus formalized branch types. GitHub Flow has no release branches, no hotfix branches, and no develop branch. Features branch from main and merge back to main via pull request. It's simpler but assumes continuous deployment where main is always deployable.

4. What is the "upstream first" rule in GitLab Flow?

Show Answer

The upstream first rule states that bug fixes must always be applied to main first, then cherry-picked to downstream branches (release branches or environment branches like pre-production and production). This prevents the common problem of fixes being made on a release branch but never making it back to main.

5. In trunk-based development, how are incomplete features handled if everything merges to main?

Show Answer

Incomplete features are hidden behind feature flags (also called feature toggles). The code exists on main but is disabled for users via a flag. When the feature is complete and tested, the flag is flipped to enable it. This allows continuous integration without exposing unfinished work.

6. A team of 3 developers is building a SaaS web app with daily deployments. Which strategy would you recommend and why?

Show Answer

GitHub Flow or trunk-based development. Both are well-suited for small teams with frequent deployments. Git Flow would be overkill — the ceremony of develop, release, and hotfix branches adds overhead without proportional benefit for a small, fast-moving team. Between the two, GitHub Flow is more accessible if the team wants PR-based code review; trunk-based is better if the team has comprehensive automated testing and wants maximum velocity.

7. Why shouldn't feature branches live for weeks or months?

Show Answer

Long-lived feature branches diverge significantly from main, leading to:

  • Large, painful merge conflicts that are hard to resolve correctly
  • Integration bugs discovered late when the branch finally merges
  • Stale dependencies as the rest of the codebase evolves
  • Delayed feedback from CI, code review, and other developers

The general rule is to keep branches under one week. If a feature takes longer, break it into smaller, independently mergeable pieces, potentially using feature flags to hide incomplete functionality.

8. What's the difference between tagging a release from main versus using a release branch?

Show Answer

Tagging from main is simpler — you mark a commit on main as a release point. This works when you only support one version at a time and deploy continuously.

Release branches (release/1.x) are needed when you must support multiple versions in production. The release branch allows you to apply bug fixes (cherry-picked from main) to older versions without pulling in new features. Each patch is tagged on the release branch (v1.0.1, v1.0.2, etc.).

9. How does git merge --squash help in GitHub Flow?

Show Answer

git merge --squash takes all the commits from a feature branch and compresses them into a single commit on main. This creates a clean, linear history where each commit on main represents a complete feature or fix. It's ideal for GitHub Flow because:

  • The main branch history is easy to read and bisect
  • Individual "WIP" and "fix typo" commits on the feature branch don't clutter main
  • Each squashed commit maps to one pull request

The trade-off is that detailed branch history is lost (though it remains visible in the PR on GitHub).

10. Your team currently uses Git Flow but is migrating to continuous deployment. What changes would you make to the branching strategy?

Show Answer

Transition from Git Flow to GitHub Flow or trunk-based development:

  1. Eliminate the develop branch — merge features directly to main
  2. Remove release branches — deploy from main using tags or automated pipelines
  3. Remove the hotfix branch type — hotfixes are just regular branches (they merge to main and deploy immediately like any other change)
  4. Add feature flags to replace long-lived feature branches for features that span multiple days
  5. Strengthen CI/CD to ensure main is always deployable (automated tests, linting, security scans)
  6. Enable branch protection on main with required reviews and passing CI

The key shift is from "branches manage release state" to "CI/CD manages release state."