git hooksのコミットワークフローフックについて
現在業務でsemantic-releaseを利用しています。
semantic-releaseは指定のブランチにmergeされるとcommitメッセージのprefixに応じてsemantic versionを上げてくれて、リリースノートを生成してくれる便利なツールなのですが、commitメッセージのprefixがConventional Commitsのルールに則った形式になっていないと適切にバージョンが更新されません。
これを防ぐために、commitlintを導入してcommit前にcommitメッセージのlintをすることにしました。
その際に調査したgit hookの特にコミットワークフローフックについて備忘録を残します。
git hooksとは
特定アクションが発生した際に指定したスクリプトを実行する機能です。
git hooksには大きくクライアントサイドフックとサーバーサイドフックが存在します。
クライアントサイドフックはコミットやマージなどのクライアント操作の際に、サーバーサイドフックはプッシュされたコミットの受け取りのようなネットワーク操作の際に実行されます。
今回説明するコミットワークフローフックはクライアントサイドフックの一つです。
コミットワークフローフックとは
Git フックのドキュメントではクライアントサイドフックとして以下3種類が紹介されています。
- コミットワークフローフック
- pre-commitフック
- prepare-commit-msgフック
- commit-msgフック
- post-commitフック
- Eメールワークフローフック
- applypatch-msgフック
- pre-applypatchフック
- post-applypatchフック
- その他クライアントフック
- pre-rebaseフック
- post-rewriteフック
- post-checkoutフック
- post-mergeフック
- pre-pushフック
- pre-auto-gcフック
今回説明するコミットワークフローフックはその名の通り、コミットプロセスに関する4つのgit hooksです。
それでは個別のgit hooksについてみていきます。
4つのコミットワークフローフック
以下がcommitに関連するgit hooksです。
これらのhooksはそれぞれ実行されるタイミングやスクリプトに渡すことができるパラメータ、commitの中止可否が異なることでユースケースが違います。
順番に確認していきます。
pre-commitフック
実行タイミング
-
git commit
コマンドの実行がトリガー - commit処理の前に実行される
ユースケース
commitを実行すべきか判定するためのテスト、lint実行
その他仕様
-
--no-verify
オプションでスキップできる - スクリプトの戻り値が0以外の場合、commitが中止される
- デフォルトのpre-commitフックはhooks.allowonascii設定オプションが未設定 or falseに設定されている場合、非ASCII文字を含むファイル名の使用を防ぐ
prepare-commit-msgフック
実行タイミング
-
git commit
コマンドの実行がトリガー - デフォルトのログメッセージを準備した直後、エディタが起動される前に実行
ユースケース
commitメッセージの準備、デフォルトコミットメッセージの書き換え
その他仕様
スクリプトにパラメータを渡すことができる
- コミットログを含むファイル名
- コミットメッセージ
- コミットのSHA-1ハッシュ
- ただし、以下のいずれかのケースでのみ付加される
- message (-m, -Fオプション使用時)
- -m: コミットメッセージをインラインで入力するオプション
- -F: コミットメッセージを特定ファイルから取得して入力するオプション
- template (-tオプション使用時、またはcommit.template設定済みの時)
- -t: templateを利用して入力するオプション
- merge (merge時または.git/MERGE_MSGが存在する時)
- squash (squash merge時または.git/SQUASH_MSGが存在する時)
- commit
- message (-m, -Fオプション使用時)
- ただし、以下のいずれかのケースでのみ付加される
(補足)コミットメッセージの書き換え
prepare-commit-msg フックは、コミットメッセージエディターが起動する直前、デフォルトメッセージが生成された直後に実行されます。
commit-msg フックは、開発者の書いたコミットメッセージを保存した一時ファイルへのパスをパラメータに取ります。
このあたりを見るにcommit時にはコミットメッセージを保存する一時ファイルが作成されるようです。
このファイルは作成時にデフォルトのコミットメッセージが生成されるようになっており、prepare-commit-msgはこのデフォルトメッセージが書き込まれた一時ファイルをcommit後のコミットメッセージエディタが開かれる前に編集するスクリプトであると考えられます。
commit-msgフック
実行タイミング
-
git commit
,git merge
コマンドの実行がトリガー - エディタでcommitメッセージを書き込んだ後に実行
ユースケース
- 書き込まれたコミットメッセージの正規化(prefixをつけるなど)
- コミットメッセージの検査
その他仕様
-
--no-verify
オプションでスキップできる - スクリプトの戻り値が0以外の場合、commitが中止される
post-commitフック
実行タイミング
-
git commit
コマンドの実行がトリガー - commitが完了した後に実行
ユースケース
- commit内容についての通知
共通仕様
全てのgit commitフックはコミットメッセージを編集するためのエディタを起動しない場合、環境変数GIT_EDITOR=:
が渡されて実行される
hooksを含めたコミットの実行順序について
以上の調査内容をまとめると実行順序は次のようになりそうです。
-
git commit
コマンドを実行する -
pre-commit
スクリプトが実行される - コミットメッセージを保管する一時ファイルの作成される
-
prepare-commit-msg
スクリプトが実行される - コミットメッセージのエディタが開き、コミットメッセージを書き込む
-
commit-msg
スクリプトが実行される -
post-commit
スクリプトが実行される
挙動確認
それでは仕様を確認したところで挙動を見てみます。
次の順序で進めていきます。全てのオプションを確認したいところですが、膨大になってしまうので、いくつか取り上げて確認します。
- セットアップ
- 実行順序の確認
セットアップ
動作の原理な部分を見てみたいので、今回はライブラリなどは使わないでやってみます。
(通常はjsならhusky, pythonならpre-commitとかを使えば良さそう)
git bookによるとgit init
した際に作成される.git/hooks
ディレクトリにそれぞれhooksの名前でスクリプトを保存すれば良さそうで、サンプルコードも用意されていますね。
$ ls
applypatch-msg.sample* pre-commit.sample* prepare-commit-msg.sample*
commit-msg.sample* pre-merge-commit.sample* push-to-checkout.sample*
fsmonitor-watchman.sample* pre-push.sample* update.sample*
post-update.sample* pre-rebase.sample*
pre-applypatch.sample* pre-receive.sample*
ただ、.git/
はgit管理できずやや不便なので別ディレクトリ/.githooks/
で管理したいと思います。変更用のコマンドは以下です。
$ git config --local core.hooksPath .githooks
では、動作確認をしてみます。
シンプルにechoして適当なcommitをします。
#!/bin/zsh
echo 'pre-commit called'
そのままadd, commitしたところ以下のエラーが出てしまいました。
hint: The '.githooks/pre-commit' hook was ignored because it's not set as executable.
hint: You can disable this warning with `git config advice.ignoredHook false`.
[main (root-commit) 7b71a08] test
1 file changed, 3 insertions(+)
create mode 100644 .githooks/pre-commit
実行権限がなさそうなので与えます。
chmod 775 ./.githooks/pre-commit
一旦git reset --soft HEAD\^
してステージングに戻して再度commitします。
$ git commit -m "test"
pre-commit called
[main 4c1f693] test
1 file changed, 0 insertions(+), 0 deletions(-)
mode change 100644 => 100755 .githooks/pre-commit
ちゃんとpre-commit called
されてからcommitのログが出ていますね。
実行順序の確認
では最後に実行順序の確認をします。
まず、pre-commit
と同様、他のhooksについてもechoを行うだけのシンプルなスクリプトを用意し、.githooks/
に配置します。
それではcommitをしてみます。
今回はprepare-commit-msg
の挙動もみたいので-m
オプションは省略します。
$ git commit
pre-commit called
prepare-commit-msg called
commit-msg called
post-commit called
[main 1e1be8b] test
3 files changed, 9 insertions(+)
create mode 100755 .githooks/commit-msg
create mode 100755 .githooks/post-commit
create mode 100755 .githooks/prepare-commit-msg
問題なくcommitできました。
実行順序もpre-commit
, prepare-commit-msg
, commit-msg
, post-commit
の順になっており、prepare-commit-msgのログが出た後にコミットメッセージのエディタが開かれており、想定通りです。
感想
git hooks周りの知識がかなり整理できた気がします。
ついでにコミット時のコミットメッセージの取り扱い方についてもキャッチアップできて有意義でした。
参考
Discussion