Workflows: How Teams Actually Use Git
This chapter covers how teams use git day to day: trunk-based, feature branches, PRs, and picking a strategy that fits your project.
Why Workflow Matters
The commands in Chapters 1 to 9 don't care about process. You can commit directly to main or run a seven-branch parallel release system, using the exact same commands.
Workflow is about the human side: who can push where, how changes get reviewed, how releases happen. Pick one that matches your team size, deploy cadence, and risk tolerance. Don't invent a new one.
Trunk-Based Development
The simplest workflow. Everyone commits to main (or a short-lived branch that merges to main within a day). No long-lived branches. No release branches.
main: ---A---B---C---D---E---F---G--- (production)
How It Works
- Every commit on
mainshould be deployable. - A feature that isn't ready is hidden behind a feature flag, not kept in a side branch.
- CI runs on
mainafter every merge; green means shippable. - Releases are tags (
v1.4.0) onmain, not separate branches.
When It Fits
- Small teams (up to ~20 engineers).
- Continuous deployment, or near-daily releases.
- Strong CI with fast, reliable tests.
- Feature flags in place for in-progress work.
When It Doesn't
- Regulated environments that require formal release approvals.
- Teams where features regularly take weeks and must be committed incrementally.
- No CI, or slow/flaky CI.
Feature Branches (GitHub Flow)
Work happens on short-lived branches. Each branch becomes a pull request. Merging to main triggers a deploy (or is always deployable).
main: ---A---B----------M---N (production)
\ /
feature/x: C---D---E (PR, merged via M)
How It Works
- Branch from
mainfor any change:feature/login,fix/rate-limit. - Push early; open a draft PR.
- Rebase or merge
mainperiodically to stay current. - When reviewed and CI passes, merge (typically squash-merge).
- Delete the branch.
Merge Options in a PR
Three styles. Pick one per repo.
- Squash-merge. The whole branch becomes one commit on
main. Clean linear history, loss of intermediate detail. The most popular default. - Merge commit. Full branch history is preserved, with a merge commit on top. More detail, messier graph.
- Rebase-merge. The branch's commits are replayed onto
mainlinearly, no merge commit. Clean and detailed, but intermediate commits must each be green (few teams enforce this).
Squash-merge is the right default for most teams. Chapter 12 has the long version.
When It Fits
- Most teams most of the time.
- Works with GitHub, GitLab, Gitea, Bitbucket.
- Scales from two engineers to a thousand.
GitFlow
A heavier-weight workflow with multiple long-lived branches: main, develop, plus release/*, hotfix/*, and feature/* branches.
main: ---M1--------------M2--------M3 (tags: releases)
\ /\ /
release: R---R---R----/ \ /
/ \ /
develop: --D1---D2---D3---D4----D5-/---D6--- (integration)
\ / \
feature: F---F---F/ \
hotfix
How It Works
developis integration. Features merge todevelop.- Periodically,
release/1.4.0branches offdevelop, gets stabilized, then merges tomainas a new release. - Production bugs get
hotfix/*branches offmain, fixed and merged back to bothmainanddevelop.
GitFlow is popular in tutorials and unpopular in working teams. It was designed for a world of quarterly releases and manual QA gates. If your team ships weekly or faster, GitFlow is overhead without benefit.
When It Fits
- Scheduled, versioned releases (desktop apps, firmware, libraries).
- Regulated environments where release branches need independent stabilization.
- Teams that genuinely support multiple released versions in parallel.
When It Doesn't
- Continuous deployment.
- Web apps where "the release" is whatever's on
main.
Release Branches (Without Full GitFlow)
A middle-ground pattern for teams that mostly do trunk-based but need an occasional release branch to stabilize a version while main moves on.
main: ---A---B---C---D---E---F---G (development continues)
\
release/1.4: C---C1---C2 (fixes during release, tagged 1.4.0)
- Cut a
release/1.4frommainwhen you're close to shipping. - Fix bugs on the release branch; cherry-pick those fixes back to
main. - Tag the release from the branch.
Works well when you need a stable candidate for final QA while feature development continues.
Hotfixes
The pattern for "production is broken and we can't wait":
git switch main
git pull
git switch -c hotfix/session-leak
# fix and commit
git push -u origin hotfix/session-leak
# open PR, merge fast
Key points:
- Hotfix branches branch from
main(or the release tag in production), not fromdevelopor a feature branch. - Keep the change as small as possible. Bigger fixes go through normal review.
- Cherry-pick the fix to any parallel branches that need it (old release branches, for example).
Choosing a Strategy
A practical decision tree:
- Are you deploying continuously or near-daily? → Trunk-based or feature branches with squash-merge.
- Do you have long-lived parallel versions in production? → Release branches on top of feature-branch flow.
- Are you shipping once a quarter with heavy manual QA? → Maybe GitFlow, though even here simpler often wins.
- Are you a team of 1 to 3 prototyping? → Just commit to
main. Worry about workflow when you have collaborators.
Write it down. A CONTRIBUTING.md that says "branch from main, open a PR, squash-merge" prevents 90% of workflow arguments.
Pull Request Practices
Whatever branching model you pick, PRs work better with a few habits:
- Keep them small. A PR that touches 40 files usually contains three PRs glued together. Split it.
- One logical change per PR. A review that mixes a refactor and a feature is harder and slower.
- Write a description. What, why, how to test. Future-you reads these when grepping commits.
- Review your own PR first. Catches more embarrassing typos than any reviewer will.
- Rebase before review, merge after. Rebase keeps your branch up to date; merge happens when the PR is approved.
Common Pitfalls
Adopting GitFlow because you read a blog post. Most teams don't need it. Overhead is real; simpler is usually better.
Long-lived feature branches. A branch that lives for a month accumulates conflicts at an exponential rate. Merge main in weekly, or split the work into smaller PRs.
No CI gating merges. Without CI, main drifts into a broken state and nobody notices until someone deploys. Protect main in the repo settings so PRs can't merge without green CI.
Deploying from a branch other than main. Confusing and error-prone. The deployed branch should be obvious. Usually it's main.
Branch name chaos. Pick a convention (feature/*, fix/*, chore/*) and enforce it with a hook if you have to. Makes sorting branches much easier.
Next Steps
Continue to 11-tooling.md for the tools around git.