DevOps · 35 Days · Week 2 Day 07 — Git Branching Strategies
1 / 22
Week 2 · Day 7

Git Branching Strategies

GitFlow, GitHub Flow, Trunk-Based Development — picking the wrong branching strategy creates merge hell, slow releases, and frustrated teams. Pick the right one for how your team actually ships.

⏱ Duration 60 min
📖 Theory 30 min
🔧 Lab 25 min
❓ Quiz 5 min
Session Overview

What we cover today

01
Why Branching Strategy Matters
Long-lived branches = merge hell. Short branches = fast delivery. The link to DORA metrics.
02
Branch Basics
create, switch, list, delete, rename — the complete branch command toolkit.
03
GitFlow
main + develop + feature/* + release/* + hotfix/*. When it helps. When it hurts.
04
GitHub Flow ⭐ Recommended
Branch → commit → PR → merge → deploy. Simple, fast, CI/CD-ready.
05
Trunk-Based Development
Everyone on main. Feature flags. How Google/Netflix ship hundreds of times per day.
06
Merge vs Rebase vs Squash
3 ways to integrate branches — when each is appropriate and what history they create.
07
🔧 Lab — GitHub Flow End-to-End
Feature branch → commits → PR → review → merge → local cleanup.
Part 1 of 6

Why branching strategy directly impacts DORA

Bad Strategy → Bad DORA
  • Feature branch lives for 3 weeks → massive merge conflict when integrating
  • 5 developers, 5 long-lived branches → "integration hell" every release
  • Developers afraid to merge → batch up changes → big bang releases → high failure rate
  • Long branches = long Lead Time = low Deployment Frequency
Good Strategy → Elite DORA
  • Feature branches live < 2 days → small diffs, trivial merges
  • Every PR is small and reviewable in minutes, not hours
  • Merge to main → auto deploy → fast feedback
  • Short branches = short Lead Time = high Deployment Frequency
The Core Principle
The longer a branch lives, the more it diverges from main, the harder it is to merge, the more bugs accumulate, the more risk in the release. Branch lifetime is the single most important metric of a branching strategy.

Target: branches merged within 1–2 days of creation. If a feature takes longer, split it into smaller deliverable chunks.
💡 Branch Naming Convention
Use prefixes to classify branches at a glance:
feat/user-login — new feature
fix/null-pointer-auth — bug fix
chore/upgrade-react — maintenance
hotfix/prod-500-error — emergency fix
Part 2 of 6

Branch commands — complete toolkit

🐧🪟 All Platforms
# === Create branch ===
git branch feat/login          # Create (stay on current branch)
git checkout -b feat/login     # Create + switch (classic)
git switch -c feat/login       # Create + switch (modern, preferred)

# === Switch branch ===
git checkout main              # Switch to main (classic)
git switch main                # Switch to main (modern)
git switch -                   # Toggle between last 2 branches

# === List branches ===
git branch                     # Local branches
git branch -a                  # Local + remote
git branch -v                  # Last commit on each branch
git branch --merged main       # Branches already merged into main
git branch --no-merged main    # Branches NOT yet merged

# === Delete branch ===
git branch -d feat/login       # Safe delete (fails if unmerged)
git branch -D feat/login       # Force delete
git push origin --delete feat/login  # Delete remote branch

# === Rename branch ===
git branch -m old-name new-name        # Rename local
git branch -m main                     # Rename current to main

# === Track remote branch ===
git checkout -b feat/login origin/feat/login
git push -u origin feat/login  # Set upstream tracking

# === See where branches diverged ===
git log --oneline --graph --all
checkout vs switch

git checkout does too many things: switch branches, restore files, checkout commits. It's confusing.

git switch (Git 2.23+) is for branches only. git restore is for files. Much clearer separation of concerns.

Both work. In new code, prefer switch. You'll see checkout everywhere in docs and StackOverflow — understand both.

💡 Clean up merged branches
After merging PRs, delete the remote branch on GitHub (there's a "Delete branch" button). Locally:
git fetch --prune — removes local tracking refs for deleted remote branches
git branch --merged main | grep -v main | xargs git branch -d — delete all locally merged branches
Part 3 of 6

GitFlow — structured release management

main v1.0 v1.1 v1.2 develop feature/login feature/dashboard release/1.1 hotfix
GitFlow Branches
  • main — production-ready only. Never commit directly.
  • develop — integration branch. Features merge here first.
  • feature/* — branched from develop. One feature per branch.
  • release/* — branched from develop for release prep (version bumps, bug fixes). Merges into both main + develop.
  • hotfix/* — branched from main for critical production fixes. Merges into both main + develop.
GitFlow: Good For
  • Scheduled releases (monthly, quarterly)
  • Multiple versions supported simultaneously
  • Mobile apps (App Store review delays)
  • Libraries/packages with SemVer releases
⚠ GitFlow: Avoid When
Continuous delivery / SaaS / web apps that deploy daily. GitFlow adds ceremony that slows you down. The develop branch becomes a bottleneck. Use GitHub Flow instead.
Part 4 of 6

GitHub Flow ⭐ — the CI/CD-ready default

main merge+deploy merge+deploy merge+deploy feat/login feat/dashboard fix/auth-bug
The 5-step GitHub Flow
  1. Branch off main: git switch -c feat/login
  2. Commit small changes: 2–5 commits, focused work
  3. Open a Pull Request: describe what and why
  4. Code review + CI passes: team reviews, tests run automatically
  5. Merge to main → deploy: every merge triggers deployment
Rules for GitHub Flow to work
  • main is always deployable — protect it with branch rules
  • Branches live < 2 days — if longer, your story is too big
  • Automated CI on every PR — no merge without tests passing
  • Deploy automatically on merge — no manual deploy steps
💡 This is what you'll use in this course
Every lab from Day 7 onward uses GitHub Flow. Week 3 CI/CD pipelines trigger on merges to main. Week 8 ArgoCD watches main. Make it your default.
Part 5 of 6

Trunk-Based Development — how elite teams ship

What is TBD?

Everyone commits directly to main (the "trunk") multiple times per day. No long-lived feature branches. Features are hidden until ready using feature flags.

  • Short-lived branches (< 1 day) are OK — but optional
  • CI must run on every single commit — no exceptions
  • Feature flags control what users see
  • Used by Google, Facebook, Netflix, Uber
Feature Flags

The key enabler of TBD. Code is deployed but the feature is hidden behind a flag:

if (featureFlags.isEnabled('new-dashboard')) {
  return <NewDashboard />;
}
return <OldDashboard />;

Turn on for 1% → 10% → 100% of users. Instant rollback: disable the flag. No redeployment needed.

TBD Requirements
  • ✅ Excellent automated test coverage (> 80%)
  • ✅ Fast CI pipeline (< 10 min)
  • ✅ Feature flag infrastructure
  • ✅ Strong team discipline and code review culture
  • ✅ Monitoring + rollback capability
TBD vs GitHub Flow
GitHub Flow: branch → PR → merge. Great default. Branches max 2 days.

TBD: everyone on main. Zero branches. Requires feature flags and excellent CI. Used when teams need > 10 deploys/day.

Start with GitHub Flow. Evolve to TBD when your team and CI are mature enough.
Part 3–5 Summary

Three strategies — side by side

Dimension GitFlow GitHub Flow ⭐ Trunk-Based Dev
Long-lived branchesmain, develop (permanent)main only (permanent)main only (no others)
Feature branch lifeDays to weeksHours to 2 daysHours or direct commit
Release processDedicated release branchMerge to main = deployContinuous deploy via flags
Hotfix processhotfix/* branch from mainQuick branch from mainCommit directly to main
CI requirementHelpfulRequiredMandatory + fast
Feature flagsNot requiredNot requiredRequired
Merge conflict riskHigh (long branches)Low (short branches)Very low (trunk is fresh)
Deployment frequencyLow (scheduled releases)High (per-merge)Very high (>10/day)
Best forVersioned libraries, mobile appsSaaS, web apps, most teamsGoogle/Netflix scale teams
Learning curveComplex (5 branch types)Simple (1 rule)High discipline required
💡 Decision Rule
Deploy manually on a schedule → GitFlow. Deploy automatically on every merge → GitHub Flow. Deploy 10+ times per day with feature flags → Trunk-Based. When in doubt: start with GitHub Flow.
Part 6 of 6

Merge vs Rebase vs Squash — integrating branches

Merge Commit

Combines both branches' histories with a new "merge commit".

* Merge branch 'feat/login'
|\
| * feat: add password validation
| * feat: add login form
|/
* previous main commit

✅ Use when: Preserving full history matters. Feature branches with multiple commits where context is important.

❌ Avoid when: You want a clean linear history.

Squash & Merge

Squashes all commits on the feature branch into ONE commit on main.

* feat: add login with validation
  (squashed 3 commits)
* previous main commit

✅ Use when: Main branch history should be clean. "WIP", "fix typo", "try again" commits on feature branch don't matter.

Most popular choice for GitHub Flow.

Rebase & Merge

Replays the feature branch commits on top of main — linear history, no merge commit.

* feat: add login form
* feat: add password validation
* previous main commit

✅ Use when: Each commit on the feature branch is a clean, logical step. Want linear history but preserve individual commits.

⚠ Never rebase branches others are working on.

💡 GitHub Branch Protection Setting
In GitHub repo Settings → Branches → protection rule for main, you can restrict which merge strategies are allowed. Most teams pick one (usually Squash) and enforce it — consistent history makes git log and git bisect much more useful.
Essential Skill

Resolving merge conflicts

🐧🪟 All Platforms
# === Conflict markers in the file ===
<<<<<<< HEAD (Current Change)
const port = 8080;       # Your change on main
=======
const port = 3000;       # Incoming change from branch
>>>>>>> feat/dev-server

# === Resolve: choose one, or combine ===
const port = process.env.PORT || 8080;  # Best: combine both intentions

# === After editing ===
git add file.js          # Mark as resolved
git commit               # Complete the merge

# === Abort if too complex ===
git merge --abort        # Go back to before the merge
git rebase --abort       # Go back to before the rebase

# === See all conflicted files ===
git diff --name-only --diff-filter=U

# === Use VS Code's merge editor ===
code .                   # Open in VS Code
# Click "Resolve in Merge Editor" on conflicted files
# Choose: Accept Current | Accept Incoming | Accept Both

# === Prevent conflicts: sync often ===
git fetch origin
git rebase origin/main   # Replay your branch on latest main
Conflict Resolution Steps
  1. Run git merge branch — conflict is reported
  2. git status — lists conflicted files
  3. Open each conflicted file — look for <<<< markers
  4. Edit: keep the right version (or combine them)
  5. Remove ALL conflict markers (<<<, ===, >>>)
  6. git add resolved-file.js
  7. git commit to complete the merge
💡 Best prevention: sync frequently
Run git fetch origin && git rebase origin/main at the start of every work session. This keeps your branch close to main and conflicts small. The longer you wait, the bigger the conflict.
⚠ VS Code merge editor
VS Code's built-in merge editor shows both sides side-by-side with "Accept" buttons. For complex conflicts, always use a visual tool — editing conflict markers by hand in a large file is error-prone.
Hands-On Lab

🔧 GitHub Flow End-to-End

Feature branch → commits → push → open PR → review → merge → delete branch. The workflow you'll repeat every day.

⏱ 25 minutes
GitHub account ✓
my-devops-app repo ✓
🔧 Lab — Steps

Implement GitHub Flow

1
Start from latest main
git switch main && git pull origin main — always sync before branching to minimise future conflicts.
2
Create feature branch
git switch -c feat/add-health-endpoint. Use the naming convention: feat/, fix/, chore/.
3
Make 2–3 commits on the branch
Create healthcheck.sh (commit 1), update README (commit 2), add a comment (commit 3). Use Conventional Commits for each.
4
Push branch and open a Pull Request
git push -u origin feat/add-health-endpoint → GitHub shows "Compare & pull request" banner → click it → write a description.
5
Review + merge
On the PR page: add a comment reviewing your own change, then click "Squash and merge". Write a clean squash commit message.
6
Clean up locally
git switch main && git pull && git branch -d feat/add-health-endpoint. Delete the remote branch on GitHub too.
🔧 Lab — Commands

Complete lab script

🐧🪟 bash / WSL2
# === 1. Sync from latest main ===
cd my-devops-app
git switch main
git pull origin main
git log --oneline -3    # Confirm you have latest

# === 2. Create feature branch ===
git switch -c feat/add-health-endpoint
git branch              # Confirm you're on new branch

# === 3. Commit 1: Add healthcheck script ===
cat > healthcheck.sh << 'EOF'
#!/bin/bash
echo "Status: OK"
echo "Version: 1.0.0"
echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "Hostname: $(hostname)"
EOF
chmod +x healthcheck.sh
git add healthcheck.sh
git commit -m "feat: add health check script"

# === 4. Commit 2: Update README ===
echo "" >> README.md
echo "## Health Check" >> README.md
echo "Run: \`./healthcheck.sh\`" >> README.md
git add README.md
git commit -m "docs: document health check endpoint"

# === 5. Push branch to GitHub ===
git push -u origin feat/add-health-endpoint
# → GitHub shows "Compare & pull request" — click it!
# → Add title: "feat: add health endpoint"
# → Add description (why? what? how tested?)
# → Open the Pull Request
🐧🪟 After merge — cleanup
# After merging on GitHub (Squash and merge):

# === 6. Sync main + clean up ===
git switch main
git pull origin main
git log --oneline -5    # See the squashed commit

# Delete the local feature branch
git branch -d feat/add-health-endpoint
# -d is safe: fails if branch is not merged

# Clean up stale remote tracking refs
git fetch --prune

# Verify: no dangling branches
git branch -a

# === Bonus: Simulate a conflict ===
git switch -c fix/test-conflict
echo "port=3000" > config.txt
git add . && git commit -m "fix: set port to 3000"

git switch main
echo "port=8080" > config.txt
git add . && git commit -m "chore: set port to 8080"

git switch fix/test-conflict
git merge main          # Creates a conflict in config.txt
# Edit config.txt, remove markers, choose port=8080
git add config.txt
git commit -m "fix: resolve port conflict"
git switch main && git branch -D fix/test-conflict
rm config.txt && git add . && git commit -m "chore: cleanup"
💡 After lab
Commit notes/day7-branching.md — which strategy suits your current team and why. Push with: docs(day7): branching strategy notes
Knowledge Check

Quiz Time

3 questions · 5 minutes · GitHub Flow, feature flags, and strategy selection

Test your branching knowledge →
QUESTION 1 OF 3
In GitHub Flow, when is the main branch deployed to production?
A
Only during scheduled release windows
B
After every merge to main
C
Once a week by the DevOps team
D
Only on hotfix merges
QUESTION 2 OF 3
What mechanism do Trunk-Based Development teams use to hide incomplete features that are already merged to main?
A
Long-lived feature branches kept off main
B
Separate release branches for unfinished features
C
Feature flags that control feature visibility at runtime
D
Hotfix branches for incomplete work
QUESTION 3 OF 3
Which branching strategy is best suited for a SaaS team practising continuous deployment?
A
GitFlow — it provides the most structured release management
B
GitFlow with the develop branch for extra stability
C
GitHub Flow or Trunk-Based Development
D
No branching strategy — commit everything directly to main without PRs
Day 7 — Complete

What you learned today

🌿
Branch Commands
switch -c, branch -d, push --delete, fetch --prune — the complete branch lifecycle.
🔀
3 Strategies
GitFlow (scheduled), GitHub Flow (CD), Trunk-Based (elite). Pick GitHub Flow as your default.
🔗
Merge Strategies
Merge commit (history), Squash (clean main), Rebase (linear). Squash is best for GitHub Flow.
⚔️
Conflict Resolution
Edit markers → git add → git commit. Use VS Code merge editor. Sync often to prevent conflicts.
Day 7 Action Items
  1. Full GitHub Flow lab completed — PR merged on GitHub ✓
  2. Conflict simulation: created, resolved, cleaned up ✓
  3. Enable branch protection on main in your GitHub repo ✓
  4. Commit notes/day7-branching.md
Tomorrow — Day 8
Pull Requests & Code Review

Writing great PR descriptions, effective code review techniques, PR templates, merge strategies, and branch protection rules. The social side of Git.

PR template code review branch protection CODEOWNERS
📌 Reference — GitHub Settings

Branch Protection — enforce your strategy

GitHub Settings → Branches → Add Rule
# Branch name pattern: main

# Recommended settings for GitHub Flow:
✅ Require a pull request before merging
   - Required approvals: 1 (min)
   - Dismiss stale reviews when new commits pushed
   - Require review from code owners (if CODEOWNERS exists)

✅ Require status checks to pass before merging
   - Search for your CI workflow name and add it
   - Require branches to be up to date before merging

✅ Require conversation resolution before merging
   - All review comments must be resolved

✅ Do not allow bypassing the above settings
   - Even admins can't force-push

✅ Restrict who can push to matching branches
   - Only allow designated deployers

# Allowed merge strategies:
✅ Allow squash merging    ← Recommended for GitHub Flow
❌ Allow merge commits     ← Optional, disable for clean history
❌ Allow rebase merging    ← Optional

# Auto-delete:
✅ Automatically delete head branches after merge
Why Branch Protection Matters
Without protection, anyone can push directly to main, merge unreviewed code, skip CI, or force-push history. Branch protection turns your strategy from "guidelines" into enforced rules. This is what enables the team to ship fast with confidence.
CODEOWNERS File

Create .github/CODEOWNERS to auto-assign reviewers:

# Global owner
*                @team-lead

# Frontend — specific owners
/src/ui/         @frontend-team

# Infrastructure — ops team reviews IaC
/terraform/      @devops-team
/k8s/            @devops-team
📌 Reference

Strategy decision guide

Your SituationRecommended StrategyKey Reason
SaaS / web app, deploy on every PR mergeGitHub FlowSimple, CI/CD-ready, main always deployable
Mobile app (iOS/Android) with App Store reviewsGitFlowRelease branches allow hotfixes while next version is in review
Open-source library with SemVer versionsGitFlowSupports multiple version lines simultaneously
Enterprise with scheduled quarterly releasesGitFlowRelease branches provide a stabilisation period
Team doing 10+ deployments per dayTrunk-BasedFeature flags + short-lived branches maximise throughput
Solo developer or tiny teamGitHub FlowMinimal overhead, just branch + PR
Team new to Git branchingGitHub FlowOne rule to learn: branch → PR → merge
Working on an existing GitFlow repoGitFlow (for now)Changing strategy mid-project requires team alignment
You want to use DevOps / CI/CD pipelinesGitHub FlowGitFlow's develop branch adds a CI/CD integration point that complicates automation
💡 Changing strategy on an existing project
Migrating from GitFlow to GitHub Flow: (1) finish all active feature branches, (2) merge develop into main, (3) delete the develop branch, (4) update CI to trigger on main only. Plan it as a sprint task.
📌 Reference

GitHub Flow — daily cheatsheet

🐧🪟 GitHub Flow — Complete Cycle
### EVERY TIME you start work ###
git switch main
git pull origin main           # Always sync first
git switch -c feat/my-feature  # Create from latest main

### WHILE working ###
git status                     # Check what's changed
git add -p                     # Stage carefully
git commit -m "feat: ..."      # Conventional commit
git push origin feat/my-feature # Push (creates remote)

### Keep branch fresh (do daily) ###
git fetch origin
git rebase origin/main         # Stay close to main

### Open PR on GitHub ###
# Write clear title + description
# Link to issue: "Closes #42"
# Request review from teammate

### After PR is merged ###
git switch main
git pull origin main           # Get the merged commit
git branch -d feat/my-feature  # Delete local branch
git fetch --prune              # Clean remote tracking refs

### Emergency hotfix ###
git switch main
git pull origin main
git switch -c hotfix/prod-500-error
# Fix the issue
git commit -m "fix: resolve 500 on /api/login"
git push -u origin hotfix/prod-500-error
# Open PR → get FAST review → merge ASAP
Branch health indicators
  • 🟢 Healthy: Branch is < 2 days old, 2–5 commits, rebased on latest main today
  • 🟡 Warning: Branch is 3–5 days old or has > 10 commits — split the PR
  • 🔴 Problem: Branch is > 1 week old — high conflict risk, rethink the scope
💡 The Morning Ritual
Every morning before starting work:
git switch main && git pull → switch to your branch → git rebase origin/main
This 10-second ritual prevents most merge conflicts.
⚠ Don't push --force to shared branches
If you rebase a branch and then push, you'll need --force-with-lease (safe) not --force. Never force-push to main or develop.
Week 2 Progress

Week 2 — 3 of 5 days complete

DayTopicKey SkillStatus
Day 6 ✅Git Fundamentals4 areas, commit, push, stash, .gitignoreComplete
Day 7 ✅Branching StrategiesGitHub Flow, GitFlow, TBD, conflict resolutionComplete
Day 8 →Pull Requests & Code ReviewPR templates, review best practices, branch protectionTomorrow
Day 9Git Hooks & AutomationHusky, lint-staged, commitlint pre-commit hooksFriday
Day 10Git Advanced Labrebase -i, cherry-pick, bisect, reflogSaturday
Week 2 → Week 3 Bridge
Days 6–10 make you a confident Git user. Week 3 (Days 11–15) builds CI/CD pipelines on top of Git. Every pipeline you build triggers on a git event — push, PR, tag. The branching strategy you choose directly determines how your CI/CD pipeline is structured.
Lab Repo Check
Your my-devops-app repo should now have:

  • ✅ At least 5 commits with Conventional Commits format
  • ✅ .gitignore committed
  • ✅ SSH key or PAT auth working
  • ✅ One merged PR visible in the repo
  • ✅ Branch protection enabled on main