Git Hooksのpre-commitを用いて望まないコミットを未然に防ぐ

本記事は[DeNA 21 新卒 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/dena-21-shinsotsu)の22日目の記事です。

## はじめに
こんにちは、[@7riatsu](https://twitter.com/7riatsu)です。

突然ですが、皆さんは自分の意図しないコードをGitHubにプッシュしてしまった経験はありませんか？
僕はあります。それも人に見られると恥ずかしいような内容でした。

プログラムの動作確認時に出力を目立たせるため、以下のコードを書いていました。

```test.rb
puts "💩"
puts hoge #検証したい変数
```

`hoge` に適切な値が入っていることを確認したので、コミットをしてGitHubにプッシュし、レビューを依頼したところで悲劇は起きました。

「💩がプッシュされているよ」

あろうことか、レビューを依頼する前に動作確認用に仕込んでいた不要な行をコミットしていたのでした。
その他にもデバッグ目的で仕込んだ `console.log` や `binding.pry` を誤ってコミットしてしまうことが多々ありました。
このような単純なミスは「レビュー前に確認をするべき」の一言で片付けるのではなく、仕組みで防ぎたいところです。

そこで今回は、Git Hooks の `pre-commit` を利用して、自分の意図しない不要な行のコミットを防ぐ方法をご紹介します。

## Git Hooks とは
Gitの特定のアクションが発生した時にスクリプトを起動するもの。
クライアントサイドとサーバーサイドの二つのhooksに大別される。

- クライアントサイドフック: `commit` や `merge` の際にトリガーされる
- サーバーサイドフック:`push` 前後にトリガーされる

今回紹介する `pre-commit` はクライアントサイドフックに属する。

## pre-commit とは
コミット実行時に一番最初に実行されるもの。
主な利用用途としては以下が想定される。

- コミットしようとしているコードの検証
- テストの実行をローカルで確認
- コードスタイルチェック(lintなど)

更に詳しく知りたい方は[Git公式ドキュメント](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)を参照してください。

## pre-commitの導入手順
それでは実際に `pre-commit` を動かすための準備をしていきましょう。

### 1. 下準備
```zsh
# テスト用のディレクトリ作成
mkdir pre-commit-test
cd pre-commit-test

# gitの初期化
git init # このコマンドで後に使う.git/hooks/pre-commitが作成される
```

### 2. pre-commitのスクリプトを作成
以下のコードを `.git/hooks/pre-commit` にコピペしてください。

```sh
#!/bin/sh

# commitに含めたくないワードをスラッシュとパイプで区切って追加する
LIST="puts\|binding.pry\|console.log"

if git rev-parse --verify HEAD >/dev/null 2>&1; then
  against=HEAD
else
  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

for FILE in `git diff-index --name-status --cached $against -- | cut -c3-` ; do
  if grep -w $LIST $FILE; then
    echo $FILE." has one of the word you don't want to commit. Please remove it"
    exit 1
  fi
    done
exit
```

※[こちらの記事](https://medium.com/@edgar/pre-commit-git-hook-that-prevents-commits-with-undesired-words-usually-debugging-statements-b02bea43e0a2)を参照

コミットしたくないワードを追加する場合、スラッシュとパイプで区切って `LIST=` の行に記載してください。
例えば新たに `debugger` を追加するなら、以下のように変更します。

```sh
LIST="puts\|binding.pry\|console.log\|debugger"
```

### 3. 追加したスクリプトに実行権限を付与
```zsh
chmod +x .git/hooks/pre-commit
```

1~3の手順で準備は完了しました。
それでは早速、 `pre-commit` が動くかどうかを検証してみましょう。

## 検証
今回は例としてRubyのスクリプトを検証します。
もちろん、他のプログラミング言語で書いたスクリプトに対しても動作します。

まずは `puts` を含んだRubyのスクリプトを準備します。

```main.rb
a, b = 1, 2

c = a + b
puts c
# => 3
```

次に `main.rb` をコミットしてみましょう。

```zsh
git add main.rb
git commit -m 'putsを含む足し算を追加'
# puts c
# main.rb. has one of the word you don't want to commit. Please remove it
```

確かにputsを含んでいるのでコミットに失敗しました。

次はmain.rbからputsを除いてコミットしてみましょう。

```main.rb
a, b = 1, 2

c = a + b
```

```zsh
git add main.rb
git commit -m 'putsを含まない足し算を追加'
# [master (root-commit) b824f3b] putsを含まない足し算を追加
#  1 file changed, 3 insertions(+)
#  create mode 100644 main.rb
```

今度は正常にコミットをすることができました。

また、以下のコマンドでhooksをトリガーさせずにコミットすることも可能です。

```zsh
git commit --no-verify
```

## 終わりに
特に他の人にレビューを依頼している場合、単純な指摘ではなく、もっと重要な指摘に時間を割いてもらうために、こういったミスは極力したくありません。
単純なミスは積極的に仕組みで防いでいきましょう！

## 宣伝
この記事を読んで「面白かった」「学びがあった」と思っていただけた方、よろしければ Twitter や facebook、はてなブックマークにてコメントをお願いします！
また [DeNA 公式 Twitter アカウント @DeNAxTech](https://twitter.com/DeNAxTech) では、 Blog記事だけでなく色々な勉強会での登壇資料も発信してます。ぜひフォローして下さい！
<a href="https://twitter.com/DeNAxTech?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-size="large" data-lang="ja" data-show-count="false">Follow @DeNAxTech</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>