Lesson 7 of 9 9 minWorkflow Essential

Git & GitHub Mastery: The Team Collaboration Playbook — Branching Strategies, PR Workflows, and Release Engineering

A production playbook for engineering teams using Git at scale. Covers trunk-based development vs Git Flow, branch protection rules, CODEOWNERS, PR review checklists, and safe hotfix procedures.

Reading Mode

Hide the curriculum rail and keep the lesson centered for focused reading.

Key Takeaways

  • Trunk-based development with short-lived feature branches is the standard for high-velocity teams; Git Flow adds overhead that slows deployment frequency.
  • Branch protection rules + CODEOWNERS eliminates entire classes of accidental main branch corruption.
  • A hotfix procedure that skips the normal review cycle is always needed — but must have compensating controls.
Recommended Prerequisites
git-mastery-01-fundamentalsgit-mastery-02-branchinggit-mastery-03-github-remotesgit-mastery-04-workflowsgit-mastery-05-advancedgit-mastery-06-debugging

Premium outcome

Professional collaboration workflows, not just basic commands.

Developers who want clean collaboration habits and stronger debugging instincts.

What you unlock

  • Confidence resolving merge conflicts, rebases, and branch hygiene issues
  • A stepwise workflow for shipping clean pull requests and debugging history
  • A collaboration foundation that pays off across every engineering project

Mental Model

Git is a communication protocol as much as it is a version control system. Your branching strategy determines how fast your team ships, how easy incidents are to diagnose, and how often engineers block each other.

This playbook is for the real world: 10-to-50 engineer teams, multiple environments, CI/CD pipelines, and the occasional 3:00 AM production incident. Every pattern here has been tested in practice and has a concrete reason to exist.


The Branching Strategy Decision

The biggest architectural decision in a team's Git workflow is not which tool to use — it is how long feature branches live.

main ──────────●──────●──────●──────●──► (always deployable)
               ↑      ↑      ↑      ↑
           feat-a  feat-b  fix-c  feat-d
           (1 day) (2 hrs) (1 hr)  (3 days)

Rules:

  • Feature branches live for 1 day maximum (ideally hours)
  • If a feature takes longer, use feature flags to merge incomplete work safely
  • Every merge to main triggers a deployment pipeline
  • Rollback = revert commit or flip the feature flag

Why trunk-based wins: Continuous integration is real integration. Long-lived branches are technical debt. A 1-day branch has minimal conflict risk. A 2-week branch is a merge disaster waiting to happen.

Git Flow (When You Have Fixed Release Cycles)

main ────────●───────────────────────────────●────►
             ↑                               ↑
           v1.0                            v2.0
             
develop ──●──────●──────●──────●──────●──────►
          ↑      ↑      ↑      ↑      ↑
        feat-a feat-b release fix hotfix

Use Git Flow when:

  • You have scheduled release cycles (e.g., "ship every 2 weeks")
  • Your deployment requires manual QA approval
  • You manage multiple live versions simultaneously (mobile SDK, firmware, etc.)

Git Flow overhead is real: For SaaS web products with continuous delivery, Git Flow's branching overhead typically reduces deployment frequency from "multiple times daily" to "weekly." Only use it if that trade-off makes sense for your context.


Branch Protection Rules (Non-Negotiable)

Every production repository must have branch protection on main. Here is the GitHub branch protection configuration your team needs:

# Enforced via GitHub repository settings or Terraform

Branch Protection Rules for: main
├── Require pull request reviews before merging: YES
│   ├── Required approving reviews: 2 (1 for small teams)
│   ├── Dismiss stale reviews: YES
│   └── Require review from CODEOWNERS: YES
├── Require status checks to pass before merging: YES
│   ├── CI build must pass
│   ├── Test suite must pass
│   └── Lint + type check must pass
├── Require branches to be up to date: YES
├── Restrict who can push to matching branches: YES
│   └── Allow: Engineering managers only (for emergency)
└── Include administrators: YES (no exceptions)

