Git & Collaboration Workflows

Project: Design a Team Git Workflow

18 min Lesson 10 of 28

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 to main after emergency review.
The 2-day rule is the most important discipline. A branch older than 2 days is a liability: it diverges further from 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):

# terraform/github-branch-protection.tf resource "github_branch_protection" "main" { repository_id = github_repository.nexora.node_id pattern = "main" required_pull_request_reviews { required_approving_review_count = 2 dismiss_stale_reviews = true require_code_owner_reviews = true } required_status_checks { strict = true # branch must be up-to-date before merge contexts = [ "ci/lint", "ci/unit-tests", "ci/integration-tests", "security/snyk", ] } enforce_admins = true # even the org owner cannot bypass allows_force_pushes = false allows_deletions = false require_linear_history = true # squash or rebase only — no merge commits on main }

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:

# .github/CODEOWNERS # pattern owner(s) * @nexora/platform-leads /app/Http/Controllers/Api/ @nexora/core-api /app/Services/Billing/ @nexora/core-api @nexora/finance-approval /resources/js/ @nexora/growth /database/migrations/ @nexora/platform-leads @nexora/sre /.github/ @nexora/sre /Dockerfile @nexora/sre /terraform/ @nexora/sre
Never skip the migrations CODEOWNER rule. A migration that drops a column or removes an index is a deployment-order-dependent change that can silently take down production. Requiring SRE sign-off on every migration file is one of the cheapest safeguards available.

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:

# .github/pull_request_template.md ## What does this PR do? <!-- One paragraph. Link the ticket: Closes NX-412 --> ## How was it tested? <!-- Local: unit tests run, manual steps taken. CI: which status checks are expected to be green. --> ## Migration impact <!-- None | Backward-compatible add | Requires deploy order (explain) --> ## Feature flag <!-- N/A | Flag name: feature.user_invites — default OFF --> ## Rollback plan <!-- How to revert if this breaks production in the first 30 minutes. -->

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.

Nexora Git workflow: ticket to production Engineer Pull ticket Create branch feature/NX-xxx-… Commit + rebase daily onto main Open PR CI & Review Lint · Tests Security scan 2 approvals CODEOWNER review Squash-merge into main Delete branch auto on merge Release Deploy to staging auto on main push Smoke tests + QA sign-off Auto-tag vX.Y.Z (semver) Deploy to prod on tag push Hotfix Path (incident only) hotfix/NX-xxx Emergency review Merge to main Tag + deploy prod
Nexora Git workflow: from ticket to production, including the emergency hotfix path.

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!: … or BREAKING 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.

Commit message enforcement: Install 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:

  1. Branch naming via hook — a pre-push hook 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's setup.sh bootstrap script.
  2. Branch protection via Terraform — the GitHub settings are code. A PR to weaken them requires two SRE approvals.
  3. CODEOWNERS auto-assignment — engineers never have to remember who to add as reviewer; GitHub does it when the PR is opened.
  4. 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.
  5. 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.
  6. Feature flag hygiene — every flag gets a remove_by date in the flag config. The stale-flag check runs weekly and creates Jira tickets automatically for overdue removals.
Write the workflow doc once, enforce it with tooling forever. A document in Confluence that nobody reads is documentation theatre. A Terraform file and a Git hook that reject non-conformant behaviour are real enforcement. At big-tech scale, the only policies that hold are the ones the system enforces automatically.

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.