From 1cad57f6b505833c555815d8e83e6ee06501b7e4 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 14 Feb 2026 19:09:09 +0000 Subject: [PATCH] ci(policy): enforce one migration per PR --- .github/workflows/ci.yml | 7 +++++ docs/policy/one-migration-per-pr.md | 23 ++++++++++++++ scripts/ci/one_migration_per_pr.sh | 49 +++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 docs/policy/one-migration-per-pr.md create mode 100755 scripts/ci/one_migration_per_pr.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61edc035..d545ce7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,13 @@ jobs: + - name: Enforce one migration per PR + if: ${{ github.event_name == 'pull_request' }} + env: + GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + ./scripts/ci/one_migration_per_pr.sh + - name: Run migration integrity gate if: ${{ github.event_name == 'pull_request' }} run: | diff --git a/docs/policy/one-migration-per-pr.md b/docs/policy/one-migration-per-pr.md new file mode 100644 index 00000000..d1188294 --- /dev/null +++ b/docs/policy/one-migration-per-pr.md @@ -0,0 +1,23 @@ +# Policy: one DB migration per PR + +## Rule +If a pull request adds migration files under: + +- `backend/migrations/versions/*.py` + +…then it must add **no more than one** migration file. + +## Why +- Makes review and rollback simpler. +- Reduces surprise Alembic multiple-head situations. +- Keeps CI/installer failures easier to debug. + +## Common exceptions / guidance +- If you have multiple Alembic heads, prefer creating **one** merge migration. +- If changes are unrelated, split into multiple PRs. + +## CI enforcement +CI runs `scripts/ci/one_migration_per_pr.sh` on PRs and fails if >1 migration file is added. + +## Notes +This policy does not replace the existing migration integrity gate (`make backend-migration-check`). It is a lightweight guardrail to prevent multi-migration PRs. diff --git a/scripts/ci/one_migration_per_pr.sh b/scripts/ci/one_migration_per_pr.sh new file mode 100755 index 00000000..83ab9000 --- /dev/null +++ b/scripts/ci/one_migration_per_pr.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Enforce: if a PR adds migration files under backend/migrations/versions/, it must add <= 1. +# Rationale: keeps review/rollback straightforward and avoids surprise multiple-head merges. + +if [ "${GITHUB_EVENT_NAME:-}" != "pull_request" ]; then + echo "Not a pull_request event; skipping one-migration-per-PR gate." + exit 0 +fi + +BASE_SHA="${GITHUB_BASE_SHA:-${GITHUB_EVENT_PULL_REQUEST_BASE_SHA:-}}" +HEAD_SHA="${GITHUB_SHA:-}" + +if [ -z "$BASE_SHA" ] || [ -z "$HEAD_SHA" ]; then + echo "Missing BASE_SHA/HEAD_SHA (BASE_SHA='$BASE_SHA', HEAD_SHA='$HEAD_SHA')" + exit 2 +fi + +# Ensure base is present in shallow clones. +git fetch --no-tags --depth=1 origin "$BASE_SHA" || true + +CHANGED=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" || true) +if [ -z "$CHANGED" ]; then + echo "No changed files detected." + exit 0 +fi + +MIGRATIONS=$(echo "$CHANGED" | grep -E '^backend/migrations/versions/.*\.py$' || true) +COUNT=$(echo "$MIGRATIONS" | sed '/^$/d' | wc -l | tr -d ' ') + +if [ "$COUNT" -le 1 ]; then + echo "Migration gate OK (migrations_added=$COUNT)." + exit 0 +fi + +echo "Migration gate FAILED: this PR adds $COUNT migration files; policy allows at most 1." +echo + +echo "Migrations detected:" +echo "$MIGRATIONS" +echo + +echo "How to fix:" +echo "- Consolidate schema changes into a single migration file (squash)." +echo "- If you have multiple Alembic heads, create ONE merge migration instead of several." +echo "- If you truly need multiple migrations, split into multiple PRs." + +exit 1