mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
ci: add the pre-push gate and stop lying about it in the canon
Section 10.1 promised pre-push as the blocking gate that replaces pull requests. The hook did not exist, so the document promised a check that was not there — worse than saying nothing, because a promise like that gets relied on. Until now process-gate ran only as the catch-up job in CI, which reports after the code is already in dev. The hook skips branch deletions and tags, and for a branch the remote has not seen it measures from the merge-base with dev rather than from the root, or every violation committed before the gate existed would make it impossible to pass. A missing script does not block a push: old checkouts and worktrees have to stay usable. gh is optional on purpose. Reading issue status needs the network, and a hook that cannot work on a train is a hook people switch off; offline it runs what it can and CI does the strict pass. The executable bit is the quiet part. Git skips a hook without +x and says nothing — the gate reports success by being absent. Measured on a real push: mode 644 produces zero lines from the gate and the push goes through, 755 stops it. The API cannot set the bit, so install-hooks restores it on every install. Issue: #121 User-Visible: no
This commit is contained in:
@@ -1,8 +1,29 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, realpathSync } from 'node:fs';
|
||||
import { chmodSync, existsSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const HOOKS = ['commit-msg', 'pre-push'];
|
||||
|
||||
// Git skips a hook that is not executable, and says nothing about it. A hook that
|
||||
// silently does not run is worse than no hook: the gate reports success by being
|
||||
// absent. The bit cannot be set through the GitHub API either — a file pushed
|
||||
// that way arrives as 100644 — so it is restored here, on every install.
|
||||
function makeHooksExecutable(hooksDir) {
|
||||
if (!existsSync(hooksDir)) return;
|
||||
for (const name of readdirSync(hooksDir)) {
|
||||
if (!HOOKS.includes(name)) continue;
|
||||
const file = join(hooksDir, name);
|
||||
try {
|
||||
const mode = statSync(file).mode & 0o777;
|
||||
if ((mode & 0o111) !== 0o111) chmodSync(file, mode | 0o111);
|
||||
} catch {
|
||||
// Windows reports modes it cannot change; git there runs hooks regardless.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const packageRoot = realpathSync(fileURLToPath(new URL('..', import.meta.url)));
|
||||
|
||||
try {
|
||||
@@ -19,6 +40,7 @@ try {
|
||||
execFileSync('git', ['config', 'core.hooksPath', '.githooks'], {
|
||||
cwd: packageRoot, stdio: 'ignore',
|
||||
});
|
||||
makeHooksExecutable(join(packageRoot, '.githooks'));
|
||||
console.log('House Plan: installed repository hooks from .githooks');
|
||||
} catch {
|
||||
// npm also runs prepare for source archives and dependency installs where
|
||||
|
||||
Reference in New Issue
Block a user