···11+#!/usr/bin/env node
22+const fs = require("node:fs");
33+// This hook receives the path to the temporary commit message file as its one and only
44+// positional argument.
55+const msg = fs.readFileSync(process.argv[2], { encoding: "utf-8" });
66+77+// NOTE While technically possible that the very first line would be a comment, this
88+// should be unlikely enough that we can safely test the first line.
99+const title = /^([^\n]+)(?:\n|$)/.exec(msg)?.[1] || "";
1010+if (/^[A-Z]/.test(title) || /:\s[A-Z]/.test(title)) {
1111+ console.error("Commit message titles should be in lowercase");
1212+ process.exitCode = 1;
1313+}
1414+if (!/.+:\s[^\n]+/.test(msg)) {
1515+ console.error("First line should be in the format `<service/top-level directory>/<affected package/directory>: <short summary of change>`");
1616+ process.exitCode = 1;
1717+}
1818+1919+if (!/\nSigned-off-by:/.test(msg)) {
2020+ console.error("Commits must be signed-off (use `git commit -s` to add the sign-off trailer)");
2121+ process.exitCode = 1;
2222+}