2児のパパ系Webエンジニア
の開発日記
A Dad-of-Two Engineer's Development Diary

Git 2.54の設定ベースのフックでhooksを~/.gitconfigにまとめた話!2.55の並列実行とpre-commitの落とし穴

Git 2.54の設定ベースのフックでhooksを~/.gitconfigにまとめた話!2.55の並列実行とpre-commitの落とし穴

🙌 結論から

Git 2.54からは、フックを .git/hooks のスクリプトではなく、gitの設定ファイルに書けるようになりました。全リポジトリ共通のチェックは ~/.gitconfig に1回書くだけで済みます!

書き方は [hook "名前"] の中に event(いつ動くか)と command(何を動かすか)を並べるだけです。

さらに6月のGit 2.55で、parallel = true を付けたフックを並べて動かせるようになりました🙌

ただし、pre-commit・commit-msg・post-checkoutなどは、設定をしても並列にはなりません。

ここを知らずに「pre-commitのLintとテストを並べて速くしよう」と考えると、たぶん肩すかしになります(・_・;)

私はまず、dd() の消し忘れチェックだけを ~/.gitconfig に移しました。

リポジトリごとにスクリプトを置かなくてよくなったので、かなり楽・・!

ちなみに今の最新は9月28日に出たGit 2.56で、git hook のマニュアルは2.55から変わっていません。

👀 フックをコピーして回るのがずっと面倒だった

会社では、Laravelのリポジトリをいくつも行き来しています。

以前は「dd() や dump() が残ったままコミットしない」ためのpre-commitを、リポジトリごとに .git/hooks/pre-commit へコピーしていました。

