彷徨えるフジワラ

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

obsolete 機能

来月(8月)冒頭にリリースされる Mercurial 2.3 版から、 obsolete (marker) と呼ばれる機能が追加される。

>>>> 2012-08-04 追記: ここから

2.3 リリース直前に、デフォルトでは obsolete 設定の追加を抑止する修正が入った模様。

そのため、2.3.x 版で試験的に obsolete 機能を試してみようと思う場合、以下のようなエクステンションを使って obsolete 機能を有効にしてやる必要がある (後述する evolve エクステンションを有効にする方法も)。

from mercurial import obsolete

def uisetup(ui):
    obsolete._enabled = True

なお、リリース間際のバタバタの影響なのか、59c14bf5a48ca1f8869f2eee の2つのリビジョンで "_enable = False" を追加していて、ソース的には冗長な状態に(笑)。

<<<< 2012-08-04 追記: ここまで

とは言っても、2.3 版リリース時点では:

  • 任意のリビジョンに対する obsolete 設定を行う debugobsolete コマンドの追加
  • obsolete なリビジョンに関連する revset 表記の追加
  • obsolete なリビジョンに絡む push/pull の中断
  • extinct なリビジョンの log 表示の抑止

ぐらいしかユーザの目に触れる部分が無く、現状で obsolete 設定を実施するのが debugobsolete コマンドに限定されていることもあり、おそらく 2.4 版 (2012/11 冒頭リリース) あたりまでは『obsolete ?何それ?』な状態が続くのではないかと思われる。

>>>> 2013-04-06 追記: ここから

evolve エクステンションの有効化によって obsolete 機能を有効にした場合、リンク先に列挙されているように:

  • uncommit
  • fold
  • prune
  • touch
  • gdwon
  • gup
  • evolve

といった obsolete 機能を使用した追加コマンドが追加される。

<<<< 2013-04-06 追記: ここまで

上記のように露出の少ない状況なので、メッセージ翻訳の際も、まず『obsolete とは何なのか?』を理解するのに難儀する有様。

しかし、これがなかなか面白い仕組みなので、折角だから簡単に(ではないかもしれないけれど…)まとめてみた。

ちなみに、以降で述べる Git の挙動に関しては、僕の理解不足から来る誤解が含まれているかもしれないので、気が付いた点があれば、随時ご指摘頂けるとありがたいです。

Mercurial のソースハックにかまけて Git はあまり勉強してないので(笑)

Git/Mercurial における『履歴参照性』と『履歴改変』

obsolete の話をする前に、まずは Mercurial における『履歴参照性』と『履歴改変』に関しておさらいを。

基本的に Mercurial では、一旦記録した履歴は:

  • 常時全てを参照可能
  • 改変できない

とされている。

厳密に言うと、後者は "can not" というよりは "must not" なニュアンス。

共有リポジトリ等に公開されたリビジョンは、既に他のリポジトリへと拡散しているかもしれないので、改変すべきではない

なので『メンバー全員 Mercurial のエキスパートで、且つコミュニケーションも万全』というのであれば、共有状態にある履歴の改変の可否は、『できる』『できない』の話ではなく、単にチームとして『許す』『許さない』レベルの話でしかない。

その一方で Git における履歴の扱いは:

  • 『(git の)ブランチ』 や HEAD などから到達可能なリビジョンだけが参照可能
  • 改変できる

前者に関しては、ハッシュ値による識別情報を直接指定すれば、参照可能ではあるけれど、"hg log" レベルの容易さでの参照性は無い。

しかしこの性質は裏を返すと:

間違ってコミットしたリビジョンがあっても、HEAD や『(git の)ブランチ』等から参照されなくなれば、そのリビジョンは基本的に『存在していない』ことに出来る

ということを意味する。

Git ではこの性質を利用して:

  1. 以前の内容を元に、『別なリビジョンの子として』 and/or 『(ちょっと)違う内容で』リビジョンを作成
  2. HEAD や『(git の)ブランチ』の参照先を、元リビジョンから新規リビジョンに切り替え
  3. 元リビジョンは(容易には)参照できなくなるので、放置しておいても、利用者から見れば『破棄』相当に見える
  4. その一方で、HEAD や『(git の)ブランチ』が参照する新規リビジョンは参照可能
  5. 結果として、ユーザには『履歴が改変された』ように見える

