0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CI/CD前のLintチェックになぜpre-pushのgithookを採用したか?pre-commitとの比較

0
Last updated at Posted at 2026-03-31

はじめに

こんにちは!株式会社C&Pの武智でございます。

この記事は Git Pre-push hookを活用して、CI/CDの前に自動Lintチェック の続編です。今すぐ導入できるセットアップ手順は前編をご参照ください。

今回はpre-pushのgithookを選んだ設計判断の背景を掘り下げます。
具体的には「なぜpre-commit githookではなく、pre-push githookなのか、そしてCI/CDだけに任せないのか」を説明します。

なぜpre-push githookなのか

  • Lintの手動実行漏れや、自動実行の場合はGitHub Actions等のCIで初めて気づく、という状況を回避できます(不要なActionsを回して順番待ちを発生させたくないこともあります)
  • エラー通知を設定している場合、ノイズログの発生も抑えることができます
  • フックは ~/.githooks/ にグローバルで配置するため、マシン上のすべてのリポジトリに自動で適用されるため、新しいプロジェクトを git clone した瞬間から、何も設定せずにpre-push githookが使用できます
  • ローカルで完結しつつ、自動実行できる
  • pre-commitフックの場合みたいに、新しいLintツールを追加するたびに各ツールの挙動を考えなくて良い(※後述)

Makefileと何が違うのか

「Makefileに定義したLintコマンドをまとめて実行するショートカットを書けばいいのでは?」という声もあると思います。こちらも比較します。

Makefile (make lint) pre-push githook
実行タイミング 手動で make lint を叩いたとき git push のたびに自動
スコープ グローバル化は可能だが競合リスクがある グローバル設定で全リポジトリに一括適用
強制力 打ち忘れても push できる エラーがあれば push がブロックされる
git git とは無関係のシェルタスク git ワークフローに直接組み込まれる
スキップ方法 そもそも実行しなければいい LINT=0 git push で意図的にスキップできる

Makefileの lint タスクは「実行する習慣がある人」には有効ですが、ヒューマンエラーもあるので習慣に頼らず強制するという点でpre-push githookには遠く及びません。

なぜpre-commit githookにしなかったのか

最初はpre-commit githookとして実装しようとしました。
コミット直後にエラーが出れば、フィードバックが最速になるからです。
しかし調べていくうちに、サクッと構築するなら今回使用するPHPのLintツール群とは構造的に相性が悪いと感じました。

3つのツールすべてがステージングエリアを読めず、ワーキングディレクトリを見る

pre-commit githookでLintを正しく動かすには、「コミットに含まれる内容(ステージされたスナップショット)」をチェックする必要があります。しかし実際には:

Laravel Pint

--staged に相当するフラグが存在しない。よく見られる回避策は git diff --cached --name-only(ステージングエリアに登録されているファイルの「名前一覧」を取得するコマンド)でファイルパスを列挙し、それをコマンドライン引数としてPintに渡す方法だが、Pintはその引数のファイルパスをもとにディスク上のファイル、つまりワーキングディレクトリ版を読む。ステージした変更と未ステージ変更が同じファイルにある場合、コミットに含まれないコードまでLintされる。

PHP_CodeSniffer

--filter=GitStaged オプションでチェック対象ファイルを絞れるが、公式リポジトリのissue(#3379)で議論されている通り、このフィルターは「どのファイルを対象にするか」を絞るだけで、ファイルの読み込み元はあくまでワーキングディレクトリである。これはバグではなくPHPCSの設計上の動作であり、ステージされたスナップショットを直接読む機能は持っていない。

PHPStan / Larastan

ステージングエリアを認識せず、ワーキングディレクトリ上のファイルをそのままスキャンするため、ステージ済み・未ステージを区別せず解析してしまう。

同一ファイルにステージ済みと未ステージの変更が混在している場合、3つのツールすべてが「コミットに含まれない変更」を含む状態でLintを行う。チェックが通ってもコミットの中身は別物、あるいはチェックが失敗してもコミット自体はクリーンという状況が起こり得る。

git stash --keep-index による回避策にもリスクがある

「ステージ以外の変更を一時的に退避してからLintを走らせる」という git stash --keep-index アプローチは存在するが、これ自体に複数のリスクがある:

同一ファイルへの競合

ステージ済みと未ステージの変更が同じ行に触れている場合、git stash pop 時にマージコンフリクトが発生する。git-scm.comの公式ドキュメントにある通り、コンフリクトが起きるとstashエントリは自動削除されず、ワーキングディレクトリは競合状態のまま残される。

未ステージ変更の消失リスク

githookが異常終了した場合、git stash pop が実行されないまま終わる。退避した未ステージ変更がstashに取り残され、気づかなければ作業内容が見えなくなる。

削除ファイルの"復活"

ファイル削除をステージしている場合、git stash --keep-index によってそのファイルがワーキングディレクトリに復活することがある。削除コミットのつもりがLint対象に含まれるという混乱が生じる。

意図しない別stashのpop

エッジケースだが、退避するものが何もない状態で git stash pop を実行すると、以前の別のstashエントリが意図せず復元される。
これらを正しく回避するには、より複雑なstashによる管理、hookの終了コードハンドリング、pop時の --index フラグの使い分けなど、スクリプトの複雑さが急増する割に、保証できる信頼性も低い。

PHPStan / Larastanはコミットのたびに全スキャンするには重い

PHPStanはパスの指定なしでは動作せず、phpstan.neonpaths 設定で解析対象を明示する必要がある(phpstan.org公式ドキュメント)。Laravelプロジェクトで app/ 全体を設定すると、十数秒かかることが珍しくありません。

「急いでいるから・さっき大丈夫だったから・面倒だからOFFにしよう...💀」
チーム開発の場合、実行コストがそのままhook無効化の動機になることも懸念すべきでしょう。

以上を踏まえて、commit中の作業には干渉せず、pushの直前に一度だけチェックし、かつローカルで完結する設計でpre-pushを選びました。

pre-commit githook pre-push githook
実行頻度 コミットのたびに毎回 pushのときのみ
ステージング精度 Pint・PHPCS・PHPStanはすべてワーキングディレクトリを読む。コミット対象のコードだけをチェックできない デフォルトはプロジェクト全体を各ツールのconfigに従ってスキャン。LINT_NEW=1 オプションでpush範囲のみに絞れる(※PHPstan/Larastan以外)。ただしツールはディスク上のファイルを読むため、未コミットのPHP変更がある場合はフックが中断される
git stash --keep-index 回避策 競合・消失・削除ファイル復活など複数の破壊的リスクあり stashは不要。ただし未コミットのPHP変更がある場合はpushを中断するガードが必要
PHPStan/Larastanの実行コスト WIPコミットのたびにプロジェクト全スキャンが走る push前の1回なら許容範囲
フィードバックの早さ 最速(commit直後) pushまで気づけない場合がある
スキップされやすさ 遅い・煩わしいと --no-verify が常態化しやすい 頻度が低い分、スキップへの誘惑も少ない

おわりに

pre-push hookの魅力は「commitには一切干渉しない」という点だと思います。

開発中のWIPコミットも、fixも、自由に打てる。
そしてチームへ共有する直前の一度だけ、静かにLintが走る。

AIを使用したワークフロー見たいな派手さはないですが、軽量で開発リズムを壊さずに品質を守れるバランスが気に入っています。

セットアップ手順は前編をご参照ください。

最後まで読んでいただきありがとうございました!
それではまた次回にお会いしましょう!

Gitリポジトリ&README

https://github.com/remtakechi/pre-push-lint-hook

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?