「リグレッションバグ」という名の悪夢
月曜日の朝に出社すると、顧客から本番環境で急ぎのバグ報告が入っていました。先週まで正常に動いていた重要な機能が消えていることに気づき、愕然とします。本当の悪夢はGitの履歴を見た時に始まります。この2週間でチームは200件以上のコミットをマージしていました。
どのコミットで壊れたのかを一つずつ手動で確認するのは苦行です。以前の私は、伝統的な git bisect を使っていました。確実に動作していた古いコミット(good)と、バグがある現在のコミット(bad)を選択し、Gitにその中間地点へ移動させます。各地点でコードをビルドし、手動でテストを行い、 git bisect good または bad と入力します。犯人を見つけるまで、このプロセスを7〜10回ほど繰り返す必要がありました。
コミットごとにチェックするよりは早いものの、ビルドを待つ時間はやはり無駄に感じます。そんな時、 git bisect run コマンドを知り、すべてが変わりました。コマンドを打つために待機する代わりに、短いテストスクリプトを書いてコーヒーを飲みに行くだけでいいのです。戻ってきた時には、Gitがバグの原因となったコミットを特定してくれています。このテクニックは、ある緊迫したリリースの際に、5,000行もの新しいコードの中に隠れていたロジックバグを見つけ出し、私を救ってくれました。
git bisect run – コンピュータにバグを自動追跡させる
Gitは、追跡を最適化するために二分探索(binary search)アルゴリズムを使用します。1,000件のコミットがあっても、バグの箇所を特定するために必要なチェックは最大10回だけです。
git bisect run の最大の特徴は、自動スクリプトを実行できる点にあります。Gitは自動的に次のコミットにチェックアウトし、そのスクリプトを実行します。返り値に基づいて、Gitはそのコミットが「成功(good)」か「失敗(bad)」かを自動的に判断し、最終的な結果が出るまでステップを繰り返します。
動作原理:終了コード(Exit Code)の力
git bisect run を正しく動作させるには、スクリプトが規約に従った終了コード(exit codes)を返す必要があります:
- Exit code 0: コミットは正常(Good)。
- Exit code 1〜127(125を除く): コミットに問題あり(Bad)。
- Exit code 125: このコミットはテスト不可(例:ビルドエラーなど無関係な理由)。Gitはこのコミットをスキップし、近傍のコミットを選択します。
成功時に0、失敗時に1を返すスクリプト(Bash、Python、Node.jsなど)を書くだけで、バグ調査のプロセス全体を自動化できます。
実践:追跡プロセスの自動化
例えば、計算ロジックに誤りがあるNode.jsプロジェクトがあるとします。タグ v1.0 では正しく動作していましたが、現在の HEAD では誤った結果が出ることがわかっています。
ステップ1:テストスクリプト(test.sh)の作成
自動チェックを行うための test.sh ファイルを作成します。Gitのチェックアウトによる影響を避けるため、このファイルはリポジトリの外(親ディレクトリなど)に置くのがおすすめです。
#!/bin/bash
# 1. package.jsonに変更がある場合に備えて依存関係を更新
npm install
# 2. 特定のロジックテストを実行
# test_logic.js は期待通りの結果でない場合にエラーをスローするようにする
node test_logic.js
# 3. 実行したコマンドのステータスを返す
exit $?
もし特定のファイルにバグの原因となるキーワードが含まれているかどうかを確認するだけであれば、 grep を使うのが手っ取り早いです:
#!/bin/bash
# "buggy_function" という文字列が見つかった場合、そのコミットは失敗とみなす
! grep -q "buggy_function" src/utils.js
ステップ2:自動モードの起動
ターミナルを開き、Gitに作業を開始させます:
git bisect start
# 開始点と終了点をマーク
git bisect bad
git bisect good v1.0
# スクリプトを使って自動モードを起動
git bisect run sh ../test.sh
Gitがコミットを順次チェックし、スクリプトを繰り返し実行します。数分後、画面に最も重要なメッセージが表示されます:
f7a3b2c... is the first bad commit
失敗しないための実践的なアドバイス
8人のチームで何度も運用してきた経験から、 bisect run の効果を最大限に引き出すための4つの重要な注意点をまとめました:
- クリーンな環境: スクリプトには環境のクリーンアップや再セットアップ(
npm installなど)を含めるべきです。そうしないと、古い依存関係がコミット間のテスト結果を歪める可能性があります。 - スピード優先: Gitはスクリプトを何度も実行します。20分かかる全テストスイートを実行するのではなく、調査対象のバグだけに焦点を当てた小さなテストケースを作成しましょう。
- ファイルパス: 常に絶対パスを使用するか、スクリプトをプロジェクトディレクトリの外に配置してください。これにより、Gitが過去にさかのぼった際にスクリプトが消えたり、内容が変わったりするのを防げます。
- 実行権限: LinuxやmacOSでは、開始前に必ず
chmod +x test.shを実行して権限を付与することを忘れないでください。
おわりに
バグの原因コミットを特定する作業は、トラブルシューティングの時間の50%を占めることもあります。 git bisect run を使えば、最も退屈な作業をコンピュータに任せることができます。
以前のプロジェクトで、原因特定が非常に困難なCSSのレイアウト崩れに遭遇したことがありました。その際、 *Puppeteer* を使ってスクリーンショットを撮り、ピクセル単位で自動比較するスクリプトを書きました。結果、5分ほど実行しただけで、グローバルなCSS変数を誤って修正したコミットを正確に見つけ出すことができました。次に厄介なバグに遭遇したときは、すぐにコードをいじり始めるのではなく、Gitに「名探偵」の役割を任せてみてください。

