彷徨えるフジワラ

年がら年中さまよってます

パーフェクト dirstate 〜 基礎編

このエントリは、Mercurial Advent Calendar 201320日目です。

エントリを公開するのは 12/29 ですが、諦めたらそこでクリスマス終了です。諦めるまでがクリスマスです(笑)。

Mercurial では、hg status を使うことで、作業領域中の管理対象ファイルに対する変更の有無を確認できます。

しかし、hg status の都度、履歴に記録されたファイル内容と、作業領域中のファイルの比較処理をやっていたのでは、履歴やファイルサイズが一定規模以上になった場合、とても使い物になりません。

そこで Mercurial は、管理領域中のファイル .hg/dirstate において、管理対象ファイルに関する各種情報を、一種の情報キャッシュとして保持することで、状態確認における性能劣化を抑止しています。

本エントリでは、.hg/dirstate の基本的な機能について説明します。よろしければ、トラブル対処編も御参照ください。

なお、説明の便宜上、基本的な理解に支障のない範囲で、多少簡略化して説明している部分もあります。厳密な挙動を知りたい方は、ソースコードを参照してください。

.hg/dirstate 管理の概要

hg update によって、特定のリビジョン時点のファイル a.txt が作業領域に取り出される際の、処理の流れは以下のようになっています。

  1. 対象リビジョンの履歴情報から、a.txt の内容を取り出す
  2. 作業領域に、a.txt の内容を書き出す
  3. 必要に応じて、a.txt に実行ビットを設定
  4. .hg/dirstatea.txt 向けエントリの情報を更新

更新の際に .hg/dirstate 中の a.txt 向けエントリには、以下のような値が格納されます。

  • 実行ビット:履歴に記録された値
  • ファイルのサイズ:書き出した内容のバイト値
  • タイムスタンプ値:「未設定」 (unset) をあらわす -1

その一方、hg status によって、作業領域中のファイルの状態を確認する場合の、処理の流れは以下のようになっています。

  1. サイズが異なれば、「変更あり」とみなす
  2. モードが異なれば、「変更あり」とみなす
  3. 作業領域中の a.txt から、タイムスタンプを取得
  4. タイムスタンプが異なれば、変更の可能性あり
    1. 履歴情報から、対象ファイルの内容を取り出す
    2. 作業領域中の、対象ファイルの内容と比較
    3. 内容が異なれば、「変更あり」とみなす
    4. .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 updatehg status に渡る総所要時間の観点では有利と言えます。

しかし、hg update 実行後には、hg status 実行無しにファイルの変更が実施されるかもしれません。

ファイルの変更により、以前とサイズが異なれば、先述した判定手順で「変更あり」扱いにすることができます。しかし、もしも変更の結果が、以前と同じサイズだった場合はどうでしょう?

  • ファイルの変更〜動作確認を繰り返して、結局元の内容に戻すことになった
  • ファイルを変更した結果、たまたま元のファイルサイズと同じになった

上記2つのケースを区別する際に、タイムスタンプ値は役に立ちません。ファイル内容を比較する必要があります。

つまり、このようなケースでは、hg update 時点で lstat(2) で取得したタイムスタンプ値は、丸々無駄になってしまうわけです。

また、対話的な実行の場合は、総所要時間が少ないことも重要ですが、個々のコマンド実行における個別所要時間が少ない=応答性が良いことも重要になってきます。

Mercurial では応答性の良さを重視して、現在のような実装を採用しているものと思われます。

トラブル対処編に続く