🥊

Git フック管理ツール「Lefthook」の紹介

2023/06/23に公開

LefthookはGit フックを管理するためのツールです。

https://github.com/evilmartians/lefthook

Git フックとは特定のGitコマンドが実行される前後に自動的に実行されるスクリプトのことを指します。これによって、たとえば、コミット前に静的解析を実行することが出来ます。

https://git-scm.com/book/ja/v2/Git-のカスタマイズ-Git-フック

Lefthookは、これらのフックをより簡単に管理し、コードの品質を保つために使用されます。

同様のことを実現しようとすると、たとえばフロントエンドではHuskylint-stagedを組み合わせ、コミット時にPrettierとESlintを実行し、コードのフォーマットや静的解析を適用しているプロジェクトも多いと思います。

以下はLefthookの特徴にあたります(readmeより引用)

Fast and powerful Git hooks manager for Node.js, Ruby or any other type of projects.

  • Fast. It is written in Go. Can run commands in parallel.
  • Powerful. It allows to control execution and files you pass to your commands.
  • Simple. It is single dependency-free binary which can work in any environment.

日本語訳はこちらとなります。

Node.js、Ruby、その他あらゆるタイプのプロジェクトのための、高速で強力なGitフックマネージャです。

  • Goで書かれているため、高速です。コマンドを並列に実行することができます。
  • コマンドに渡すファイルや実行を制御することができます。
  • シンプルなバイナリで、どのような環境でも動作します。

Husky+lint-stagedはnpmやyarnでインストールしますが、その際にNode.jsが必要です。そのためフロントエンド以外の開発で利用する際は、場合によってはこのためだけにNode.jsを入れる必要があります。

しかし、Lefthookではそのような依存関係がありません。そのため、各自が利用したい環境に合わせて利用することが出来ます。詳しくはInstallation guideをご覧ください。

また、Lefthook公式が他の手法との比較を挙げているのでその他の違いについては以下をご確認ください。

https://github.com/evilmartians/lefthook/wiki/Comparison-with-other-solutions

私自身、FlutterやRailsを使った開発を行なっていますが、そのどちらでもLefthookを利用しています。この記事では、インストール手順とFlutter・Railsにおける設定内容の一例をご紹介します。

インストール手順

様々な方法がありますが、今回はHomeBrewでインストールします。

brew install lefthook

その後、Lefthookを利用したいプロジェクトで下記のコマンドを実行します。

lefthook install

するとルート直下に下記のようなlefthook.ymlが生成されます。

# EXAMPLE USAGE:  
#  
#   Refer for explanation to following link:  
#   https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md  
#  
# pre-push:  
#   commands:  
#     packages-audit:  
#       tags: frontend security  
#       run: yarn audit  
#     gems-audit:  
#       tags: backend security  
#       run: bundle audit  
#  
# pre-commit:  
#   parallel: true  
#   commands:  
#     eslint:  
#       glob: "*.{js,ts,jsx,tsx}"  
#       run: yarn eslint {staged_files}  
#     rubocop:  
#       tags: backend style  
#       glob: "*.rb"  
#       exclude: "application.rb|routes.rb"  
#       run: bundle exec rubocop --force-exclusion {all_files}  
#     govet:  
#       tags: backend style  
#       files: git ls-files -m  
#       glob: "*.go"  
#       run: go vet {files}  
#   scripts:  
#     "hello.js":  
#       runner: node  
#     "any.go":  
#       runner: go run

このファイルを編集することで、コミット前後やプッシュ前後に実行したい処理を定義できます。
次にFlutter・Railsの各開発において、定義している処理をご紹介します。

Flutterでの使用例

Flutterでの開発において、Dartの静的解析としてflutter analyzeがあります。

https://docs.flutter.dev/testing/debugging#the-dart-analyzer

Visual Studio Codeを利用しているとコードフォーマットについては公式プラグインによって保存時にフォーマットされますが、Linterは警告を発するのみで対応しなければ気づけば山のような警告となってしまいます。

そこで、Lefthookを使ってコミット時に静的解析をして、問題があれば修正するようにしました。以下が設定内容です。

