コミット前に自動でRubocopを実行する(仮想開発環境編)


GithubのPR作成タイミングで、Rubocop を使ったコーディングスタイルのチェックをしている方々は多いのではないでしょうか。
私もその一人です。

原則、オールグリーンにならないとマージできないので、極力コーディングチェックで怒られたくありません。
pushする前に、Rubucopでチェックをしているのですが、仮想環境を使っているため

  • 別画面に切り替えたり
  • コードの同期を待ったり
  • コマンドを実行したり

・・・地味なストレスが溜まります。

Gitのpre-commitを使い、commitタイミングでRubocopを自動実行させる方法を教えていただいたので、仮想開発環境バージョンを残します。

Pre-commitとは

Git hookの仕組みの一つで、 コミットメッセージが入力される前に、任意のスクリプトを実行するできる機能です。
大雑把なイメージとしては、「git commitコマンドを実行前に、スクリプトを走らせる機能」です。

その前に、Git hookとは、gitの特定のアクションを

実行する直前 または、 実行後

に、特定のスクリプトを実行するための仕組みです。
詳しくは、Gitの公式サイトへ

今回は、Rubocop で利用しますが、様々なタイミングでスクリプトを実行させることができるので、いろいろな活用方法が考えられます

仮想開発環境でRubocopを自動実行

Macで開発環境を構築している場合は、こちらの記事を参考に! 仮想開発環境版もこちらの記事を参考に作りました

仮想開発環境を使っている人向けのpre-commit
$ cd <Railsプロジェクトのディレクトリ>
$ vim .git/hooks/pre-commit

===========================
#!/bin/bash

vagrant_path="<vagrantのディレクトリ>"
workspace_path="<Vagrant上のRailsプロジェクトディレクトリ>"

files=`git diff --cached --name-only --diff-filter=AM | grep '.rb$' | tr '\n' ' '`               --- (1)

# 実行するファイルがなければ、何もしない
if [ ! -n "${files}" ]; then
  exit
fi

echo 変更があったファイルにrubocopを実行します。

cd ${vagrant_path} && vagrant ssh -c "cd ${workspace_path} && bundle exec rubocop -RDa ${files}"  --- (2)

echo rubocopの実行が完了しました。

exit $?
===========================

コマンドの解説

(1)では、変更差分があったファイル(git diffの実行結果)の中から、 Rubyファイル(grep)を抽出します。
その後、trコマンドで、文字列に含まれる改行を空白に変換します。

(1)で抽出されるRubyファイルの例
app/model/query.rb

(2)では、vagrantディレクトリに移動し、vagrantにSSH(vagrant ssh)します。
vagrant sshコマンドは、-cオプションをつけると、引数で渡したコマンドを実行してくれます。

vagrant内で実行されるコマンド
cd ${workspace_path} && bundle exec rubocop -RDa ${files}

まず、vagrant内のRailsプロジェクトのディレクトリに移動(cd)します。
Mac側のディレクトリではなく、vagrantに同期しているディレクトリなので気をつけましょう。

(1)で抽出したRubyファイルに対し、rubocopを実行します。

rubocopコマンド
bundle exec rubocop -RDa app/model/query.rb

実行結果

pre-commitファイルを作った状態で、git commitを試してみます。

実行結果
yn-misaki$ git commit -m ":cop: テスト用"
変更があったファイルにrubocopを実行します。
Inspecting 1 file
.

1 file inspected, no offenses detected
rubocopの実行が完了しました。
[rubocop_test 5502ea0] :cop: テスト用
 2 files changed, 9 insertions(+), 1 deletion(-)

vagrant内で、rubocopを実行し、その実行結果が返ってきます。
実行結果が2 files changedとなっているのは、 変更対象に.ymlファイルがあるからです。
つまり、rubocopの実行対象外です。

ローカルと仮想開発環境の両方で使いたい場合・・・!

ローカル派と仮想派で、開発環境が分かれている場合、if文を入れて対応します。

ローカル環境と仮想環境を共存させる
$ vim .git/hooks/pre-commit

===========================
if [ -e ${vagrant_path} ]; then
  cd ${vagrant_path} && vagrant ssh -c "cd ${workspace_path} && bundle exec rubocop -RDa ${files}"
else
  bundle exec rubocop -RDa ${files}
fi
===========================

vagrantディレクトリが存在しないとき、ローカルでrubocopを実行します。
これで、ローカル派と仮想派が共存できる pre-commitの完成です。

まとめ

pre-commitを使うことで、自動でコーディングチェックができるようになりました。

pre-commitを使いはじめてから、PR上でrubocopで怒られなくなり、どういうコードがダメなのか、気付く -> 直すができて良いです
あとは、rspecもコミット前に実行させようかなーと思っています(もう一台仮想環境を作るか、同一のVagrant内で実行するか迷い中...)

参考