【Git】bisectでバグを仕込んだコミットを特定する方法

Git

Gitで「いつからバグが入ったのか分からない」状況に直面したら、git bisectを使って問題を導入したコミットを高速に特定できます。bisectは履歴を二分探索で辿り、正常(good)と不正(bad)をマークしながら原因コミットを絞り込む仕組みです。ここでは手動手順と自動化(bisect run)、実務のコツまで一気に解説します。

準備:前提と基本の流れ

バグが再現する最新のコミット(bad)と、バグが存在しないことが分かっている古いコミット(good)を用意します。二点が決まれば、その間の履歴を二分探索で検査できます。作業ツリーはクリーンにしてから開始しましょう(未コミット変更があると切り替え時に失敗します)。

# 未コミット変更を退避(任意)
git stash -u

# bisect開始
git bisect start
git bisect bad            # 現在のHEADがバグを含む場合
git bisect good <good_commit_hash_or_tag>  # 例: v1.2.0 など

以降、Gitが自動で過去と現在の中間点へチェックアウトします。テストして結果に応じてgood/badを返します。

# テストしてバグが出なかったら
git bisect good

# テストしてバグが再現したら
git bisect bad

数回繰り返すと、原因コミット(first bad commit)が特定されます。終了したら状態を戻します。

git bisect reset
git stash pop  # 退避していた変更を戻す(使っていた場合)

手動検証の実例とチェックポイント

1. 最新のmainにバグあり → git bisect startgit bisect bad
2. 安定版タグv1.2.0では再現しない → git bisect good v1.2.0
3. Gitが中間コミットへ移動 → アプリをビルド/起動し再現確認。
4. 再現すればbad、再現しなければgoodを入力。
5. 特定後にgit showで差分を精査、修正方針を決める。

自動化:git bisect runでテストを自動判定

テストや再現手順をスクリプト化できるなら、git bisect runで全工程を自動化できます。スクリプトは「正常=終了コード0、不正=非0」を返すようにします。

# 例: scripts/test_bug.sh(UNIX系)
#!/usr/bin/env bash
set -euo pipefail
# 依存のインストールやビルド(必要ならキャッシュ化)
npm ci >/dev/null 2>&1 || exit 125   # 125は「このコミットは判定不可」の合図
npm run build >/dev/null 2>&1 || exit 125
# 実際の再現テスト:失敗=バグあり → 非0、成功=バグなし → 0
npm test --silent && exit 0 || exit 1
# 自動 bisect
git bisect start
git bisect bad
git bisect good v1.2.0
git bisect run bash scripts/test_bug.sh

判定不可(skip)やマージ込みの履歴への対処

ビルドが壊れているなど「このコミットでは判定不能」というケースはskipします。二分探索は継続します。

git bisect skip
# または自動化スクリプトで exit 125 を返して「判定不可」を伝える

マージコミットが多い履歴で原因がブランチ側にある場合、bisectは通常どおり機能しますが、原因が「マージ結果」でのみ現れることもあります。必要に応じてgit bisect visualizeで探索範囲の履歴を眺め、再現条件を見直します。

テストの安定化と高速化のコツ

  • フレーク対策:テストを冪等にし、非決定的要素(時間・乱数・外部API)をモック化。失敗時に1回だけリトライするラッパーを用意すると誤判定を減らせます。
  • ビルド時間の短縮:依存インストールをキャッシュする、不要なタスクを切る、ユニットレベルの最小再現テストに絞る。
  • 環境固定:Node/Python/Javaなどのバージョンを.tool-versionsやDockerで固定し、各コミットで環境差による誤差を無くす。
  • 部分テストnpm test --pytest -kmvn -Dtest=...などで対象を最小化。

よくあるエラーと対処

  • 作業ツリーがクリーンでないgit stash -uで退避してからbisect。
  • サブモジュールのズレgit submodule update --init --recursiveをスクリプトに組み込む。
  • 依存が古くてビルド不能:そのコミットはskip。多発する場合は探索範囲の基準goodを新しめに見直す。
  • 再現手順が人手依存:できる限り自動テスト化。難しければCLI操作をexpect/Playwright等でスクリプト化。

原因コミット特定後のチェックリスト

  1. git show <commit>で差分とメッセージを精査(周辺コミットとの連携を確認)。
  2. 回帰テストを追加(CIに組み込み再発防止)。
  3. 修正コミットを作成し、必要ならcherry-pickで安定ブランチへ反映。
  4. リリースノートに影響範囲と対策を記載。

まとめ

git bisectは「バグの導入点」を二分探索で高速に絞り込む強力なツールです。最新=bad、既知の安定点=goodをセットし、手動でgood/badを返すか、bisect runで自動判定すれば、最小回数のチェックで原因コミットに到達できます。フレークを排除したテストと環境固定を徹底し、特定後は回帰テストを追加して再発を防ぐ、までをワンセットで運用しましょう。