さよならHusky:Git Hooksの管理をLefthookに移行した理由

Git tutorial - IT technology blog
Git tutorial - IT technology blog

背景:深夜2時のCI/CDトラブル

深夜2時、Slackの通知が鳴り止みませんでした。本番環境のビルドパイプラインがエラーで停止したのです。原因は意外なものでした。チームのある開発者がWindowsマシンでGoのコードをプッシュした際、リンターの実行を忘れていました。本来、こうしたエラーはGit Hooksを使ってローカル環境で事前に阻止すべきものです。

以前は、Huskyを愛用していました。しかし、プロジェクトがPython、Go、Rustを含むモノレポ(Monorepo)へと拡大するにつれ、問題が発生しました。バックエンドエンジニアに、Git Hookを動かすためだけにNode.jsをインストールさせるのは不合理な要求です。さらに、Huskyは逐次実行(シリアル実行)のため、非常に低速です。大規模なコミットでは、リンターの完了を待つのに30秒以上かかることもあり、その待ち時間のせいで集中力が切れ、誤ってgit push --forceという悲惨なコマンドを叩いてしまうこともありました。

そこで出会ったのがLefthookです。Goで書かれたLefthookは単一のバイナリファイルで動作し、非常に軽量で、Node.js環境に依存しません。

Lefthookのインストール:あらゆるワークフローに柔軟に対応

Lefthookは環境を選びません。プロジェクトに合わせて、いくつかの方法で導入できます。

方法1:Node.jsプロジェクトの場合

npm install lefthook --save-dev
# または
yarn add -D lefthook

方法2:スタンドアロンでのインストール(Go、Python、Rustなど)

プロジェクトでpackage.jsonを使用していない場合は、システムに直接インストールします:

# macOS (Homebrew)
brew install lefthook

# Go経由でインストール
go install github.com/evilmartians/lefthook@latest

インストール後、lefthook installコマンドで初期化します。プロジェクトのルートディレクトリにlefthook.ymlファイルが作成されます。ここでコミットプロセス全体を管理します。

設定:並列実行による真のパワー

Lefthookの最大の魅力は、YAMLによる極めて明快な設定ファイルです。以下は、JSのリンターチェックとGoのコードフォーマットを同時に実行するための設定例です:

# lefthook.yml

pre-commit:
  parallel: true # 時間短縮のためコマンドを並列実行
  commands:
    linter-js:
      glob: "*.{js,ts,tsx}" # 変更されたファイルのみをフィルタリング
      run: npx eslint {staged_files} --fix
    formatter-go:
      root: "backend/" 
      glob: "*.go"
      run: go fmt {staged_files} && git add {staged_files}
    check-secret:
      run: gitleaks detect --source . --verbose

なぜこの設定がHuskyより優れているのか?

  • parallel: true: Huskyは各コマンドを1つずつ実行しますが、LefthookはCPUを最大限に活用して並列実行します。5つのタスクがある場合、待ち時間は30秒から5〜7秒程度に短縮されます。
  • {staged_files}: プロジェクト全体をスキャンする代わりに、Lefthookはgit addされたファイルのリストのみをコマンドに渡します。これにより、処理速度が飛躍的に向上します。
  • globフィルタ: ファイル形式ごとに実行するリンターを正確に指定できるため、リソースの無駄を省けます。

実践的なシチュエーションへの対応

緊急の修正(ホットフィックス)が必要で、軽微なスタイルエラーのチェックを待ちたくないこともあるでしょう。

プロフェッショナルなHookのスキップ方法

安全性の低い--no-verifyフラグを使う代わりに、Lefthookでは環境変数を使って特定のコマンドをスキップできます:

LEFTHOOK=0 git commit -m "緊急の修正"

または、確信がある場合に限り、シークレットチェックのみをスキップすることも可能です:

SKIP=check-secret git commit -m "一時的なコミット"

チーム全員が確実に実行するようにする

同僚がHookのインストールを「忘れる」のを防ぐために、package.jsonに以下のスクリプトを追加しましょう:

{
  "scripts": {
    "postinstall": "lefthook install"
  }
}

npm installを実行するたびに、Lefthookが自動的に.git/hooks内のHookを再設定します。これで、サーバーにプッシュする前のコードチェックを誰も「回避」できなくなります。

実戦経験:シークレット漏洩で損失を出さないために

設定はいつでもlefthook run pre-commitコマンドでテストできます。このコマンドはコミットプロセスをシミュレートし、詳細なログを出力します。エラーが発生した場合、Lefthookは雑多なログを出すのではなく、どのコマンドが失敗したかを明確に示してくれます。

私自身の苦い経験から得た教訓です。常にpre-commitステップにgitleaksを組み込んでください。以前、誤ってAWSのシークレットキーをGitHubにプッシュしてしまい、わずか5分後にAmazonから警告を受けたことがあります。それ以来、私のすべてのプロジェクトでcheck-secretは必須のステップとなっています。

結論

Huskyは純粋なJavaScriptの小規模プロジェクトには依然として適しています。しかし、速度、モノレポへの柔軟な対応、そして多人数チームでの安定性を求めるなら、Lefthookは代替不可能な選択肢です。移行にかかる時間はわずか15分ですが、それによって何時間もの待ち時間を節約し、不必要なビルド失敗を未然に防ぐことができます。

Share: