Project: Design a Team Git Workflow
Project: Design a Team Git Workflow
Everything in this tutorial — branching models, rebase hygiene, trunk-based development, feature flags, code review gates, hooks, and monorepo trade-offs — collapses into one deliverable every engineering team eventually needs: a Git Workflow Design Document that tells every engineer exactly what to do from the moment they start a feature to the moment it ships to production. This lesson is hands-on: you will produce that document for a realistic sample team, make concrete decisions with real justifications, and wire it all up with working config files.
The Sample Team
You are the lead DevOps engineer for Nexora, a 40-person product company with three squads: Platform (8 engineers), Growth (10 engineers), and Core API (12 engineers), plus a small SRE team of 4. They ship a SaaS product. Deployments happen multiple times per day. There is one QA engineer per squad. The current situation is painful: long-lived feature branches, merge conflicts that take hours to resolve, no consistent review process, and releases that block each other.
Step 1 — Choose the Right Branching Model
Nexora's situation — multiple squads, frequent deploys — is a near-perfect match for Trunk-Based Development (TBD) with short-lived feature branches (max 2 days). Git Flow is explicitly ruled out: its long-lived develop and release branches would make the current merge pain worse, not better.
The branching convention is minimal but strict:
main— the trunk. Always deployable. Protected. No direct pushes.feature/{ticket-id}-short-description— e.g.feature/NX-412-user-invites. Max lifespan: 2 days. If a feature takes longer, it ships behind a feature flag.fix/{ticket-id}-short-description— bug fixes, same rules as feature branches.hotfix/{ticket-id}-short-description— production incidents only. Merged directly tomainafter emergency review.
main every hour, making merge conflicts exponentially more painful. If the feature is not ready, hide it behind a flag and merge the code anyway. The branch still ships; the UI does not.
Step 2 — Branch Protection and Merge Requirements
Enforce the model at the platform level. On GitHub, configure the following for main (codified as Terraform so no one can quietly weaken it):
The require_linear_history flag is critical. It forces every PR to be squash-merged or rebased, keeping main's history a clean, bisectable line. Merge commits with multiple parents make git bisect and automated changelog generation far more painful.
Automatic reviewer assignment via CODEOWNERS:
Step 3 — The Pull Request Convention
A PR is a communication artifact, not just a code delivery mechanism. Nexora standardises the PR description with a template stored in the repository. Every field is mandatory — CI fails if the template sections are deleted:
Step 4 — The End-to-End Workflow Diagram
The following diagram shows the complete lifecycle of a feature from ticket creation to production tag. Every engineer at Nexora can reference this one picture when in doubt.
Step 5 — Release Convention and Versioning
Semantic versioning is enforced automatically. A CI step runs semantic-release on every push to main, reading Conventional Commits to determine whether to bump major, minor, or patch:
feat: …— minor bump (1.4.0 → 1.5.0)fix: …— patch bump (1.5.0 → 1.5.1)feat!: …orBREAKING CHANGE:footer — major bump (1.5.1 → 2.0.0)
The release bot creates a GitHub Release with a generated changelog, pushes a git tag, and triggers the production deployment pipeline. No human needs to remember to tag a release — the commit message discipline does it automatically.
commitlint as a commit-msg Git hook so engineers see an error immediately if they write a non-conformant message, before it ever reaches the remote. Add it to .husky/commit-msg:
npx --no -- commitlint --edit "$1"
This costs nothing and prevents the entire class of "oops my message broke the release bot" incidents.
Step 6 — Operationalising the Workflow
A workflow document no one can find is worthless. Nexora makes the workflow self-enforcing at every layer:
- Branch naming via hook — a
pre-pushhook on every developer machine rejects pushes from branches that do not match^(main|feature|fix|hotfix|release)/. Installed via a one-liner in the repo'ssetup.shbootstrap script. - Branch protection via Terraform — the GitHub settings are code. A PR to weaken them requires two SRE approvals.
- CODEOWNERS auto-assignment — engineers never have to remember who to add as reviewer; GitHub does it when the PR is opened.
- PR template mandatory fields — a CI check scans the PR body for the template headings using the GitHub API and fails if any section is blank.
- Stale branch bot — a GitHub Action runs nightly, comments on branches older than 2 days, and closes them at 5 days. Engineers cannot forget their long-lived branches because the bot will remind them loudly.
- Feature flag hygiene — every flag gets a
remove_bydate in the flag config. The stale-flag check runs weekly and creates Jira tickets automatically for overdue removals.
Delivering the Design Document
Your final deliverable for this project is a CONTRIBUTING.md file committed to the repository root. It must cover:
- Branch naming convention with examples
- The 2-day branch lifetime rule and what to do instead (feature flags)
- How to open a PR: template fields, who is auto-assigned, required checks
- Merge strategy (squash into main) and why
- Commit message format (Conventional Commits) with examples
- Release process: how semantic-release decides the version
- Hotfix process: step-by-step, who to notify, SLA
- How to escalate if a protected branch rule is blocking legitimate emergency work
Committing this file to main closes the loop: the workflow is version-controlled, diff-able, and discoverable by every engineer who clones the repository. Combined with the Terraform branch protection and hooks installed by setup.sh, the workflow is now both documented and enforced — the two properties that make a Git workflow actually work at team scale.