Git bisect:コミット履歴を二分探索で絞り込む
はじめに
開発中にいつの間にかバグが混入していることがあります。
バグがないことが確認済みのコミットと最新のコミットに対して、二分探索してバグが入ったコミットを洗い出すことがあります。今まで手動でこの作業をしていたのですが、何やらgitにそのための機能が備わっているらしいので調べてみます。
参考
まとめ
git bisectは、コミット履歴を二分探索で絞り込むためのコマンドです。
good,badだけではなく自由な用語を自分で指定してコミットを探せます。
git bisect bad
等コマンドを実行すると調査対象が自動チェックアウトされます。
git bisect bad | good
を繰り返すことで特定のコミットを特定できます。
コマンド一覧
bit bisectには以下の11個のコマンドがあります。
- git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>] [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
- git bisect (bad|new|<term-new>) [<rev>]
- git bisect (good|old|<term-old>) [<rev>...]
- git bisect terms [--term-(good|old) | --term-(bad|new)]
- git bisect skip [(<rev>|<range>)...]
- git bisect reset [<commit>]
- git bisect (visualize|view)
- git bisect replay <logfile>
- git bisect log
- git bisect run <cmd> [<arg>...]
- git bisect help
概要
Git bisectは、バグの発生したコミットを特定するための便利なツールです。二分探索により特定のバグがいつ発生したかを効率的に見つけることができます。最初にバグがあることがわかっている "bad" なコミットとバグが入る前であることがわかっている "good" なコミットを git bisectに知らせます。その後 git bisect
がgoodとbadの間のコミットを選択し、 "good" か "bad" か質問をしてきます。これを繰り返しバグが入ったコミットを見つけます。
git bisect
はバグの混入を見つけるだけではなく、バグの修正やパフォーマンス改善を見つけることもできます。そのために、より一般的な用語として "good" や "bad" 等を使うこともできますし自分で用語を決められます。詳細は代替用語を参照ください。
基本的なbisectコマンドのユースケースシナリオ
v2.6.13-rc2
では動作したことが確認できているプロジェクトでバグが混入したコミットを探しています。以下のようにbisectセッションを開始します。
badとgoodを指定するとgit bisect
は履歴の真ん中のコミットをチェックアウトし以下のような表示を出力します。
$ git bisect start
# status: waiting for both good and bad commits
$ git bisect bad # Current version is bad
# status: waiting for good commit(s), bad commit known
$ git bisect good v2.6.13-rc2 # v2.6.13-rc2 is known to be good
# Bisecting: 675 revisions left to test after this (roughly 10 steps)
チェックアウトバージョンでバグが発生しているかどうかを確認します。発生していなければ、git bisect good
を、発生していればgit bisect bad
を実行します。そうすると以下のような結果が返ります。
Bisecting: 337 revisions left to test after this (roughly 9 steps)
上記を繰り返していくと、最終的にはバグが発生したコミットが特定できます。
最終的に調査対象のコミットがなくなったらbad commit
の情報を表示し、そのコミットを指すrefs/bisect/bad
を作成します。
以下でコミット情報を確認できます。
git rev-parse refs/bisect/bad # バグが発生したコミットのハッシュ
git show refs/bisect/bad # バグが発生したコミットの詳細
以下のコマンドでbisectセッションを終了し、HEADに戻ります。
$ git bisect reset
reset時に <commit>
をつけると、そのコミットに戻ります。例えば、git bisect reset bisect/bad
とすると、refs/bisect/bad
が指すコミットに戻ります。git bisect reset HEAD
とすると、現在のHEADのままで移動しません。
代替用語
バグが混入した時だけではなく、パフォーマンス改善や機能追加などの変更を追跡したい場合もあります。このような場合、バグが発生したコミットを特定するために "good" と "bad" の代わりに、"old" と "new" といった用語を使うことができます。
old/new でもない用語を使いたい場合は以下のように指定できます。
git bisect start --term-old <term-old> --term-new <term-new>
git bisect start --term-good <term-old> --term-bad <term-new>
現在のセッションで使用している用語は、git bisect terms
コマンドで確認できます。
2分岐の状態を可視化する
git bisect visualize|view
コマンドを使うと、bisectセッション中の2分岐の状態をグラフで可視化できます。
$ git bisect view
commit d092a9ed3f4acdb3f7365a37529789245618c994 (origin/feature/add_data_for_tumbling_window, feature/add_data_for_tumbling_window)
Author: kiitosu <kh0412@gmail.com>
Date: Tue May 13 23:03:30 2025 +0900
Update aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
Co-authored-by: Leandro Damascena <lcdama@amazon.pt>
Signed-off-by: kiitosu <kh0412@gmail.com>
commit 58ab8f10fb6f58136996b83dc463ab4659ba41a0 (HEAD)
Author: kiitosu <kh0412@gmail.com>
Date: Tue May 13 23:03:19 2025 +0900
Update aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
Co-authored-by: Leandro Damascena <lcdama@amazon.pt>
Signed-off-by: kiitosu <kh0412@gmail.com>
二分探索のログ確認とリプレイ
git bisect log
コマンドを使うと、bisectセッション中のログを確認できます。以下のイメージです。
$ git bisect log
git bisect start
# status: waiting for both good and bad commits
# bad: [d092a9ed3f4acdb3f7365a37529789245618c994] Update aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
git bisect bad d092a9ed3f4acdb3f7365a37529789245618c994
# status: waiting for good commit(s), bad commit known
# good: [a0adaeed470a9552959b56ad4629d555d8cef382] add test test_kinesis_stream_with_tumbling_window_event
git bisect good a0adaeed470a9552959b56ad4629d555d8cef382
# good: [41adc0bf63312ffaee426cf0ebbd1ea4ecc32fa7] Update aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
git bisect good 41adc0bf63312ffaee426cf0ebbd1ea4ecc32fa7
上記ログをテキストファイルに保存しておき、git bisect replay
コマンドを使ってbisectセッションを再現することもできます。
テスト対象のコミットを指定する
bisectによって選択されたコミットが確認に不適切な場合があります。例えばビルドができず、確認したいバグとは全く異なるモンガイアがある場合などです。その場合は、git reset
で対象コミットを変更できます。
$ git bisect good/bad # previous round was good or bad.
Bisecting: 337 revisions left to test after this (roughly 9 steps)
$ git bisect visualize # oops, that is uninteresting.
$ git reset --hard HEAD~3 # try 3 revisions before what
あるいは、git bisect skip
コマンドを使って、bisectセッションからコミットをスキップすることもできます。
$ git bisect skip # Current version cannot be tested
$ git bisect skip v2.5..v2.6 # スキップ対象をレンジ指定することもできる。v2.5より後ろでv2.6(を含む)まで
$ git bisect skip v2.5 v2.5..v2.6 # v2.5も含めたい時
二分探索を効率的に行う
プロジェクトのディレクトリを指定して、探索対象を絞ることができます。例えば以下では、arch/i386
と include/asm-i386
ディレクトリを探索対象に含めています。
$ git bisect start -- arch/i386 include/asm-i386
また、goodであることがわかっているコミットがある場合は以下のように指定もできます。
$ git bisect start v2.6.20-rc6 v2.6.20-rc4 v2.6.20-rc1 --
# v2.6.20-rc6 is bad
# v2.6.20-rc4 and v2.6.20-rc1 are good
Bisect runでスクリプトを実行する
以下のコマンドでスクリプトでコミットの状態を自動でチェックし二分探索できます。
$ git bisect run my_script arguments
my_scriptは、コミットの状態をチェックするスクリプトで、以下の戻り値を返す必要があります。単体テストを回してチェックするなどできそうですね。
- 0 → 正常(good) を意味する
- 125 → このコミットはスキップ(テスト不能な場合)
- 1〜127 → 問題あり(bad) を意味する(125以外)
オプション
--no-checkout
bisectセッション中にコミットをチェックアウトしないようにするオプションです。
--first-parent
マージコミット時に、マージ元のコミットを探索対象に含めないようにするオプションです。
例
makeが通らなくなったコミットを特定する例です。
$ git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good
$ git bisect run make # "make" builds the app
$ git bisect reset # quit the bisect session
testが通らなくなったコミットを特定する例です。
$ git bisect start HEAD origin -- # HEAD is bad, origin is good
$ git bisect run make test # "make test" builds and tests
$ git bisect reset # quit the bisect session
壊れたコードがある場合にtestが通らなくなるコミットを特定する例です。
$ cat ~/test.sh
#!/bin/sh
make || exit 125 # this skips broken builds
~/check_test_case.sh # does the test case pass?
$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10
$ git bisect run ~/test.sh
$ git bisect reset # quit the bisect session
おわりに
Git bisectは、バグの発生したコミットを効率的に特定するための強力なツールです。
バグの発生などのコードの変更を調査する時に非常に役立ちそうです!
Discussion