TT Lab
はじめる
学ぶ 学習パス コース

Git実戦

すぐ知らせるフックと、最後に止めるフックは別の場所にある

TT Labで続きを見る

一言でいうと

クライアントフック(commit-msg・pre-commit・pre-push)は、素早いフィードバックを与え、サーバーフックとCIは、強制を行います。クライアントフックは、--no-verifyという1語で突破され、cloneで付いてくることもないため、強制力を期待して、ここにルールを課してはいけません。

なぜ必要なのか

チームにルールができます。コミットのタイトルは決まった形式で、100KBを超えるファイルはコミット禁止、mainには直接push禁止。ドキュメントに書いておいても守られず、レビューで指摘しても、すでにコミットが積み上がったあとなので、直すのが面倒です。

.git/hooks/にスクリプトを置くと、gitが特定の瞬間にそれを実行します。コミットの直前、メッセージを書いた直後、pushの直前のような場所です。終了コードが0でなければ、その動作が止まります。

ところが、.git/hooks/には、大きな問題が1つあります。.gitディレクトリは、cloneで付いてきません。自分がどれだけうまく作っておいても、他の人のマシンにはありません。そのため、チームごとに「セットアップスクリプトを実行してください」のような案内を付けましたが、誰も実行しませんでした。

どう動くのか

git 2.9から、core.hooksPathという設定ができました。フックを探す場所を変える設定です。

git config core.hooksPath .githooks

これで、.githooks/は、リポジトリが追跡する通常のディレクトリになるため、フックのスクリプトがコミットされて共有されます。ただし、core.hooksPathの設定自体は.git/configにあるため、やはり各自が一度はオンにする必要があります。その1行をREADMEやブートストラップスクリプトに入れておくのが、今の慣例です。

よく使うフック3つは、次のとおりです。

フック いつ 受け取るもの 止めると
pre-commit コミットが作られる直前 なし(インデックスを直接見ます) コミットできません
commit-msg メッセージを書き終えたあと メッセージファイルのパス($1) コミットできません
pre-push push直前 リモート名・アドレス($1,$2)と、標準入力で参照の一覧 pushできません

pre-commitには、落とし穴が1つあります。ワーキングツリーではなく、インデックスを見る必要があります。コミットされるのは、インデックスに載った内容で、ワーキングツリーは、そのあといくらでも変わることがあります。ファイルに機密情報を書いてgit addしたあとで、ファイルをきれいに直しておくと、ワーキングツリーを読むフックは何も検知できず、機密情報がそのままコミットされます。インデックスの内容は、git show :<경로>やgit cat-file -p <blob>で読みます(プレースホルダーはパスです)。

pre-pushは、標準入力で行ごとに로컬참조 로컬해시 원격참조 원격해시を受け取ります(プレースホルダーは、ローカルの参照、ローカルのハッシュ、リモートの参照、リモートのハッシュです)。원격참조がrefs/heads/mainである行があれば止める、というように使います(プレースホルダーはリモートの参照です)。

クライアントフックでは止められないもの

3つあります。

1つ目は、--no-verifyによる回避です。git commit --no-verifyはpre-commitとcommit-msgを、git push --no-verifyはpre-pushを、スキップします。これは欠陥ではなく設計です。クライアントフックは、自分のマシンで自分がオンにするツールであり、急ぐときにオフにできる必要があります。

2つ目は、設定をオンにしていない人です。core.hooksPathは、各自がオンにする必要があります。

3つ目は、別の経路で入ってくるコミットです。Web画面で直接直したり、別のツールがプッシュしたり、フックをサポートしていないクライアントを使ったりすると、そもそも実行されません。

そのため、ルールを実際に強制する場所は、サーバーです。サーバー側のpre-receive・updateフックや、ホスティングサービスのブランチ保護、そしてCIの検査です。その場所には、--no-verifyのようなものはありません。その代わり、フィードバックが遅いです。すでにコミットを積んでpushしたあとで、ようやく拒否されます。

まとめると、同じルールを2か所に課します。クライアントフックは30秒で知らせてくれる側で、サーバーは最後に止める側です。クライアントにだけ課すと突破され、サーバーにだけ課すと、人々は毎回pushしてからはじめて気づきます。

現場での姿

次のラボですること

core.hooksPathでフックをリポジトリに入れ、タイトルのルールと、大きなファイル・機密情報のブロックを、自分で作ります。ワーキングツリーだけを見るフックがなぜ突破されるかを、インデックスにだけ機密情報がある状態で再現し、--no-verifyという1語で、3つのフックをすべて通り抜けてみます。cloneして、フックのファイルは付いてくるのに、設定は付いてこないことを確認し、pre-pushでmainへの直接pushを止めてみます。

公式ドキュメントは、githooks、git-config、Pro Git 8.3 Git Hooksです。