Q39 of 40 · Git

As a QA lead, how would you configure branch protection rules for `main` and `release/*` branches to enforce quality gates without slowing down the team?

GitLeadgitbranch-protectioncodeownersgovernancecilead

Short answer

Short answer: Protect `main` with: required PR reviews (1-2), required CI status checks (unit + integration), signed commits, and block force-pushes. For `release/*` add: manual QA approval gate, linear history, and no direct pushes. Use CODEOWNERS to auto-assign reviewers to test files.

Detail

Branch protection rules are the primary enforcement mechanism for a quality-first Git workflow. The goal is to make the safe path the default path, not to create bureaucracy.

main branch rules (every merge goes here):

  • Require pull request before merging (min 1-2 reviews).
  • Require status checks: unit tests, integration tests, linting, and security scan — all must pass.
  • Require branches to be up to date before merging (prevents race conditions where two PRs pass CI independently but conflict when both land).
  • Block direct pushes (including from admins — this is often controversial but important for audit trails).
  • Block force-pushes (even --force-with-lease).
  • Require signed commits (-S) in high-compliance environments.
  • Enable Require linear history if the team uses squash or rebase merge strategy.

release/* branch rules (additional):

  • Require approval from a named QA lead (via CODEOWNERS or a required reviewer team).
  • Restrict who can push: only the release manager role.
  • Require all conversations resolved on PRs.

CODEOWNERS (.github/CODEOWNERS): maps file patterns to GitHub teams. src/test/ @company/qa-team means every PR touching test files auto-requests review from the QA team. This ensures test changes are never merged without QA sign-off, even if the developer forgets to request review.

Performance considerations: slow CI is the biggest source of developer friction. Structure checks into fast (< 3 min, required) and slow (smoke/e2e, non-blocking) tiers to avoid making every PR wait 30 minutes.

// EXAMPLE

# .github/CODEOWNERS
# QA team must review any changes to test code
src/test/                   @company/qa-team
test/                       @company/qa-team
**/*Test.java               @company/qa-team
**/*Spec.ts                 @company/qa-team

# Release manager must approve release config changes
.github/workflows/release*  @company/release-managers
config/release/             @company/release-managers

# --- GitHub CLI: view current branch protection ---
gh api repos/company/repo/branches/main/protection

# --- Enforce signed commits globally (team .gitconfig) ---
git config --global commit.gpgsign true
git config --global user.signingkey YOUR_KEY_ID

# --- Verify your commits are signed ---
git log --show-signature -1

# --- Local pre-push hook to catch unsigned commits ---
#!/usr/bin/env bash
# hooks/pre-push
UNSIGNED=$(git log @{u}..HEAD --format="%H %G?" | grep -v " G$" | grep -v " U$")
if [[ -n "$UNSIGNED" ]]; then
  echo "ERROR: unsigned commits detected. Sign with: git commit --amend -S"
  exit 1
fi

// WHAT INTERVIEWERS LOOK FOR

Specific rule names (required status checks, up-to-date branches, signed commits). CODEOWNERS for auto-assignment. The tiered CI approach (fast required + slow non-blocking). Understanding that blocking admins from direct push is a deliberate governance decision, not an oversight. Lead signal: thinking about developer experience friction alongside enforcement.