The key insight: "Include administrators" is critical. If admins are exempt, your protection rules are theater — one stressed-out engineer with admin rights bypassing review at 2:00 AM is all it takes.


CODEOWNERS: Automatic Review Assignment

CODEOWNERS lives at .github/CODEOWNERS and automatically assigns reviewers based on which files were changed:

# .github/CODEOWNERS

# Global: all changes need at least one senior review
*                           @engineering/senior-engineers

# Payment system: always needs payment team review
/src/services/payment/      @engineering/payment-team
/src/api/checkout/          @engineering/payment-team

# Security-sensitive code: needs security team
/src/config/Security*.java  @engineering/security-team
/src/auth/                  @engineering/security-team

# Infrastructure: platform team only
/k8s/                       @engineering/platform
/.github/workflows/         @engineering/platform
/terraform/                 @engineering/platform

# Database migrations: DBA must review
/db/migrations/             @engineering/dba-team

# Docs: anyone can review
/docs/                      @engineering/any

Why CODEOWNERS works: Engineers no longer have to figure out "who should review my PR?" The right reviewers are automatically assigned. Security changes get security reviews. Payment code changes get payment expert reviews. No exceptions, no guessing.


The Perfect Pull Request

A PR is a communication artifact as much as a code change. Here is the PR template that works for production engineering teams:

## What does this PR do?
<!-- One sentence. What is the actual change? -->

## Why?
<!-- What problem does this solve? Link to the ticket. -->
Closes: #TICKET-123

## How was this tested?
<!-- What did you run to verify this works? -->
- [ ] Unit tests added/updated
- [ ] Integration tests pass locally  
- [ ] Tested in staging environment
- [ ] Manual test scenario: [describe what you clicked/called]

## Deployment notes
<!-- Does this require a feature flag? A config change? A migration? -->
- [ ] No deployment notes needed
- [ ] Requires feature flag: `feature.new_checkout_flow`
- [ ] Requires DB migration (already included)
- [ ] Requires environment variable: `NEW_PAYMENT_TIMEOUT_MS`

## Screenshots (if UI change)
<!-- Before / After screenshots help reviewers understand intent -->

## Checklist
- [ ] PR is < 400 lines of meaningful change
- [ ] Tests cover the happy path and at least one failure case
- [ ] No console.log or debug code left in
- [ ] Documentation updated if behavior changed

The 400-Line Rule

PRs larger than 400 lines of non-generated code are consistently reviewed poorly. Research from Google's engineering teams shows review quality drops sharply above 200 lines and becomes essentially ceremonial above 400.

When a feature requires 2,000 lines of change, break it into a series of PRs:

PR 1: Add new domain model (no behavior change)
PR 2: Add new service with feature-flagged routing
PR 3: Migrate controller to new service (flag: 0% traffic)
PR 4: Enable flag for 5% of users
PR 5: Clean up old code after full rollout

Code Review Standards

Good code reviews have a consistent vocabulary. Use these conventions:

nit: Minor style issue, doesn't block merge
     nit: Could use a more descriptive variable name here

question: Clarifying question, not blocking
     question: Why did you choose HashMap over LinkedHashMap here?

suggestion: Non-blocking improvement
     suggestion: Consider extracting this to a utility method

issue: Blocking concern — must be addressed before merge
     issue: This doesn't handle the null case from getUserById()

BLOCKING: Critical issue — security, correctness, or data integrity
     BLOCKING: This SQL query is vulnerable to injection via userId param

The rule: Reviewers must distinguish between "I'd do it differently" (non-blocking) and "this is wrong" (blocking). Conflating them creates frustrating review cycles that slow the team without improving quality.


The Hotfix Procedure

At 3:00 AM, production is down. You need to ship a fix without your normal review process. Here is the safe way:

Step 1: Create hotfix branch directly from main (not develop)
   git checkout main && git pull
   git checkout -b hotfix/payment-null-pointer-fix

Step 2: Make the smallest possible fix
   # Fix ONLY the bug. Do not refactor. Do not add features.

