はじめに
こんにちは!株式会社C&Pの武智でございます。
CI/CDで「Lint導入前」のプロジェクトに関わる私:
「MakefileにまとめてLint実行するコマンド書いてあるのに、忘れる時があるんですよね」
CI/CDで「Lint導入後」の私:
「1行直しただけなのに、CI/CDでLintエラーが数分後に怒ってきた...?」
現在の私:
「pre-push githookを試してみよう」
今回はGitのpre-push hookを活用して、git pushを実行する直前にLintチェックを自動で走らせる仕組みを作ってみました!
こちらはグローバルのgithookとなるため、git実行可能な環境ならどのプロジェクトディレクトリからでも実行でき、Lintチェックで違反があればpushをブロックされる仕様にします。
例としてPHP/Laravelプロジェクトで使われることの多い以下の3つのツールを使用して、色んなプロジェクトでLintチェックできるように作成してみました:
-
Laravel Pint(コードスタイル自動修正ツール。このhookでは常に
--testオプションで実行するためファイルは自動で修正されないようにします) - PHP_CodeSniffer(コーディング規約チェック)
- PHPStan / Larastan(静的解析)
インストールされていないツールは自動的にスキップされるので、プロジェクトごとに使っているツールが違っても問題ありません!
このフックの特徴
| 機能 | 内容 |
|---|---|
| グローバル設定 |
~/.githooks/ に置くだけで、マシン上のすべてのgitリポジトリに適用される。 |
| 自動検出 | プロジェクトにインストールされているツールだけを実行。未インストールはスキップ。 |
| 既存フックとの共存 | git-scm.com公式ドキュメントにある通り、core.hooksPath を設定するとGitは .git/hooks/ を完全に無視する。ただしこのスクリプトはチェック完了後にプロジェクト固有の .git/hooks/pre-push を検出して明示的に呼び出す実装になっているため、共存が成立する。 |
| 柔軟なスコープ設定 | デフォルトは各ツールの設定ファイル(pint.json / phpcs.xml / phpstan.neon)に従ってプロジェクト全体をスキャン。LINT_NEW=1 でpushするコミットのみに絞れる。 |
| スキップ機能 |
LINT=0 git push で緊急回避も可能。 |
インストール
注意
※スクリプトは学習目的で作成したため、動作は保証されません。自己責任でのご使用をお願いいたします。
Step 1 — hookファイルを配置する
まずはHomeディレクトリに移動し、GitHubからグローバル.githooksディレクトリをcloneします。(既に存在する場合、gitの中身だけこのディレクトリに移動させてください)
git clone https://github.com/remtakechi/pre-push-lint-hook.git "$HOME/.githooks"
Step 2 — 実行権限を付与する
chmod +x "$HOME/.githooks/pre-push"
Step 3 — Gitに場所を教える
git config --global core.hooksPath "$HOME/.githooks"
この設定はマシン上のすべてのリポジトリに対して有効になります。一度設定すれば、以降は設定不要です!
✅ インストールの確認
# $HOME/.githooks と表示されれば成功
git config --global core.hooksPath
# pre-pushの行を確認し「-rwxr-xr-x」のように、実行可能パーミッション(x)が付いていることを確認
ls -la "$HOME/.githooks/"
▶️ 実際の動作イメージ
すべてパスした場合
[GITHOOK] Running pre-push checks (full project)
→ Laravel Pint ✔
→ PHP_CodeSniffer - (not installed)
→ PHPStan / Larastan ✔
✔ All checks passed
Lintエラーがあった場合
[GITHOOK] Running pre-push checks (full project)
→ Laravel Pint ✗
... pint errors ...
↳ Laravel Pint failed
→ PHP_CodeSniffer - (not installed)
→ PHPStan / Larastan ✔
✖ Pre-push checks failed
エラーがあった場合はpushがブロックされ、ターミナルにエラー内容が表示されます。
修正してもう一度pushするだけ!シンプルですね。
🛠️ 使い方・オプション
基本動作
デフォルトでは各ツールを引数なしで実行します。各ツールが自身の設定ファイル(pint.json / phpcs.xml / phpstan.neon)に従ってスキャン対象を決定するため、プロジェクトで直接ツールを実行した場合と同じ結果になります。
新しいコミットのみLintする(実験的機能)
新しくpushされるコミットに絞りたい場合はこちら(PHPStan/Larastanは LINT_NEW=1 を設定しても phpstan.neon の設定に従ってプロジェクト全体を常にスキャンします):
LINT_NEW=1 git push
プッシュ範囲を検出できない場合や対象PHPファイルがない場合はデフォルトモード(プロジェクト全体)にフォールバックします。
Laravel Pintの制限事項
LINT_NEW=1でファイルを明示指定した場合、Pintはpint.jsonのexcludeのみスクリプト側で手動フィルタリングします。notPath/notNameは除外されません。これらを使用している場合はLINT_NEW=1を使わずデフォルトモードで実行してください。
スキップ
緊急でpushしたいときや、意図的にLintチェックを飛ばしたいときは:
LINT=0 git push
ターミナルには警告が表示されますが、pushは通ります。プロジェクト固有の .git/hooks/pre-push は引き続き実行されます:
[GITHOOK] ⚠️ Global pre-push lint checks skipped (LINT=0)
テスト実行
実際にはpushせず、Lintチェックだけを手動で実行して動作確認したいときはこちら。
通常はgitがpush時にフックへ情報を渡すが、ここでは現在のブランチ情報を使って手動で作成し、直接フックを呼び出す。
リモートにブランチが存在しない場合は、ゼロSHAを渡します。
BRANCH=$(git branch --show-current)
REMOTE_SHA=$(git rev-parse --verify "origin/$BRANCH" 2>/dev/null || echo 0000000000000000000000000000000000000000)
echo "refs/heads/$BRANCH $(git rev-parse HEAD) refs/heads/$BRANCH $REMOTE_SHA" | "$HOME/.githooks/pre-push" origin x
テスト実行(新しいコミットのみ)
LINT_NEW=1 でpushするコミットに含まれるファイルだけをLint対象に絞る。
BRANCH=$(git branch --show-current)
REMOTE_SHA=$(git rev-parse --verify "origin/$BRANCH" 2>/dev/null || echo 0000000000000000000000000000000000000000)
echo "refs/heads/$BRANCH $(git rev-parse HEAD) refs/heads/$BRANCH $REMOTE_SHA" | LINT_NEW=1 "$HOME/.githooks/pre-push" origin x
💻 動作環境
| 環境 | サポート状況 |
|---|---|
| macOS | ✅ 動作確認済み |
| WSL / WSL2 | ☑️ 理論上動作する(未検証) |
| ネイティブWindows (Git Bash) | ⚠️ 未検証。CMDおよびPowerShellは非対応 |
Windowsユーザーへの注意
Windowsでファイルを編集すると改行コードがCRLFに変換されスクリプトが動作しなくなることがあります。編集後はdos2unix "$HOME/.githooks/pre-push"を実行してください。
🗑️ アンインストール
グローバルフックの設定を解除したい場合:
git config --global --unset core.hooksPath
おわりに
仕組みとしては 「pushのたびにLintが自動で走り、エラーがあればブロックされる」 というシンプルな構成です。
make lint等、Lint一括実行コマンドの打ち忘れがなくなる、CI待ち時間の前にローカルでエラーを検出できる、ノイズログを軽減し、複数プロジェクトを一括カバーできる、といったメリットは実際に試してみる価値があると思います。
AIで自動化が進む世の中ですが、Lint以外の用途でもpre-push・pre-commit hookで色々試し見流のもありだと思いました。
興味があればコードを読んで、ご自身の環境に合わせてカスタマイズしてみてください!
設計判断の詳細(なぜpre-commit hookではなくpre-push hookなのか)については、続編の記事をご覧ください。
最後まで読んでいただきありがとうございました!後編でお会いしましょう!