Vectrum
Sign In
66 lines · 2.5 KB · TypeScript
// The git hook every push runs, as a Node.js script. See lib/push-hooks.ts for the other side.

/**
 * The pre-receive hook, run by git with the push's "<old> <new> <ref>" lines on stdin. It refuses
 * refs with a path component starting with "-" (git commands could mistake them for options) by
 * itself, and asks the server about everything else. Without a session (receive-pack started by
 * something other than Vectrum) it only does the first.
 */
export function hookScript() {
  return `#!${process.execPath}
"use strict";
// Written by Vectrum (lib/hook-script.ts); rewritten on every start.
const { execFileSync } = require("node:child_process");
const net = require("node:net");

const ZERO = /^0+$/;
let input = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (d) => (input += d));
process.stdin.on("end", () => {
  const updates = input.split("\\n").filter(Boolean).map((line) => {
    const [oldSha, newSha, ref] = line.trim().split(" ");
    return { old: oldSha, new: newSha, ref };
  });
  const bad = updates.filter((u) => ("/" + u.ref).includes("/-"));
  if (bad.length) {
    for (const u of bad) console.error("Vectrum: refusing " + u.ref + " (ref name components must not start with '-')");
    process.exit(1);
  }
  const socket = process.env.VECTRUM_HOOK_SOCKET;
  const token = process.env.VECTRUM_PUSH_TOKEN;
  if (!socket || !token) process.exit(0);
  for (const u of updates) {
    u.ff = true;
    if (ZERO.test(u.old) || ZERO.test(u.new) || !u.ref.startsWith("refs/heads/")) continue;
    try {
      execFileSync("git", ["merge-base", "--is-ancestor", u.old, u.new], { stdio: "ignore" });
    } catch {
      u.ff = false;
    }
  }
  const fail = (message) => {
    console.error("Vectrum: " + message);
    process.exit(1);
  };
  const timer = setTimeout(() => fail("timed out checking the branch rules."), 20000);
  let reply = "";
  const conn = net.createConnection(socket, () => conn.write(JSON.stringify({ token, updates }) + "\\n"));
  conn.setEncoding("utf8");
  conn.on("data", (d) => (reply += d));
  conn.on("error", () => fail("couldn't check the branch rules. Try again."));
  conn.on("end", () => {
    clearTimeout(timer);
    let answer;
    try {
      answer = JSON.parse(reply);
    } catch {
      return fail("couldn't check the branch rules. Try again.");
    }
    for (const m of answer.messages || []) for (const line of String(m).split("\\n")) console.error("Vectrum: " + line);
    process.exit(answer.ok ? 0 : 1);
  });
});
`;
}