新しくcloneするたびに入れ忘れて、気づいたらレビューで「dd残ってますよ〜」と言われる・・・のくり返しです(´・ω・`)

core.hooksPath で共通のフォルダを指す方法もありますが、これはそのフォルダのフックがどこでも同じように動くのが難点でした。

Huskyを入れているリポジトリだと、Huskyが core.hooksPath を自分のフォルダに書き換えるので、グローバルの設定とぶつかります。

「全部のリポジトリで動かしたい小さなチェック」と「そのリポジトリだけのチェック」を、きれいに分けられなかったんですよね。

💡 Gitの設定にフックを書く

2.54で増えたキーは、主にこの3つです。

  • hook.<名前>.event:どのタイミングで動くか(pre-commit など。複数書ける)
  • hook.<名前>.command:動かすコマンド。実行ファイルのパスでも、シェルの1行でもOK
  • hook.<名前>.enabled:false にすると、設定を残したまま止められる

<名前> は自分で付ける名前で、ここに pre-commit のようなイベント名は使えません(使うとエラーで止まります)。

書く場所は ~/.gitconfig、/etc/gitconfig、リポジトリの .git/config のどれでも大丈夫です。

同じイベントに何個でも登録できて、設定を読んだ順に動き、昔ながらの hooks フォルダのスクリプトは最後に動きます。

なので、今あるフックを消さずに少しずつ移せるのがありがたいです(^^)

登録されているフックは、次のコマンドで一覧にできます。

git hook list --show-scope pre-commit

--show-scope を付けると、global や local のように、どの設定ファイルから来たフックかも出ます👍

コミットせずに動作だけ見たいときは git hook run pre-commit で、そのイベントのフックをまとめて実行できます。

2.55の並列実行は、フックごとに parallel = true を付けて、同時にいくつ動かすかを hook.jobs(全体)か hook.<イベント名>.jobs(イベントごと)で決めます。

-1 にするとCPUのコア数になります。

同じイベントのフックが1つでも parallel なしだと、そのイベントは全部1つずつの実行に戻るので、ここも覚えておきたいところです。

🔧 私の設定はこれ

まず、全リポジトリ共通の dd() チェックです。

~/.gitconfig には2行だけ書いています。

[hook "no-dd"]
	event = pre-commit
	command = ~/bin/no-dd.sh

中身のスクリプトはこれだけです。

#!/bin/sh
# ステージした差分の追加行に dd( / dump( があれば止める(->dump( や ::dump( は除外)
if git diff --cached -U0 -- '*.php' | grep -nE '^\+.*[^[:alnum:]_>:](dd|dump)\('; then
  echo "dd() / dump() が残っています"
  exit 1
fi

chmod +x ~/bin/no-dd.sh を忘れずに、ですね。

差分の追加行だけを見るので、もともとファイルにあった dump() で止まらないのがポイントです!

次に、リポジトリごとのチェックです。

Laravelのリポジトリでは、pre-commitはPintの見た目チェックだけにして、重いPHPStanとPestはpre-pushに回しました。

# リポジトリの .git/config
[hook "pint"]
	event = pre-commit
	command = ./vendor/bin/pint --test --dirty

[hook "phpstan"]
	event = pre-push
	command = ./vendor/bin/phpstan analyse --no-progress
	parallel = true

[hook "pest"]
	event = pre-push
	command = ./vendor/bin/pest --compact
	parallel = true

pre-pushの2つは両方 parallel = true なので、同時に動かせます。

同時に動かす数は、ターミナルからこう決めました。

git config set hook.pre-push.jobs 2

--dirty はGitで変更があるファイルだけを見るオプションなので、プロジェクト全体を毎回チェックせずに済みます🙌

PHPStanの細かいルールはLarastanのキュー用ルールの記事、Pestの待ち時間を減らす話はPest 5の—tiaの記事に書いています。

🤔 ハマりやすいところ

一番の落とし穴は、並列にできないイベントが決まっていることです。

公式のマニュアルでは、applypatch-msg・pre-commit・prepare-commit-msg・commit-msg・post-commit・post-checkout・push-to-checkout の7つは、-j や hook.jobs を指定しても1つずつ動くと書かれています。

どれもインデックスや作業ツリー、コミットメッセージを見るフックなので、同時に触ると壊れやすいからだと思います。

速くしたい重いチェックは、pre-pushのような並列にできるイベントへ移すのが現実的でした。

2つ目は、jobs の書き方です。

hook.pre-push.jobs のようにイベント名で書かないといけなくて、hook.phpstan.jobs のようにフックの名前で書くと、エラーも出ずに無視されます(T_T)

3つ目は、チームで共有したいときです。

.git/config はリポジトリに入らないので、そのままでは自分のPCにしか効果がありません。

私は、リポジトリに .githooks/hooks.gitconfig を置いて、各自が1回だけ次を実行する形にしました。

git config set --local include.path ../.githooks/hooks.gitconfig

include.path の相対パスは、その設定を書いたファイル(ここでは .git/config)から見た位置になるので、../ から始めます。

Gitは、リポジトリに入っている設定を勝手に読み込まないので、この1行は自分で打ってもらうしかないというのは割り切りです。

最後に、Gitのバージョンが古いと、この設定はエラーも出ずに無視されます。

知らないキーは読み飛ばされるだけなので、「書いたのに動かない・・・」のときは、まず git --version で2.54以上(並列は2.55以上)かを見るのが早いです。

Macの場合、最初から入っているApple版のGitは新しい版より遅れていることが多いので、Homebrewなどで入れたGitを使っているかも確認しておきたいですね。

🔗 HuskyやLefthookとはこう使い分けている

  • huskyとlint-stagedの記事:Node.jsのプロジェクトは今もHuskyのままです。npm install のときに prepare で自動で入るので、メンバーに何も頼まなくていいのは大きいです
  • HuskyとLefthookの比較記事:変更したファイルだけに絞る・ジョブを並べる、を細かく決めたいならLefthookのほうが楽です
  • 設定ベースのフック:全リポジトリ共通の小さなチェックと、Node.jsを入れていないLaravelのリポジトリ用。追加のツールなしで済むのがいいところです

Huskyを入れたリポジトリでも、~/.gitconfig の no-dd はそのまま動きます。

Huskyのフックは昔ながらのスクリプトの扱いで最後に動くので、両方を並べて使えるのも気に入っています(。•̀ᴗ-)✧

パパ系Webエンジニア〜愛用ガジェット一覧 🛍️

この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌

👍 まとめ

「全リポジトリで動かしたいチェックは ~/.gitconfig、そのリポジトリだけのチェックは .git/config」と分けるだけで、フックをコピーして回る作業がなくなりました!

並列実行はpre-commitには使えないので、重いチェックはpre-pushに回して parallel = true を付けるのがちょうどいいと感じています。

正直、最初は「Huskyがあるし、わざわざ変えなくても・・・」と思っていました( .. )

でも、dd() チェックのような小さいフックを全部のリポジトリに入れるなら、こっちのほうが手間はかなり少ないです。

Gitの更新って後回しにしがちなので、気になった人はまず git --version からですね😊

みんなはフック、Husky派?Gitの設定派?

※ バージョン番号・機能は今後も変わる可能性があります。最新情報は Gitの公式ドキュメント(git-hook) をご確認ください👀

参考になったらシェアしてもらえるとうれしいです🙌

Xでポスト Threads はてブ LINE