git bisect
が素晴らしいと言っている記事をいくつか読みました。しかし、私はネイティブスピーカーではないので、なぜそれが素晴らしいのか理解できません。
誰かがいくつかのコードサンプルでデモしてください:
svn blame
のようなものですか?git bisect
の背後にある考え方は、特定の回帰を見つけるために履歴内でバイナリ検索を実行することです。次の開発履歴があることを想像してください。
... --- 0 --- 1 --- 2 --- 3 --- 4* --- 5 --- current
プログラムがcurrent
リビジョンで正常に動作していないこと、およびリビジョン0
で動作していたことがわかります。そのため、リグレッションは、コミット1
、2
、3
、4
、5
、current
のいずれかで発生した可能性があります。
各コミットをチェックアウトしてビルドし、リグレッションが存在するかどうかを確認することができます。多数のコミットがある場合、これには長い時間がかかる可能性があります。これは線形検索です。バイナリ検索を行うことで、より良い結果を得ることができます。これがgit bisect
コマンドの機能です。各ステップで、潜在的に悪いリビジョンの数を半分に減らします。
次のようなコマンドを使用します。
$ git stash save
$ git bisect start
$ git bisect bad
$ git bisect good 0
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[< ... sha ... >] 3
このコマンドの後、git
はコミットをチェックアウトします。この例では、3
をコミットします。プログラムをビルドし、回帰が存在するかどうかを確認する必要があります。また、回帰が存在する場合はgit bisect bad
、存在しない場合はgit bisect good
を使用して、git
にこのリビジョンのステータスを伝える必要があります。
回帰がコミット4
で導入されたと仮定しましょう。その後、このリビジョンには回帰は存在せず、git
に通知します。
$ make
$ make test
... ... ...
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[< ... sha ... >] 5
その後、別のコミットをチェックアウトします。 4
または5
のいずれか(コミットが2つしかないため)。 5
を選択したとしましょう。ビルド後、プログラムをテストし、回帰が存在することを確認します。それをgit
に伝えます:
$ make
$ make test
... ... ...
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[< ... sha ... >] 4
最後のリビジョン4
をテストします。そして、それが回帰を導入したものなので、それをgit
に伝えます:
$ make
$ make test
... ... ...
$ git bisect bad
< ... sha ... > is the first bad commit
< ... commit message ... >
この単純な状況では、4つのバージョン(3
、4
、5
、1
)ではなく、3つのバージョン(2
、3
、4
)のみをテストする必要がありました。これは小さな勝利ですが、これは私たちの歴史が非常に小さいためです。検索範囲がNコミットの場合、線形検索で約N/2コミットする代わりに、git bisect
で1 + log2 Nコミットをテストする必要があります。
回帰を引き起こしたコミットを見つけたら、それを調べて問題を見つけることができます。これが完了したら、git bisect reset
を使用して、git bisect
コマンドを使用する前にすべてを元の状態に戻します。
git bisect run
自動二分法テストに問題がなければ、終了ステータスが0の自動化された./test
スクリプトがある場合は、bisect run
でバグを自動的に見つけることができます。
git checkout KNOWN_BAD_COMMIT
git bisect start
# Confirm that our test script is correct, and fails on the bad commit.
./test
# Should output != 0.
echo $?
# Tell Git that the current commit is bad.
git bisect bad
# Same for a known good commit in the past.
git checkout KNOWN_GOOD_COMMIT
./test
# Should output 0.
echo $?
# After this, git automatically checks out to the commit
# in the middle of KNOWN_BAD_COMMIT and KNOWN_GOOD_COMMIT.
git bisect good
# Bisect automatically all the way to the first bad or last good rev.
git bisect run ./test
# End the bisect operation and checkout to master again.
git bisect reset
これはもちろん、テストスクリプト./test
がgitで追跡されている場合、2分割中の以前のコミットで消えないことを前提としています。
ツリー内のスクリプトをツリーからコピーして、PATH
のような変数で遊んで、代わりにそこから実行することで、多くの場合逃げることができることがわかりました。
もちろん、test
が依存するテストインフラストラクチャが古いコミットに違反する場合、解決策はなく、コミットを1つずつテストする方法を決定して、手動で作業を行う必要があります。
master
に戻るのではなく、bisectの後の最初の失敗したコミットにとどまります。
git bisect reset HEAD
start
+最初のbad
およびgood
を一度に:
git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~
次と同じです:
git checkout KNOWN_BAD_COMMIT
git bisect start
git bisect bad
git bisect good KNOWN_BAD_COMMIT
これまでにテストされたものを参照してください(手動のgood
およびbad
またはrun
による):
git bisect log
サンプル出力:
git bisect log
git bisect start
# bad: [00b9fcdbe7e7d2579f212b51342f4d605e53253d] 9
git bisect bad 00b9fcdbe7e7d2579f212b51342f4d605e53253d
# good: [db7ec3d602db2d994fe981c0da55b7b85ca62566] 0
git bisect good db7ec3d602db2d994fe981c0da55b7b85ca62566
# good: [2461cd8ce8d3d1367ddb036c8f715c7b896397a5] 4
git bisect good 2461cd8ce8d3d1367ddb036c8f715c7b896397a5
# good: [8fbab5a3b44fd469a2da3830dac5c4c1358a87a0] 6
git bisect good 8fbab5a3b44fd469a2da3830dac5c4c1358a87a0
# bad: [dd2c05e71c246f9bcbd2fbe81deabf826c54be23] 8
git bisect bad dd2c05e71c246f9bcbd2fbe81deabf826c54be23
# bad: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05] 7
git bisect bad c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05
# first bad commit: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c0
時間のより良い概念を得るために、gitログに良い参照と悪い参照を表示します。
git log --decorate --pretty=fuller --simplify-by-decoration master
これは、対応するrefを持つコミットのみを示し、ノイズを大幅に減らしますが、タイプの自動生成されたrefを含みます:
refs/bisect/good*
refs/bisect/bad*
どのコミットが良いか悪いかをマークします。
コマンドを試してみたい場合は、 このテストリポジトリ を検討してください。
時々:
それらの場合、例えば失敗は常に5秒で発生し、実際にテストをより具体的にするのが面倒であれば、次のようにtimeout
を使用できます。
#!/usr/bin/env bash
timeout 5 test-command
if [ $? -eq 1 ]; then
exit 1
fi
これは、timeout
が124
を終了し、test-command
の失敗が1
を終了するため、機能します。
git bisect run
は終了ステータスについて少し気難しいです:
127を超えると、次のように二分に失敗します。
git bisect run failed:
exit code 134 from '../test -aa' is < 0 or >= 128
特に、Cのassert(0)
はSIGABRT
につながり、ステータス134で終了し、非常に迷惑です。
125は魔法で、git bisect skip
で実行をスキップします。
これの目的は、無関係な理由により壊れたビルドをスキップすることです。
詳細については、man git-bisect
を参照してください。
したがって、次のようなものを使用できます。
#!/usr/bin/env bash
set -eu
./build
status=0
./actual-test-command || status=$?
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
status=1
fi
exit "$status"
Git 2.16.1でテスト済み。
$ git bisect start
$ git bisect bad
$ git bisect good <goodcommit>
Bisecting: X revisions left to test after this (roughly Y steps)
問題はまだ存在しますか?
$ git bisect bad
$ git bisect good
<abcdef> is the first bad commit
git bisect reset
さらにポイントを追加するには:
バグが特定のファイルに由来することがわかっている場合は、git bisect start
へのファイル名またはパスを指定できます。たとえば、リグレッションの原因となった変更がcom/workingDirディレクトリにあることを知っていた場合、git bisect start com/workingDir
を実行できます。これは、このディレクトリの内容を変更したコミットのみがチェックされることを意味します。
また、特定のコミットが良いか悪いかを判断するのが難しい場合は、git bisect skip
を実行できますが、これは無視されます。他にも十分なコミットがある場合、git bisectは代わりに別のコミットを使用して検索を絞り込みます。
$ git bisect ..
bascicallyデバッグ用のGitツール。 「Git Bisect」は、前回の(既知の)コミット以降、前のcommitsを経由してデバッグします。バイナリ検索を使用してこれらすべてのコミットを実行し、リグレッション/バグを引き起こしたコミットに到達します。
$ git bisect start
#bisectの開始
$ git bisect bad
#現在のコミット(v1.5)にリグレッション/設定「不良」ポイントがあると述べる
$ git bisect good v1.0
#最後の正常なコミット(リグレッションなし)に言及
この「悪い」点と「良い」点の言及は、git bisect(バイナリ検索)中間要素を選択するのに役立ちます(コミットv1.3)。コミットv1.3でリグレッションがある場合、それを新しい「不良」ポイントとして設定します。つまり、(Good-> v1.0 and Bad-> v1.3))
$ git bisect bad
または同様に、コミットv1.3にバグがない場合は、それを新しい「良い点」として設定します(* Good-> v1.3 and Bad-> v1.6)。
$ git bisect good
注:good
およびbad
という用語は、特定のプロパティの有無にかかわらずコミットをマークするために使用できる唯一の用語ではありません。
Git 2.7(2015年第4四半期)では、新しいgit bisect
オプションが導入されました。
git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
追加ドキュメント:
時々、破損を引き起こしたコミットを探しているのではなく、他の「古い」状態と「新しい」状態の間で変化を引き起こしたコミットを探している場合があります。
たとえば、特定の修正を導入したコミットを探している場合があります。
または、ソースコードのファイル名が最終的にすべて会社の命名基準に変換された最初のコミットを探しているかもしれません。または何でも。このような場合、「変更前の状態」と「変更後の状態」を指す「良い」と「悪い」という用語を使用するのは非常に混乱する可能性があります。
その代わり、「
old
」と「new
」の代わりに、それぞれ「good
」と「bad
」という用語を使用できます。
(ただし、「good
」および「bad
」と「old
」および「new
」を単一セッションで混在させることはできません。)このより一般的な使用法では、
git bisect
にいくつかのプロパティを持つ「new
」コミットと、そのプロパティを持たない「old
」コミットを提供します。
git bisect
がコミットをチェックアウトするたびに、そのコミットにプロパティがあるかどうかをテストします。
その場合、コミットを「new
」としてマークします。それ以外の場合は、「old
」としてマークします。二分が完了すると、
git bisect
は、どのコミットがプロパティを導入したかを報告します。
commit 06e6a74 、 commit 21b55e 、 commit fe67687 (2015年6月29日)by Matthieu Moy(moy
) を参照してください。
Antoine Delaite(CanardChouChinois
) による commit 21e5cfd (2015年6月29日)を参照してください。
( 浜野潤二-gitster
- in commit 22dd6eb 、2015年10月5日)