Git Hooks管理を簡単に - overcommitを使おう

#  overcommitとは？

git hooksの管理と、その拡張を簡単にできるRuby製ツールです。同様のツールに[pre-commit by Yelp](http://pre-commit.com/)やRailsに特化した[pre-commit](https://github.com/jish/pre-commit)があります。overcommitはこれらのツールと違い、pre-commit以外のフックにも対応しています。

リポジトリはこちら

https://github.com/brigade/overcommit

## overcommitの導入でできること

- コミットのタイミングでrubocopやeslintなどlintツールをかけられる
    - コードの読みやすさが向上することを期待できます
- checkoutやpullのタイミングで`bundle install`や`npm install`を実行できる
    - 他の開発者が`Gemfile`や`package.json`に追加したらしくなんか動かない問題を回避できる
- pushする前に変更したファイルのみ対象に`rspec`を実行できる
    - CIする前に自分が行った変更の結果を再確認できるので、少なくとも自分が書いたコードに問題がないか確認できる
- コミットコメントのフォーマットをチェックできる
    - 1行80文字以下に収めることで`git log`実行時の一覧性をあげたり、不当に大きなコミットにならないように示せる
- 既存のgit hookを使い回せる
    - CustomScriptというHookがあり、これを使うと既存のhookに処理を委譲できます

### 制限事項

- 残念ながら[git-worktreeには未対応](https://github.com/brigade/overcommit/issues/336)とのこと

# インストール

overcommitはRuby製のツールなので、Rubyのインストールを忘れずに。その後、rubygemsで配布されているので下記のコマンドでOKです。rvmやrbenvを使っている方は適宜コマンドを補完してください。

```shell-session
$ gem install overcommit
```

その後、overcommitを利用したいリポジトリで下記のコマンドを実行します。**もし既存の`git hook`を利用している場合は、そのファイルをバックアップしておきましょう。上述した通り、`CustomScript`として再利用できます。バックアップをお忘れなく**

```shell-session
$ cd git-repo
$ overcommit --install
```

これでovercommitのベースとなるhookが対象のgitリポジトリにインストールされました。試しに適当なファイルをコミットしてみましょう。

![overcommit1.gif](https://qiita-image-store.s3.amazonaws.com/0/4244/a07ae61a-1366-8c43-eb64-0e0ac02f240b.gif)

こんな感じで最初は`Run `overcommit --sign` if you trust the hooks in this repository.`と出ます。`overcommit --sign`はovercommit経由で実行するスクリプトのハッシュを記録しておき、fetchした時に不正にスクリプトが書き換わって意図しない動作を防ぐための機構です。overcommitに何も登録していない状態ではコミットメッセージのチェックだけなので、signしてしまいましょう。

```shell-session
$ overcommit --sign
```

を実行後、再度`git commit`をすると下記のようにコミットメッセージがチェックされるはずです。

![overcommit.gif](https://qiita-image-store.s3.amazonaws.com/0/4244/ba12676e-9856-ccdd-99ea-b29f4a55c0e8.gif)


**リポジトリからのアンインストールも下記のコマンドでおkっす**

```shell-session
$ overcommit --uninstall
```

# 設定方法

**設定ファイルたる`.overcommit.yml`を書き換えたら`overcommit --sign`を実行するのを忘れずに**

## pre-commit
### Rubocopの設定

overcommitの設定は、`.overcommit.yml`ファイルに記述します。例えばコミット前のタイミング(言い換えればpre-commit)で`rubocop`を実行したいのであれば、下記のように`.overcommit.yml`を設定します。

```yaml:.overcommit.yml
PreCommit:
  RuboCop:
    enabled: true
    command: ['bundle', 'exec', 'rubocop'] # bundlerの中でrubocopを実行する場合
```

これで、`.rb`や`Gemfile`の変更が`commit`される前に`rubocop`のチェックが走ります。1行目の`PreCommit`は`pre-commit`グループにフックを追加する、という意味で、フックポイントごとにグループを宣言します。

commandは実際に実行するコマンドを記述します。実行ファイルが渡される前に部分と考えていいでしょう。

### ESLintの設定

同様にESLintのチェックを行いたい場合は下記のように`.overcommit.yml`を記述します。(overcommit側のメッセージ解析の都合上、`package.json`も調整が必要です)

```yaml:.overcommit.yml
  ESLint:
    enabled: true
    required_executable: 'npm'
    command: ['npm', 'run', 'lint'] 
```

```json:package.json
{
  "scripts": {
    "lint": "eslint -f compact"
  },
  "devDependencies": {
    "eslint": "^2.0.0"
  }
{
```

ESLintの場合、jsファイル等がステージングにある時に実行されます。詳しくはhookのソースをみるとよいです。

`required_executable`は必要な実行ファイルを指定する設定です。相対パスの場合はリポジトリ内のパス、そうでない場合はPATH上に存在するかどうかをチェックします。npmはPATHに存在しなければ実行できないので、指定する必要があります。これがないと、`npm run lint`が動作しません。

## カスタムスクリプト

すでにスクリプトを利用している場合は下記の設定

```
  CustomScript:
    enabled: true
    required_executable: '.pre-commit.sh' # スクリプトのパスを指定する
```

## post-checkout

### bundle install

他のブランチをcheckoutしたタイミングでgem fileの更新をしたい場合は下記のように`.overcommit.yml`を設定しましょう

```yaml:.overcommit.yml
PostCheckout:
  BundleInstall:
    enabled: true
```

### npm install

npm installの場合もこんな感じです。

```yaml:.overcommit.yml
  NpmInstall:
    enabled: true
```

ここまでの設定を全て有効にする場合、`.overcommit.yml`はこんな感じになっているでしょう。

```yaml:.overcommit.yml
PreCommit:
  RuboCop:
    enabled: true
    command: ['bundle', 'exec', 'rubocop'] # bundlerの中でrubocopを実行する場合
  ESLint:
    enabled: true
    required_executable: 'npm'
    command: ['npm', 'run', 'lint'] 
  CustomScript:
    enabled: true
    required_executable: '.pre-commit.sh' # スクリプトのパスを指定する
PostCheckout:
  BundleInstall:
    enabled: true
  NpmInstall:
    enabled: true
```

[これらのHookの詳細はリポジトリの説明からたどることができます。](https://github.com/brigade/overcommit#built-in-hooks)

こんな感じでプロジェクトに必要なhookを組み合わせて設定できます。

# FAQ
## コミット時に特定のHookをスキップしたい

```shell-session
$ SKIP=RuboCop git commit
```

## コミット時に全てのHookをスキップしたい

```shell-session
OVERCOMMIT_DISABLE=1 git commit
```