pre-commit gemでgit commitに連動してRubocopを実施する
丹内です。
gitのcommit直前に自動でrubocopによるスタイルチェックが行われるように設定します。
なぜスタイルチェックが必要なのか
GitFlowあるいはGitHub Flowにのっとり複数人で開発する場合、Pull Requestベースのコードレビューが重要です。
Pull Requestでは実装が仕様を満たしているか、クラスやメソッドの設計が適切か、ナレッジを共有できているか、といった観点から、議論を交わし、チームとしてコードを良くしていきます。
その際に、
と言った、コーディング規約についての議論があると、下手をすると宗教戦争になり、生産性が高いとは言えません。
事前にコーディング規約を決めていても、つい油断して規約違反のままコミットし、指摘を受けてしまうこともあるでしょう。
これを防ぐため、スタイルチェックを自動で行うライブラリが、メジャーな各言語には存在します。
Rubyの場合、それがrubocopです。
コミットのたびにスタイルチェックを実施する理由
設定したRubocopは、コミット前に手動で実行するか、あるいはCIにセットするなどの運用が考えられます。
しかし、手動だと忘れるかもしれませんし、pushしてからCIでスタイルチェックに引っかかってまた修正をpushするというのも、勢いをそがれてしまい、個人的には不十分だと思います。
そこで、git commitに連動してスタイルチェックを走らせる設定を行います。
これにより、スタイルチェックを通過したコードのみがコミットされるため、コーディング規約の確実な運用が期待できます。
Railsアプリにpre-commit gemを設定する
サンプルのディレクトリを作成し、適当なクラスを作っておきます。
$ mkdir rubocopsample $ cd rubocopsample; touch sample.rb $ git init $ rbenv exec bundle init
例えば、sample.rbは以下のとおりです。
class Sample def hoge end end
次に、bundlerでpre-commit gemを導入し、初期設定を行います。
gem 'pre-commit' と gem 'rubocop' の2行をGemfileに追記し、bundle installしてください。
そして、pre-commitのファイルを生成します。
$ rbenv exec bundle exec pre-commit install Installed /Users/tannaiyuki/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/pre-commit-0.23.0/templates/hooks/default to .git/hooks/pre-commit
最後に、.git以下の設定ファイルを編集し、git commitコマンドに連動するようにします。
.git/hooks/pre-commit というファイルを以下のように編集してください。
#!/usr/bin/env sh # This hook has a focus on portability. # This hook will attempt to setup your environment before running checks. # # If you would like `pre-commit` to get out of your way and you are comfortable # setting up your own environment, you can install the manual hook using: # # pre-commit install --manual # cmd=`git config pre-commit.ruby 2>/dev/null` if test -n "${cmd}" then true elif which rvm >/dev/null 2>/dev/null then cmd="rvm default do ruby" elif which rbenv >/dev/null 2>/dev/null then cmd="rbenv exec ruby" <=== 修正前 then cmd="rbenv exec bundle exec ruby" <=== 修正後 else cmd="ruby" fi export rvm_silence_path_mismatch_check_flag=1 ${cmd} -rrubygems -e ' begin require "pre-commit" true rescue LoadError => e $stderr.puts <<-MESSAGE pre-commit: WARNING: Skipping checks because: #{e} pre-commit: Did you set your Ruby version? MESSAGE false end and PreCommit.run '
ここで、rubocopを動かしてみます。
$ bundle exec rubocop Inspecting 2 files CC Offenses: Gemfile:2:8: C: Prefer single-quoted strings when you don't need string interpolation or special symbols. source "https://rubygems.org" ^^^^^^^^^^^^^^^^^^^^^^ sample.rb:1:1: C: Missing top-level class documentation comment. class Sample ^^^^^ 2 files inspected, 2 offenses detected
Gemfile内の、変数展開の無いダブルクォートが引っかかってしまいました。
もちろんGemfileのダブルクォートをシングルクォートに変えても良いのですが、Gemfileはチェック対象から外すように設定してみましょう。
.rubocop.ymlを作成し、以下の内容を記述します。
AllCops: Exclude: - Gemfile
これで再び実行してみます。
$ bundle exec ubocop Inspecting 1 file C Offenses: sample.rb:1:1: C: Missing top-level class documentation comment. class Sample ^^^^^ 1 file inspected, 1 offense detected
今度はクラスの上にコメントがないと言われました。
修正を忘れたという前提で、このままコミットしてみましょう。
ただ、その前に、pre-commitがrubocopを実行するように設定を行い、動作確認を行います。
$ git config pre-commit.checks "rubocop" $ bundle exec pre-commit run all pre-commit: Stopping commit because of errors. Inspecting 1 file C Offenses: sample.rb:1:1: C: Missing top-level class documentation comment. class Sample ^^^^^ 1 file inspected, 1 offense detected pre-commit: You can bypass this check using `git commit -n`
良さそうです。では、実際にコミットしてみます。
$ git add . $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: sample.rb $ git commit -m 'add sample' pre-commit: Stopping commit because of errors. Inspecting 1 file C Offenses: sample.rb:1:1: C: Missing top-level class documentation comment. class Sample ^^^^^ 1 file inspected, 1 offense detected pre-commit: You can bypass this check using `git commit -n` $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: sample.rb
このように、コミットしようとするとpre-commitでrubocopが走り、引っかかったらコミットが中断されるようになりました。
pre-commitの設定ポリシーについて
pre-commit gemはrubocop以外にも様々なhookを設定できます。
$ bundle exec pre-commit list Available providers: default(0) git(10) git_old(11) yaml(20) env(30) Available checks : before_all ci closure coffeelint common console_log csslint debugger gemfile_path go jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml Default checks : rubocop Enabled checks : rubocop Evaluated checks : rubocop Default warnings : Enabled warnings : Evaluated warnings :
もちろんRSpecも設定できるのですが、その場合はテストに通らなければコミットできない、ということになります。
私はテストはpre-commit hookせず、rubocopだけhookするように設定しています。
理由は、以下のとおりです。
まとめ
pre-commit gemにより、git commitの前にrubocopによるスタイルチェックを行うよう設定し、通らなければコミットできないようにしました。
プロジェクトの初期から設定しておくと、有意義なコードレビューができてとても良いのでオススメです。