Block pushes and commits to main with git hooks
pre-push rejects any push that would update refs/heads/main, however the command is spelled; pre-commit and pre-merge-commit refuse commits while main is checked out. npm install enables them via core.hooksPath. The README documents the three layers of protection for main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
183e44db62
commit
8b08c7b27a
6 files changed
+84
-1
No files matched your search
@@ -0,0 +1,55 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const HOOKS = fileURLToPath(new URL("../.githooks/", import.meta.url));
|
||||
const SHA = "1".repeat(40);
|
||||
const ZERO = "0".repeat(40);
|
||||
|
||||
function prePush(...lines) {
|
||||
return spawnSync("sh", [join(HOOKS, "pre-push"), "origin", "git@example:repo.git"], {
|
||||
input: lines.map((l) => l + "\n").join(""),
|
||||
encoding: "utf8",
|
||||
});
|
||||
}
|
||||
|
||||
function repoOn(branch) {
|
||||
const dir = mkdtempSync(join(tmpdir(), "todo-hooks-"));
|
||||
spawnSync("git", ["init", "-q", "-b", branch, dir]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
for (const [label, line] of [
|
||||
["an update", `refs/heads/fix/x ${SHA} refs/heads/main ${SHA}`],
|
||||
["a new ref", `refs/heads/fix/x ${SHA} refs/heads/main ${ZERO}`],
|
||||
["a deletion", `(delete) ${ZERO} refs/heads/main ${SHA}`],
|
||||
]) {
|
||||
test(`pre-push rejects ${label} of main`, () => {
|
||||
const res = prePush(line);
|
||||
assert.equal(res.status, 1);
|
||||
assert.match(res.stderr, /pushing to main is not allowed/);
|
||||
});
|
||||
}
|
||||
|
||||
test("pre-push rejects --all style pushes that include main", () => {
|
||||
const res = prePush(`refs/heads/fix/x ${SHA} refs/heads/fix/x ${SHA}`, `refs/heads/main ${SHA} refs/heads/main ${SHA}`);
|
||||
assert.equal(res.status, 1);
|
||||
});
|
||||
|
||||
test("pre-push allows other branches, including ones named like main", () => {
|
||||
const res = prePush(`refs/heads/fix/x ${SHA} refs/heads/fix/x ${ZERO}`, `refs/heads/maintenance ${SHA} refs/heads/maintenance ${SHA}`);
|
||||
assert.equal(res.status, 0);
|
||||
});
|
||||
|
||||
for (const hook of ["pre-commit", "pre-merge-commit"]) {
|
||||
test(`${hook} rejects main and allows other branches`, () => {
|
||||
const onMain = spawnSync("sh", [join(HOOKS, hook)], { cwd: repoOn("main"), encoding: "utf8" });
|
||||
assert.equal(onMain.status, 1);
|
||||
assert.match(onMain.stderr, /committing on main is not allowed/);
|
||||
assert.equal(spawnSync("sh", [join(HOOKS, hook)], { cwd: repoOn("fix/x") }).status, 0);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user