という具合に『履歴改変』を実現している (と僕は理解している)。

『履歴を改変できる』と謳う Git が、実は履歴(の見え方)を『切り替え』ているだけなのに対して、『履歴を改変できない(しない)』と謳っている Mercurial では、MQ/rebase/histedit での履歴改変の際には、対応する新規リビジョンを作成したら、改変対象リビジョンをバンバン破棄することで、履歴を『書き換え』ている、という対比はなかなか面白い。

通常は目に付かないリビジョンが隠れている Git では、『(git の)ブランチ』や HEAD から参照されていないリビジョンは、いわゆる『ゴミ集め』の一環として、一定期間が経過したらザックリ捨てられる。

『参照されていないものは捨てられても当然』と考えるか、『明示的に不要性を表明してないのに破棄するとは何事か』と考えるかで、この挙動は全然異なって見えてくるけれど、それも『履歴参照性』がどのように設計されているかと密接に関係している (⇒ 自然とそれに応じた運用方法になる) と考えれば、一概にどちらか一方が有利/不利という事にはならない気がする。

とりあえずここで重要なのは:

Mercurial において履歴改変があまり推奨されないのは、常時全てのリビジョンが見える(見えてしまう)ため

obsolete 機能

さて、今回導入される obsolete は、端的に言えば:

『このリビジョンは破棄された(or いずれ破棄される)』という情報を伝播させる

という機能と言える。

もうちょっと正確に言うと、各 obsolete 情報は以下の情報から構成されていて:

  • OBSOLETED: obsolete 対象リビジョン(1 revision/1 obsolete)
  • REPLACEMENT: obsolete 主体リビジョン(N revision/1 obsolete)

"[REPLACEMENT] obsolete OBSOLETED" という表記をした場合、obsolete 情報は以下のような意味を持つことになる。

表記意味現行対応機能
[] obsolete AA は単純に不要なリビジョン MQ の strip
[B] obsolete AB による A の置き換え commit --amend や、MQ の qrefresh、rebase など
[B, C] obsolete AA の B/C への分割 MQ + record での分割操作的な運用相当?
[C] obsolete A
[C] obsolete B
A/B の C への統合 MQ の qfold や histedit の fold、collapse など

obsolete 機能の提案者が、上記の各ケースを履歴ツリー付きで説明しているページを公開しているので、そちらも参照のこと。ちなみにこのページでは、"[REPLACEMENT] obsolete OBSOLETED" 表記と図では異なる識別子を使っているものがあるので、参照する場合は要注意(笑)。

さらに obsolete 機能では:

OBSOLETED なリビジョンの子孫が全て OBSOLETED ならば、一連のリビジョンは、リビジョン表示や push/pull の対象から除外したり、いっそリポジトリから破棄しても良い

と定義していて、『OBSOLETED なリビジョンの子孫が全て OBSOLETED』なものを、revset では "extinct" という述語で列挙できるようになっている: "extinct" は『死に絶えた』等の意味。

以上のことから、MQ/rebase/histedit 等の履歴改変機能は:

  • 改変後リビジョンの記録
  • "[改変後] obsolete [改変元]" な obsolete 情報を記録
  • 改変元リビジョンは extinct になるので、表示や push/pull 対象から除外 (+ 早々に破棄)

といった実現方式に順次移行するものと思われる。

通常は、上記のような履歴改変機能によって自動的に obsolete 情報が付与され、エンドユーザ自身は、obsolete 云々は意識しなくても良いようにするだろう。

Git の場合は『参照の切り替え』という若干暗黙的な方法で指定していた『リビジョン破棄の可否』を、もう少し明示的な情報として伝播させようという位置付けだと理解すれば良いのではないかな?

現行方式と obsolete の比較も参照のこと

ちなみに、『obsolete な先祖を持つ、非 obsolete なリビジョン』を、revset では "unstable" という述語で列挙できるようになっているのだが、これは:

非 obsolete な親リビジョンの下に、(rebase とかで)ぶら下げ直すよね? それまでの一時的な状態であって、永続性は無いよね?

ということから由来した名前っぽい。