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

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

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

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

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

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


### Pre-commitとは

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

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

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

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

- [Git のカスタマイズ - Git フック](https://git-scm.com/book/ja/v1/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF)

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


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

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

- [gitコマンド派閥](https://tech.misoca.jp/entry/2016/02/26/140938)


```sh:仮想開発環境を使っている人向けの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`コマンドで、文字列に含まれる改行を**空白**に変換します。

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

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

- [vagrant ssh - Command-Line Interface - Vagrant by HashiCorp](https://www.vagrantup.com/docs/cli/ssh.html#c-command)

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

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

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

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

#### 実行結果
`pre-commit`ファイルを作った状態で、`git commit`を試してみます。

```sh:実行結果
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文を入れて対応します。

```sh:ローカル環境と仮想環境を共存させる
$ 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で怒られなくなり、どういうコードがダメなのか、**気付く -> 直す**ができて良いです :cherry_blossom: 
あとは、`rspec`もコミット前に実行させようかなーと思っています（もう一台仮想環境を作るか、同一のVagrant内で実行するか迷い中...）


#### 参考
- [gitコマンド派閥](https://tech.misoca.jp/entry/2016/02/26/140938)
- [Git-のカスタマイズ-Git-フック](https://git-scm.com/book/ja/v1/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF)
- [vagrant ssh - Command-Line Interface - Vagrant by HashiCorp](https://www.vagrantup.com/docs/cli/ssh.html#c-command)