「先月は動いていた機能が、いつの間にか壊れている」。原因のコミットを探そうとして履歴を1つずつ遡った経験はないでしょうか。100 コミットあれば、単純に確かめていくと最大 100 回のチェックが必要です。
ここで使えるのが git bisect です。「正常だったコミット」と「壊れているコミット」の2点を伝えると、あとは Git が二分探索でその間を絞り込んでくれます。100 コミットなら7回ほど確認するだけで原因のコミットにたどり着けます。
この記事では、git bisect の基本的な流れから、テストコマンドで判定を自動化する git bisect run、判定できないコミットの飛ばし方、途中でやり直す方法までを解説します。
目次
git bisect は履歴を半分ずつ絞り込むコマンド
仕組みは辞書の引き方と同じです。まず「ここは正常(good)」「ここは壊れている(bad)」という2点を Git に伝えます。すると Git はその中間のコミットをチェックアウトするので、動作を確認して good か bad かを答えます。答えるたびに調べる範囲が半分になり、最後に「最初に壊れたコミット」が1つ残ります。
必要な確認回数は、コミット数の対数に比例します。おおよその目安は次のとおりです。
| 調べる範囲のコミット数 | 1つずつ確認した場合 | bisect の確認回数(目安) |
|---|---|---|
| 16 | 最大 16 回 | 約 4 回 |
| 100 | 最大 100 回 | 約 7 回 |
| 1000 | 最大 1000 回 | 約 10 回 |
前提として、探したい問題が「ある時点を境に good から bad へ一方向に変わる」必要があります。直ったり壊れたりを繰り返す不安定な現象には向きません。
基本の流れ
まず git bisect start で探索を開始し、現在の状態を bad、正常だったコミットを good として登録します。
# 探索を開始する
$ git bisect start
# いまのコミットは壊れている
$ git bisect bad
# v1.0.0 の時点では動いていた
$ git bisect good v1.0.0
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[4a71d20f8c3b...] 検索フォームのスタイルを調整
この時点で、Git は中間のコミットを自動的にチェックアウトしています。「残り 12 コミット、あと4回くらい」と教えてくれるのが親切なところです。あとは表示されたコミットの状態でアプリを動かし、結果を答えていきます。
# 動作を確認して…壊れていた
$ git bisect bad
Bisecting: 6 revisions left to test after this (roughly 3 steps)
# 次のコミットは動いた
$ git bisect good
Bisecting: 2 revisions left to test after this (roughly 2 steps)
# 繰り返していくと、最後に犯人が表示される
$ git bisect bad
28f9e36c1d4a7b09e2f5a3b8c1d0e9f7a6b5c4d3 is the first bad commit
commit 28f9e36c1d4a7b09e2f5a3b8c1d0e9f7a6b5c4d3
Author: taro <taro@example.com>
Date: Tue Sep 9 14:03:51 2025 +0900
検索クエリのエスケープ処理を共通化
src/search.js | 18 +++++++++---------
is the first bad commit と表示されたコミットが、問題を持ち込んだコミットです。変更ファイルも一緒に表示されるので、そのまま git show 28f9e36 で差分を読めば原因の見当が付きます。
終わったら必ず reset する
探索中はあちこちのコミットをチェックアウトした状態になっています。調査が終わったら git bisect reset を実行して、開始前のブランチに戻してください。
$ git bisect reset
Previous HEAD position was 28f9e36 検索クエリのエスケープ処理を共通化
Switched to branch 'main'
開始と同時に good・bad を指定する
毎回3コマンド打つのは手間なので、git bisect start <bad> <good> の順で一度に指定できます。並び順は「悪いほうが先」です。
# HEAD が bad、v1.0.0 が good
$ git bisect start HEAD v1.0.0
git bisect run でテストを自動判定させる
判定が自動化できる場合、git bisect run にコマンドを渡すだけで最後まで走りきってくれます。判定にはコマンドの終了コードが使われます。
| 終了コード | 判定 |
|---|---|
| 0 | good(正常) |
| 1〜127(125 を除く) | bad(壊れている) |
| 125 | 判定できない(skip と同じ扱い) |
| 128 以上 | 探索を中止する |
テストランナーは失敗すると 0 以外を返すので、そのまま渡せばうまく動きます。
$ git bisect start HEAD v1.0.0
# 特定のテストだけを流して自動判定させる
$ git bisect run npm test -- search.test.js
running 'npm test' '--' 'search.test.js'
...
28f9e36c1d4a7b09e2f5a3b8c1d0e9f7a6b5c4d3 is the first bad commit
bisect found first bad commit
テストがない場合は、確認用のシェルスクリプトを書いて渡します。ビルドに失敗したコミットを skip 扱いにしたいときは、終了コード 125 を返すのがポイントです。
#!/bin/sh
# check.sh : bisect 用の判定スクリプト
# ビルドできないコミットは判定不能として飛ばす
npm run build || exit 125
# 出力に想定の文字列が含まれていれば good(0)、無ければ bad(1)
node dist/report.js | grep -q "合計: 120" || exit 1
exit 0
$ chmod +x check.sh
$ git bisect run ./check.sh
スクリプトは探索対象のリポジトリの外(あるいは Git 管理外の場所)に置くのが安全です。チェックアウトのたびにファイルが入れ替わるため、リポジトリ内に置いた判定スクリプトは、古いコミットに移動した瞬間に消えてしまうことがあります。
判定できないコミットは skip で飛ばす
途中のコミットがビルドエラーで動かせない、そもそも別の理由で確認できない、ということがあります。そのときは good でも bad でもなく git bisect skip と答えます。
# このコミットは判定できない
$ git bisect skip
# 範囲をまとめて飛ばすこともできる
$ git bisect skip v1.1.0..v1.2.0
skip したコミットがあると、Git は代わりに近くの別のコミットを選びます。飛ばした数が多いと犯人を1つに絞りきれず、「最初に壊れたコミットは次のいずれか」と候補が複数表示されることがあります。その場合は表示された候補の差分を読んで判断します。
途中経過の保存とやり直し
ここまでの判定の記録は git bisect log で確認できます。答えを間違えた気がするときは、ログをファイルに保存して修正し、git bisect replay で途中からやり直せます。
# これまでの判定を確認・保存する
$ git bisect log
git bisect start
# bad: [9f3c1ab...] リリースノートを更新
git bisect bad 9f3c1ab...
# good: [1c7a833...] 初期化
git bisect good 1c7a833...
$ git bisect log > bisect.log
# 間違えた行を消してから、記録を再生する
$ git bisect reset
$ git bisect replay bisect.log
思ったコミットが見つからないとき
good と bad を逆に答えてしまった
よくある失敗です。1回逆に答えるだけで探索範囲がずれ、無関係なコミットが犯人として表示されます。心当たりがあるときは git bisect log で判定の履歴を見直し、おかしい箇所を直してから replay でやり直してください。
なお「バグを探す」のではなく「ある機能が入った時点を探す」ような使い方では、good と bad という言葉が直感に反します。その場合は git bisect start --term-old=動かない --term-new=動く のように、判定に使う言葉を自分で決められます。
ビルド生成物や依存パッケージが残っていて誤判定する
git bisect が切り替えるのは Git の管理下にあるファイルだけです。node_modules や dist のような管理外のディレクトリは古いまま残るので、実際に動いているコードがチェックアウトしたコミットと食い違うことがあります。判定スクリプトの中で、毎回 npm ci やビルドをやり直すようにしておくと確実です。
作業中の変更があって start できない
コミットしていない変更が残っていると、チェックアウトが妨げられて探索を始められません。git stash で退避するか、コミットしてから始めてください。探索が終わって git bisect reset したあとに git stash pop で戻せば、作業を中断せずに済みます。
まとめ
git bisect は、正常だったコミットと壊れているコミットの2点さえ分かれば、その間を二分探索して原因のコミットを特定してくれるコマンドです。git bisect start、git bisect bad、git bisect good <コミット> で始めて、表示されるコミットごとに good か bad を答え、最後に git bisect reset で元に戻す。この流れだけ覚えておけば使えます。
再現手順をコマンド1つで判定できるなら、git bisect run に任せてしまうのが早道です。終了コード 0 が good、0 以外が bad、125 が判定不能という約束を押さえておけば、テストランナーでも自作スクリプトでも自動化できます。
原因のコミットが分かれば、そのコミットの差分を読むだけで修正の見当が付きますし、必要なら git revert で取り消すこともできます。履歴を1つずつ遡る前に、まず bisect を試してみてください。