lefthook を使って git hook を管理する¶
導入¶
あらゆるプロジェクトで、コードの品質を機械的にチェックするための仕組みが欲しかった。git hook を使って、コミット時に自動でコードの品質がチェックされる仕組みを考えてみた。加えて、コミット時以外の任意のタイミングでもチェックできるように色々とやってみた。
lefthook の採用¶
git hook は、git の操作時に自動で実行されるスクリプトである。例えば、.git/hooks/pre-commit
にコードの品質をチェックするようなスクリプトを書いておくと、コミット時に自動でコードの品質がチェックされるようになる。git hook を自分で書くこともできるが、pre-commit
、husky
、lefthook
などのツールを使うと設定を良い感じにしてくれる。
これらのツールの中で、lefthook
を次の理由で採用した。
- シングルバイナリで依存関係が少ない
- 並列実行ができる
- 設定ファイルを YAML で記述する。GitHub Actions や Kubernetes マニフェストなどでスクリプトを書くのと同じ感覚で設定できる
- プロジェクト用の設定ファイルを個人用の設定ファイルで上書きできる
- git hook 以外にもタスクランナーとしても使える
lefthook の設定¶
基本的な使い方¶
lefthook
の設定ファイルは .lefthook.yml
に記述する。これを上書きするための設定は .lefthook-local.yml
に記述する。他のパスにある設定ファイルを読み込んだり、Web 上の設定ファイルを読み込むこともできる。
設定ファイルには、pre-commit
、commit-msg
、pre-push
などのフックを記述する。フック以外にも、lefthook run <task>
で任意のタスクを実行することもできる。次の例ではtest
というタスクが定義されている。
.lefthook.ymlの例 | |
---|---|
今回作ったもの¶
今回設定したものはこちら。Python や Terraform、Dockerfile、ShellScript、Kubernetes マニフェストなど複数の言語やファイル形式に対応している。これらを使うことで、あらゆるプロジェクトでコードの品質を機械的にチェックできる。 git hook 以外にも、lint や test などの名前でタスクの実行もできる。--all-files
オプションをつけることで、stage されていないファイルを含む全てのファイルを対象に実行される。
今回作ったlefthook
の設定を全てのプロジェクトで利用できるように、.lefthook.yml
と .lefthook-local.yml
を自動で作成するlh
コマンドを作った。作成した設定ファイルに、先のディレクトリの設定ファイルを読み込むようにしている。lh
コマンドは、lefthook
のエイリアスのようにも使える。
lh コマンドは、実行時に.lefthook.yml
と .lefthook-local.yml
がなければ作成し、lefthook を実行する。これにより、プロジェクトごとに設定ファイルを作成する手間を効率化している。本当はプロジェクトごとに設定ファイルを作成せずに、1 箇所に設定ファイルを置いて、それを使うようにしたかった。調べたところlefthook
もそれ以外のツールも、プロジェクトごとに設定ファイルを作成するようになっていたので、このような形にした。
lh コマンドの補完¶
私の環境ではargc-completion
を使って、コマンドの補完をしている。argc-complesion に lefthook 用の補完スクリプトがあり、補完が効くようになっている。lh コマンドでも同様の補完が聞くように、lefthook 用の補完スクリプトを複製し、編集している。
lefthook の気になった挙動¶
今回の設定をするにあたり、lefthook
の挙動についていくつか気になる点があったので、それについて記述する。
-
extends のプロパティでマシン内の設定ファイルを読み込む。.lefthook.yml から設定ファイル A を読み込み、設定ファイル A から設定ファイル B を読み込んでみた。設定ファイル A に相対パスで B を読み込む記述をするのであれば、元の.lefthook.yml からの相対パスで指定しないといけない。そのため、設定ファイル A をあらゆるところから読み取って、共通で使い回すのは難しいように感じた。複数のファイルを経由せずに、対象のファイルを直接読み込むのが良さそう。
graph TD A[.lefthook.yml] --> B[設定ファイルA] B --> C[設定ファイルB]
-
group
というプロパティがあり、command
を論理的にグループ化できる。グループ化することで、並列処理や直接実行の制御がしやすくなる。group を利用する際の注意点として、個別のコマンドは実行できない。group を使わなければ、lefthook run pre-commit rubocop
のように個別のコマンドを実行できる。group を使うと、lefthook run pre-commit
のようにグループ内のコマンドを個別に実行できない。 - 実行の対象は
files
で記述したファイルをglob
,exclude
でフィルタリングしたものになる。files
の結果はファイルである必要があり、ディレクトリを含むことはできいない。--all-files
オプションを使うと、files
の設定は考慮されず、全てのファイルが対象になる。このことから、files
で細かいフィルタリングをしていると、--all-files
を使った時にフィルタリングが効かなくなる。オプションの有無で挙動が変わらないように、細かいフィルタリングはfiles
でやらずに、run
の中でやるのが良さそう。