i-Vinci TechBlog
株式会社i-Vinciの技術ブログ

Git hookを使ってみる

こんにちは、 i-Vinciの田中です。
好きな言葉は「少しコーディングして、少しテスト」です。
今回はGit hookというGitの機能について少し解説しようと思います。

Git hooksとは

特定のGitコマンドをhookして、スクリプトを実行するGitの機能です。
https://git-scm.com/book/ja/v2/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

Gitのアクションの中でスクリプトを動かせるので、使い方によっては様々な業務を自動化することができそうですね。
とても手軽に使えるので、是非活用してみて下さい。

Git hooksの準備

git init を実行すると.git という隠しディレクトリが作成され.git/hooksの中にサンプルのスクリプトが用意されています。

PS C:\work\07.blog> ls .git/hooks

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2020/12/02     18:09            478 applypatch-msg.sample
-a----       2020/12/02     18:09            896 commit-msg.sample
-a----       2020/12/02     18:09           4655 fsmonitor-watchman.sample
-a----       2020/12/02     18:09            189 post-update.sample
-a----       2020/12/02     18:09            424 pre-applypatch.sample
-a----       2020/12/02     18:09           1643 pre-commit.sample
-a----       2020/12/02     18:09            416 pre-merge-commit.sample
-a----       2020/12/02     18:09           1374 pre-push.sample
-a----       2020/12/02     18:09           4898 pre-rebase.sample
-a----       2020/12/02     18:09            544 pre-receive.sample
-a----       2020/12/02     18:09           1492 prepare-commit-msg.sample
-a----       2020/12/02     18:09           3650 update.sample

ファイル名をリネームして”.sample”を外すだけでスクリプトが実行されるようになります。
準備はこれで終わりです。
とっても手軽ですねぇ。

サンプルスクリプトを動かしてみる

手始めにpre-commit.sampleを動かしてみます。
pre-commitはコミットアクション(git commit)をhookして実行されるスクリプトです。
サンプルの中身は以下の内容になっています。

  • ascii文字以外のファイル名のコミットを禁止
  • 空白行が含まれるファイルのコミットを禁止

試しにascii文字を含んだファイル名をコミットしてみます。

> git commit --quiet --allow-empty-message --file -
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true

怒られて、コミットに失敗しました。
スクリプトが動いているようです。

pre-commit.sampleの中身を見てみる

pre-commit.sampleで何をやってるのか気になりますよね。
サンプルの中身を見ながら、git hooksのお作法を軽く解説します。※コメントは省略

#!/bin/sh

if git rev-parse --verify HEAD >/dev/null 2>&1
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=$(git hash-object -t tree /dev/null)
fi

シェルスクリプトで書かれていて、gitコマンドで何かしてます。
めちゃくちゃざっくり説明すると、againstに最新のコミットハッシュを詰めてます。(現在地をセットしてる的な)
gitコマンドや、HEADの意味についてはググっててください。
この辺の記述は必須になりそうです。
次の解説に行きます。

allownonascii=$(git config --type=bool hooks.allownonascii)

exec 1>&2

if [ "$allownonascii" != "true" ] &&
    test $(git diff --cached --name-only --diff-filter=A -z $against |
      LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
    cat <<\EOF
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
EOF
    exit 1
fi

exec git diff-index --check --cached $against --

ascii文字以外のファイル名、または空白行が含まれるファイルならexit 1してますね。
https://git-scm.com/book/ja/v2/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
↑に詳しく書かれていますが、exit 0以外を返せばエラーになります。
かなりざっくり解説するとサンプルスクリプトはこんな感じです。

サンプルからわかったこと

サンプルを読んでみてざっくり何をすれば良いかわかりました。

  • とりあえずagainst=HEADする
  • エラーにしたかったらexit 1

といったところでしょうか。
あとはgitコマンドとシェルスクリプトを書いていくだけですね。

実際に書いてみる

サンプルのpre-commitに習ってコミットを制限出来れば、余計なレビュー工数を削減できそうです。
サンプルを流用しつつコミット時に動く簡単なチェックスクリプトを書いてみます。

ファイル名によくあるyyyyMMddをチェックしてみる

ファイル名でよくあるyyyyMMdd、よく間違えてしまうのでこれをGit hooksで自動でチェックさせたいと思います。
今回は実際に業務で使えるようにしたかったので、特定のフォルダ内のファイル名にYYYYMMDDhhmmssの14桁の数値が無ければコミットさせないようなスクリプトを書いてみました。

#!/bin/sh

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

IS_ERROR=0

for FILE in `git diff --staged --stat --diff-filter=A --name-only $against -- | grep -E 'migration/db/' | cut -d '/' -f 4 | cut -d '_' -f 1`; do

    MATCH='[0-9]{14}'
    `echo "$FILE" | grep -wE "$MATCH"` > /dev/null 2>&1
    RES=$?
    if [ $RES -eq 0 ]; then
        echo "マイグレーションファイル名の日時は14桁の数値にしてください"
        IS_ERROR=1
    fi

    date +%Y%m%d%H%M%S --date "$FILE" > /dev/null 2>&1
    RES=$?
    if [ $RES -ne 0 ]; then
        echo "マイグレーションファイル名の日時フォーマットがおかしいです"
        IS_ERROR=1
    fi
done

exit $IS_ERROR

14桁の数値のチェックと、日時フォーマットのチェックをしてみました。
echoすることもできるので、エラーメッセージをechoするとより良いかなぁと思います。

まとめ

Git hooks、便利そうですよね。
git initしたら用意されているので準備も必要ないですし、Python , Perl, Rubyなんかのスクリプト言語でもかけたと思うので、これらのスクリプト言語が書ければすぐに使えそうです。
次回はチームでGit hooksを運用する際の解説をしてみようと思います。
最後まで読んでいただき、ありがとうございました!