Mercurial の Subversion convert extension

Mercurial 0.9.5 以降には,他の SCM からレポジトリを取り込むための convert extension が装備されました(⇒ ConvertExtension - Mercurial)。Subversion のレポジトリから convert するための extension ももちろん含まれています。

利点も多いのですが,残念ながらまだあまり練られていない印象で,欠点が多くなっています。

利点
  • Mercurial に標準添付されている
  • trunk, branches, tags というディレクトリを認識して,自動的に branch や tag を貼ってくれる
    • Mercurial には,branch, tag を貼るための機構(changeset の属性)が builtin されています。Subversion convert extiosion では,Subversion での慣例的なディレクトリ branches 等を認識して,Mercurial システム上の branch や tag に変換してくれます。うまく動けば非常に面白いです。
欠点(バグ)
  • あくまで import サイドの converter であり,push できない(仕様)
  • branch がうまく切れないことがある
  • tag がうまく貼られないことが多い
  • 削除されたファイルをうまく取り扱えない

これらは Mercurial に欠陥があることを示しているわけではありません。convert extension の歴史が浅いため,まだきちんと動いていないというだけです。ですが,まだ実運用には難しいようです。hgsvn を使う方が現状まだマシかと。

実際に convert してみる

下記のような Subversion レポジトリを convert します。

% svn cp trunk branches/1.x
% svn ci -m "branch 1.x added"

% svn cp branches/1.x tags/1.0
% svn ci -m "tag 1.0 released"

% svn cp trunk branches/2.x
% svn ci -m "branch 2.x added"

% svn cp branches/2.x tags/2.0
% svn ci -m "tag 2.0 released"

branch を2つ切っているのは,「convert extension で branch がうまく切れないことがある」バグを回避するためです。

hg convert コマンドで取り込んでみましょう。

% hg convert http://localhost/svn/example example
initializing destination example repository
scanning source...
sorting...
converting...
3 initial import
2 base.txt added
1 branch 1.x added
0 branch 2.x added

% cd example

% hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

無事 convert できたようです。

どのような branch があるのか確認してみます。

% hg branches
2.x                            3:48531de71762
1.x                            2:003612635ee3
default                        1:bebb1d832479

% hg branch
default

Subversion の branches に対応して,1.x と 2.x が切られているようです。trunk にあたるのは default のようです。Working copy も default branch になっています。

では heads の様子はどうでしょうか(一部出力を省略しています)。

% hg heads
changeset:   3:48531de71762
branch:      2.x
tag:         tip
parent:      0:b0a5ad784788
summary:     branch 2.x added

changeset:   2:003612635ee3
branch:      1.x
parent:      0:b0a5ad784788
summary:     branch 1.x added

changeset:   1:bebb1d832479
summary:     base.txt added

各 branch に呼応して heads が存在しています。

では,tag の扱いはどうなっているでしょうか。

% hg tags
tip                                3:48531de71762

レポジトリの最新 changeset を示す tip しか存在していません。tags はうまく convert できなかったようです。

tags をうまく convert するには?

branches についてはおおむねうまく convert されているようですが,tags については convert されませんでした。いろいろ試行錯誤を繰り返した結果,tags を svn cp した後に trunk に何らかの変更がないと changeset として認識してくれないことがわかりました。

じゃあ実例をば。

% svn cp trunk branches/1.x
% svn ci -m "branch 1.x added"

% svn cp branches/1.x tags/1.0
% svn ci -m "tag 1.0 released"

% svn cp trunk branches/2.x
% svn ci -m "branch 2.x added"

% svn cp branches/2.x tags/2.0
% svn ci -m "tag 2.0 released"
% touch trunk/dummy_2.0
% svn add trunk/dummy_2.0
% svn ci -m "dummy_2.0 added"

% touch branches/2.x/for_2.1
% svn add branches/2.x/for_2.1
% svn ci -m "for_2.1 added"

% svn cp branches/2.x tags/2.1
% svn ci -m "tag 2.1 released"
% touch trunk/dummy_2.1
% svn add trunk/dummy_2.1
% svn ci -m "dummy_2.1 added"

tags を貼ったあとに,trunk にダミーファイルをこしらえて commit しています。またテストのため,tags/2.1 以降では branches/2.x に for_2.1 というファイルを置いています。

convert します。

% hg convert http://localhost/svn/example example
initializing destination example repository
scanning source...
sorting...
converting...
6 initial import
5 base.txt
4 dummy_2.0 added
3 dummy_2.1 added
2 branch 1.x added
1 branch 2.x added
0 for_2.1 added
updating tags

% hg update
4 files updated, 0 files merged, 0 files removed, 0 files unresolved

「updating tags」というメッセージからすると,うまくいったくさいですね。

tag の状態をみてみましょう。

% hg tags
tip                                8:959cc2d717d1
2.1                                6:4e2662fb6936
2.0                                6:4e2662fb6936
1.0                                5:50ddb61bc526

おお,先ほどと違って tag が貼られていますよ。

ん,でも 2.0 と 2.1 が同じ changeset を指していないですか?

% hg update -C 2.0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved

% ls
base.txt


% hg update -C 2.1
0 files updated, 0 files merged, 0 files removed, 0 files unresolved

% ls
base.txt


% hg update -C 2.x
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

% ls
base.txt  for_2.1

正しくは tag 2.1 では「for_2.1」というファイルが存在してしかるべきです。ですが,2.1 と 2.0 が同一になってしまっているので for_2.1 というファイルもありません。branch 2.x ではきちんと存在していますが。

このように,0.9.5 では仮に tag が import されていたとしても,正しくハンドリングされないようです。

削除されたファイルをうまく取り扱えない

さらに,より致命的なバグも存在します。

下記のような Subversion repository があるとします。

% mkdir a
% touch a/x.txt a/y.txt
% svn add a
A         a
A         a/x.txt
A         a/y.txt
% svn ci -m "a/x.txt, a/y.txt added"

% svn rm a/x.txt
D         a/x.txt
% svn ci -m "a/x.txt removed"

一度 a/x.txt を作成し,その後削除しただけです。

このレポジトリを convert すると……

% hg convert http://localhost/svn/example example
initializing destination example repository
scanning source...
sorting...
converting...
2 initial import
1 a/x.txt, a/y.txt added
0 a/x.txt removed
** unknown exception encountered, details follow
** report bug details to http://www.selenic.com/mercurial/bts
** or mercurial@selenic.com
** Mercurial Distributed SCM (version 0.9.5)
Traceback (most recent call last):
  File "/usr/bin/hg", line 14, in ?
    mercurial.dispatch.run()

...... snip ......

libsvn._core.SubversionException: ("PROPFIND request failed on '/trunk/a/x.txt'", 175002)

エラーを吐いて止まってしまいました。