Git Hook pre-commit プログラミングの仕組みと解説
About Hook
Hookとは、ある機能を実行する前後に、ユーザーにプログラムを実行する機会を与える機能です。
git hook とは、commit や push などの命令をする際、命令が実行される前や後に、シェル・スクリプトを実行できる機能です。
pre-commit や pre-push のポピュラーな機能として、クラウドサーバーの秘密キーの公開を抑止するというのがあります。
* pre とは、 previous の略で、〇〇する前という意味です。
About git hook
リポジトリーをクローンしたディレクトリーには、.git
という隠しディレクトリがあります。そのディレクトリーの中に、 hooks
というディレクトリーがあり、ここに git hook で実行したいシェル・スクリプトを作成・設置します。
gitの特定のコマンド( commit や push )を実行すると、.git/hooks
ディレクトリーに保存されているシェル・スクリプトが実行されます。これが git hook です。
保存したファイル名で hook されますので、任意のファイル名は使えません。
よく使われる git hook には以下のようなものがあります。
- master に push したら、自動でサーバーにデプロイする
- master を更新したら、自動でユニットテストが実行される
- push する前に、特定の文字列やフォーマットをチェックする
できることの一覧は、公式サイト や git hook はじめの一歩 をご覧ください。
サンプルファイルは、 .git/hooks
ディレクトリに入っています。
何ができるか、どのように使うか、サンプルファイルをざっと眺めるのが一番勉強になります。個人的には git diff のオプションについて、とても参考になりました。
Point
git hook用の命令や変数などはありません。 (多分😅)
コミットやプッシュのタイミングで、シェル・スクリプトが実行されるだけです。
スクリプトでは、既存のgitのコマンドを使って、更新されたファイルの一覧を取得したり、差分をチェックしたりします。
Important - 強制コミット
例えば、var_dump
という文字列を発見したら、commit や push を禁止するというスクリプトを書いたとします。しかし、var_dump
が必要で、どうしてもコミットしたいという場合があります。
そのような場合は git hook をスキップする(実行しない)というオプションを付与します。具体的なオプションは、-n
(--no-verify
の略) ですが、git commit
の場合は、-mの前に付与する必要があります。
git commit -nm "Commit message"
↑nは、mの前に付ける。逆だとエラーになります。
Script
今回は、 var_dump
、print_r
、exit
が記述されていたら push できないというスクリプトを書きました。
スクリプトが、1を返すと命令は中断され、pre-commit の場合はコミットが実行されません。
vi .git/hooks/pre-commit
#!/usr/bin/env bash
# Case sensitive
shopt -s nocasematch
# Change delimiteir to only LF
IFS=$'\n'
# Get target files
files=`git diff --cached --name-only`;
# Loop for each file name
for file in $files
do
# Get diff lines
LINES=`git diff --cached "$file"`
# Loop for each lines
for LINE in $LINES
do
# Skip delete line
if [[ $LINE =~ ^- ]]; then
continue
fi
# Search deny word
if [[ $LINE =~ var_dump|print_r|exit ]]; then
# Error message
echo
echo ".git/hooks/pre-commit : $file --> ${BASH_REMATCH[0]}"
echo
echo "$LINES"
exit 1
fi
done # end each lines
done # end each files
# No problem
exit 0
余談
シェル・プログラミングは普段おこなわないので、PHPで書きたいと思ったのですが、シェバンを #!/usr/bin/env php
として実行したところ、上手くいかずに断念しました。
また、シェルの正規表現でハマりました。
そもそも正規表現のやり方が分からない。
if [[ $LINE =~ ^.+$ ]]
という記法で正規表現が使えます
for in ループで、スペース毎で区切られる → 行毎で取得できない
git diff の差分を for ループしようとしたら、差分が 『行』 毎ではなく、『単語』 毎に変数へ代入された。
シェルの区切り文字のデフォルトは、改行・タブ・スペースだったので、改行(LF)だけに変更した。
# Change delimiteir to only LF
IFS=$'\n'
大文字・小文字の区別(ケースセンシティブ)をしない
# Case sensitive
shopt -s nocasematch
こちらの記事が参考になりました。
https://qiita.com/myblackcat7112/items/5e8205f1b5161a1f8590
https://qiita.com/kazu56/items/83340a2e284298b9e237