コミット前のlintやテストを毎回手動で実行するのは忘れがち

mise.tomlにlintやテストを実行するタスクを定義していても、コミット前に毎回自分で実行するのは忘れがちである。mise generate git-pre-commitを使うと、コミット時にmise run <タスク名>を自動実行するgit pre-commitフックを生成できる。

mise generate git-pre-commitの基本

mise.tomlpre-commitタスクを定義したリポジトリで実行する。

$ mise generate git-pre-commit
#!/bin/sh
STAGED="$(git diff-index --cached --name-only -z HEAD | xargs -0)"
export STAGED
export MISE_PRE_COMMIT=1
exec mise run pre-commit "$@"

生成されるシェルスクリプトは、ステージ済みファイルの一覧をSTAGED環境変数にエクスポートしてからmise run pre-commitを実行する。既定では標準出力に表示するだけで、ファイルへの書き込みは行わない。

–writeで.git/hooks/pre-commitに書き込む

-w(--write)を付けると、.git/hooks/pre-commitに書き込まれ、実行権限も付与される。

$ mise generate git-pre-commit --write
Wrote to /path/to/project/.git/hooks/pre-commit

実際にファイルをステージしてコミットすると、フックが実行される。

$ git add bar.txt foo.txt
$ git commit -m "second commit"
[pre-commit] $ echo staged files: $STAGED
staged files: bar.txt foo.txt
[master 5206cb3] second commit

タスク内で$STAGEDを参照すると、コミット対象のファイルだけを取り出して処理できる。

–taskで実行するタスクを変更する

既定ではpre-commitタスクが実行されるが、-t(--task)で別のタスク名を指定できる。

$ mise generate git-pre-commit --task=lint
#!/bin/sh
STAGED="$(git diff-index --cached --name-only -z HEAD | xargs -0)"
export STAGED
export MISE_PRE_COMMIT=1
exec mise run lint "$@"

–hookでpre-commit以外のフックを生成する

--hookを指定すると、.git/hooks/$hookという別のフックとして書き込める。

$ mise generate git-pre-commit --hook=pre-push --write
Wrote to /path/to/project/.git/hooks/pre-push

--hookだけを指定した場合、実行するタスク名はpre-commitのままなので、フックの種類ごとにタスク名も変えたい場合は--taskと組み合わせる。

mise.tomlがリポジトリのルート以外にある場合

git hookは常にリポジトリのトップレベルディレクトリから実行される。mise.tomlがサブディレクトリにある場合、--の後にmiseへ渡す引数を指定すると、miserunの間に埋め込まれる。

$ mise generate git-pre-commit -- -C subdir
#!/bin/sh
STAGED="$(git diff-index --cached --name-only -z HEAD | xargs -0)"
export STAGED
export MISE_PRE_COMMIT=1
exec mise -C subdir run pre-commit "$@"

-C subdirを挟むことで、subdir配下のmise.tomlを対象にタスクを実行できる。

より高度なpre-commitが必要な場合

複数タスクの並列実行や、変更されたファイルに応じたタスクの絞り込みなど、より高度な機能が必要な場合はmiseの姉妹プロジェクトであるhk の利用も検討するとよい。