MercurialとSubversionのメンタルモデルの違い

このエントリは Mercurial Advent Calendar 2012 の17日目です。


SubversionからDVCSへの乗り換えを勧める言説は数多くありますが、以前の私はどれを読んでもピンと来ませんでした。なるほど利点はいろいろあるのだろうけど、そこまでクリティカルなのかな、と。
しかし自分でMercurialを触るようになると、「確かにこれはSubversionには戻れない」と感じるようになりました。
この感覚はどこから来るのか。思うに、それはバージョン管理のモデルの違いです。
私がSubversionを使う際のメンタルモデルは「ディレクトリツリー」です。一方、Mercurialを使う際のメンタルモデルは「リビジョングラフ」です。本来、この二つのモデルに優劣はないでしょうが、ことバージョン管理においてはリビジョングラフの方が都合がよいのです。


Subversionリポジトリの中にディレクトリツリーがあります。
トランクは /trunk というディレクトリのことであり、ブランチは /branches/topicA, /branches/topicB といったディレクトリのことです。これはシステムによって規定されるわけではなく、標準的な構成として広く知られている、いわば「お約束」です。
ブランチやタグの作成とはすなわちディレクトリのコピーです。これもシステムに認識されるわけではなく、「/trunkを/branches/下にコピーして開発を進めるのがブランチである」、「/tags/下にコピーして、以後編集しないものがタグである」といったお約束があるだけです。
なお、ここで言うディレクトリツリーとは、バージョン管理対象となっているファイル・ディレクトリのツリーとは異なる概念です。(ただし、シームレスにつながっています。ブランチやタグを作るのと同じコマンドで、任意のサブディレクトリ(たとえば/trunk/foo/bar)をコピーすることも可能です。)

Subversionでも、TortoiseSVNなどのツールを使えばリビジョングラフを見ることができます。しかし、ディレクトリツリーをもとにグラフを作っているために、Mercurialを知った今にして思えば物足りないところがあります。
たとえば、リビジョングラフの分岐はありますが、合流がありません。そのままではマージしたブランチの履歴をたどることができないため、ファイル・ディレクトリに付属する属性情報にmergeinfoを書き込んでいます。
もうひとつ、ブランチを作成した時点でリビジョングラフが分岐してしまいます。このため、ファイル単位でリビジョングラフを見ると、ブランチ内で一度も更新されてないのに分岐していて不自然です。
Subversionのディレクトリツリーが、ブランチを扱うのに向いてないことの査証といえるでしょう。

リポジトリのクローンが複数あり、それぞれに独立したコミットが追加されたとして、それを統合することを考えてみましょう。
Mercurialでは、マルチプルヘッドが増えることを厭わなければ機械的に統合が可能です。
Subversionは機械的な統合ができません。リポジトリ全体で一意なリビジョン番号を持っており、別のクローンから来たコミットを置く場所がありません。事前にマージしてどちらかを棄却しなければ統合は不可能です。単一のコミットならばどうにかなるかもしれませんが、コミット群だったりブランチができていたりマージされていたりすると、絶望的に面倒でしょう。
Subversionが集中型なのはもともとそれを意図して作られたからというのもあると思いますが、ディレクトリツリーでは分散型に対応できないからである、という面もあると思います。


全てはつながっています。ディレクトリツリーであること、マージがやりにくいこと、集中型であること。リビジョングラフであること、マージがやりやすいこと、分散型であること。
総じてリビジョングラフの方がバージョン管理にふさわしいメンタルモデルであり、ディレクトリツリーによる制約や回り道から解放されると感じます。

(ただ、ディレクトリツリーの利点として、普段使っているファイルシステムのメンタルモデルを流用できるので始めるコストが低い、というのはあるかもしれません。Mercurialは、リビジョングラフという、ファイルシステムとは関係のないメンタルモデルを一から構築する必要があるため、学習コストが比較的高い気がします。)

私はバージョン管理システムをそれほどハードに使っているわけではなく、Subversionでもマージ地獄にハマったりした経験はありません。それでもMercurialの方がバージョン管理をキメてる爽快感があって好きです。