Skip to content

Branch protection sync #6

Branch protection sync

Branch protection sync #6

name: Branch protection sync
# Applies the declarative branch-protection configuration in
# .github/branch-protection/*.json to main and develop. See that directory's
# README for rationale and the trigger matrix.
on:
schedule:
# Weekly drift re-assertion, Monday 06:00 UTC. Same slot as the security
# workflow and Dependabot for governance-activity clustering.
- cron: "0 6 * * 1"
workflow_dispatch:
push:
branches: [main]
paths:
- .github/branch-protection/**
- .github/workflows/branch-protection.yml
permissions:
contents: read
# Serialise concurrent applies. A scheduled run, a manual dispatch, and a
# push landing around the same time could otherwise race on the same PUT.
# cancel-in-progress: false — never interrupt a protection apply mid-flight.
concurrency:
group: branch-protection-sync
cancel-in-progress: false
jobs:
apply:
name: Apply ${{ matrix.branch }} protection
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branch: [main, develop]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Verify token is configured
env:
GH_TOKEN: ${{ secrets.BRANCH_PROTECTION_TOKEN }}
run: |
if [ -z "$GH_TOKEN" ]; then
echo "::error::BRANCH_PROTECTION_TOKEN secret is not set."
echo "::error::See docs/DEVELOPMENT.md#branch-protection-sync-setup."
exit 1
fi
- name: Snapshot current protection (before)
env:
GH_TOKEN: ${{ secrets.BRANCH_PROTECTION_TOKEN }}
run: |
# May 404 if protection doesn't exist yet (first run). That's fine.
gh api \
-H "Accept: application/vnd.github+json" \
"/repos/${{ github.repository }}/branches/${{ matrix.branch }}/protection" \
> before.json 2>/dev/null || echo '{"note": "no protection set before run"}' > before.json
- name: Apply ${{ matrix.branch }} protection
env:
GH_TOKEN: ${{ secrets.BRANCH_PROTECTION_TOKEN }}
run: |
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/${{ github.repository }}/branches/${{ matrix.branch }}/protection" \
--input ".github/branch-protection/${{ matrix.branch }}.json"
- name: Snapshot current protection (after)
env:
GH_TOKEN: ${{ secrets.BRANCH_PROTECTION_TOKEN }}
run: |
gh api \
-H "Accept: application/vnd.github+json" \
"/repos/${{ github.repository }}/branches/${{ matrix.branch }}/protection" \
> after.json
- name: Summary (with before → after diff)
if: always()
run: |
{
echo "### Branch protection: ${{ matrix.branch }}"
echo ""
echo "**Config source:** \`.github/branch-protection/${{ matrix.branch }}.json\`"
echo ""
echo "**Before → after diff** (empty block = no drift was present):"
echo ""
echo '```diff'
diff -u before.json after.json || true
echo '```'
echo ""
echo "Review current state at https://github.com/${{ github.repository }}/settings/branches"
} >> "$GITHUB_STEP_SUMMARY"