人は間違えるものですが、その結果をより上手に扱ってこそ、優れた構成管理システムと言えます。この章では、プロジェクトに忍び込んだ問題を発見した際に、使える手法について説明します。 Mercurial は、問題の元を隔離し適切に処理するための優れた機能を持っています。
筆者は、時として考えるよりも先に入力してしまう、という根深い問題を抱えているため、不完全であったり、単純に間違った内容のチェンジセットをコミットしてしまうことがあります。筆者の場合、不完全なチェンジセットをコミットしてしまう のは、新しいソースファイルを作成したのに“hg add” の実行を忘れている場合が殆どです。 “単純に間違っている”チェンジセットをコミットしてしまうケースには、特に共通点はありませんが、 but 非常に迷惑 (no less annoying) XXXXX。
Mercurial が、リポジトリへの個々の変更をトランザクションとして扱っていることを4.2.2 節で述べました。チェンジセットをコミットしたり、他のリポジトリから変更を pull する際に、 Mercurial は常に処理したことを記録しています。 “hg rollback” コマンドを使用することで、きっちり一回分の処理を元に戻す、別な言い方をするなら、巻き戻すことができます(このコマンドを使用する際の重要な注意が述べられていますので、 9.1.4 節を参照してください)。
新しくファイルを作成したのに、そのファイルに対して“hg add” コマンドを実行するのを忘れてコミットしてしまう、という筆者のよくやる間違いは、以下のようなものです。
コミット後の“hg status” 出力を見れば、すぐさま間違いを確証できます。
先のコミットは、a の変更は捉えていますが、新規のファイル b は把握していません。同僚と共有しているリポジトリに、このチェンジセットを反映してしまったら、同僚がこのチェンジセットを取り込んだ際に、 a 中の何かが、同僚 のリポジトリには存在しない b を参照してしまいます。そうなれば、私は同僚の憤りの対象になってしまうでしょう。
しかし、幸いなことに、チェンジセットを共有リポジトリへと反映する前に、自分の間違いを見つけています。“hg rollback” コマンドを使うことで、 Mercurial は最後のチェンジセットを消してくれます。
リポジトリの履歴上、最早最前のチェンジセットは存在しませんので、作業領域ディレクトリは、再び a ファイルが変更されている 状態だとみなされます。コミット後のロールバックは、作業領域ディレクトリをコミット前の状態そのままに戻し、チェンジ セットは完全に消去されます。そうなったなら、安全に b ファイルを“hg add” し、再度コミットすることができま す。
1つのプロジェクトで、別々に開発の進んでいるブランチを Mercurial で保守する場合、それぞれ異なるリポジトリで保守することが 一般的な慣習となっています。開発チームは、プロジェクトの “0.9” リリース用に共有リポジトリを持つ一方で、異なる変更履歴を持 つ “1.0” リリース用のリポジトリを別途持つかもしれません。
この場合、ローカルな “0.9” リポジトリがあって、そこに偶然 “1.0” 用共有リポジトリの成果を取り込んだ場合、面倒な事 態になることが想像できます。最悪の場合、十分な注意を払わないために、 “1.0” のリポジトリから取り込んだ変更 を “0.9” の共用リポジトリへと反映してしまったチーム全体を混乱させてしまうでしょう(この恐ろしいケースに関 しては、後ほど解決方法を示しますので御安心を。)。しかし、 Mercurial は成果取り込み先の URL を表示する か、 Mercurial が怪しげな大量の変更をリポジトリに取り込んだことが表示されますから、すぐに気付く方があり得ま す1 。
“hg rollback” コマンドは、今まさに取り込んだ全てのチェンジセットを、きちんと綺麗にします。 Mercurial は、一回の“hg pull” 起動により取り込まれるチェンジセット全体を、単一のトランザクションに分類するので、一回の“hg rollback” 起動でこの 失敗を取り消すことができます。
“hg rollback” は、一旦他のリポジトリに反映した変更でも、(手元のリポジトリにおいては)無かったことにできます。取り消しにより変更は完全に消されますが、それができるのは、 “hg rollback” を実施したリポジトリにおける取り消しのみです。取り消しは履歴を削除しますので、変更の取り消しをリポジトリ間で伝播する手段が無いので す。
変更を他のリポジトリ – 典型的な例では共有リポジトリ – に反映した場合、本質的には、その変更は “野生に逃げ出し” ており、取 り消しとは別な方法で間違いを埋め合わせる必要があります。変更を他のリポジトリに反映し、(手元のリポジトリで)その変更を取り 消した後で、変更を反映したリポジトリから変更を取り込んだ時には、取り消した変更が(手元のリポジトリに)再び現れま す。
(取り消したい変更が、変更を反映したリポジトリにおける最新のもので、且つ、誰もそれをそのリポジトリから取り込んでいない ことが確実である場合、その変更を取り消すこともできますが、取り消しが機能することには依存しないようにしてください。遅かれ早 かれ変更は直接触ることのできない(あるいは存在を忘れていた)リポジトリへと反映され、回りまわって戻ってきた時に噛み付かれて しまいます。)
Mercurial は、当該リポジトリにおける最も最新のトランザクションを、1つだけトランザクションログに記録します。そのため、取り 消せるトランザクションは1つ分だけです。トランザクションを1つ取り消した後で、その前のトランザクションも取り消せることを期 待しても、期待通りの結果は得られません。
あるリポジトリでトランザクションの取り消しを行った場合、別な変更をコミットするなり取り込むなりしない限り、そのリポジト リで取り消しを行うことはできません。
ファイルを変更した後で、ファイルの変更が全く必要ないことに気付いた場合、変更をコミットする前であれば、“hg revert” コマン ドが利用できます。このコマンドは、作業領域ディレクトリの親チェンジセットを参照し、ファイルの内容を元の状態に戻します。(説 明すると長くなりますが、通常の場合、このコマンドは変更を取り消します。)
“hg revert” コマンドの機能を、ちょっとしたサンプルで説明します。 Mercurial により既に構成管理されているファイルを変更 します。
変更が必要ない場合、単純に“hg revert” コマンドをファイルに適用します。
“hg revert” コマンドは、ある程度の安全性を確保するために、.orig 拡張子付きのファイルに、変更されたファイルの内容を保 存します。
“hg revert” コマンドが扱うことのできる状況を以下にまとめます。個々の状況に関する詳細は、以後の節で説明しま す。
“hg revert” は変更されたファイル以外に対しても有用なコマンドです。このコマンドは、 Mercurial の全てのファイル管理コマンド —“hg add” や“hg remove” など — の実施を反転させます。
ファイルに対して“hg add” を行った後で、そのファイルを Mercurial で構成管理する必要が無いことに気付いたなら、“hg revert” によりファイルの追加を取り消せます。 Mercurial はファイル自体には何も変更を行いませんので安心してください。ファイ ル追加の取り消しは、ファイルに対して “印を消す” だけです。
同様に、ファイルに対して“hg remove” を行った後でも、“hg revert” を使うことで、作業領域ディレクトリの親チェンジセッ トにおける状態に、ファイルの内容を復旧することができます。
これは、 Mercurial を通さずに手動で削除したファイル(Mercurial の用語ではこの種のファイルが “紛失”(missing) と呼ばれるこ とを思い出してください)であっても機能します。
“hg copy” されたファイルに取り消しを行った場合、複製先ファイルは作業領域ディレクトリに、構成管理されない状態でそのまま 残ります。複製操作は複製元ファイルには何も作用しないので、取り消しの際に Mercurial は複製元ファイルに対して特に何もしませ ん。
ファイルに対して“hg rename” を行った場合、覚えていて欲しいことがあります。“hg rename” 実行に対して“hg revert” を行う 際には、以下に示すように、変更後のファイル名を指定しただけでは不十分です。
“hg status” コマンドの出力からもわかるように、変名後のファイルは既に未追加状態と認識されていますが、変名前のファ イルは未だに削除状態と認識されています!これは(少なくとも著者にとっては)直感に反しますが、扱いは簡単で す。
“hg rename” の取り消しを行うには、変名前後のファイル名を両方指定することを忘れないでください。
(ちなみに、ファイルの変名後に、変名後のファイルを変更し、それから変名前後のファ イル名の両方を指定して取り消しを行った場合、 Mercurial は変名の際に削除されたファイ ル3 を何も 変更されていない状態に戻します。変名後のファイルに対する変更を変名前ファイルに反映したい場合には、変名後ファイルから変名前 ファイルへのコピーを忘れないでください。)
変名の取り消しにおけるこれらの厄介な側面は、おそらく Mercurial の小さなバグに由来するものです。
ある変更 a をコミットし、その上で別の変更 b をコミットした後で、変更 a が間違っていたことに気付いたとします。 Mercurial に は、チェンジセットそのものを自動的に “無かったことにする” 機能や、チェンジセットの一部を手動で無効にするための情報を提供す る機能があります。
この節を読む前に、覚えておいて欲しいことが幾つかあります。“hg backout” コマンドによる変更の取り消しは、履歴を追加する ことで行われるものであり、変更そのものを修正したり削除したりするものではありません。そのため、バグの修正をするのには向いて いますが、破壊的な結果を伴う取り消しといった用途には向いていません。そのような取り消しに関しては、 9.4 節を参照してくださ い。
“hg backout” コマンドは、自動化された形式でチェンジセットの効果全体を “取り消し” ます。 Mercurial の履歴は改変できないの で、このコマンドは取り消したいチェンジセットを取り除いたりはしません。その代わりにこのコマンドは、取り消したいチェンジセッ トによる改変内容を反転させる、新たなチェンジセットを作成します。
“hg backout” コマンドの操作は少々複雑ですので、例を使って説明します。まずは単純なチェンジセットを幾つか持つリポジトリ を作成します。
“hg backout” コマンドは、 “bakc out” 対象とする単一のチェンジセット識別子を引数に取ります。通常、“hg backout” はコミットメッセージを書くためにテキストエディタを起動しますので、変更を back out する理由を記録するこ とができます。この例では、-m オプションを用いることで、コマンドラインからコミットメッセージを与えていま す。
以下の例では、最後にコミットしたチェンジセットを back out します。
myfile が既に2行目を持たないことがおわかりでしょう。“hg log” 出力を見れば、“hg backout” コマンドが何を行ったかを理 解できます。
“hg backout” が生成した新しいチェンジセットは、 back out したチェンジセットの子チェンジセットとなる点に注意してく ださい。変更履歴を図示した9.3.2 図を見れば、このことがわかるでしょう。ご覧の通り、履歴は見事に一直線で す。
最後にコミットしたチェンジセット以外を back out したい場合、“hg backout” コマンドに--merge オプションを指定してくださ い。
このコマンド実行は、任意のチェンジセットを、簡単で素早い “一回限りの” 操作で back out できます。
back out 完了後の myfile の内容には、1回目と3回目の変更に相当する内容は見ることができますが、2回目の変更に相当する 内容は見ることができないでしょう。
履歴を図示した9.3.3 図に見られるように、このような状況の場合、 Mercurial は実際には2つのチェンジセットをコミットします(Mercurial が自 動的にコミットしたも4 の は矩形で示してあります)。 Mercurial は back out 処理を始める前に、現時点での作業領域ディレクトリにおける親チェンジセットを 覚えておきます。その上で対象チェンジセットを back out し、チェンジセットとしてコミットします。最後に、作業領域ディレクトリ の親チェンジセットとマージした結果をコミットします footnote 訳注: 前述のように、自動的にはコミットされませ ん。
結果として、 back out したいチェンジセットによる変更内容を取り消すための、幾つかの余分な履歴のみを伴って、 “以前の状態への復旧 ” が行われます。
実のところ、 back out 対象のチェンジセットが tip か否かに関わらず、--merge オプションは “正しく機能” します(back out 対象が tip の場合は、必要が無いのでマージしようとはしません)ので、“hg backout” コマンドを実行する際には常に--merge オプション を指定するべきでしょう。
先の記述では、変更の back out の際の--merge オプションの常用を推奨しましたが、その一方で、 back out 対象となるチェンジセッ トのマージ方法を、“hg backout” コマンドの利用者が決定することもできます。 back out 処理を手動で制御する必要は 滅多にありませんが、手動制御の方法を知ることは、“hg backout” が自動的に行っていることの内情を理解する上 で有用です。手動制御の説明のために、最初に作成したリポジトリを複製しますが、ここでは back out は行いませ ん。
先の例と同様に、第3のチェンジセットをコミットし、その上でその親を back out した結果を見てみましょう。
新たなチェンジセットも第3のチェンジセット同様に、 back out 対象のチェンジセットの子になりますので、それまで tip だったチェ ンジセット5 の 子ではなく、新たなヘッドになります。“hg backout” コマンドは、このことを告げる非常にはっきりとしたメッセージを表示してい ます。
ここでも、履歴を図示した9.3.4 図を見ることで、どういった状況にあるのかが理解し易いと思います。この図から、“hg backout” コマンドを tip 以外のチェンジセットに適用した際に、 Mercurial が新しいヘッドをリポジトリに追加する(Mercurial によ り追加されたチェンジセットは矩形で表しています)ことがよくわかります。
“hg backout” コマンドの実行が完了すると、作業領域ディレクトリの親チェンジセットが、新しい “backout” チェンジセットになります。
この時点で、2つの独立した変更のまとまり6 が 存在します。
この時点で、myfileはどのような内容であることが期待されるかを考えてみましょう。第1の変更は back out していませんから、 それに関する内容は存在していなければなりません。第2の変更は back out しましたので、それに関する内容は消失していなければな りません。履歴図で別個のヘッドとして図示されているように、第3の変更に関する内容がmyfile に存在してはなりませ ん。
第3の変更の内容をファイルに取り込むには、2つのヘッドをいつものようにマージすれば良いのです。
マージすることで、リポジトリ中の履歴は9.4 図に示すようになります。
“hg backout” コマンドの振る舞いを簡単にまとめると以下のようになります。
作業領域ディレクトリを弄繰り回すことなく“hg backout” コマンド相当の効果を得るもう一つの方法は、 back out される チェンジセットに対して“hg export” することで得た diff ファイルを、作用を反転させる--reverse オプション を指定したpatch コマンドに用いることです。この方法は非常に簡単に感じるでしょうが、全く上手く機能しませ ん。
“hg backout” が update、 commit、 merge および再度の commit を行うのは、 back out 対象のチェンジセットと現在の tip の 間の全てのチェンジセットを扱う際に、良好な結果を得るための最善の機会を Mercurial のマージ機構に与えるためで す。
例えば、プロジェクトの履歴から、 100 リビジョン分前のチェンジセットを back out しようとした場合、patch がパッチの適用可 否を判定するコンテキスト情報を、 back out 対象との間にあるチェンジセットが “破壊” してしまうかもしれない (この意味がわからない場合は、12.4 節のpatch に関する説明を参照してください)ので、patch コマンドが反転 diff を綺麗に適用できることは期待できません。 Mercurial のマージ機構は、ファイルやディレクトリの変名、ファイ ル権限の変更や、バイナリファイルの変更といったpatch コマンドが扱うことのできないものも扱うことができま す。
変更内容を取り消そうとした場合の殆どは、“hg backout” コマンドの利用が妥当です。“hg backout” コマンドは、元のチェンジ セットのコミットと、後からそれを取り消した際の両方に関して、正確で永続的な記録を残します。
しかし、非常に稀な状況ですが、リポジトリ中に存在して欲しくない変更をコミットしてしまうかもしれません。例えば、ソース ファイルと同様にオブジェクトファイルをコミットしてしまうような事態は、滅多に無いので通常は「間違い」とみなされます。オブ ジェクトファイルには本質的な価値はありませんし、非常にサイズが大きいですから、リポジトサイズや複製/変更取り込みに要する時 間が増加してしまいます。
XXXXXXXXXX Before I discuss the options that you have if you commit a “brown paper bag” change (the kind that’s so bad that you want to pull a brown paper bag over your head), let me first discuss some approaches that probably won’t work. XXXXXXXXXX
Mercurial は履歴を「蓄積的なもの」 — 全ての変更が先行する変更の上に適用される — として扱いますので、破壊的な影響を持つ チェンジセットに対してであっても、それを破棄することは通常はできません。9.1.2 節で詳細を述べますが、例外的に“hg rollback” コマンドを安全に使用できるのは、変更をコミットした直後で、別なリポジトリへ“hg push” も“hg pull” もされていな い場合だけです。
不適切なチェンジセットを他のリポジトリへ“hg push” してしまった後でも、“hg rollback” コマンドにより、ローカルなリポジ トリでそのチェンジセットを破棄することはできますが、それはおそらく本来やりたかったことでは無い筈です。遠隔リポジトリ中には 不適切なチェンジセットが存在し続けますので、次に変更の取り込みを行った際には、その変更が再びローカルリポジトリに現れるかも しれません。
このような状況が発生した場合、どのリポジトリが不適切なチェンジセットを保持しているかを把握しているなら、それら全てのリ ポジトリからの不適切なチェンジセットの除去を、試みることが可能です。勿論、これは申し分の無い解法ではありません。たっ た一つでも抹消し損ねたリポジトリがあれば、 “野に放たれた” ままのチェンジセットは更に伝播してしまうでしょ う。
除去したいチェンジセットの後に、幾つかのチェンジセットをコミットしてしまった場合、取り得る選択肢は更に限られ てしまいます。 Mercurial は、チェンジセットに手をつけないままで、履歴に “穴を開ける” 機能は提供していませ ん。
XXX This needs filling out. examples ディレクトリ配下のhg-replay スクリプトは機能しますが、チェンジセットのマージを行 いません。重大な手抜きです。
ローカルリポジトリにコミットした幾つかのチェンジセットが、“hg push” ないし“hg pull” 等によってそれらが他のリポジトリへ と反映されたからといって、そのこと自体は必ずしも大失敗というわけではありません。ある種の不正なチェンジセットに対して、あら かじめ自己防衛することも可能です。開発チームが変更を中央のリポジトリから“hg pull” するような体制の場合、事故防衛は非常に 簡単です。
中央のリポジトリの幾つかのフックを、追加されるチェンジセットの検証を行うように設定する(10 章を参照してください)こと で、ある種の不正なチェンジセットが、中央リポジトリに全く反映されないように自動化することができます。設定が適切であれば中央 のリポジトリに反映できなくなるため、このようなチェンジセットは自然と “死に絶え” ます。なお良いことに、この手法は明示的な介 入を必要としません。
例えば、当該チェンジセットが実際にコンパイル可能かどうかを検証する incoming フックは、うっかり “ビルドできなくしてしま う” ことを防止できます。
バグをもたらしたチェンジセットを back out できるのは非常に結構なのですが、どのチェンジセットを back out すべきかを知ってい る必要があります。 Mercurial には、チェンジセット特定の自動化と非常に効率的な実施を補助する、bisect と呼ばれる重要な拡張が あります。
チェンジセットによる変更は振る舞いに変化をもたらすので、その変化を簡単な2値テストによりそれを特定することができる、と いうのがbisect 拡張の原理です。どのコード片が変化をもたらしているのかはわからなくても、バグの有無を試験する方法はわかる でしょう。bisect 拡張は、バグの原因となったコードをもたらしたチェンジセットを探すのに、あなたのテストプログラムを直接使用 します。
bisect 拡張の適用方法を理解しやすいように、幾つかのシナリオを例示します。
これらの例から、bisect 拡張がバグの元を探すだけのものでないことは明らかでしょう。その特性に関する2値テストを書けるな ら、リポジトリにおける(ソースツリー中のファイルに対する単純な文字列検索では探し出せない)任意の “特性の出現” を探し出すこ とができます。
利用者と Mercurial のそれぞれが、検索処理においてどの部分に責任を負うのかをはっきりとさせるために、ここでもう少し用語の 説明をしましょう。テスト(test) とは、bisect 拡張がチェンジセットを選択する際に、利用者が実行するものです。調査(probe) と は、あるリビジョンの良否を判定するためにbisect が実行するものです。最後に、 “bisect” という言葉を、 “bisect 拡張を用いた 検索” の代用として、名詞および動詞として使用します。
検索処理を自動化する簡単な方法の一つが、全てのチェンジセットを調査する遣り方です。しかしながら、この遣り方には殆どス ケーラビリティがありません。1つのチェンジセットのテストに10分必要で、リポジトリに1万のチェンジセットがあったとすると、 徹底的に調査する遣り方では、バグをもたらしたチェンジセットを見つけるのに、平均で35 日必要です。検索対象を最新の500 チェンジセットに限定できるとしても、バグをもたらしたチェンジセットを見つけるのには、それでもなお40時間必要で す。
bisect 拡張は、確認するチェンジセット数に対して対数のオーダーで検索(この種の検索は “二分探索” と呼ばれます)できるよう に、プロジェクト履歴の “形” に関する情報を利用します。この方法により、仮にテストあたりの所要時間が10分掛かるとしても、1 万チェンジセットに対する検索は2時間以内で終わります。検索対象を最新の500チェンジセットに限定できるならば、1時間以内に 検索できるでしょう。
bisect 拡張は、 Mercurial で管理されているプロジェクトの履歴の持つ “枝分かれ” の特質をわかっていますので、リポ ジトリにおける枝分かれ・マージ・複数ヘッドの扱いも問題ありません。単一の調査で履歴の枝分かれ全体を刈り取 る9 ことが できるため、bisect 拡張は効率的に検索することができるのです。
ここではbisect 拡張の実行例を示します。 Mercurial 自体の簡便性を維持するために、bisect は拡張機能として提供されます。その ため、明示的に有効にしなければ、その機能は提供されません。bisect 拡張を有効にするには、(存在しない場合には) hgrc に以 下のセクションヘッダを追加し:
続いて、bisect 拡張を有効化するための行をこのセクションに追加しま す10 。
bisect 拡張を隔離して利用するために、リポジトリを作成しましょう。
ループによって幾つかの些細な変更を行い、その中の特定の変更が “バグ” を持つようにする、という単純な方法で、バグを持った プロジェクトのシミュレーションを行います。このループは 50 のチェンジセットを生成し、それぞれが1つのファイルをリポジトリに 追加します。ここでは、ファイルが “i have a gub” というテキストを含んでいることをもって、 “バグ” とみなしま す。
それでは、bisect 拡張の使用方法を理解しましょう。bisect 拡張に関しても、通常の Mercurial の組み込み help 機能が使用で きます。
bisect 拡張は段階を踏んで機能します。各段階は以下のように進みます。
2値テストの結果が “成功” から “失敗” に変化した点を示す、一意なチェンジセットをbisect 拡張が特定できた時点で、この手順 は終了します。
検索の開始に当たっては、“hg bisect init” コマンドの実行が必要です。
今回の実行例で使用する2値テストは簡単なもので、リポジトリ中の何れかのファイルが “i have a gub” 文字列を含んでいるか否か を判定します。含んでいる場合、そのチェンジセットは “バグの要因となる” チェンジセットです。慣習上、検索対象となる特性を持っ ているチェンジセットを “bad”、持っていないチェンジセットを “good” と呼びます。多くの場合、作業領域ディレクトリが同期して いるリビジョン(通常は tip)はバグを持つチェンジセットにより問題を抱えているものですから、これを “bad” とみなしま す。
次の作業は、バグが無いチェンジセットを指定することです。bisect 拡張は最初の “good” と “bad” のチェンジセット間の検査状 況を “括弧” で括って表示するでしょう。今回の事例では、リビジョン 10 にはバグがありません(最初の “good” チェンジセットの選 択に関しては、後ほど補足があります)。
コマンド出力には以下の意味があります。
早速作業領域ディレクトリでテストをしてみましょう。grep を使用して、作業領域ディレクトリの “bad” ファイルの有無を調べ、 ファイルが無ければそのリビジョンは “good” です。
このテストは完全に自動化できそうですので、シェル関数にしてしまいましょう。
これで、テスト手順全体を単一のmytest コマンドで実行できます。
テスト手順が記録されたコマンドをあと数回起動することで、当初の目的が達成されます。
40 程のチェンジセット全体の検索にも関わらず、bisect拡張はわずか5回のテストで “バグ” をもたらしたチェンジセットを特定で きました。調査対象チェンジセット数に対して、bisect 拡張は対数のオーダーでテスト対象を選定するので、チェンジセットを追加し ただけテスト回数が増加する “力尽く” の手法よりも有利です。
リポジトリにおけるbisect 拡張の使用が終わったなら、検索に使用していた情報を“hg bisect reset” コマンドにより破棄するこ とができます。bisect 拡張はそれほど多くの領域を消費するわけではありませんので、この作業を忘れても問題にはなりませ ん。しかし、“hg bisect reset” を実行するまでは、bisect はそのリポジトリで別の検索を開始させてくれませ ん。
bisect 拡張には、実施した全てのテストの結果が正しく指定されなければなりません。本当はテストが成功してい たにも関わらず、テストの失敗をbisect 拡張に伝えた場合、矛盾した結果を出すかもしれません。テスト結果に対 して矛盾が検知された場合、bisect は、特定のチェンジセットが “good” でも “bad” でもある、と言ってきます。 しかし、この検知は完璧に行われるわけではないので、間違ったチェンジセットをバグの要因として報告するでしょ う。
筆者がbisect 拡張を使い始めた頃は、検索のためのテストをコマンドラインで手動で実行していましたが、少なくとも私には、この 手法は馴染みません。何度かbisect を使用した後で、最終的に正しい結果を得る前に、いつも手違いのために何度も検索をやり直し ていることに気付きました。
bisect 拡張を手動で駆動していた際には、小さなリポジトリにおける単純な検索であっても問題が発生していました。テストの内 容が複雑であったり、bisect が要求するテスト実行回数が増えれば、それだけテスト実行における操作ミスの可能性は高まります。テ ストを自動化するようになって以来、非常に良好な結果を得られています。
テスト自動化のための鍵は2つあります。
前述の実行例では、grep コマンドにより「症状」を調べていて、if ステートメントが「検査」の結果を受けて“hg bisect” コマ ンドに同じ入力を与えることを保証していました。mytest 関数が、これらを再現しやすい形式に統合したことで、全てのテストが均一 で整合性の取れたものになっています。
bisect による検索の出力結果は与えた情報程度にしか正しくないので、bisect により “good” と報告されたチェンジセットを、絶対 的に正しいものとみなさないでください。報告内容をクロスチェックする簡単な方法は、以下のようなチェンジセットのそれぞれに対し て、手動で自身のテストを実行してみることです。
あるバグを探す際に、他のバグの存在により混乱させられる可能性もあります。例えば、リビジョン 100 でソフトウェアがクラッシュ し、リビジョン 50 では正しく動作していたとします。あなたの知らない間に、ソフトウェアをクラッシュさせる別のバグを、他の人が リビジョン 60 で入れてしまい、それをリビジョン 80 で修正した場合、なんらかの方法で検索結果を混乱させるかもしれませ ん。
他のバグの存在によって、探しているバグが完全に “覆い隠される” かもしれず、探しているバグがその存在を示す機会を得る前に 他のバグが発生している、と言えます。他のバグを回避したテストが(例えば、そのバグがプロジェクトのビルドを阻害するなどの理由 で)できないために、特定のチェンジセットにおける検索対象のバグの有無を明言できない場合、bisect 拡張の助けを直接受けること はできません。その替わり、他のバグが存在するチェンジセットを手動で取り除くことで、 “周辺” での別な検索を行いましょ う。
バグの存在に関するテストが十分明確でない場合には、別な問題が発生し得ます。 “プログラムのクラッシュ” でバグの有無を確認 している場合、ソフトウェアをクラッシュさせる全然関係ないバグにより、検索対象であるバグが覆い隠されてしまい、両方とも同じも のとみなされるために、bisect が惑わされてしまいます。
検索における終端の印となる “good” および “bad” なチェンジセットの最初の選択は、通常は簡単なことですが、そうであっても多少 は議論の余地があります。bisect の立場から見た場合、 “最新” のチェンジセットは通例では “bad” で、最古のチェンジセットは “good” です。
bisect の使用に当たって “good” にふさわしいチェンジセットがどれかを思い出すのが難しい場合には、でたらめにテストするの も悪くはないでしょう。どうあってもバグの兆候が見出せない(例えば、バグの発生に関連する機能がまだ提供されていない)ものや、 他の問題が(前述したように)バグを覆い隠してしまうようなものを、テスト候補のチェンジセットから除外するのを忘れないようにし ましょう。
数千のチェンジセット、ないし数ヶ月の履歴の “初期” のものが最終結果だったとしても、対数オーダーの振る舞いのお陰 で、bisect が実施しなければならない総回数が数回増えるだけです。