miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

git-svn dcommit 時に svn copy させるには

Twitter で、

なんてことをつぶやいてたんですけど、git-svn でもコピーの検出はできるっぽいことに気づいたので訂正です。

結論としては、

git svn dcommit -C50

みたいに -C オプションに続けて+二桁の数字で「これ以上一致がある場合はコピーとみなすよ」という値(パーセント)を渡してあげれば svn copy 相当の処理を行ってくれます。以下詳細です。

きっかけ

今のプロジェクトは中央リポジトリSubversion なんですけど、Git に慣れるとローカルでコミットできないのは不便で。ソースコードがらみの作業は git-svn で行っています。そこでたまに「svn copy したいなー」「svn propset したいなー」と思うことがあるので、git-svn のソース(Perl スクリプト)を読んでみたんですが、どうも

--copy-similarity, -C <num>

のオプションを指定していればコピーの検出もやってるみたいで。これ git svn コマンドをサブコマンドなしで叩いたときには一覧にあるけど、man には書かれてないような・・・。(--find-copies-harder のほうは載ってるけど)

さっそく実行してみる→失敗

なにはともあれ、実装はされているようなので、試しに実行してみます。

>git svn dcommit -C
Option C requires an argument

あれ?引数省略できないの?

じゃあ git-diff-tree のヘルプを参考に、とりあえず 90% でも指定してみようかな。

>git svn dcommit -C90%
Unknown option: %

えー。

ソースを読んでみる

どうも処理に問題がありそうなので、ソースをもう少しまじめに読んでみます。

引数の解析には Getopt::Long が使われているようで。

		'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity

こんな感じで型として =i が指定されているせいで、整数しか渡せないし省略もできないと。ちょっと処理を書き換えれば期待どおりの動作に書き換えられそうだけど、-C オプションのデフォルト値って何なんだろう?

・・・と、マニュアルを探したけど見つからないので、またソースを読んで。

#define MAX_SCORE 60000.0
#define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
#define DEFAULT_BREAK_SCORE  30000 /* minimum for break to happen (50%) */
#define DEFAULT_MERGE_SCORE  36000 /* maximum for break-merge to happen (60%) */

ということで、-M や -C のデフォルト値は 50% みたいです。

素直にデフォルト値を指定するなら、

$SVN::Git::Editor::_cp_similarity = '50%';
#(中略)
		'copy-similarity|C:s'=> \$SVN::Git::Editor::_cp_similarity

になるんでしょうけど、「このオプションが指定されている場合だけコピー検出を有効にする」という動作にしたいので、これは NG。以下のように変更すれば期待どおりの動作になりました。

--- git-svn.org	Fri Feb 04 11:45:24 2011
+++ git-svn	Wed Sep 14 19:32:29 2011
@@ -126,7 +126,7 @@
 		'rmdir' => \$SVN::Git::Editor::_rmdir,
 		'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder,
 		'l=i' => \$SVN::Git::Editor::_rename_limit,
-		'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity
+		'copy-similarity|C:s'=> sub { $SVN::Git::Editor::_cp_similarity = $_[1] || '50%' }
 );
 
 my %cmd = (

git-svn に手を入れたくない場合は

数値の解析処理を読んでみると、

まあこんな感じの処理になってまして。0.5 と 50% が同じ意味になるのはいいんですが、整数の場合は数字が出現するたびに分母も 10 倍にしているので、5、50、500、5000・・・あたりの数字を指定しても、同じ意味になります。まあ二桁の数字で渡せばパーセント指定するのと同じ意味になるので、冒頭の説明どおり「値を二桁でパーセント指定する」と考えておくのが間違いがなくていいような気がします。

補足

上記のとおり -C オプションを付加すれば dcommit 時にコピー元の検出を行ってくれるわけですが、git がファイル単位の管理しかできないので、フォルダをリネームしたようなケースでも、

  • 新規フォルダ作成
  • 中のファイルを svn copy

というような動作になります。(ディレクトリ単位のトラッキングが行えません。)

また、コピー元ファイルの検出は自動で行うので、おそらく意図されないコピー元から svn copy したとみなされるケースもあるでしょう。このあたり気持ち悪いですが、ファイル単位の履歴が残せないよりはマシなので割り切って使おうと思っています。

結局まだ試せていないんですが、hgsubversion はファイルの移動やコピーの検出をやってくれそうな雰囲気でした。bzr-svn は移動は検出するけどコピーは検出しない(そもそも Bazaar がコピーを扱えない)らしいです。

hgsubversion、bzr-svn どちらかひとつでも svn propset 相当のことができるなら乗り換えようと思ったんですが、どちらもこのようなことはできないようです。残念。

感想というか愚痴というか

  • git-svn のマニュアルに -C オプションの説明が載ってない
  • git-svn のオプション指定が間違っている
  • git-tree-diff 等のマニュアルに -M や -C のデフォルト値が載っていない
  • オプション値の解析処理がちょっと特殊(仕様なのかなぁ・・・)
  • タイミング悪く kernel.org がハッキングを受けて復旧中

ということで、無駄に大変でした。最初の 3 つについては改善要望を出したいところですけど、ちょっと余力がないので、気が向いた方はお願いします・・・。

(2011-11-07 追記)dcommit 前に確認する場合は

git diff -C50 HEAD~1 HEAD

のように -C つきで diff を取得すれば、どういう形でコミットされるか確認できます。

「同一ファイルとみなしてほしいのに別ファイルになっちゃうな」という場合は -C の数値を下げます。

git diff -C10 HEAD~1 HEAD

期待どおりの diff が得られたら、その数値を使って dcommit してやればよいです。

git svn dcommit -C10

本当は --dry-run つきで dcommit して、どういう差分がコミットされる予定か出力して・・・

>git svn dcommit --dry-run -C50
Committing to https://****/svn/***/src/common/trunk ...
diff-tree b6f4e217870317390ebcd2f4fe7410b8b5220b96~1 b6f4e217870317390ebcd2f4fe7410b8b5220b96

この diff-tree を -p と -C を付加して

git diff-tree -p -C50 b6f4e217870317390ebcd2f4fe7410b8b5220b96~1 b6f4e217870317390ebcd2f4fe7410b8b5220b96

のように実行するのがお作法なんでしょうけど、わかりにくいのでふつうに diff でいい気がします。

(2012-02-15 追記)うまくコピー元が検出できない場合は

  • C はそのチェンジセットに含まれるファイルしかコピー元として認識してくれないので、


git diff -C50 --find-copies-harder HEAD~1 HEAD
のように --find-copies-harder を付加すればよいです。-C -C のように指定しても同じ意味のようです。