Git 2.54から、commit hookはgitconfigに書けるようになったので、リポジトリで共有する
Git 2.54から、hookを設定ファイルへ書けるようになりました。Massivのモノレポでもこの仕組みを使い、hookを起動するためのシェルスクリプトを置かずにpre-commitを登録しています。
hookをgitconfigに書けるようになった
これまでGitは、hookを.git/hooks/<イベント名>に置かれた実行可能ファイルとして探してきました。.git配下のファイルはコミットできないため、チームでhookを揃えるには、各自が.git/hooksへスクリプトを書き込むか、リポジトリに置いたスクリプトをcore.hooksPathで指すことになります。
Git 2.54のリリースノートには次のように書かれています。
Hook commands are now allowed to be defined (possibly centrally) in the configuration files, and run multiple of them for the same hook event.
書き方は、hook.<名前>.eventでイベントを、hook.<名前>.commandで実行するコマンドを指定する形です。<名前>はhookを識別するために付ける任意の名前です。
[hook "eslint"]
event = pre-commit
command = npx eslint .
git hookのドキュメントによると、押さえておきたいのは次の3点です。
- 同じイベントへ複数のhookを設定でき、Gitが設定を読む過程で
eventに出会った順で実行される - 設定はシステム、グローバル、リポジトリのどの階層にも書ける
- 従来どおり
.git/hooksに置かれたhookは最後に実行される
置き場所が1か所に限られるcore.hooksPathと違い、既存の構成を壊さずにhookを足せます。git hook list pre-commitを実行すると、そのイベントで動くhookの一覧を確認できます。
hkの起動をgitconfigに書く
Massivでは、開発ツールのバージョンをmiseで揃え、コミット前のフォーマッターとリンターの実行をhkに任せています。hkが管理するのはhookの中身なので、Gitへの登録は別に用意する必要があります。
core.hooksPathを使うなら、hk run pre-commitを呼ぶだけのスクリプトを実行権限付きでリポジトリへ置くことになります。設定ベースのhookであれば、リポジトリルートの.gitconfigへ次の2行を置き、miseのpostinstall (mise installのあとに走る処理) で各メンバーの.git/configへ取り込むだけで済みます。
# .gitconfig
[hook "hk-pre-commit"]
command = mise exec -- hk run pre-commit
event = pre-commit
mise exec --を挟んでいるのは、mise.tomlでバージョンを固定したhkと各ツールがPATHに乗った状態でhookを動かすためです。実行権限を持つファイルをリポジトリで管理する必要はありません。
Git hookの設定をリポジトリで統一する
リポジトリに置いた.gitconfigをGitが自動で読むことはありません。各メンバーの.git/configからinclude.pathで取り込む必要があります。その1行は、miseのpostinstall hookに任せています。
# mise.toml
[hooks]
postinstall = "git config set --local include.path ../.gitconfig"
include.pathは.git/configを起点とする相対パスなので、../.gitconfigはリポジトリルートのファイルを指します。つまりmise installを通すだけでpre-commit hookが設定される仕組みで、参加したばかりのメンバーが追加のコマンドを叩く必要はありません。
各メンバーの.git/configに書き込まれるのはinclude.pathの1行だけで、hookの設定そのものはリポジトリの.gitconfigに置かれたままです。そのため.gitconfigの変更をgit pullで取り込めば、次のコミットから新しい設定が使われます。
注意点が1つあります。hook.*はGit 2.54以上でしか解釈されません。Gitは知らない設定キーを暗黙的に読み飛ばすので、古いGitではhookが登録されないまま、警告も出ずにコミットが進みます。そのため、git本体もmiseなどでバージョンを揃えて管理するのがよいと思っています。