patorashのブログ

方向性はまだない

Lefthookを使ってgitでcommitする際にDocker環境でrubocopを自動実行できるようにした

長年の悩みで、git commitするタイミングでrubocopを実行したいというのがあった。以前に個人的にgitのpre-commitを使って行う方法を調べて、それをやっていたのだが、それだと自分の環境ではできるけれど、他の開発メンバーに同じことを適用できない。また、PCが変わったタイミングでcloneしてきてから気づいたが、hookの定義は.git以下にあるので管轄外で、また定義し直さなくてはいけなくて面倒になったりしていた。

RedMineのチケットに、「開発メンバー全員がcommit前にrubocopを実行されるようにする」というのを登録していたので、時間が多少できたのでやってみることにした。よく聞いていたのはpre-commitというgemとovercommitというgemである。

pre-commitを試す

github.com

pre-commitは、その名の通り、commitする前にフックして処理を実行するライブラリ。処理を定義ファイルに書いておくことができるので、個別に.git/hooks/pre-commitファイルを修正しなくていいのが助かる…はずであった。

pre-commitを入れてみたが、前提がホストOSのRubyのため、まともに動かず。以下のQiitaの記事を見て、git config pre-commit.ruby "docker-compose run --rm web bundle exec ruby"と打って、pre-commitで呼ばれるコマンドを定義してみたものの、動かず。(もちろん、コンテナ名は適宜変えている)

qiita.com

以下のエラーが出た。

-e:1: syntax error, unexpected end-of-input

pre-commitのコードを見ると、コンテナのRubyを呼び出して-eオプションで、渡された文字列をコードとして実行しようとしているのだが、どうすれば上記のエラーが消せるのかがわからなかった…。まぁどちらにしても、pre-commitのコードを修正せざるを得なさそうなので、メンバー全員に適用させるには修正コストが高くなりそうであった。

そのため、一旦諦め、overcommitを試すことに。

overcommitを試す

github.com

overcommitもpre-commitと同じようなものだけれど、ホストOSにRubyが入っていることが前提となり、コンテナ側の呼び出しができない。多機能そうなのだけれど、こちらはすぐに気づいたので早々に諦めた。

pre-commitでなんとかするしかないのか…しかし、最近はコンテナに開発環境を入れて開発することが増えているんだから、なにか別の方法があるはずだろう…と思い、調べること数十分。遂に見つけたのがLefthookだった。

Lefthookを試す

github.com

Lefthookは、gemではなく、Goで作られているgitのhookを纏めるツールである。lefthook.ymlに定義を書いておくと、それをgitのhookのタイミングで実行できる。

見つけた記事は、twitterでフォローしている、れいなさんのQiitaの記事。

qiita.com

読んですぐに、「これだ!」と思った。因みにtechrachoでも紹介されていた。

techracho.bpsinc.jp

インストール

Lefthookはシングルバイナリで提供されているので、インストールも簡単。Macならば、Homebrewで入れることもできる。

brew install lefthook

そして、プロジェクトのルートディレクトリで、以下を行うと、lefthook.ymlが出来上がる。

lefthook install

pre-commitの処理を定義する

定義の仕方は、lefthook.ymlを見れば大体わかる。しかし、Dockerコンテナでやるのは、れいなさんの記事を参考に、piped: trueを追加して、処理を順番に書いた。

pre-commit:
  piped: true
  commands:
    1_docker-compose:
      root: .
      run: docker-compose up -d rails
    2_exec_rubocop:
      glob: "*.{rb,rake}"
      exclude: "application.rb|routes.rb"
      run: docker-compose exec rails bundle exec rubocop -P {staged_files}

対象をstaged_filesとすることで、rubocopで検証するファイルを少なくして、実行時間を短くできた🚀。今までは自力でgit diffを駆使して差分ファイルを絞り込んだりしていたのに、この指定だけで済むのは素晴らしい🥳

定義を動作を検証する

定義をテストするには、実際に実行してみるのが一番。以下のコマンドを打つと、gitでcommitせずに試すことができる。

lefthook run pre-commit

問題ないことを確認!

Lefthookはタスクランナーでもある

一番驚いたのが、Lefthookはgitのhookに関係なく、タスクを定義できるのである。

https://github.com/evilmartians/lefthook#your-own-tasks

上記のURLの箇所を参考に、rubocopのauto-correct(自動修正)のタスクを定義した。

fixer:
  commands:
    ruby-fixer:
      glob: "*.{rb,rake}"
      exclude: "application.rb|routes.rb"
      run: docker-compose exec rails bundle exec rubocop --force-exclusion --safe-auto-correct {staged_files}

今までは、細かいオプションを設定するのが面倒で以下のコマンドを打っていた。

docker-compose exec rails bundlle exec rubocop -a

しかしこれだと、対象ファイルが全部になるので、時間がかかる。あと単純にコマンドが長い。まぁpecoを使って履歴から呼び出していたのでさほど苦でもないんだけれど…。

だが、上記のタスクを定義したことで、これで済む。

lefthook run fixer

圧倒的に短い!そして、対象ファイルがstaged_filesなので、処理速度も速い!それに、今後Prettierでのフォーマットも入れていきたいと思っていたので、それの定義も追加すれば更に自動化できる。

他にも便利なタスクを定義していけそうなのは嬉しい。

まとめ

gitのフック系ツールはLefthookが一番使い勝手が良さそう。シングルバイナリでインストールも楽だし、設定もyaml形式で簡単。Dockerコンテナを指定して処理もできるので、開発環境をコンテナ化している場合はもちろん、そうでなくてもLefthookを使うのがいいと思う。