The repo that rejects my own pushes to master
Subtitle: Branch protection rules are step one. Two small GitHub Actions turned "please don't do that" into "the repo won't let you."
The Self-Driving Repo · Part 1 — Governance

Every team has the unwritten rule: don't push straight to master. And every team has the Tuesday afternoon where someone does it anyway — a fast hotfix, a muscle-memory git push, a rebase gone sideways — and now production history has a commit that never saw review.
You can write that rule in the README. You can put it in the onboarding doc. People will still break it, including the person who wrote it. (Hi.)
So I stopped relying on discipline and made the repo enforce itself. Two small workflows, under 200 lines combined. One guards the branch. One guards the files that quietly break everyone's build.
The problem
Two recurring failures, both cheap individually and expensive in aggregate:
1. Direct pushes to master. Not malicious — just human. The cost isn't the one commit; it's the eroded guarantee. Once "master is always reviewed" stops being true, you can't trust it for releases, bisects, or rollbacks.
2. The "innocent" file change that detonates a build. Lockfiles (pubspec.lock, Podfile.lock), generated iOS symlinks, IDE settings, CI config. Someone commits their local pubspec.lock from a different Flutter version, and now CI fails for everyone until somebody figures out why. These files shouldn't change in a feature PR, but nothing stops them from doing so.
GitHub's built-in branch protection helps with (1), but it's coarse, it's UI-configured (not in the repo), and it doesn't address (2) at all. I wanted guardrails as code — versioned, reviewable, with an explicit escape hatch.
The idea
Two push/PR-triggered workflows that don't ask, they correct:
protect-masterruns on every push tomaster. If the push isn't an allowed merge, it reverts it automatically.protect-filesruns on PRs. If a protected path was touched, it restores that file frommasterand commits the restoration back to the PR branch.
Both share one principle: reversible enforcement. Nothing is blocked with a scary red X you have to beg an admin to override. The bad change is undone, in the open, with a commit you can read — and there's a documented way to say "yes, I really mean it."
How it works

Guarding the branch
protect-master triggers on push and asks one question: was this an allowed way to land on master?
on:
push:
branches: [master]
Allowed means one of three things: it's a PR merge, the actor is an allow-listed maintainer, or the commit explicitly opts out. The merge check is the interesting part — GitHub writes recognizable commit subjects for merges, squashes, and rebases, so a regex covers all three:
# A normal merge: "Merge pull request #123 from ..."
# A squash/rebase: subject ends with "(#123)"
if echo "$COMMIT_MSG" | grep -qE '^Merge pull request #|\(#[0-9]+\)