Step 3: Get ONE reviewer (the on-call lead or most senior available)
   # Async Slack message with a link to the diff
   # If no reviewer available within 15 minutes: proceed with compensating control

Step 4: Deploy via the standard pipeline
   # Merge to main → triggers CI → deploys to staging → deploy to production
   # DO NOT skip CI even in an incident

Step 5: Compensating controls if no reviewer was available
   # - Post the diff in the incident Slack channel
   # - Assign a post-incident review ticket for the next business day
   # - Add a comment to the PR: "Emergency hotfix — reviewed async by @oncall"

Step 6: After the fix: backport to develop (if using Git Flow)
   git checkout develop
   git cherry-pick <hotfix-commit-hash>

The critical rule: The hotfix procedure exists so you have a safe path to move fast in an incident. But skipping CI — even once — creates precedent. CI is the minimum viable safety net.


Automated Branch Hygiene

Stale branches are noise. Add this GitHub Action to automatically clean up merged branches:

# .github/workflows/cleanup-branches.yml
name: Cleanup Merged Branches

on:
  pull_request:
    types: [closed]

jobs:
  cleanup:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true
    steps:
      - name: Delete merged branch
        uses: actions/github-script@v7
        with:
          script: |
            const ref = context.payload.pull_request.head.ref;
            // Don't delete protected branches
            if (['main', 'develop', 'staging'].includes(ref)) return;
            
            await github.rest.git.deleteRef({
              owner: context.repo.owner,
              repo: context.repo.repo,
              ref: `heads/${ref}`
            });

Add a weekly audit to catch anything that slipped through:

# .github/workflows/stale-branch-report.yml
name: Weekly Stale Branch Report
on:
  schedule:
    - cron: '0 9 * * MON'

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: List stale branches
        run: |
          # Branches not updated in 30 days
          git for-each-ref --format='%(refname:short) %(committerdate:relative)' \
            refs/remotes/origin/ | \
            awk '$2 > 30 {print}' # pseudocode — use date arithmetic in practice

Interview: The Merge Conflict Scenario

Interviewer: "Walk me through how you'd handle a major merge conflict between two teams that have been working independently for 2 weeks."

Strong Answer:

"First, I'd run git merge-base feature-a feature-b to find the common ancestor commit. This tells me how far the branches have diverged. If it's a 2-week divergence, I'd expect significant conflicts.

Before touching any code, I'd open a quick 30-minute call with the leads from both teams. Understanding the intent behind each change is more important than the syntax. Often, what looks like a conflict at the code level isn't actually a logical conflict.

Then I'd prefer to rebase rather than merge where possible. I'd have the team with the simpler changes rebase onto the team with more complex changes. git rebase -i lets me squash intermediate commits first, which makes the conflicts cleaner.

For the actual conflict resolution, I'd use git checkout --ours or --theirs for any files where one team's change is clearly the right one. For files with genuine conflicts, I'd resolve them line by line, ensuring the final result is logically coherent rather than just syntactically valid.

Finally, I'd never declare 'merge complete' until the full test suite passes, not just the changed files. Integration conflicts often show up in unexpected test failures."


Production Readiness Checklist

Before going live with a new Git workflow for your team:

  • Branch protection enabled on main with admin enforcement
  • CODEOWNERS file covers all security-sensitive and payment-critical paths
  • PR template is in .github/pull_request_template.md
  • Automated branch cleanup is configured
  • Hotfix procedure is documented and the team has practiced it once
  • CI/CD pipeline triggers on every merge to main
  • Feature flag system is available for merging incomplete work safely

Key Takeaways

  • Trunk-based development with short-lived feature branches is the standard for high-velocity teams; Git Flow adds overhead that slows deployment frequency.
  • Branch protection rules + CODEOWNERS eliminates entire classes of accidental main branch corruption.
  • A hotfix procedure that skips the normal review cycle is always needed — but must have compensating controls.

Want to track your progress?

Sign in to save your progress, track completed lessons, and pick up where you left off.