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.
feat/user-login — new featurefix/null-pointer-auth — bug fixchore/upgrade-react — maintenancehotfix/prod-500-error — emergency fix
# === 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
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.
git fetch --prune — removes local tracking refs for deleted remote branchesgit branch --merged main | grep -v main | xargs git branch -d — delete all locally merged branches
git switch -c feat/loginEveryone commits directly to main (the "trunk") multiple times per day. No long-lived feature branches. Features are hidden until ready using 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.
| Dimension | GitFlow | GitHub Flow ⭐ | Trunk-Based Dev |
|---|---|---|---|
| Long-lived branches | main, develop (permanent) | main only (permanent) | main only (no others) |
| Feature branch life | Days to weeks | Hours to 2 days | Hours or direct commit |
| Release process | Dedicated release branch | Merge to main = deploy | Continuous deploy via flags |
| Hotfix process | hotfix/* branch from main | Quick branch from main | Commit directly to main |
| CI requirement | Helpful | Required | Mandatory + fast |
| Feature flags | Not required | Not required | Required |
| Merge conflict risk | High (long branches) | Low (short branches) | Very low (trunk is fresh) |
| Deployment frequency | Low (scheduled releases) | High (per-merge) | Very high (>10/day) |
| Best for | Versioned libraries, mobile apps | SaaS, web apps, most teams | Google/Netflix scale teams |
| Learning curve | Complex (5 branch types) | Simple (1 rule) | High discipline required |
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.
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.
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.
git log and git bisect much more useful.
# === 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
git merge branch — conflict is reportedgit status — lists conflicted files<<<< markers<<<, ===, >>>)git add resolved-file.jsgit commit to complete the mergegit 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.
Feature branch → commits → push → open PR → review → merge → delete branch. The workflow you'll repeat every day.
git switch main && git pull origin main — always sync before branching to minimise future conflicts.git switch -c feat/add-health-endpoint. Use the naming convention: feat/, fix/, chore/.healthcheck.sh (commit 1), update README (commit 2), add a comment (commit 3). Use Conventional Commits for each.git push -u origin feat/add-health-endpoint → GitHub shows "Compare & pull request" banner → click it → write a description.git switch main && git pull && git branch -d feat/add-health-endpoint. Delete the remote branch on GitHub too.# === 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 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"
notes/day7-branching.md — which strategy suits your current team and why. Push with: docs(day7): branching strategy notes
3 questions · 5 minutes · GitHub Flow, feature flags, and strategy selection
main in your GitHub repo ✓notes/day7-branching.md# 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
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
| Your Situation | Recommended Strategy | Key Reason |
|---|---|---|
| SaaS / web app, deploy on every PR merge | GitHub Flow | Simple, CI/CD-ready, main always deployable |
| Mobile app (iOS/Android) with App Store reviews | GitFlow | Release branches allow hotfixes while next version is in review |
| Open-source library with SemVer versions | GitFlow | Supports multiple version lines simultaneously |
| Enterprise with scheduled quarterly releases | GitFlow | Release branches provide a stabilisation period |
| Team doing 10+ deployments per day | Trunk-Based | Feature flags + short-lived branches maximise throughput |
| Solo developer or tiny team | GitHub Flow | Minimal overhead, just branch + PR |
| Team new to Git branching | GitHub Flow | One rule to learn: branch → PR → merge |
| Working on an existing GitFlow repo | GitFlow (for now) | Changing strategy mid-project requires team alignment |
| You want to use DevOps / CI/CD pipelines | GitHub Flow | GitFlow's develop branch adds a CI/CD integration point that complicates automation |
### 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
git switch main && git pull → switch to your branch → git rebase origin/main--force-with-lease (safe) not --force. Never force-push to main or develop.
| Day | Topic | Key Skill | Status |
|---|---|---|---|
| Day 6 ✅ | Git Fundamentals | 4 areas, commit, push, stash, .gitignore | Complete |
| Day 7 ✅ | Branching Strategies | GitHub Flow, GitFlow, TBD, conflict resolution | Complete |
| Day 8 → | Pull Requests & Code Review | PR templates, review best practices, branch protection | Tomorrow |
| Day 9 | Git Hooks & Automation | Husky, lint-staged, commitlint pre-commit hooks | Friday |
| Day 10 | Git Advanced Lab | rebase -i, cherry-pick, bisect, reflog | Saturday |
my-devops-app repo should now have: