彷徨えるフジワラ

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

パーフェクト dirstate 〜 トラブル対処編

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

本エントリでは、基礎編での .hg/dirstate に関する説明を踏まえて、いくつかのよくあるトラブルに対して、その原因と対処方法について説明します。

プラットフォームによるモード判定の可否

Mercurial では、いわゆる「実行権限」ビットの有無も、履歴管理の対象になります。

但し、Windows 環境では、以下のように「実行権限」に関する情報は無視されます。

  • 作業領域へのファイルの取り出しでは、「実行権限」の有無は無視
  • 作業領域中のファイルに対する「実行権限」の変更は、履歴記録の際に無視

WindowsNTFS の場合、「親フォルダの権限設定の引き継ぎ」等の機能があり、UNIX 系環境が期待する権限設定周りの話とは馴染まないのが、上記のような「実行権限」を無視する挙動の理由だと思われます(あくまで私自身の推測ですが)。

なお、作業領域中のファイルに関しては、「実行権限」の有無を無視しますが、.hg/dirstate への記録/更新は、どの環境でも同じ挙動になります。

そのため、Windows 環境で作業領域が更新されたリポジトリを、以下のような方法で、そのまま UNIX 系環境に持って行った場合:

作業領域中のファイルと .hg/dirstate 中の情報の間で、「実行権限」に関する食い違いが生じる可能性があります。

このようなケースでは、以下のような現象が見られる筈です。

  • hg status 出力で想定外のファイルが「変更あり」扱いされる
  • hg diff 出力が無い

UNIX 環境側であれば、hg diff--git/-g オプションを指定することで、「実行権限」変更の有無を確認することができます。

$ hg status
M a.sh
$ hg diff
$ hg diff --git
diff --git a/a.sh b/a.sh
old mode 100755
new mode 100644

想定外の「実行権限」変更が見られる場合は、対象ファイルの実行権限設定を、以下の要領で本来の状態に戻して下さい。

$ hg revert 対象ファイル

もしも hg diff --git でも差分が表示されない場合は、後述する eol エクステンション絡みの現象かもしれませんので、そちらの説明も参照してみてください。

なお、設定ファイルにおいて [diff] git = True を指定することで、常時 Git 形式差分を使うようにすれば、hg diff への --git オプション指定無しでも、「実行権限」変更の有無が確認できるようになります。

但し、Git 形式差分を選択した場合、バイナリファイル変更の際には、BASE64 形式のバイナリ差分も出力されてしまいますので、各自の利用形態に応じて設定するようにしてください。

Mercurial がデフォルトで Git 形式差分を生成しないのは、Git 形式差分を受理できない、旧来の patch コマンド等への配慮によるものです。決して、Git へのライバル意識によるものではありません(笑)。

eol による変換

eol エクステンションが有効な場合、hg status による作業領域中のファイルの状態確認は、以下の流れになります。

  1. サイズが異なれば、変更あり
  2. モードが異なれば、変更あり
  3. 作業領域中の a.txt から、タイムスタンプを取得
  4. タイムスタンプが異なれば、変更の可能性あり
    1. 履歴情報から、対象ファイルの内容を取り出す
    2. 作業領域中の対象ファイルを、.hgeol での設定に従って、履歴格納時の形式に変換
    3. 対象ファイルの内容と比較
    4. 内容が異なれば、変更あり
    5. .hg/dirstate 中のエントリを、最新の情報で更新

eol による介入は、サイズ確認の後になってしまいますので、「.hgeol 設定とファイルの改行形式」の一致/不一致と、hg status での「変更あり」出力の有無は、感覚的な期待と必ずしも一致するとは限りません。例えば、以下のような食い違いが発生するかもしれません:

  • 作業領域中のフィルの改行形式と .hgeol の設定が食い違っている:
    作業領域中のファイルの「サイズ」/「タイムスタンプ」が、hg update 時に .hg/dirstate に記録されたものと一致するなら、「変更なし」扱い
  • 作業領域中のフィルの改行形式と .hgeol の設定が一致している:
    hg update 後に作業領域中のファイルの改行形式を変更した場合、.hg/dirstate 中の「サイズ」情報との食い違いから、「変更あり」扱い

.hgeol に対する事後変更を元に、作業領域中のファイルの改行形式を変更したい場合は、以下の様な手順を踏んでください。

  1. .hgeol の設定を変更
  2. 対象ファイルを Mercurial経由せずに削除
    UNIX系環境なら rm コマンド、Windows環境なら del コマンドやエクスプローラ経由)
  3. hg revert 対象ファイル を実施
  4. hg debugrebuildstate を実施

hg revert は、作業領域中のファイルの内容/実行権限設定は復旧しますが、.hg/dirstate の内容には関与しません。

そのため、変更後の .hgeol 設定に従って書き出したファイルのサイズは、.hg/dirstate に記録されているサイズ(= 以前の .hgeol 設定に従って作業領域に書き出したファイルのサイズ)とは、異なることになります。結果として、状態判定処理では、内容を比較する前の段階で、サイズ違いにより「変更あり」とみなされてしまいます。

そこで hg debugrebuildstate の出番です。

hg debugrebuildstate は、.hg/dirstate に保持されている全てのファイルのサイズ情報を、「未設定」状態を表す -1 に設定します。

サイズ情報として -1 を保持するエントリが検出された場合は、「同一サイズでタイムスタンプ違い」時と同じ手順で、内容の比較〜.hg/dirstate エントリ情報の更新が実施されます。

「内容一致」と判断された場合のエントリ情報更新では、サイズ情報は作業領域中のファイルのサイズ、つまり改行コード形式変更後のファイルサイズで更新され、以後のファイル状態判定には、変更後の改行コード形式に従ったファイルサイズが用いられるわけです。

なお、hg debugrebuildstate を実行した場合、hg addhg remove 等で指示した、追加登録/登録除外の情報も破棄してしまいますので、未コミットの追加/除外が残っている場合は、十分注意して実行してください。