Merge Tracking 17:29

Posted at 2010/01/11 17:29, Modified at 2010/01/15 00:57

年の瀬に内輪でライトニングトーク的な催しがあり、マージトラッキングについて話した。

ブランチとマージ

image:1:1263206218-branch-merge.png

多人数で開発する場合、バージョン管理システムと、そのシステム上でのブランチの作成とマージは、必要不可欠だと思う。最近、Flickr はブランチもマージもしない というのを読んでだいぶ驚いたけど、こういうのはかなり稀だ。

マージは面倒な作業だ。その面倒さは、あるソフトウェア x が xa, xb と分岐したときに、それを統合した x(a+b) をつくる、という行為自体がそもそも難しいというのもあるし、いい加減行ベースの diff が野蛮すぎるというのもあると思う。

バージョン管理システムがもっとがんばれるのでは、というのもある。広く使われているバージョン管理システムである Subversion について、マージまわりの機能の貧弱さはよく指摘される。Linus Torvalds も Google で Git について講演した 際に

Merging in Subversion is a complete disaster. (Subversion のマージはマジでひどい)

といっている。

マージトラッキング

Linus の講演は2007年3月におこなわれたもので、その4ヶ月後の OSCON では Subversion commiter である Justin Erenkrantz の講演 でもふれられた。そして2008年、Subversion 1.5 にマージトラッキング機能が導入される。ちなみにマージトラッキングの必要性自体は Linus 関係なく以前からいわれていて、現在の設計のもととなった Daniel Berlin のメール は2006年に投稿されている。


マージトラッキングがあると、実際に、なにがどのように便利になるのだろうか。たとえば stable である trunk に新機能 foo を加えるにあたってブランチを作るとしよう。

% svn copy svn://example.com/app/trunk svn://example.com/app/branches/foo
%

ここはいままでと変わらない。次に、foo ブランチでの開発をしているうちに trunk に大きめの変更が入ったので、それをブランチにとりこみたいとする。

(foo が svn://example.com/app/branches/foo のワーキングコピーだと思ってください)

% cd foo
% svn merge svn://example.com/app/trunk
...
% svn commit

マージの際にリビジョンを一切指定していないところに注目してほしい。これを実現可能にするのがマージトラッキングだ。

さらに進んで、foo の開発が完了したので、これを trunk にいれるとする。

(trunk は trunk のワーキングコピーです)

% cd trunk
% svn merge --reintegrate svn://example.com/app/branches/foo
...
% svn commit

trunk の変更がとりこまれたブランチを、簡単に trunk に戻すことが出来る。

注意点がひとつ。マージトラッキングを使うためにはクライアント、サーバー、レポジトリすべてが Subversion 1.5 以降である必要があり、Subversion をアップグレードしたら、レポジトリを svnadmin (1) で明示的にアップグレードしなくてはいけない。勝手に手元にあたらしい Subversion をいれてもマージトラッキングは使えるようにならない。

Functional Spec. をみると、これは

A repository 1.4.x- doesn't provide any way to retrieve inherited merge info for a path (regardless of client version).

という理由らしい。mergeinfo には親からの「継承」がある。つまり trunk/src/hello.c は trunk/src の mergeinfo を引きついでいる。この親子関係を意識したプロパティの取得が、svn protocol 上に新しく定義された get-mergeinfo に (たぶん) あたり、そこで双方のアップグレードが必要なのだろう。

というわけで、全国の root のかたは Subversion のアップグレードを検討していただければ幸いです。

実装

実装についてもすこし。Subversion には「プロパティ」という仕組みがあり、たとえば

  • 画像ファイルの svn:mime-type には image/gif
  • chmod +x されたファイルの svn:executable には *

が格納される。プロパティは自動で設定されるものもあるし、手動で設定するものもある。また、これはフォルダでも同様だ。例えば autoconf で管理されたプロジェクトだったら

  • src の svn:ignore に Makefile

みたいな設定を手動で追加するのはありがちだと思う。

マージトラッキングでは、このプロパティとして新たに svn:mergeinfo というのが追加される。たとえば trunk の svn:mergeinfo に

/branches/foo:1-24
/branches/bar:1-9,14-18,15

という値があったら、これは内部では

{
  "/branches/foo" => [1..24]
  "/branches/bar" => [1..9, 14..18, 25]
}

このような、パスをキー、リビジョンのリストを値とした連想配列として扱われて

  • /branches/foo のリビジョン1から24
  • /branches/bar のリビジョン1から9, 14から18, 25

は trunk にマージされてますよ、ということを表している。Subversion のサブコマンドのうち svn merge がこのプロパティを読み書きし、svn copy, svn rename のふたつが、複製やファイル名の変更に際に忘れず元ファイルのものを引き継ぐことで、マージトラッキングは実現されている。

Mercurial

Subversion の特徴として、コピーの多用が挙げられる。

ブランチの作成はコピーだし、特定のリビジョンにタグを打つのもコピーだ。さらに、レポジトリは線形に進んでいく。ある changeset n の親は n-1 で、n の子は n+1 だ。線形に進むレポジトリの中で、ブランチとマージを繰り返すフォルダが、向きがあってループがないグラフ (これを DAG - Directed Acyclic Graph という) をつくっている。

image:2:1263206225-svn-repos.png

これに対して、Mercurial のような分散バージョン管理システムでは、DAG の個々のノードが、レポジトリ内のどこかのフォルダではなくレポジトリそのものになっている。

image:3:1263206231-hg-repos.png

たとえば Mercurial では、ある changeset の親は大抵ひとつなのだけど、マージを行ったときだけ親がふたついる changeset ができ、これを merge changeset と呼んでいる。

このマージの扱いは、さすがに後生だけあってきれいだと思う。

おわり

というような話を7分程度でやりました。

はなしのねらいはそこで使っている Subversion のアップグレードで、実装のはなしの前に「でもうちのは 1.4 です」で 外人4コマ をいれて一回落としたりしたのだけど、結局まだアップグレードはなされず。ちゃんと正規の手順でお願いしないとだめみたいです。

2 comments
morita : なるほど。プロパティだったとは。プロパティで実現するならサーバ更新は必要なさそうですが、必要なのはなぜなんでしょうね? (2010/01/11 18:07)
kzys : いわれてみれば気になるので調べて追記しました。たぶん完全プロパティでやってたのが svnmerge.py なんじゃないかなあ。 (2010/01/11 19:18)
riddle for guest comment authorization:
Where is the capital city of Japan? ...

blog.8-p.info加藤和良 の個人的なブログで、プログラミングのはなしが多めです。