Zenn
🤧

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

(補足)コミットメッセージの書き換え

https://git-scm.com/book/ja/v2/Git-のカスタマイズ-Git-フック

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を含めたコミットの実行順序について

以上の調査内容をまとめると実行順序は次のようになりそうです。

  1. git commitコマンドを実行する
  2. pre-commitスクリプトが実行される
  3. コミットメッセージを保管する一時ファイルの作成される
  4. prepare-commit-msgスクリプトが実行される
  5. コミットメッセージのエディタが開き、コミットメッセージを書き込む
  6. commit-msgスクリプトが実行される
  7. post-commitスクリプトが実行される

挙動確認

それでは仕様を確認したところで挙動を見てみます。
次の順序で進めていきます。全てのオプションを確認したいところですが、膨大になってしまうので、いくつか取り上げて確認します。

  1. セットアップ
  2. 実行順序の確認

セットアップ

動作の原理な部分を見てみたいので、今回はライブラリなどは使わないでやってみます。
(通常は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

https://git-scm.com/docs/githooks#_description

では、動作確認をしてみます。
シンプルにechoして適当なcommitをします。

/.githooks/pre-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周りの知識がかなり整理できた気がします。
ついでにコミット時のコミットメッセージの取り扱い方についてもキャッチアップできて有意義でした。

参考

https://git-scm.com/book/ja/v2/Git-のカスタマイズ-Git-フック

https://git-scm.com/docs/githooks#_pre_commit

Discussion

ログインするとコメントできます