Q36 of 40 · Git

How would you implement a pre-commit hook that blocks QA engineers from committing test files containing skipped tests or hard-coded credentials?

GitSeniorgithookspre-commitcicredentialsquality-gate

Short answer

Short answer: Write a `.git/hooks/pre-commit` shell script that greps staged files for `@Disabled`/`xit`/`xtest` and common credential patterns. Exit non-zero to block the commit. Distribute via a committed `hooks/` directory and a setup script or `core.hooksPath` config.

Detail

Git hooks are shell scripts that execute at defined points in the Git workflow. The pre-commit hook runs before the commit message is created; if it exits with a non-zero status, the commit is aborted.

What to check in QA repos:

  1. Skipped/disabled tests: @Disabled, @Ignore, xit(, xtest(, it.skip( in staged Java/JS/TS test files.
  2. Hard-coded credentials: password/token/apiKey patterns in staged files — simple regex catches obvious cases; for thorough scanning use gitleaks or truffleHog.
  3. TODO/FIXME in test assertions: test assertions with TODO comments are incomplete tests that should not be committed.

Distribution challenge: hooks in .git/hooks/ are not committed to the repository — they're local. Solutions:

  1. Commit hooks to a hooks/ directory and provide a setup script (scripts/setup-hooks.sh) that copies or symlinks them.
  2. Use git config core.hooksPath hooks/ to redirect Git to the committed directory (Git 2.9+) — run this in the setup script.
  3. Use a tool like pre-commit (the Python framework) which manages hook installation via a committed .pre-commit-config.yaml.

Bypass: git commit --no-verify skips all hooks. Document that this is for emergencies only and audited by CI (CI should re-run the same checks).

// EXAMPLE

#!/usr/bin/env bash
# hooks/pre-commit  (committed to the repo)
set -euo pipefail

STAGED=$(git diff --cached --name-only --diff-filter=ACM)

# ── Check for skipped/disabled tests ────────────────────────────────────
SKIP_PATTERN='@Disabled|@Ignore|xit(|xtest(|it.skip(|test.skip('
SKIPPED_FILES=$(echo "$STAGED" | xargs grep -lE "$SKIP_PATTERN" 2>/dev/null || true)

if [[ -n "$SKIPPED_FILES" ]]; then
  echo "ERROR: Staged files contain skipped tests:"
  echo "$SKIPPED_FILES"
  echo "Remove @Disabled/@Ignore/xit before committing."
  exit 1
fi

# ── Check for hard-coded credentials ────────────────────────────────────
CRED_PATTERN='passwords*=s*["'"'"'][^"'"'"']+["'"'"']|api[_-]?keys*=s*["'"'"']|tokens*=s*["'"'"']'
CRED_FILES=$(echo "$STAGED" | xargs grep -liE "$CRED_PATTERN" 2>/dev/null || true)

if [[ -n "$CRED_FILES" ]]; then
  echo "ERROR: Potential hard-coded credentials in:"
  echo "$CRED_FILES"
  echo "Use environment variables or a secrets manager instead."
  exit 1
fi

echo "pre-commit checks passed."
exit 0

// WHAT INTERVIEWERS LOOK FOR

The core.hooksPath distribution mechanism (not just copying to .git/hooks). Checking only staged files (git diff --cached). Multiple check categories. Documenting --no-verify as an emergency bypass that CI catches. Mentioning tools like gitleaks for thorough credential scanning.