パーフェクト dirstate 〜 基礎編
このエントリは、Mercurial Advent Calendar 2013 の20日目です。
エントリを公開するのは 12/29 ですが、諦めたらそこでクリスマス終了です。諦めるまでがクリスマスです(笑)。
Mercurial では、hg status
を使うことで、作業領域中の管理対象ファイルに対する変更の有無を確認できます。
しかし、hg status
の都度、履歴に記録されたファイル内容と、作業領域中のファイルの比較処理をやっていたのでは、履歴やファイルサイズが一定規模以上になった場合、とても使い物になりません。
そこで Mercurial は、管理領域中のファイル .hg/dirstate
において、管理対象ファイルに関する各種情報を、一種の情報キャッシュとして保持することで、状態確認における性能劣化を抑止しています。
本エントリでは、.hg/dirstate
の基本的な機能について説明します。よろしければ、トラブル対処編も御参照ください。
なお、説明の便宜上、基本的な理解に支障のない範囲で、多少簡略化して説明している部分もあります。厳密な挙動を知りたい方は、ソースコードを参照してください。
.hg/dirstate 管理の概要
hg update
によって、特定のリビジョン時点のファイル a.txt
が作業領域に取り出される際の、処理の流れは以下のようになっています。
- 対象リビジョンの履歴情報から、
a.txt
の内容を取り出す - 作業領域に、
a.txt
の内容を書き出す - 必要に応じて、
a.txt
に実行ビットを設定 .hg/dirstate
のa.txt
向けエントリの情報を更新
更新の際に .hg/dirstate
中の a.txt
向けエントリには、以下のような値が格納されます。
- 実行ビット:履歴に記録された値
- ファイルのサイズ:書き出した内容のバイト値
- タイムスタンプ値:「未設定」 (unset) をあらわす -1
その一方、hg status
によって、作業領域中のファイルの状態を確認する場合の、処理の流れは以下のようになっています。
- サイズが異なれば、「変更あり」とみなす
- モードが異なれば、「変更あり」とみなす
- 作業領域中の
a.txt
から、タイムスタンプを取得 - タイムスタンプが異なれば、変更の可能性あり
- 履歴情報から、対象ファイルの内容を取り出す
- 作業領域中の、対象ファイルの内容と比較
- 内容が異なれば、「変更あり」とみなす
.hg/dirstate
中のエントリを、最新の情報で更新
初期状態で、問答無用にタイムスタンプ値に -1 を設定しているのは:
タイムスタンプ値取得には、
lstat(2)
システムコールの発行が必要。
hg update
処理は元々軽くないので、lstat(2)
システムコール発行は、更に応答性を悪くする
そもそも、hg status
するまでは、タイムスタンプ値は必要無い。
という判断から、必要になる(= hg status
を実行する)までは、不要なオーバヘッドを回避しよう、という方針によるものです。
タイムスタンプ値としての -1 は、現在時刻を得る time(2)
システムコールのエラー時戻り値としても使われる、「正常値ではありえない」値です。lstat(2)
で得たタイムスタンプ値と -1 との比較は、常に「不一致」となり、ファイル内容の比較〜最新のタイムスタンプ値の記録が実施されるわけです。
なお、既にお気付きかもしれませんが、作業領域中のファイルのサイズ/タイムスタンプ値が、共に .hg/dirstate
に記録されたものと一致する場合は、たとえ内容が変更されていても、「変更なし」として扱われます。
こういった状況は、意図的な操作を行わない限り、通常の利用シーンでは発生しないでしょうから、効率とのトレードオフの上では、止むを得ないといったところでしょうか。
ちなみに、非対話的な実行等により、同一タイムスタンプ値の範囲で、連続的に改変/コミットが実施される、といった状況に対しても配慮済みですので御安心を。
総所要時間と応答性
例えば以下の2つの実行コストを考えた場合:
hg update
時点における、lstat(2)
でのタイムスタンプ取得hg status
時点における、ファイル内容比較
一定サイズ以上のファイル(= 内容比較が高コスト)であれば、明らかに前者の方が低コストで済みます。
もしも、「hg update
直後に、絶対に hg status
が実行される」と仮定できるなら、hg update
時点にタイムスタンプ値を取得してしまった方が、複数のコマンド実行 hg update
〜 hg status
に渡る総所要時間の観点では有利と言えます。
しかし、hg update
実行後には、hg status
実行無しにファイルの変更が実施されるかもしれません。
ファイルの変更により、以前とサイズが異なれば、先述した判定手順で「変更あり」扱いにすることができます。しかし、もしも変更の結果が、以前と同じサイズだった場合はどうでしょう?
- ファイルの変更〜動作確認を繰り返して、結局元の内容に戻すことになった
- ファイルを変更した結果、たまたま元のファイルサイズと同じになった
上記2つのケースを区別する際に、タイムスタンプ値は役に立ちません。ファイル内容を比較する必要があります。
つまり、このようなケースでは、hg update
時点で lstat(2)
で取得したタイムスタンプ値は、丸々無駄になってしまうわけです。
また、対話的な実行の場合は、総所要時間が少ないことも重要ですが、個々のコマンド実行における個別所要時間が少ない=応答性が良いことも重要になってきます。
Mercurial では応答性の良さを重視して、現在のような実装を採用しているものと思われます。
※ トラブル対処編に続く