彷徨えるフジワラ

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

"hg rollback" のガード機能

TokyoMercurial #1 で話題になった件の詳細シリーズ - その1。

ちなみに、TokyoMercurial #1 開催日の 01/14 から随分時間が経っているのは、決して忘れていたとかではなく、なんとか 2.1 版リリースに間に合わせようと case insensitive filesystem 対応の修正を頑張っていたため。

もっとも 01/19 時点でリリースに向けたコード凍結宣言が出てしまったので、結局修正は間に合わなかったのだけれど .... まぁ、影響範囲が思った以上に広いので、ボチボチ行くとするか。

で、"hg rollback" のガード機能の話。

今更ではあるかもしれないが、"hg rollback" の機能を再確認しておくと、『直前に追加記録された履歴情報を、管理領域から破棄する』というもの。

ちなみに一見同じような『やりなおし』的ニュアンスを持つ "hg revert" の機能は、『特定時点 (※ 通常は親リビジョン時点) の内容で、作業領域中のファイルの内容を上書きする』というもので、履歴情報への影響の有無の点で見れば、両者は全く異なる機能であることがわかる。

"hg rollback" の破棄対象となる『履歴情報の追加記録』を行うのは、以下のコマンド群:

  • commit
  • import
  • unbundle
  • pull

オンラインヘルプ上では、これに加えて "hg push" も対象に列挙されているけれど、実行結果を取り消すためには push 先の連携先リポジトリ上において "hg rollback" を実施する必要があることから、実質的な "hg rollback" 対象は上記の4コマンドと考えて構わない。

# pull との対称性を理解していれば、push について殊更意識する必要は無い

さて、上記のコマンド一覧において、commit が作業領域の内容を元に履歴情報の追加を行うのに対して、import/unbundle/pull は何らかの元データを利用して履歴情報の追加を行う点で、両者は毛色が異なると言える。

例えば import なら export 結果 (or 何らか方法で生成した差分データ)、unbundle なら bunlde 結果、pull なら連携先リポジトリの内容が元データとなる。

普通の利用シーンであれば、これらの元データは有る程度の寿命を持っている筈なので、"hg rollback" による記録内容の破棄が間違い (= 本来なら破棄が不要) だったとしても、再実行によって同じ履歴情報を追記することが可能である。

その一方で、commit 処理は『作業領域の内容』という非常に揮発性の高い内容を元にしていることから、以下の手順で "hg rollback" を実施した場合、同一内容での再度の履歴記録が出来なくなってしまう。

  1. 作業領域の内容を commit (※ REV-A の記録)
  2. "hg update" によって作業領域の親リビジョンを REV-A 以外に変更
    ⇒ 作業領域から REV-A の内容が破棄
  3. "hg rollback" により REV-A の記録を破棄
    ⇒ 履歴情報から REV-A の内容が破棄

この手順で破棄された REV-A の内容は、どこにも元データが残っていないため、記憶を元に再現するのでも無い限り、取り返しが付かない。

通常の利用であれば、上記のような手順はかなりのレアケースではあるのだけれど、やっぱりそれってマズくね?ということなのか、2.0 の時点で "hg rollback" に以下のような仕様が追加された。

破棄対象のリビジョンが、作業領域の親リビジョンと異なる場合、実行を中断。 破棄を強制する場合は -f/--force 指定が必要。

自分の慌て者っ振りに自信がある人は、2.0 以降を使用した方が良いかも(笑)。

さて、以上のことを説明するオンラインヘルプの文章 (@2.0版) は以下のようになっている:

It's possible to lose data with rollback: commit, update back to an older changeset, and then rollback. The update removes the changes you committed from the working directory, and rollback removes them from history.

で、上記に続くのが以下の文章:

To avoid data loss, you must pass --force in this case.

あれ? --force を付けるのは、『データ喪失を防ぐ = To avoid data loss』ためではなくて、『データが喪失されても良い場合』なんじゃないの?動作確認/ソース確認は共に --force 無しなら処理が中断 = データ喪失を防止してるんだけど?

という疑問を投げたところ:

『データ喪失の防止』によって、『例示した処理を強行するためには --force 指定が必要になった』と読んでくれない?(意訳)

ということらしい。つまり、"to avoid data loss" は、"pass --force" の『目的』ではなく、『理由』 (※ 従来は不要だったものが必要になった理由) に対する修飾なのか。英語読解力が無くて御免ね! > Matt

結局、Matt も紛らわしいと思ったのか、この辺の説明は 2.0.2 版でざっくり簡略化されることに。