Skip to content

Git Workflows & Branching

Key Points

  • Trunk-Based Development (TBD) is the default for high-performing teams (DORA). Branches live hours, not weeks.
  • Feature flags > long-lived branches. Hide unfinished work in main, don't isolate it in a branch.
  • CI is the integration point, not a "release branch". If you can't merge to trunk daily, fix CI first.
  • GitFlow still earns its keep for versioned/regulated/shipped-to-customer products. Don't cargo-cult it onto SaaS.
  • Master the porcelain AND the plumbing: rebase -i, bisect, reflog, worktree, cherry-pick are senior table stakes.
  • Conventional Commits are not aesthetic — they drive changelogs, semver bumps, and release automation.

Concepts (deep dive)

The four mainstream workflows

Workflow Branches Best for Pain points
Trunk-Based main + short-lived (<1 day) SaaS, CD, high-velocity teams Requires strong CI + flags
GitHub Flow main + feature branches via PR Mid-size teams, web products Branch lifetime creep
GitFlow main, develop, feature/*, release/*, hotfix/* Versioned products, regulated shops Merge hell, ceremony
Release Flow (MS) main + release/x.y cut at ship Long-lived products with multiple supported versions Cherry-pick discipline

TBD wins for continuous delivery. GitHub Flow is "TBD with PRs" and is fine for most teams. GitFlow is for products where v1.4 and v1.5 both ship and both need patches.

Why TBD at staff/senior level

  • Smaller diffs → faster review, fewer conflicts, lower defect rate.
  • Real continuous integration: integrating once a sprint is not CI.
  • Forces the painful conversations early (API breaks, schema migrations) instead of at merge time.
  • Composable with feature flags + branch-by-abstraction for risky/long work.

When GitFlow still makes sense

  • Multiple supported releases in the wild (v2.x and v3.x both patched).
  • Regulatory release gating (signed builds, change advisory boards).
  • Hotfix isolation matters more than integration speed.
  • Mobile / desktop apps shipped through stores (not your servers).

If you're a SaaS team using GitFlow because someone read a 2010 blog post — that's cargo cult.

Branching anti-patterns

  • The 3-month feature branch. Renames, refactors upstream, schema drift — merge becomes a re-implementation.
  • develop + main for SaaS. Two branches that always converge add ceremony, not safety.
  • Per-environment branches (dev, qa, staging, prod). Promote artifacts, not commits.
  • Squash-only with no commit hygiene. "Fix tests" ×40 in PR history is fine; in main history it's noise.
  • Reviewer-blocking long branches. A PR that grew past 800 LOC will not get a real review.

Senior-level git ops cheat sheet

# Interactive rebase + autosquash (clean up PR history)
git commit --fixup <sha>
git rebase -i --autosquash main

# Find the commit that broke something
git bisect start
git bisect bad
git bisect good v1.4.0
# git runs through the history; mark each as good/bad

# Recover from "I just deleted/reset something"
git reflog              # every HEAD move, ~90 days
git reset --hard HEAD@{2}

# Two checkouts, no stash dance
git worktree add ../proj-hotfix hotfix/critical

# Big monorepo: clone only what you need
git clone --filter=blob:none --sparse <url>
git sparse-checkout set src/billing src/shared

# Move work between branches
git stash push -m "wip" --keep-index
git cherry-pick -x <sha>     # -x records origin commit in message

The reflog is the safety net seniors quietly rely on. New engineers think git reset --hard deleted their work; you know it's recoverable for ~90 days.

Conflict resolution strategy

  1. Pull/rebase often — conflicts grow superlinearly with branch age.
  2. Resolve in the IDE with 3-way view, not by hand-editing <<<<<<< markers.
  3. git rerere (reuse recorded resolution) for repeated conflicts during long rebases.
  4. When in doubt, keep both, then delete. Lost code is worse than ugly diffs.
  5. Run tests after the resolution, not just after the merge button.

Commit message conventions

Conventional Commits format:

<type>(<scope>): <subject>

<body>

<footer: BREAKING CHANGE / refs>

Types: feat, fix, docs, refactor, test, chore, perf, ci, build.

Why it's worth the discipline: - Auto-generated changelogs (semantic-release, release-please). - Auto semver bumps: feat → minor, fix → patch, BREAKING CHANGE → major. - Searchable history: git log --grep="^fix(auth)".

Rebase vs merge for PRs

Strategy Use when
Squash & merge Default for feature PRs; keeps main linear and readable
Rebase & merge When individual commits tell a story (refactor + behavior change separated)
Merge commit Long-lived release branches, preserving topology

Pick one team-wide. Mixed strategies make git log --graph unreadable.


Patterns

  • Branch by abstractionIntent: ship a large change incrementally on main by routing through an abstraction layer; flip the implementation when ready. Replaces long-lived branches.
  • Feature flag + dark launchIntent: merge unfinished code; gate behavior by config or per-user flag. Decouples deploy from release.
  • Expand / contract migrationIntent: schema or API changes via additive step → dual-write → cutover → cleanup. Each step ships independently to trunk.
  • Stacked PRsIntent: chain dependent small PRs (Graphite, Sapling, git-spr) instead of one giant PR. Reviewer-friendly substitute for long branches.
  • Release trainIntent: cut release/x.y from main on a schedule; cherry-pick fixes, never new features. Combines TBD on main with controlled release branches.

Pros & cons / trade-offs

Approach Pros Cons
Trunk-Based Fast feedback, small diffs, real CI Demands flags + strong tests
GitHub Flow Simple, PR-driven, familiar Branch creep without discipline
GitFlow Clean release isolation, hotfix path Heavyweight, merge hell
Release Flow Multiple shipped versions supportable Cherry-pick discipline overhead
Squash merge Linear, readable history Loses intermediate commits
Rebase merge Preserves authored intent Force-push hazards on shared branches

When to use / when to avoid

TBD when: SaaS, CD pipeline exists, team can write tests, feature flags available. ✅ GitFlow when: shipping versioned artifacts, multiple releases supported in production, regulated change control. ✅ Release Flow when: you ship to enterprises that pin versions and you patch each minor.

Avoid TBD if CI is broken or flaky (fix CI first; don't paper over with branches). ❌ Avoid GitFlow for a SaaS where main deploys hourly — the ceremony buys nothing. ❌ Avoid per-env branches universally — promote artifacts, not commits.


Senior-level tips

  • 💡 Branch lifetime is a leading indicator of team health. Track median branch age; rising trend = process smell.
  • 💡 Teach bisect and reflog to your team. Two skills that turn 4-hour debugging into 10 minutes.
  • 💡 PR template > prose discipline. A required checklist (tests, migration plan, flag, rollback) does more than nagging.
  • 💡 Pair "rewrite history" sessions for juniors learning rebase. The fear of breaking things is the bigger blocker than the syntax.
  • 💡 Forbid force-push on shared branches at the server level (receive.denyNonFastForwards); allow it only on the author's PR branch.
  • 💡 Auto-delete merged branches in repo settings. The cruft compounds.
  • 💡 Tags are immutable, branches are not. Sign release tags (git tag -s) when supply chain matters.
  • 💡 git log is a debugging tool, not a vanity metric. Optimize history for git blame's reader, who is future you at 2 AM.

Common pitfalls

  • ⚠️ Long-lived feature branches that "just need one more week" — they never do.
  • ⚠️ Rebasing a shared branch and force-pushing — strands collaborators' commits.
  • ⚠️ Merging without rebasing/updating first — CI passed against stale main.
  • ⚠️ Squash-merging a PR that contained an actual refactor — lost reviewable history.
  • ⚠️ Using git push --force instead of --force-with-lease — overwrites teammates' work.
  • ⚠️ Cherry-picking a hotfix to main and forgetting release/* (or vice versa) — silent regression next release.
  • ⚠️ Storing secrets in commits then "rebasing them out" — they're still in the reflog and on every clone.
  • ⚠️ Adopting Conventional Commits without the tooling that consumes them — pure ceremony.

Further reading