created: 2024-03-05T09:34:01.277Z

git hook を zx で書くとよかった

Git フック を zx で書くとすごいよかったという話。

よかったところ

  • js だとややこしい文字列処理の実装が簡単
  • 実装ファイルの拡張子を .mjs にして jsdoc を書くと補完がバリバリ効く
  • 実装ファイルの横に .test.mjs を置けば node:assert でテストがひととおり書ける
  • 「いまいるブランチを調べる」みたいなコマンドはシェルスクリプトっぽく書ける

zx はハマるところも少なくて本当にシェルと js の良いとこ取りだった。

基本的に nodejs に child_process.spawn のシンタックスシュガーが入っただけのモノなので nodejs が書ければもうこれを使ったほうがいいと思う。

がんばれば TypeScript の文法を使えるらしいけど、たぶんそれはもうやりすぎなのでやらなかった。

実装

「ブランチ名の prefix に数字があったら GitHub の issue 番号扱いする」というよくあるもの。

拡張子が mjs なのがミソで、node にオプションをつけなくてもちゃんと動き、保管も効く。

#!/usr/bin/env zx
import { writeFileSync } from "node:fs";

/**
 * @param {string} branchName
 * @param {string} originalCommitMsg
 * @returns {string}
 */
export function prepareCommitMsg(branchName, originalCommitMsg) {
  if (originalCommitMsg.startsWith("fixup!")) {
    return originalCommitMsg;
  }

  const match = branchName.match(/^(?:\w+\/)?(\d+)-/);
  if (!match) {
    return originalCommitMsg;
  }

  const issueNumber = match[1];
  const padding = originalCommitMsg.startsWith(" ") ? "" : " ";
  return `[#${issueNumber}]${padding}${originalCommitMsg}`;
}

async function main() {
  const commitMsgFile = process.argv[3];
  const originalCommitMsg = await $`cat ${commitMsgFile}`;
  const branchName = await $`git rev-parse --abbrev-ref HEAD`;
  const commitMsg = prepareCommitMsg(
    branchName.stdout,
    originalCommitMsg.stdout
  );
  writeFileSync(commitMsgFile, commitMsg);
}
main();
import * as assert from "node:assert";
import { prepareCommitMsg } from "./prepare-commit-msg.mjs";
const cases = [
  [["123-test-branch", "bugfix"], "[#123] bugfix"],
  [["123-test-branch", " bugfix"], "[#123] bugfix"],
  [["test-branch", "bugfix"], "bugfix"],
  [["topic/123-test-branch", "bugfix"], "[#123] bugfix"],
  [["123-test-branch", "fixup! bugfix"], "fixup! bugfix"],
];
for (const c of cases) {
  const [args, expected] = c;
  assert.equal(prepareCommitMsg(...args), expected);
}

配置

prepare-commit-msg.mjs みたいな名前をつけて、symlink を貼れば動く。

$ ln -s $(pwd)/scripts/git-hooks/prepare-commit-msg.mjs .git/hooks/prepare-commit-msg
ピタゴラスイッチ おりたたみハンカチ劇場 ピタとゴラけんかのまき
[ad] ピタゴラスイッチ おりたたみハンカチ劇場 ピタとゴラけんかのまき
ベネリック (Baby Product)