pre-commit:
  commands:
    1_analyzer:
      run: flutter analyze
    2_linter:
      run: dart fix --apply 

設定について

  • commandのprefixについて
    • commandを複数定義した場合に実行順が辞書順となるため、prefixをつけて実行順を制御しています。これによって、1_analyzer2_linterの順で実行されます。
  • 実行順について
    • 先にflutter analyzeで問題を検知して、その後dart fix --applyで修正できるところは自動修正する構成にしました。
    • 実行順を入れ替えて、自動修正後にコミットすることもできますが、自動修正の内容を確認した上でコミットしたいため、このようにしています。
  • コードフォーマットについて
    • コードフォーマットを設定することもできますが、Visual Studio Codeにプラグインを入れていて、かつ開発者が1人でやっているのでlefthookの設定としては入れていません(Visual Studio Codeのプラグインによってフォーマットされるため)。
    • ただ、複数人での開発時は入れたほうが良いと思います。

その他、プッシュ時にテストを実行する等も出来るので、コードの品質を保つために役立ちます。

Railsでの使用例

Railsでの開発において、Rubyの静的解析およびフォーマッターとしてRubocopを導入しているケースは多いと思います。

https://github.com/rubocop/rubocop

また、Dangerを利用して、CIでRubocopを実行して指摘事項があればコメントをつけるという運用しているプロジェクトも多いかと思います。

ただ、開発環境で事前にRubocopを実行していないと、DangerによってGitHubのPRに大量のコメントがついて見にくい・・・となってしまった経験がある方はいるのではないでしょうか(私もその1人です)。

毎回手動で実行するのも面倒だと思っていましたが、そんなときにLefthookです。以下のようにlefthook.ymlを設定してみましょう。

pre-commit:  
  commands:  
    1_rubocop_check:  
      glob: "*.rb"  
      exclude: "schema.rb"  
      run: bundle exec rubocop {staged_files}  
    2_rubocop_autocorrect:  
      glob: "*.rb"  
      exclude: "schema.rb"  
      run: bundle exec rubocop -a {staged_files}

設定について

  • 実行対象を下記のようにしています。これによって不要なファイルを対象としないようにしています。
    • staged_files
    • 拡張子が.rbのファイル
    • schema.rbに対しては適用しない。
  • commandのprefixについて
    • Flutterのケースと同様に、commandを複数定義した場合に実行順が辞書順となるため、prefixをつけて実行順を制御しています。これによって、1_rubocop_check2_rubocop_autocorrectの順で実行されます。
  • 実行順について
    • 先にautocorrectなしで実行し、問題がなければコミットされます(autocorrectの対象がないため、2_rubocop_autocorrectも通ります)
    • 問題があればコミットに失敗しますが、2_rubocop_autocorrectは実行されるため、autocorrectで修正できるものであれば自動修正されます。

その他のオプションについて

現在は利用していませんが、どこかのタイミングで使えそうなオプションについて紹介します。

parallel

parallel: true を指定することで、指定したコマンドを並列実行することが出来ます。これは、例えば、複数のテストスクリプトを同時に実行したい場合などに便利です。ただし、一つのコマンドの結果が他のコマンドの実行に影響を与える場合など、コマンド間に実行順序の依存関係がある場合は注意が必要です。

piped

piped: trueを指定することで、指定したコマンドが辞書順に実行され、なおかつ指定したコマンドのどれかが失敗した場合、そこで処理が停止します。これは、一連のコマンドを実行する際に、前のコマンドが成功した場合のみ次のコマンドを実行したいというケースに特に便利です。例えば、テストプロセスが複数のステップから成る場合、前のステップが失敗したら後続のステップを実行するのは無駄になるかもしれません。このような場合、piped: trueを使用すると、前のステップが失敗した時点で全体のプロセスが停止し、開発者は問題のある部分にすぐに対処できます。

その他についてはこちらのページをご参照ください。

おわりに

Lefthookについての説明とインストール方法、Flutter・Railsにおける利用事例をご紹介しました。

ローカル環境で手動で確認しているコマンドがあれば、Lefthookを使ってコミット前やプッシュ前に動作するようにしてみると自然とチェックできて楽になるので、ぜひインストールして活用してみてください。

Discussion