🔖

Git bisect:コミット履歴を二分探索で絞り込む

に公開

はじめに

開発中にいつの間にかバグが混入していることがあります。

バグがないことが確認済みのコミットと最新のコミットに対して、二分探索してバグが入ったコミットを洗い出すことがあります。今まで手動でこの作業をしていたのですが、何やらgitにそのための機能が備わっているらしいので調べてみます。

参考

まとめ

git bisectは、コミット履歴を二分探索で絞り込むためのコマンドです。
good,badだけではなく自由な用語を自分で指定してコミットを探せます。
git bisect bad 等コマンドを実行すると調査対象が自動チェックアウトされます。
git bisect bad | good を繰り返すことで特定のコミットを特定できます。

コマンド一覧

bit bisectには以下の11個のコマンドがあります。

  1. git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>] [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
  2. git bisect (bad|new|<term-new>) [<rev>]
  3. git bisect (good|old|<term-old>) [<rev>...]
  4. git bisect terms [--term-(good|old) | --term-(bad|new)]
  5. git bisect skip [(<rev>|<range>)...]
  6. git bisect reset [<commit>]
  7. git bisect (visualize|view)
  8. git bisect replay <logfile>
  9. git bisect log
  10. git bisect run <cmd> [<arg>...]
  11. 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/i386include/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