はじめに
RubocopのCIを導入しているRuby開発の現場ではRubocopの実行を忘れてpushし、
CIで弾かれてから「忘れてたぁぁ」と言うシーンをよく見ます。
はい、私もそんなことによく出くわしている一人です。
そこでcommitコマンドの実行と同時に毎回Rubocopを回してやろうと思い,
動くものができたので記事にしました。
※環境
OS => macOS Big Sur
シェル => zsh
結論、以下の手順で設定を行えばコミット時に毎回Rubocopが実行される!!
1. 「git init」したディレクトリ内の「.git/hooks」に移動
2. pre-commitファイルを作成
3. 以下の内容をpre-commitファイルに記述する
# 対象のファイル名を取得
function getfilenames() {
git diff --cached --name-only --diff-filter=AM | grep '\.rb$'
}
# NGフラグ
ISNG=0
if getfilenames; then
echo '---Rubocopの自動修正---'
getfilenames | xargs bundle exec rubocop -a
# 変更があった場合はAddする
git diff --name-only --diff-filter=AM | grep '\.rb$' | xargs git add
echo '---Rubocopで警告がないかチェック---'
getfilenames | xargs bundle exec rubocop --fail-level C --display-only-fail-level-offenses
# 警告が出たかどうかのチェック(直前の処理の実行結果がNGの場合は1が取得できる。この場合はrubocopのチェック機能の実行結果)
if [ $? -gt 0 ]; then
echo '---RubocopチェックNG---'
ISNG=1
fi
fi
# NGの場合はコミットをしない
if [ $ISNG = 1 ]; then
echo '---Rubocop解析にNGがあったためコミットできませんでした---'
exit 1
else
echo '---Rubocop解析OK:コミットします---'
fi
4. rubyファイルを修正してコミットしてみよう!!!(完)
以上であなたの上記で実装したディレクトリではコミットする度にRubocopが実行されるようになりました。
ここまで読んで頂けただけで私は感激です。
これより以降はあまり重要でない個人の感想とか自分用のメモをつらつら書こうと思います。
解説
pre-commitファイルって?
「commit」コマンドの前に実行されるファイルです。
お察しの通り、「push」コマンドの前に実行されるファイルはpre-pushファイルです。
詳しい説明はこちらを参照ください。
pre-commitで何しているの?
以下の流れでRubocopを実行する処理を行なっています。
- 対象のファイル名を取得(拡張子が「rb」のgit addされているファイル・修正・追加されたファイルのみで削除されたファイルは対象外です)
- 対象のファイルが存在する場合にそれらのファイルに対してRubocopによる自動修正を行う
- 自動修正されたファイルに対して「git add」を行う
- Rubocopによるチェックを行う
- チェックがOKであればそのままコミットを行い、チェックがNGであればコミットを中断する
コードについてのメモ
git diffコマンドのパラメータ
git diff --cached --name-only --diff-filter=AM
- --cached => git addしたファイルのみ取得する
- --diff-filter=AM => AがAddで追加されたもの、MがModifyで修正されたもののみを取得するためのフィルター
rubocopコマンドのパラメータ
bundle exec rubocop --fail-level C --display-only-fail-level-offenses
「--fail-level C --display-only-fail-level-offenses」を記述することでレベルC以上の項目のみ表示されます。
レベルについては以下です。下に行くにつれてレベルが上がります。
[A] autocorrect
[I] info
[R] refactor
[C] convention
[W] warning
[E] error
[F] fatal
xargsって?
getfilenames | xargs bundle exec rubocop -a
「getfilenames」で取得した情報を
「bundle exec rubocop -a」の引数にすることができます。
例えばgetfilenamesで「a.rb」と「b.rb」とファイル名が取得できた場合に
「bundle exec rubocop -a a.rb b.rb」が実行されます。
「if [ $? -gt 0 ]; then」の「$?」には何が入る?
getfilenames | xargs bundle exec rubocop --fail-level C --display-only-fail-level-offenses
# 警告が出たかどうかのチェック(直前の処理の実行結果がNGの場合は1が取得できる。この場合はrubocopのチェック機能の実行結果)
if [ $? -gt 0 ]; then
echo '---RubocopチェックNG---'
ISNG=1
fi
「$?」を使うことで直近のコマンド実行の結果を取得することができます。
ここでは「getfilenames | xargs bundle exec rubocop --fail-level C --display-only-fail-level-offenses」によるRubocopのチェック結果が「$?」に入り、
正常終了でない場合は0以外が設定されます。
「exit 1」を行うとどうなるの?
コミットされることを防ぐことができ、そのまま以降の処理を行わず処理を終了します。
最後に
日頃携わらないzshについて触れてみて慣れてない分、苦労しましたが、学びも多かったです。
シェルを学習し、自分の開発環境をよりよくするための一歩として踏み出せてよかったです。
この記事が誰かの役に立てば幸いです。