この前、10 年以上前に趣味で作っていたフリーソフトについてメールで質問が来た。もはや完全に記憶から消えているだけでなく、いま使っている PC にソースコードもない。何も分からない、答えられない。
そのままでは古いソースコードも成仏しきれない。供養するために、古い HDD を引っ張り出して探したところ、自宅サーバーをやってた HDD の中に CVS レポジトリーが見つかった。せっかくなので、Git に変換して GitHub で公開してみた (その1, その2)。これで成仏できるだろう。
そこで、この記事では CVS レポジトリーを Git に移行した手順をまとめておく。レガシーな CVS から Git に移行したい人の参考になるとうれしい。
git cvsimport の使い方
Git には git-cvsimport というコマンドがある。CVS の履歴を Git に変換してくれる。
CVS はファイルごとに履歴を保存する構造になってるんだけど、cvsps
というツールを組み合わせることで、同時に変更したファイルを 1 コミットとして扱ってくれるようになる。
大きいレポジトリーだと結構時間かかる。cygwin だと超絶に時間がかかったので、UNIX 上で実行した。Git for Windows には git-cvsimport
がない。
自分は次のように実行した。
git cvsimport -v -i -R -A author-conv-file.txt \
-d :local:/path/to/CVS <module>
それぞれのパラメータの意味は次の通り。詳しくは git-cvsimport を参照のこと。
-v
: 出力を verbose にする。-i
: インポートだけを行ってチェックアウトしない。-R
: 「CVS のリビジョン番号」と「それに対応する Git のコミット」の対応付けを.git/cvs-revisions
に出力する。一応生成しておく。-A
: ユーザー名の変換テーブル。詳細は後述。-d
: CVSROOT のパス。cvs コマンドで-d
に指定するやつ。もしくは、環境変数のCVSROOT
に指定してるやつ。<module>
: モジュール名。サブディレクトリを指定することもできる。
-d
で指定したパスに CVSROOT
ディレクトリがないと Expected Valid-requests from server, but got: E Cannot access /path/to/CVS
というエラーになる。ダミーでよいので mkdir /path/to/CVS/CVSROOT
しておけば回避できた。
author-conv-file の書き方
author-conv-file には「CVS のユーザー名」から「Git の Author 名・メールアドレス」への対応を記述していく。
user1=User 1 <user1@example.com>
user2=User 2 <user1@example.com> Asia/Tokyo
1.8.1 からはタイムゾーンも指定できるようになっている。それ以前のバージョンだと、すべて UTC でのコミットとなってしまう。
実行が終わったら git log | grep Author: | sort | uniq
を実行して、author-conv-file に漏れがないか確認しておくといいだろう。
cvsps のキャッシュ
個人的に悩まされたのが、cvsps
が結果を ~/.cvsps
にキャッシュすること。CVS レポジトリーを変更したあとに git cvsimport
を実行しても反映されない。そういうときは、rm -rf ~/.cvsps
して再実行するとよい。
文字コードの変換
いまなら「全部 UTF-8 でやっちゃえ」となるんだけど、CVS 全盛の時代は Windows は ShiftJIS で、UNIX 上では EUC-JP がまだまだ主流だった。
コミットログやファイル名が UTF-8 でない場合は、ちょいとソースコードを修正する必要がある。
以下は 1.7.3.1 の git-cvsimport
に対する修正。
--- /usr/local/libexec/git-core/git-cvsimport.bak
+++ /usr/local/libexec/git-core/git-cvsimport
@@ -722,6 +722,11 @@
sub update_index (\@\@) {
my $old = shift;
my $new = shift;
+
+ # file name
+ use Encode qw/decode encode/;
+ $_ = encode('utf8', decode('shift-jis', $_)) for @$old;
+ $_->[2] = encode('utf8', decode('shift-jis', $_->[2])) for @$new;
+
open(my $fh, '|-', qw(git update-index -z --index-info))
or die "unable to open git update-index: $!";
print $fh
@@ -810,6 +816,11 @@
substr($logmsg,32767) = "" if length($logmsg) > 32767;
$logmsg =~ s/[\s\n]+\z//;
+ # commit log
+ use Encode qw/encode decode/;
+ $logmsg = encode('utf8', decode('shift-jis', $logmsg));
+
if (@skipped) {
$logmsg .= "\n\n\nSKIPPED:\n\t";
$logmsg .= join("\n\t", @skipped) . "\n";
ここではコミットログやファイル名が Shift_JIS だという前提で書いてある。
自動判定したい場合は
+ use Encode::Guess qw/euc-jp shiftjis/;
+ $logmsg = encode('utf8', decode('guess', $logmsg));
のように書けばいける。
歴史の書き換え
push する前に、インポート結果を十分に確認しておきたい。次のような点に注意して作業した。
コミットログを書き換えたいなら git rebase -i
でやっちゃう。
不要なファイルがあったら git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
のようにして消しておく (参照: Git - 歴史の書き換え)。
一度でも歴史を書き換えたら、commit date が実行時刻になってしまうので、git filter-branch --env-filter 'GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE; export GIT_COMMITTER_DATE'
で author date にそろえておく (参照: git rebase without changing commit timestamps - Stack Overflow)。
このあたりは git-cvsimport
の Tips というよりも、Git での歴史書き換えの Tips。
まとめ
負の遺産 CVS を捨てて、健全な Git ライフを送ろう!