foozy@708: \chapter{Finding and fixing your mistakes} foozy@708: \label{chap:undo} foozy@708: foozy@708: 人は間違えるものですが、その結果をより上手に扱ってこそ、 foozy@708: 優れた構成管理システムと言えます。 foozy@708: この章では、 foozy@708: プロジェクトに忍び込んだ問題を発見した際に、 foozy@708: 使える手法について説明します。 foozy@708: Mercurial は、 foozy@708: 問題の元を隔離し適切に処理するための優れた機能を持っています。 foozy@708: foozy@708: \section{Erasing local history} foozy@708: foozy@708: \subsection{The accidental commit} foozy@708: foozy@708: 筆者は、時として考えるよりも先に入力してしまう、 foozy@708: という根深い問題を抱えているため、 foozy@708: 不完全であったり、 foozy@708: 単純に間違った内容のチェンジセットをコミットしてしまうことがあります。 foozy@708: 筆者の場合、 foozy@708: 不完全なチェンジセットをコミットしてしまうのは、 foozy@708: 新しいソースファイルを作成したのに foozy@708: \hgcmd{add} の実行を忘れている場合が殆どです。 foozy@708: ``単純に間違っている''チェンジセットをコミットしてしまうケースには、 foozy@708: 特に共通点はありませんが、but 非常に迷惑(no less annoying) XXXXX。 foozy@708: foozy@708: \subsection{Rolling back a transaction} foozy@708: \label{sec:undo:rollback} foozy@708: foozy@708: Mercurial が、 foozy@708: リポジトリへの個々の変更を\emph{トランザクション}として扱っていることを foozy@708: \ref{sec:concepts:txn} 節で述べました。 foozy@708: チェンジセットをコミットしたり、 foozy@708: 他のリポジトリから変更を pull する際に、 foozy@708: Mercurial は常に処理したことを記録しています。 foozy@708: \hgcmd{rollback} コマンドを使用することで、 foozy@708: きっちり一回分の処理を元に戻す、 foozy@708: 別な言い方をするなら、\emph{巻き戻す}ことができます foozy@708: (このコマンドを使用する際の重要な注意が述べられていますので、 foozy@708: \ref{sec:undo:rollback-after-push} 節を参照してください)。 foozy@708: foozy@708: 新しくファイルを作成したのに、 foozy@708: そのファイルに対して \hgcmd{add} foozy@708: コマンドを実行するのを忘れてコミットしてしまう、 foozy@708: という筆者のよくやる間違いは、以下のようなものです。 foozy@708: foozy@708: \interaction{rollback.commit} foozy@708: foozy@708: コミット後の \hgcmd{status} 出力を見れば、 foozy@708: すぐさま間違いを確証できます。 foozy@708: foozy@708: \interaction{rollback.status} foozy@708: foozy@708: 先のコミットは、 foozy@708: \filename{a} の変更は捉えていますが、 foozy@708: 新規のファイル \filename{b} は把握していません。 foozy@708: 同僚と共有しているリポジトリに、 foozy@708: このチェンジセットを反映してしまったら、 foozy@708: 同僚がこのチェンジセットを取り込んだ際に、 foozy@708: \filename{a} 中の何かが、 foozy@708: 同僚のリポジトリには存在しない \filename{b} を参照してしまいます。 foozy@708: そうなれば、私は同僚の憤りの対象になってしまうでしょう。 foozy@708: foozy@708: しかし、幸いなことに、 foozy@708: チェンジセットを共有リポジトリへと反映する前に、 foozy@708: 自分の間違いを見つけています。 foozy@708: \hgcmd{rollback} コマンドを使うことで、 foozy@708: Mercurial は最後のチェンジセットを消してくれます。 foozy@708: foozy@708: \interaction{rollback.rollback} foozy@708: foozy@708: リポジトリの履歴上、最早最前のチェンジセットは存在しませんので、 foozy@708: 作業領域ディレクトリは、 foozy@708: 再び \filename{a} ファイルが変更されている状態だとみなされます。 foozy@708: コミット後のロールバックは、 foozy@708: 作業領域ディレクトリをコミット前の状態そのままに戻し、 foozy@708: チェンジセットは完全に消去されます。 foozy@708: そうなったなら、 foozy@708: 安全に \filename{b} ファイルを \hgcmd{add} し、 foozy@708: 再度コミットすることができます。 foozy@708: foozy@708: \interaction{rollback.add} foozy@708: foozy@708: \subsection{The erroneous pull} foozy@708: foozy@708: 1つの プロジェクトで、 foozy@708: 別々に開発の進んでいるブランチを Mercurial で保守する場合、 foozy@708: それぞれ異なるリポジトリで保守することが一般的な慣習となっています。 foozy@708: 開発チームは、 foozy@708: プロジェクトの ``0.9'' リリース用に共有リポジトリを持つ一方で、 foozy@708: 異なる変更履歴を持つ ``1.0'' リリース用のリポジトリを別途持つかもしれません。 foozy@708: foozy@708: この場合、 foozy@708: ローカルな ``0.9'' リポジトリがあって、 foozy@708: そこに偶然 ``1.0'' 用共有リポジトリの成果を取り込んだ場合、 foozy@708: 面倒な事態になることが想像できます。 foozy@708: 最悪の場合、 foozy@708: 十分な注意を払わないために、 foozy@708: ``1.0'' のリポジトリから取り込んだ変更を foozy@708: ``0.9'' の共用リポジトリへと反映してしまった foozy@708: チーム全体を混乱させてしまうでしょう foozy@708: (この恐ろしいケースに関しては、 foozy@708: 後ほど解決方法を示しますので御安心を。)。 foozy@708: しかし、 foozy@708: Mercurial は成果取り込み先の URL を表示するか、 foozy@708: Mercurial foozy@708: が怪しげな大量の変更をリポジトリに取り込んだことが表示されますから、 foozy@708: すぐに気付く方があり得ます foozy@708: \footnote{訳注: ``display the URL it's pulling from'' foozy@708: の関係がよくわからない}。 foozy@708: foozy@708: \hgcmd{rollback} コマンドは、 foozy@708: 今まさに取り込んだ全てのチェンジセットを、 foozy@708: きちんと綺麗にします。 foozy@708: Mercurial は、一回の \hgcmd{pull} 起動により取り込まれるチェンジセット全体を、 foozy@708: 単一のトランザクションに分類するので、 foozy@708: 一回の \hgcmd{rollback} 起動でこの失敗を取り消すことができます。 foozy@708: foozy@708: \subsection{Rolling back is useless once you've pushed} foozy@708: \label{sec:undo:rollback-after-push} foozy@708: foozy@708: \hgcmd{rollback} は、 foozy@708: 一旦他のリポジトリに反映した変更でも、 foozy@708: (手元のリポジトリにおいては)無かったことにできます。 foozy@708: 取り消しにより変更は完全に消されますが、 foozy@708: それができるのは、 foozy@708: \hgcmd{rollback} を実施したリポジトリにおける取り消し\emph{のみ}です。 foozy@708: 取り消しは履歴を削除しますので、 foozy@708: 変更の取り消しをリポジトリ間で伝播する手段が無いのです。 foozy@708: foozy@708: 変更を他のリポジトリ--典型的な例では共有リポジトリ--に反映した場合、 foozy@708: 本質的には、その変更は``野生に逃げ出し''ており、 foozy@708: 取り消しとは別な方法で間違いを埋め合わせる必要があります。 foozy@708: 変更を他のリポジトリに反映し、 foozy@708: (手元のリポジトリで)その変更を取り消した後で、 foozy@708: 変更を反映したリポジトリから変更を取り込んだ時には、 foozy@708: 取り消した変更が(手元のリポジトリに)再び現れます。 foozy@708: foozy@708: (取り消したい変更が、変更を反映したリポジトリにおける最新のもので、 foozy@708: \emph{且つ}、 foozy@708: 誰もそれをそのリポジトリから取り込んでいないことが確実である場合、 foozy@708: その変更を取り消すこともできますが、 foozy@708: 取り消しが機能することには依存しないようにしてください。 foozy@708: 遅かれ早かれ変更は直接触ることのできない foozy@708: (あるいは存在を忘れていた)リポジトリへと反映され、 foozy@708: 回りまわって戻ってきた時に噛み付かれてしまいます。 foozy@708: ) foozy@708: foozy@708: \subsection{You can only roll back once} foozy@708: foozy@708: Mercurial は、 foozy@708: 当該リポジトリにおける最も最新のトランザクションを、 foozy@708: 1つだけトランザクションログに記録します。 foozy@708: そのため、取り消せるトランザクションは1つ分だけです。 foozy@708: トランザクションを1つ取り消した後で、 foozy@708: その前のトランザクションも取り消せることを期待しても、 foozy@708: 期待通りの結果は得られません。 foozy@708: foozy@708: \interaction{rollback.twice} foozy@708: foozy@708: あるリポジトリでトランザクションの取り消しを行った場合、 foozy@708: 別な変更をコミットするなり取り込むなりしない限り、 foozy@708: そのリポジトリで取り消しを行うことはできません。 foozy@708: foozy@708: \section{Reverting the mistaken change} foozy@708: foozy@708: ファイルを変更した後で、 foozy@708: ファイルの変更が全く必要ないことに気付いた場合、 foozy@708: 変更をコミットする前であれば、 foozy@708: \hgcmd{revert} コマンドが利用できます。 foozy@708: このコマンドは、 foozy@708: 作業領域ディレクトリの親チェンジセットを参照し、 foozy@708: ファイルの内容を元の状態に戻します。 foozy@708: (説明すると長くなりますが、 foozy@708: 通常の場合、このコマンドは変更を取り消します。) foozy@708: foozy@708: \hgcmd{revert} コマンドの機能を、 foozy@708: ちょっとしたサンプルで説明します。 foozy@708: Mercurial により既に構成管理されているファイルを変更します。 foozy@708: foozy@708: \interaction{daily.revert.modify} foozy@708: foozy@708: 変更が必要ない場合、 foozy@708: 単純に \hgcmd{revert} コマンドをファイルに適用します。 foozy@708: foozy@708: \interaction{daily.revert.unmodify} foozy@708: foozy@708: \hgcmd{revert} コマンドは、 foozy@708: ある程度の安全性を確保するために、 foozy@708: \filename{.orig} 拡張子付きのファイルに、 foozy@708: 変更されたファイルの内容を保存します。 foozy@708: foozy@708: \interaction{daily.revert.status} foozy@708: foozy@708: \hgcmd{revert} コマンドが扱うことのできる状況を以下にまとめます。 foozy@708: 個々の状況に関する詳細は、以後の節で説明します。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item ファイルが変更されていたなら、変更前の状態に戻します。 foozy@708: foozy@708: \item ファイルが \hgcmd{add} されていたなら、 foozy@708: ファイルの``追加''されている状態を取り消しますが、 foozy@708: ファイルそのものには何も変更を行いません。 foozy@708: foozy@708: \item Mercurial への指示無くファイルを削除していたなら、 foozy@708: 変更前\footnote{訳注: ``削除前''ではない点に注意}の状態に戻します。 foozy@708: foozy@708: \item \hgcmd{remove} コマンドでファイルを削除していたなら、 foozy@708: ファイルの``削除された''状態を取り消し、 foozy@708: 変更前の状態に戻します。 foozy@708: foozy@708: \end{itemize} foozy@708: foozy@708: \subsection{File management errors} foozy@708: \label{sec:undo:mgmt} foozy@708: foozy@708: \hgcmd{revert} は変更されたファイル以外に対しても有用なコマンドです。 foozy@708: このコマンドは、 foozy@708: Mercurial の全てのファイル管理コマンド foozy@708: ---\hgcmd{add} や \hgcmd{remove} など--- foozy@708: の実施を反転させます。 foozy@708: foozy@708: ファイルに対して \hgcmd{add} を行った後で、 foozy@708: そのファイルを Mercurial で構成管理する必要が無いことに気付いたなら、 foozy@708: \hgcmd{revert} によりファイルの追加を取り消せます。 foozy@708: Mercurial はファイル自体には何も変更を行いませんので安心してください。 foozy@708: ファイル追加の取り消しは、 foozy@708: ファイルに対して``印を消す''だけです。 foozy@708: foozy@708: \interaction{daily.revert.add} foozy@708: foozy@708: 同様に、 foozy@708: ファイルに対して \hgcmd{remove} を行った後でも、 foozy@708: \hgcmd{revert} を使うことで、 foozy@708: 作業領域ディレクトリの親チェンジセットにおける状態に、 foozy@708: ファイルの内容を復旧することができます。 foozy@708: foozy@708: \interaction{daily.revert.remove} foozy@708: foozy@708: これは、Mercurial を通さずに手動で削除したファイル foozy@708: (Mercurial の用語ではこの種のファイルが``紛失''(missing) foozy@708: と呼ばれることを思い出してください)であっても機能します。 foozy@708: foozy@708: \interaction{daily.revert.missing} foozy@708: foozy@708: \hgcmd{copy} されたファイルに取り消しを行った場合、 foozy@708: 複製先ファイルは作業領域ディレクトリに、 foozy@708: 構成管理されない状態でそのまま残ります。 foozy@708: 複製操作は複製元ファイルには何も作用しないので、 foozy@708: 取り消しの際に Mercurial は複製元ファイルに対して特に何もしません。 foozy@708: foozy@708: \interaction{daily.revert.copy} foozy@708: foozy@708: \subsubsection{A slightly special case: reverting a rename} foozy@708: foozy@708: foozy@708: ファイルに対して \hgcmd{rename} を行った場合、 foozy@708: 覚えていて欲しいことがあります。 foozy@708: \hgcmd{rename} 実行に対して \hgcmd{revert} を行う際には、 foozy@708: 以下に示すように、 foozy@708: 変更後のファイル名を指定しただけでは不十分です。 foozy@708: foozy@708: \interaction{daily.revert.rename} foozy@708: foozy@708: \hgcmd{status} コマンドの出力からもわかるように、 foozy@708: 変名後のファイルは既に未追加状態と認識されていますが、 foozy@708: 変名\emph{前}のファイルは未だに削除状態と認識されています! foozy@708: これは(少なくとも著者にとっては)直感に反しますが、 foozy@708: 扱いは簡単です。 foozy@708: foozy@708: \interaction{daily.revert.rename-orig} foozy@708: foozy@708: \hgcmd{rename} の取り消しを行うには、 foozy@708: 変名前後のファイル名を\emph{両方}指定することを忘れないでください。 foozy@708: foozy@708: (ちなみに、 foozy@708: ファイルの変名後に、変名後のファイルを変更し、 foozy@708: それから変名前後のファイル名の両方を指定して取り消しを行った場合、 foozy@708: Mercurial は変名の際に削除されたファイル\footnote{訳注: 変名前のファイル foozy@708: }を何も変更されていない状態に戻します。 foozy@708: 変名後のファイルに対する変更を変名前ファイルに反映したい場合には、 foozy@708: 変名後ファイルから変名前ファイルへのコピーを忘れないでください。 foozy@708: ) foozy@708: foozy@708: 変名の取り消しにおけるこれらの厄介な側面は、 foozy@708: おそらく Mercurial の小さなバグに由来するものです。 foozy@708: foozy@708: \section{Dealing with committed changes} foozy@708: foozy@708: ある変更 $a$ をコミットし、その上で別の変更 $b$ をコミットした後で、 foozy@708: 変更 $a$ が間違っていたことに気付いたとします。 foozy@708: Mercurial には、 foozy@708: チェンジセットそのものを自動的に``無かったことにする''機能や、 foozy@708: チェンジセットの一部を手動で無効にするための情報を提供する機能があります。 foozy@708: foozy@708: この節を読む前に、覚えておいて欲しいことが幾つかあります。 foozy@708: \hgcmd{backout} コマンドによる変更の取り消しは、 foozy@708: 履歴を\emph{追加}することで行われるものであり、 foozy@708: 変更そのものを修正したり削除したりするものではありません。 foozy@708: そのため、バグの修正をするのには向いていますが、 foozy@708: 破壊的な結果を伴う取り消しといった用途には向いていません。 foozy@708: そのような取り消しに関しては、 foozy@708: ~\ref{sec:undo:aaaiiieee} 節を参照してください。 foozy@708: foozy@708: \subsection{Backing out a changeset} foozy@708: foozy@708: \hgcmd{backout} コマンドは、 foozy@708: 自動化された形式でチェンジセットの効果全体を``取り消し''ます。 foozy@708: Mercurial の履歴は改変できないので、 foozy@708: このコマンドは取り消したいチェンジセットを取り除いたりは\emph{しません}。 foozy@708: その代わりにこのコマンドは、 foozy@708: 取り消したいチェンジセットによる改変内容を\emph{反転}させる、 foozy@708: 新たなチェンジセットを作成します。 foozy@708: foozy@708: \hgcmd{backout} コマンドの操作は少々複雑ですので、例を使って説明します。 foozy@708: まずは単純なチェンジセットを幾つか持つリポジトリを作成します。 foozy@708: foozy@708: \interaction{backout.init} foozy@708: foozy@708: \hgcmd{backout} コマンドは、 foozy@708: ``bakc out''対象とする単一のチェンジセット識別子を引数に取ります。 foozy@708: 通常、 foozy@708: \hgcmd{backout} foozy@708: はコミットメッセージを書くためにテキストエディタを起動しますので、 foozy@708: 変更を back out する理由を記録することができます。 foozy@708: この例では、 foozy@708: \hgopt{backout}{-m} オプションを用いることで、 foozy@708: コマンドラインからコミットメッセージを与えています。 foozy@708: foozy@708: \subsection{Backing out the tip changeset} foozy@708: foozy@708: 以下の例では、 foozy@708: 最後にコミットしたチェンジセットを back out します。 foozy@708: foozy@708: \interaction{backout.simple} foozy@708: foozy@708: \filename{myfile} が既に2行目を持たないことがおわかりでしょう。 foozy@708: \hgcmd{log} 出力を見れば、 foozy@708: \hgcmd{backout} コマンドが何を行ったかを理解できます。 foozy@708: foozy@708: \interaction{backout.simple.log} foozy@708: foozy@708: \hgcmd{backout} が生成した新しいチェンジセットは、 foozy@708: back out したチェンジセットの子チェンジセットとなる点に注意してください。 foozy@708: 変更履歴を図示した \ref{fig:undo:backout}~図を見れば、 foozy@708: このことがわかるでしょう。 foozy@708: ご覧の通り、履歴は見事に一直線です。 foozy@708: foozy@708: \begin{figure}[htb] foozy@708: \centering foozy@708: \grafix{undo-simple} foozy@708: \label{fig:undo:backout} foozy@708: \caption{Backing out a change using the \hgcmd{backout} command} foozy@708: \end{figure} foozy@708: foozy@708: \subsection{Backing out a non-tip change} foozy@708: foozy@708: 最後にコミットしたチェンジセット以外を back out したい場合、 foozy@708: \hgcmd{backout} コマンドに foozy@708: \hgopt{backout}{--merge} オプションを指定してください。 foozy@708: foozy@708: \interaction{backout.non-tip.clone} foozy@708: foozy@708: このコマンド実行は、 foozy@708: 任意のチェンジセットを、 foozy@708: 簡単で素早い``一回限りの''操作で back out できます。 foozy@708: foozy@708: \interaction{backout.non-tip.backout} foozy@708: foozy@708: back out 完了後の \filename{myfile} の内容には、 foozy@708: 1回目と3回目の変更に相当する内容は見ることができますが、 foozy@708: 2回目の変更に相当する内容は見ることができないでしょう。 foozy@708: foozy@708: \interaction{backout.non-tip.cat} foozy@708: foozy@708: 履歴を図示した \ref{fig:undo:backout-non-tip}~図に見られるように、 foozy@708: このような状況の場合、 foozy@708: Mercurial は実際には\emph{2つ}のチェンジセットをコミットします foozy@708: (Mercurial が自動的にコミットしたも\footnote{訳注: foozy@708: 実行例で Mercurial が出力するメッセージを見ればわかるように、 foozy@708: マージされたチェンジセットのコミットは利用者責任となっているため、 foozy@708: 「自動的にコミット」したものではなく foozy@708: 「自動的に生成したもの」が正しい表現です。}のは矩形で示してあります)。 foozy@708: Mercurial は back out 処理を始める前に、 foozy@708: 現時点での作業領域ディレクトリにおける親チェンジセットを覚えておきます。 foozy@708: その上で対象チェンジセットを back out し、 foozy@708: チェンジセットとしてコミットします。 foozy@708: 最後に、 foozy@708: 作業領域ディレクトリの親チェンジセットとマージした結果をコミットします foozy@708: footnote{訳注: 前述のように、自動的にはコミットされません}。 foozy@708: foozy@708: \begin{figure}[htb] foozy@708: \centering foozy@708: \grafix{undo-non-tip} foozy@708: \label{fig:undo:backout-non-tip} foozy@708: \caption{Automated backout of a non-tip change using the \hgcmd{backout} command} foozy@708: \end{figure} foozy@708: foozy@708: 結果として、 foozy@708: back out したいチェンジセットによる変更内容を取り消すための、 foozy@708: 幾つかの余分な履歴のみを伴って、 foozy@708: ``以前の状態への復旧''が行われます。 foozy@708: foozy@708: \subsubsection{Always use the \hgopt{backout}{--merge} option} foozy@708: foozy@708: 実のところ、 foozy@708: back out 対象のチェンジセットが tip か否かに関わらず、 foozy@708: \hgopt{backout}{--merge} オプションは``正しく機能''します foozy@708: (back out 対象が tip の場合は、必要が無いのでマージしようとはしません) foozy@708: ので、 foozy@708: \hgcmd{backout} コマンドを実行する際には\emph{常に} foozy@708: \hgopt{backout}{--merge} オプションを指定するべきでしょう。 foozy@708: foozy@708: \subsection{Gaining more control of the backout process} foozy@708: foozy@708: 先の記述では、変更の back out の際の foozy@708: \hgopt{backout}{--merge} オプションの常用を推奨しましたが、 foozy@708: その一方で、 foozy@708: back out 対象となるチェンジセットのマージ方法を、 foozy@708: \hgcmd{backout} コマンドの利用者が決定することもできます。 foozy@708: back out 処理を手動で制御する必要は滅多にありませんが、 foozy@708: 手動制御の方法を知ることは、 foozy@708: \hgcmd{backout} が自動的に行っていることの内情を理解する上で有用です。 foozy@708: 手動制御の説明のために、 foozy@708: 最初に作成したリポジトリを複製しますが、 foozy@708: ここでは back out は行いません。 foozy@708: foozy@708: \interaction{backout.manual.clone} foozy@708: foozy@708: 先の例と同様に、 foozy@708: 第3のチェンジセットをコミットし、 foozy@708: その上でその親を back out した結果を見てみましょう。 foozy@708: foozy@708: \interaction{backout.manual.backout} foozy@708: foozy@708: 新たなチェンジセットも第3のチェンジセット同様に、 foozy@708: back out 対象のチェンジセットの子になりますので、 foozy@708: それまで tip だったチェンジセット\footnote{訳注: 第3のチェンジセットのこと} foozy@708: の子\emph{ではなく}、新たなヘッドになります。 foozy@708: \hgcmd{backout} コマンドは、 foozy@708: このことを告げる非常にはっきりとしたメッセージを表示しています。 foozy@708: foozy@708: \interaction{backout.manual.log} foozy@708: foozy@708: ここでも、 foozy@708: 履歴を図示した\ref{fig:undo:backout-manual}~図を見ることで、 foozy@708: どういった状況にあるのかが理解し易いと思います。 foozy@708: この図から、 foozy@708: \hgcmd{backout} コマンドを tip 以外のチェンジセットに適用した際に、 foozy@708: Mercurial が新しいヘッドをリポジトリに追加する foozy@708: (Mercurial により追加されたチェンジセットは矩形で表しています) foozy@708: ことがよくわかります。 foozy@708: foozy@708: \begin{figure}[htb] foozy@708: \centering foozy@708: \grafix{undo-manual} foozy@708: \label{fig:undo:backout-manual} foozy@708: \caption{Backing out a change using the \hgcmd{backout} command} foozy@708: \end{figure} foozy@708: foozy@708: \hgcmd{backout} コマンドの実行が完了すると、 foozy@708: 作業領域ディレクトリの親チェンジセットが、 foozy@708: 新しい ``backout'' チェンジセットになります。 foozy@708: foozy@708: \interaction{backout.manual.parents} foozy@708: foozy@708: この時点で、2つの独立した変更のまとまり foozy@708: \footnote{訳注: マージが必要な「複数ヘッド状態」のことを指していると思われます} foozy@708: が存在します。 foozy@708: foozy@708: \interaction{backout.manual.heads} foozy@708: foozy@708: この時点で、\filename{myfile} foozy@708: はどのような内容であることが期待されるかを考えてみましょう。 foozy@708: 第1の変更は back out していませんから、 foozy@708: それに関する内容は存在していなければなりません。 foozy@708: 第2の変更は back out しましたので、 foozy@708: それに関する内容は消失していなければなりません。 foozy@708: 履歴図で別個のヘッドとして図示されているように、 foozy@708: 第3の変更に関する内容が foozy@708: \filename{myfile} に存在しては\emph{なりません}。 foozy@708: foozy@708: \interaction{backout.manual.cat} foozy@708: foozy@708: 第3の変更の内容をファイルに取り込むには、 foozy@708: 2つのヘッドをいつものようにマージすれば良いのです。 foozy@708: foozy@708: \interaction{backout.manual.merge} foozy@708: foozy@708: マージすることで、 foozy@708: リポジトリ中の履歴は foozy@708: \ref{fig:undo:backout-manual-merge}~図に示すようになります。 foozy@708: foozy@708: \begin{figure}[htb] foozy@708: \centering foozy@708: \grafix{undo-manual-merge} foozy@708: \caption{Manually merging a backout change} foozy@708: \label{fig:undo:backout-manual-merge} foozy@708: \end{figure} foozy@708: foozy@708: \subsection{Why \hgcmd{backout} works as it does} foozy@708: foozy@708: \hgcmd{backout} コマンドの振る舞いを簡単にまとめると以下のようになります。 foozy@708: foozy@708: \begin{enumerate} foozy@708: \item 作業領域ディレクトリが``クリーン''な状態、 foozy@708: 即ち \hgcmd{status} の出力が空であることを確認します。 foozy@708: foozy@708: \item その時点での作業領域ディレクトリの親チェンジセットを覚えておきます。 foozy@708: 以下、このチェンジセットを \texttt{orig} と呼称します。 foozy@708: foozy@708: \item 作業領域ディレクトリを back out 対象チェンジセットに同期するために、 foozy@708: \hgcmd{update} と同等の処理を行います。 foozy@708: 以下、このチェンジセットを \texttt{backout} と呼称します。 foozy@708: foozy@708: \item \texttt{backout} の親チェンジセットを調べます。 foozy@708: 以下、この親チェンジセットを \texttt{parent} と呼称します。 foozy@708: foozy@708: \item \texttt{backout} チェンジセットが影響する個々のファイルに対して、 foozy@708: \hgcmdargs{revert}{-r parent} 相当の処理を行い、 foozy@708: \texttt{backout} チェンジセットがコミットされる前の内容に復元します。 foozy@708: foozy@708: \item 復元結果を新しいチェンジセットとしてコミットします。 foozy@708: このチェンジセットの親は \texttt{backout} です。 foozy@708: foozy@708: \item コマンドラインで \hgopt{backout}{--merge} が指定されていた場合、 foozy@708: 新しいチェンジセットと \texttt{orig} をマージし、 foozy@708: その結果をコミットします。 foozy@708: foozy@708: \end{enumerate} foozy@708: foozy@708: 作業領域ディレクトリを弄繰り回すことなく foozy@708: \hgcmd{backout} コマンド相当の効果を得るもう一つの方法は、 foozy@708: back out されるチェンジセットに対して \hgcmd{export} foozy@708: することで得た diff ファイルを、 foozy@708: 作用を反転させる foozy@708: \cmdopt{patch}{--reverse} オプションを指定した foozy@708: \command{patch} コマンドに用いることです。 foozy@708: この方法は非常に簡単に感じるでしょうが、 foozy@708: 全く上手く機能しません。 foozy@708: foozy@708: \hgcmd{backout} が update、commit、merge および再度の commit を行うのは、 foozy@708: back out 対象のチェンジセットと現在の tip foozy@708: の\emph{間}の全てのチェンジセットを扱う際に、 foozy@708: 良好な結果を得るための最善の機会を Mercurial のマージ機構に与えるためです。 foozy@708: foozy@708: 例えば、 foozy@708: プロジェクトの履歴から、100 リビジョン分前のチェンジセットを foozy@708: back out しようとした場合、 foozy@708: \command{patch} がパッチの適用可否を判定するコンテキスト情報を、 foozy@708: back out 対象との間にあるチェンジセットが``破壊''してしまうかもしれない foozy@708: (この意味がわからない場合は、 foozy@708: \ref{sec:mq:patch}~節の \command{patch} に関する説明を参照してください) foozy@708: ので、 foozy@708: \command{patch} コマンドが反転 diff を綺麗に適用できることは期待できません。 foozy@708: Mercurial のマージ機構は、 foozy@708: ファイルやディレクトリの変名、 foozy@708: ファイル権限の変更や、 foozy@708: バイナリファイルの変更といった foozy@708: \command{patch} コマンドが扱うことのできないものも扱うことができます。 foozy@708: foozy@708: \section{Changes that should never have been} foozy@708: \label{sec:undo:aaaiiieee} foozy@708: foozy@708: 変更内容を取り消そうとした場合の殆どは、 foozy@708: \hgcmd{backout} コマンドの利用が妥当です。 foozy@708: \hgcmd{backout} コマンドは、 foozy@708: 元のチェンジセットのコミットと、 foozy@708: 後からそれを取り消した際の両方に関して、 foozy@708: 正確で永続的な記録を残します。 foozy@708: foozy@708: しかし、非常に稀な状況ですが、 foozy@708: リポジトリ中に存在して欲しくない変更をコミットしてしまうかもしれません。 foozy@708: 例えば、 foozy@708: ソースファイルと同様にオブジェクトファイルをコミットしてしまうような事態は、 foozy@708: 滅多に無いので通常は「間違い」とみなされます。 foozy@708: オブジェクトファイルには本質的な価値はありませんし、 foozy@708: 非常に\emph{サイズが大きい}ですから、 foozy@708: リポジトサイズや複製/変更取り込みに要する時間が増加してしまいます。 foozy@708: foozy@708: XXXXXXXXXX foozy@708: Before I discuss the options that you have if you commit a ``brown foozy@708: paper bag'' change (the kind that's so bad that you want to pull a foozy@708: brown paper bag over your head), let me first discuss some approaches foozy@708: that probably won't work. foozy@708: XXXXXXXXXX foozy@708: foozy@708: Mercurial は履歴を「蓄積的なもの」--- foozy@708: 全ての変更が先行する変更の上に適用される---として扱いますので、 foozy@708: 破壊的な影響を持つチェンジセットに対してであっても、 foozy@708: それを破棄することは通常はできません。 foozy@708: \ref{sec:undo:rollback}~節で詳細を述べますが、 foozy@708: 例外的に \hgcmd{rollback} コマンドを安全に使用できるのは、 foozy@708: 変更をコミットした直後で、 foozy@708: 別なリポジトリへ \hgcmd{push} も \hgcmd{pull} もされていない場合だけです。 foozy@708: foozy@708: 不適切なチェンジセットを他のリポジトリへ foozy@708: \hgcmd{push} してしまった\emph{後でも}、 foozy@708: \hgcmd{rollback} コマンドにより、 foozy@708: ローカルなリポジトリでそのチェンジセットを破棄することはできますが、 foozy@708: それはおそらく本来やりたかったことでは無い筈です。 foozy@708: 遠隔リポジトリ中には不適切なチェンジセットが存在し続けますので、 foozy@708: 次に変更の取り込みを行った際には、 foozy@708: その変更が再びローカルリポジトリに現れるかもしれません。 foozy@708: foozy@708: このような状況が発生した場合、 foozy@708: どのリポジトリが不適切なチェンジセットを保持しているかを把握しているなら、 foozy@708: それら\emph{全ての}リポジトリからの不適切なチェンジセットの除去を、 foozy@708: \emph{試みる}ことが可能です。 foozy@708: 勿論、これは申し分の無い解法ではありません。 foozy@708: たった一つでも抹消し損ねたリポジトリがあれば、 foozy@708: ``野に放たれた''ままのチェンジセットは更に伝播してしまうでしょう。 foozy@708: foozy@708: 除去したいチェンジセットの\emph{後に}、 foozy@708: 幾つかのチェンジセットをコミットしてしまった場合、 foozy@708: 取り得る選択肢は更に限られてしまいます。 foozy@708: Mercurial は、 foozy@708: チェンジセットに手をつけないままで、 foozy@708: 履歴に``穴を開ける''機能は提供していません。 foozy@708: foozy@708: XXX This needs filling out. foozy@708: \texttt{examples} ディレクトリ配下の foozy@708: \texttt{hg-replay} スクリプトは機能しますが、 foozy@708: チェンジセットのマージを行いません。 foozy@708: 重大な手抜きです。 foozy@708: foozy@708: \subsection{Protect yourself from ``escaped'' changes} foozy@708: foozy@708: ローカルリポジトリにコミットした幾つかのチェンジセットが、 foozy@708: \hgcmd{push} ないし \hgcmd{pull} foozy@708: 等によってそれらが他のリポジトリへと反映されたからといって、 foozy@708: そのこと自体は必ずしも大失敗というわけではありません。 foozy@708: ある種の不正なチェンジセットに対して、 foozy@708: あらかじめ自己防衛することも可能です。 foozy@708: 開発チームが変更を中央のリポジトリから \hgcmd{pull} するような体制の場合、 foozy@708: 事故防衛は非常に簡単です。 foozy@708: foozy@708: 中央のリポジトリの幾つかのフックを、 foozy@708: 追加されるチェンジセットの検証を行うように設定する foozy@708: (\ref{chap:hook}~章を参照してください)ことで、 foozy@708: ある種の不正なチェンジセットが、 foozy@708: 中央リポジトリに全く反映されないように自動化することができます。 foozy@708: 設定が適切であれば中央のリポジトリに反映できなくなるため、 foozy@708: このようなチェンジセットは自然と``死に絶え''ます。 foozy@708: なお良いことに、この手法は明示的な介入を必要としません。 foozy@708: foozy@708: 例えば、当該チェンジセットが実際にコンパイル可能かどうかを検証する foozy@708: incoming フックは、 foozy@708: うっかり``ビルドできなくしてしまう''ことを防止できます。 foozy@708: foozy@708: \section{Finding the source of a bug} foozy@708: \label{sec:undo:bisect} foozy@708: foozy@708: バグをもたらしたチェンジセットを back out できるのは非常に結構なのですが、 foozy@708: どのチェンジセットを back out すべきかを知っている必要があります。 foozy@708: Mercurial には、 foozy@708: チェンジセット特定の自動化と非常に効率的な実施を補助する、 foozy@708: \hgext{bisect} と呼ばれる重要な拡張があります。 foozy@708: foozy@708: チェンジセットによる変更は振る舞いに変化をもたらすので、 foozy@708: その変化を簡単な2値テストによりそれを特定することができる、 foozy@708: というのが \hgext{bisect} 拡張の原理です。 foozy@708: どのコード片が変化をもたらしているのかはわからなくても、 foozy@708: バグの有無を試験する方法はわかるでしょう。 foozy@708: \hgext{bisect} 拡張は、 foozy@708: バグの原因となったコードをもたらしたチェンジセットを探すのに、 foozy@708: あなたのテストプログラムを直接使用します。 foozy@708: foozy@708: \hgext{bisect} 拡張の適用方法を理解しやすいように、 foozy@708: 幾つかのシナリオを例示します。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item 数週間前には見られなかったバグが、最新の版で発見されましたが、 foozy@708: 何時それが混入されたのかがわかりません。 foozy@708: この場合、binary test でバグの有無を調べることができます foozy@708: \footnote{訳注: 「バグの有無」という2値を判定するテストを用いることで、 foozy@708: バグの混入したチェンジセットを探します}。 foozy@708: foozy@708: \item 大急ぎでバグを修正し、 foozy@708: 開発チームのバグデータベースの状態を「クローズ」にできるようになりました。 foozy@708: 「クローズ」状態にする際に、 foozy@708: バグデータベースがチェンジセットIDを求めてきましたが、 foozy@708: どのチェンジセットでバグを修正したのか覚えていませんでした。 foozy@708: ここで再び binary test でバグの有無を調べることができます。 foozy@708: foozy@708: \item ソフトウェアが正しく動作していますが、 foozy@708: 以前計測した時よりも 15\% 遅くなってました。 foozy@708: どのチェンジセットが性能低下の要因となっているのかを知りたいです。 foozy@708: この場合、binary test はソフトウェアの性能を計測し、 foozy@708: ``早い''のか``遅い''のかを判定します。 foozy@708: foozy@708: \item ここ最近、 foozy@708: 出荷したプロジェクトの構成要素のサイズが爆発的に大きくなっていて、 foozy@708: プロジェクトのビルド手順の何らかが変更されたのではないかと推測しています foozy@708: \footnote{訳注: ビルド結果の「構成要素サイズの大小」 foozy@708: という2値を判定するテストを用いることで、 foozy@708: 変更が混入されたチェンジセットを探します}。 foozy@708: foozy@708: \end{itemize} foozy@708: foozy@708: これらの例から、 foozy@708: \hgext{bisect} 拡張がバグの元を探すだけのものでないことは明らかでしょう。 foozy@708: その特性に関する2値テストを書けるなら、 foozy@708: リポジトリにおける foozy@708: (ソースツリー中のファイルに対する単純な文字列検索では探し出せない) foozy@708: 任意の``特性の出現''を探し出すことができます。 foozy@708: foozy@708: 利用者と Mercurial のそれぞれが、 foozy@708: 検索処理においてどの部分に責任を負うのかをはっきりとさせるために、 foozy@708: ここでもう少し用語の説明をしましょう。 foozy@708: \emph{テスト}(test)とは、 foozy@708: \hgext{bisect} 拡張がチェンジセットを選択する際に、 foozy@708: \emph{利用者}が実行するものです。 foozy@708: \emph{調査}(probe)とは、 foozy@708: あるリビジョンの良否を判定するために \hgext{bisect} が実行するものです。 foozy@708: 最後に、 foozy@708: ``bisect'' という言葉を、 foozy@708: ``\hgext{bisect} 拡張を用いた検索''の代用として、 foozy@708: 名詞および動詞として使用します。 foozy@708: foozy@708: 検索処理を自動化する簡単な方法の一つが、 foozy@708: 全てのチェンジセットを調査する遣り方です。 foozy@708: しかしながら、この遣り方には殆どスケーラビリティがありません。 foozy@708: 1つのチェンジセットのテストに10分必要で、 foozy@708: リポジトリに1万のチェンジセットがあったとすると、 foozy@708: 徹底的に調査する遣り方では、 foozy@708: バグをもたらしたチェンジセットを見つけるのに、 foozy@708: 平均で35~\emph{日}必要です。 foozy@708: 検索対象を最新の500チェンジセットに限定できるとしても、 foozy@708: バグをもたらしたチェンジセットを見つけるのには、 foozy@708: それでもなお40時間必要です。 foozy@708: foozy@708: \emph{bisect} 拡張は、 foozy@708: 確認するチェンジセット数に対して\emph{対数}のオーダーで検索 foozy@708: (この種の検索は``二分探索''と呼ばれます)できるように、 foozy@708: プロジェクト履歴の``形''に関する情報を利用します。 foozy@708: この方法により、 foozy@708: 仮にテストあたりの所要時間が10分掛かるとしても、 foozy@708: 1万チェンジセットに対する検索は2時間以内で終わります。 foozy@708: 検索対象を最新の500チェンジセットに限定できるならば、 foozy@708: 1時間以内に検索できるでしょう。 foozy@708: foozy@708: \hgext{bisect} 拡張は、 foozy@708: Mercurial で管理されているプロジェクトの履歴の持つ foozy@708: ``枝分かれ''の特質をわかっていますので、 foozy@708: リポジトリにおける枝分かれ・マージ・複数ヘッドの扱いも問題ありません。 foozy@708: 単一の調査で履歴の枝分かれ全体を刈り取る\footnote{訳注: foozy@708: 「枝分かれ先全体を検索対象から除外する」の意}ことができるため、 foozy@708: \hgext{bisect} 拡張は効率的に検索することができるのです。 foozy@708: foozy@708: \subsection{Using the \hgext{bisect} extension} foozy@708: foozy@708: ここでは \hgext{bisect} 拡張の実行例を示します。 foozy@708: Mercurial 自体の簡便性を維持するために、 foozy@708: \hgext{bisect} は拡張機能として提供されます。 foozy@708: そのため、明示的に有効にしなければ、その機能は提供されません。 foozy@708: \hgext{bisect} 拡張を有効にするには、 foozy@708: (存在しない場合には) \hgrc\ に以下のセクションヘッダを追加し: foozy@708: foozy@708: \begin{codesample2} foozy@708: [extensions] foozy@708: \end{codesample2} foozy@708: foozy@708: 続いて、\hgext{bisect} 拡張を有効化するための行をこのセクションに追加します foozy@708: \footnote{1.0 版以降の Mercurial では、 foozy@708: \hgext{bisect} 機能は基本機能に取り込まれていますので、 foozy@708: 「拡張機能の有効化」は不要です}。 foozy@708: foozy@708: \begin{codesample2} foozy@708: hbisect = foozy@708: \end{codesample2} foozy@708: foozy@708: \begin{note} foozy@708: \hgext{bisect} 拡張の名前の先頭に``\texttt{h}''が付くのは間違っていません。 foozy@708: この文字が付くのは、Mercurial が Python で実装されていて、 foozy@708: Python の標準ライブラリの \texttt{bisect} を使用しているためです。 foozy@708: 誤って ``\texttt{hbisect}''から``\texttt{h}''を省略した場合、 foozy@708: \hgrc ファイルの記述のスペルを修正するまでは、 foozy@708: Mercurial は Python 標準の \texttt{bisect} パッケージを見つけ出し、 foozy@708: それを Mercurial 拡張として利用しようとしてクラッシュし続けることでしょう。 foozy@708: \end{note} foozy@708: foozy@708: \hgext{bisect} 拡張を隔離して利用するために、 foozy@708: リポジトリを作成しましょう。 foozy@708: foozy@708: \interaction{bisect.init} foozy@708: foozy@708: ループによって幾つかの些細な変更を行い、 foozy@708: その中の特定の変更が``バグ''を持つようにする、 foozy@708: という単純な方法で、 foozy@708: バグを持ったプロジェクトのシミュレーションを行います。 foozy@708: このループは 50 のチェンジセットを生成し、 foozy@708: それぞれが1つのファイルをリポジトリに追加します。 foozy@708: ここでは、 foozy@708: ファイルが``i have a gub''というテキストを含んでいることをもって、 foozy@708: ``バグ''とみなします。 foozy@708: foozy@708: \interaction{bisect.commits} foozy@708: foozy@708: それでは、\hgext{bisect} 拡張の使用方法を理解しましょう。 foozy@708: \hgext{bisect} 拡張に関しても、 foozy@708: 通常の Mercurial の組み込み help 機能が使用できます。 foozy@708: foozy@708: \interaction{bisect.help} foozy@708: foozy@708: \hgext{bisect} 拡張は段階を踏んで機能します。 foozy@708: 各段階は以下のように進みます。 foozy@708: foozy@708: \begin{enumerate} foozy@708: \item 2値テストを実行します。 foozy@708: \begin{itemize} foozy@708: \item テストが成功した場合、 foozy@708: \hgcmdargs{bisect}{good} コマンドにより foozy@708: \hgext{bisect} 拡張にそのことを伝えます。 foozy@708: \item テストが失敗した場合、 foozy@708: \hgcmdargs{bisect}{bad} コマンドにより foozy@708: \hgext{bisect} 拡張にそのことを伝えます。 foozy@708: \end{itemize} foozy@708: \item \hgext{bisect} 拡張は伝えられた情報を元に、 foozy@708: 次にテストすべきチェンジセットを決定します。 foozy@708: foozy@708: \item \hgext{bisect} 拡張は、 foozy@708: 作業領域ディレクトリをそのチェンジセットで更新しますので、 foozy@708: 以上の手順を繰り返します。 foozy@708: foozy@708: \end{enumerate} foozy@708: foozy@708: 2値テストの結果が``成功''から``失敗''に変化した点を示す、 foozy@708: 一意なチェンジセットを \hgext{bisect} 拡張が特定できた時点で、 foozy@708: この手順は終了します。 foozy@708: foozy@708: 検索の開始に当たっては、 foozy@708: \hgcmdargs{bisect}{init} コマンドの実行が必要です。 foozy@708: foozy@708: \interaction{bisect.search.init} foozy@708: foozy@708: 今回の実行例で使用する2値テストは簡単なもので、 foozy@708: リポジトリ中の何れかのファイルが foozy@708: ``i have a gub'' 文字列を含んでいるか否かを判定します。 foozy@708: 含んでいる場合、そのチェンジセットは foozy@708: ``バグの要因となる''チェンジセットです。 foozy@708: 慣習上、 foozy@708: 検索対象となる特性を持っているチェンジセットを ``bad''、 foozy@708: 持っていないチェンジセットを ``good'' と呼びます。 foozy@708: 多くの場合、 foozy@708: 作業領域ディレクトリが同期しているリビジョン(通常は tip) foozy@708: はバグを持つチェンジセットにより問題を抱えているものですから、 foozy@708: これを``bad''とみなします。 foozy@708: foozy@708: \interaction{bisect.search.bad-init} foozy@708: foozy@708: 次の作業は、 foozy@708: バグが\emph{無い}チェンジセットを指定することです。 foozy@708: \hgext{bisect} 拡張は foozy@708: 最初の ``good'' と ``bad'' のチェンジセット間の検査状況を foozy@708: ``括弧''で括って表示するでしょう。 foozy@708: 今回の事例では、 foozy@708: リビジョン 10 にはバグがありません foozy@708: (最初の ``good'' チェンジセットの選択に関しては、 foozy@708: 後ほど補足があります)。 foozy@708: foozy@708: \interaction{bisect.search.good-init} foozy@708: foozy@708: コマンド出力には以下の意味があります。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item バグをもたらしたチェンジセットの特定までに、 foozy@708: どれだけのチェンジセットに対して考慮が必要であるか、 foozy@708: また、どれだけのテストを要求するかを表示します。 foozy@708: foozy@708: \item \hgext{bisect} foozy@708: 拡張は次にテストすべきチェンジセットへと作業領域ディレクトリを更新し、 foozy@708: どのチェンジセットがテスト対象であるのかを表示します。 foozy@708: foozy@708: \end{itemize} foozy@708: foozy@708: 早速作業領域ディレクトリでテストをしてみましょう。 foozy@708: \command{grep} を使用して、 foozy@708: 作業領域ディレクトリの``bad''ファイルの有無を調べ、 foozy@708: ファイルが無ければそのリビジョンは``good''です。 foozy@708: foozy@708: \interaction{bisect.search.step1} foozy@708: foozy@708: このテストは完全に自動化できそうですので、 foozy@708: シェル関数にしてしまいましょう。 foozy@708: foozy@708: \interaction{bisect.search.mytest} foozy@708: foozy@708: これで、 foozy@708: テスト手順全体を単一の foozy@708: \texttt{mytest} コマンドで実行できます。 foozy@708: foozy@708: \interaction{bisect.search.step2} foozy@708: foozy@708: テスト手順が記録されたコマンドをあと数回起動することで、 foozy@708: 当初の目的が達成されます。 foozy@708: foozy@708: \interaction{bisect.search.rest} foozy@708: foozy@708: 40 程のチェンジセット全体の検索にも関わらず、 foozy@708: \hgext{bisect} foozy@708: 拡張はわずか5回のテストで``バグ''をもたらしたチェンジセットを特定できました。 foozy@708: 調査対象チェンジセット数に対して、 foozy@708: \hgext{bisect} 拡張は対数のオーダーでテスト対象を選定するので、 foozy@708: チェンジセットを追加しただけテスト回数が増加する foozy@708: ``力尽く''の手法よりも有利です。 foozy@708: foozy@708: \subsection{Cleaning up after your search} foozy@708: foozy@708: リポジトリにおける \hgext{bisect} 拡張の使用が終わったなら、 foozy@708: 検索に使用していた情報を foozy@708: \hgcmdargs{bisect}{reset} コマンドにより破棄することができます。 foozy@708: \hgext{bisect} 拡張はそれほど多くの領域を消費するわけではありませんので、 foozy@708: この作業を忘れても問題にはなりません。 foozy@708: しかし、\hgcmdargs{bisect}{reset} を実行するまでは、 foozy@708: \hgext{bisect} はそのリポジトリで別の検索を開始させてくれません。 foozy@708: foozy@708: \interaction{bisect.search.reset} foozy@708: foozy@708: \section{Tips for finding bugs effectively} foozy@708: foozy@708: \subsection{Give consistent input} foozy@708: foozy@708: \hgext{bisect} 拡張には、 foozy@708: 実施した全てのテストの結果が正しく指定されなければなりません。 foozy@708: 本当はテストが成功していたにも関わらず、 foozy@708: テストの失敗を \hgext{bisect} 拡張に伝えた場合、 foozy@708: 矛盾した結果を出す\emph{かも}しれません。 foozy@708: テスト結果に対して矛盾が検知された場合、 foozy@708: \hgext{bisect} は、 foozy@708: 特定のチェンジセットが``good''でも``bad''でもある、 foozy@708: と言ってきます。 foozy@708: しかし、この検知は完璧に行われるわけではないので、 foozy@708: 間違ったチェンジセットをバグの要因として報告するでしょう。 foozy@708: foozy@708: \subsection{Automate as much as possible} foozy@708: foozy@708: 筆者が \hgext{bisect} 拡張を使い始めた頃は、 foozy@708: 検索のためのテストをコマンドラインで手動で実行していましたが、 foozy@708: 少なくとも私には、この手法は馴染みません。 foozy@708: 何度か \hgext{bisect} を使用した後で、 foozy@708: 最終的に正しい結果を得る前に、 foozy@708: いつも手違いのために何度も検索をやり直していることに気付きました。 foozy@708: foozy@708: \hgext{bisect} 拡張を手動で駆動していた際には、 foozy@708: 小さなリポジトリにおける単純な検索であっても問題が発生していました。 foozy@708: テストの内容が複雑であったり、 foozy@708: \hgext{bisect} が要求するテスト実行回数が増えれば、 foozy@708: それだけテスト実行における操作ミスの可能性は高まります。 foozy@708: テストを自動化するようになって以来、 foozy@708: 非常に良好な結果を得られています。 foozy@708: foozy@708: テスト自動化のための鍵は2つあります。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item 常に同じ「症状」をテストすることと、 foozy@708: \item 常に一貫した入力を \hgcmd{bisect} コマンドに与えること foozy@708: \end{itemize} foozy@708: foozy@708: 前述の実行例では、 foozy@708: \command{grep} コマンドにより「症状」を調べていて、 foozy@708: \texttt{if} ステートメントが「検査」の結果を受けて foozy@708: \hgcmd{bisect} コマンドに同じ入力を与えることを保証していました。 foozy@708: \texttt{mytest} 関数が、 foozy@708: これらを再現しやすい形式に統合したことで、 foozy@708: 全てのテストが均一で整合性の取れたものになっています。 foozy@708: foozy@708: \subsection{Check your results} foozy@708: foozy@708: \hgext{bisect} による検索の出力結果は与えた情報程度にしか正しくないので、 foozy@708: \hgext{bisect} により ``good'' と報告されたチェンジセットを、 foozy@708: 絶対的に正しいものとみなさないでください。 foozy@708: 報告内容をクロスチェックする簡単な方法は、 foozy@708: 以下のようなチェンジセットのそれぞれに対して、 foozy@708: 手動で自身のテストを実行してみることです。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item 最初の ``bad'' リビジョンであると報告されたもの foozy@708: (以下、「障害チェンジセット」と呼称)。 foozy@708: あなたのテストはこれに関して ``bad'' と報告しなければなりません。 foozy@708: foozy@708: \item 上記チェンジセットの親チェンジセット foozy@708: (マージされた場合は両方の親)。 foozy@708: あなたのテストはこれ(これら)に関して ``good'' と報告しなければなりません。 foozy@708: foozy@708: \item 障害チェンジセットの子チェンジセット。 foozy@708: あなたのテストはこれに関して ``bad'' と報告しなければなりません。 foozy@708: foozy@708: \end{itemize} foozy@708: foozy@708: \subsection{Beware interference between bugs} foozy@708: foozy@708: あるバグを探す際に、 foozy@708: 他のバグの存在により混乱させられる可能性もあります。 foozy@708: 例えば、 foozy@708: リビジョン 100 でソフトウェアがクラッシュし、 foozy@708: リビジョン 50 では正しく動作していたとします。 foozy@708: あなたの知らない間に、 foozy@708: ソフトウェアをクラッシュさせる別のバグを、 foozy@708: 他の人がリビジョン 60 で入れてしまい、 foozy@708: それをリビジョン 80 で修正した場合、 foozy@708: なんらかの方法で検索結果を混乱させるかもしれません。 foozy@708: foozy@708: 他のバグの存在によって、 foozy@708: 探しているバグが完全に``覆い隠される''かもしれず、 foozy@708: 探しているバグがその存在を示す機会を得る前に他のバグが発生している、 foozy@708: と言えます。 foozy@708: 他のバグを回避したテストが foozy@708: (例えば、そのバグがプロジェクトのビルドを阻害するなどの理由で) foozy@708: できないために、 foozy@708: 特定のチェンジセットにおける検索対象のバグの有無を明言できない場合、 foozy@708: \hgext{bisect} 拡張の助けを直接受けることはできません。 foozy@708: その替わり、 foozy@708: 他のバグが存在するチェンジセットを手動で取り除くことで、 foozy@708: ``周辺''での別な検索を行いましょう。 foozy@708: foozy@708: バグの存在に関するテストが十分明確でない場合には、 foozy@708: 別な問題が発生し得ます。 foozy@708: ``プログラムのクラッシュ''でバグの有無を確認している場合、 foozy@708: ソフトウェアをクラッシュさせる全然関係ないバグにより、 foozy@708: 検索対象であるバグが覆い隠されてしまい、 foozy@708: 両方とも同じものとみなされるために、 foozy@708: \hgext{bisect} が惑わされてしまいます。 foozy@708: foozy@708: \subsection{Bracket your search lazily} foozy@708: foozy@708: 検索における終端の印となる foozy@708: ``good'' および ``bad'' なチェンジセットの最初の選択は、 foozy@708: 通常は簡単なことですが、 foozy@708: そうであっても多少は議論の余地があります。 foozy@708: \hgext{bisect} の立場から見た場合、 foozy@708: ``最新''のチェンジセットは通例では``bad''で、 foozy@708: 最古のチェンジセットは``good''です。 foozy@708: foozy@708: \hgext{bisect} の使用に当たって foozy@708: ``good''にふさわしいチェンジセットがどれかを思い出すのが難しい場合には、 foozy@708: でたらめにテストするのも悪くはないでしょう。 foozy@708: どうあってもバグの兆候が見出せない foozy@708: (例えば、バグの発生に関連する機能がまだ提供されていない)ものや、 foozy@708: 他の問題が(前述したように)バグを覆い隠してしまうようなものを、 foozy@708: テスト候補のチェンジセットから除外するのを忘れないようにしましょう。 foozy@708: foozy@708: 数千のチェンジセット、 foozy@708: ないし数ヶ月の履歴の``初期''のものが最終結果だったとしても、 foozy@708: 対数オーダーの振る舞いのお陰で、 foozy@708: \hgext{bisect} が実施しなければならない総回数が数回増えるだけです。 foozy@708: foozy@708: %%% Local Variables: foozy@708: %%% mode: latex foozy@708: %%% TeX-master: "00book" foozy@708: %%% End: