諸般の事情でCVSリポジトリのインデックスを作成する必要があるので一旦Gitリポジトリを経由するのはどうかと考えた。
... 巨大なCVSリポジトリのルーズな運用に耐えるのは困難っぽいので諦めかな。。なるべく自前のRCSパーサを書くのは避けたいんだけど。。
-
https://okuoku.visualstudio.com/_git/gcc-cvs-new
- 実際にgccのCVSリポジトリを変換してみた
CVSリポジトリのミラー
普通は rsync
サーバを併設していることが多いのでそれを使う。例えば gcc のリポジトリであれば、
rsync -azv --delete rsync://gcc.gnu.org/gcc-cvs gcc-cvs
gccは開発を既にSubversionに移行していて、更にGitへの移行も今年を目処に進行中だけど、(EDIT: 完了した https://gcc.gnu.org/git/gitweb.cgi?p=gcc.git )Subversionリポジトリに入っているCVS時代の履歴は破損しているので完全な歴史はCVSに埋まっている。 ... そもそもCVSはそこまでロバストな仕組みではないので完全な履歴は考古学者の如く手で繋ぎ合わせる必要がありそう。
CVSを現役で開発に使用しているものとしてはOpenBSDやNetBSDがある。
rsync -azv --delete rsync://anoncvs4.usa.openbsd.org/cvs/src openbsd-src
rsync -azv --delete rsync://rsync2.jp.NetBSD.org/netbsd-cvs/src netbsd-src
cvs-fast-export
のビルド
ESRがメンテナンスしている cvs-fast-export はCVSリポジトリを読み取ってgitのfast-import形式で書き出すことができる。
動作には cvs
、ビルドには asciidoc
、 bison
、 flex
が必要なのでそれぞれバージョンを確認して、
$ a2x --version
a2x 8.6.10
$ bison --version
bison (GNU Bison) 3.0.4
$ flex --version
flex 2.6.4
$ cvs --version
Concurrent Versions System (CVS) 1.12.13-MirDebian-25 (client/server)
普通に make
でビルドする。
make
普通に make install
すると /usr/local
に入るが、prefixは手で指定できる。今回は /opt/cvs-fast-export/bin
にインストールした。
sudo make DESTDIR=/opt prefix=/cvs-fast-export install
変換
cvsconvert
コマンドが提供されているので、それにモジュールを1つ渡せばカレントディレクトリにGitリポジトリが作成される。
cvsconvert -pv gcc-cvs/gcc
cvsconvert
は生成されたリポジトリのブランチやタグを実際にチェックアウトして一致を確認してくれる。このため、変換の完了にはそれなりの時間が掛かる。
CVS → Git 変換の難しさ (タイムパラドックス)
Git変換の難しさは cvs-fast-exportのman page の"RCS/CVS LIMITATIONS" 節に詳しいが、そもそものセマンティクスが大きく異なるのでCVSリポジトリをGitに変換するのはあまり簡単な問題ではない。
最大の違いは、 CVSは 原則的にファイル単位でしかバージョンを管理しない 点で、CVS → Git 変換をする際には "ファイル毎にバラバラになったコミット処理を1つに纏める" 処理が必要になる。
例えば、CVSにおけるtagは、 複数のファイルの異なるリビジョンに同じ名前を付けている だけで、ファイル間の関係性についてはどこにも記録されない。
file1 file2 file3 file4 file5
1.1 1.1 1.1 1.1 /--1.1* <-*- TAG
1.2*- 1.2 1.2 -1.2*-
1.3 \- 1.3*- 1.3 / 1.3
1.4 \ 1.4 / 1.4
\-1.5*- 1.5
1.6
(SCCSやCVSのような伝統的なVCSはファイル単位でリビジョン番号を振り、かつ、管理コストを節約するために連番ではなく枝番を使用している。
例えば、 file 1 のリビジョン 1.3
からブランチを作成した場合、あらたに採番されるブランチ番号 .2
と ブランチ上のファイルの新規リビジョン .1
を組み合わせて 1.2.2.1
のようなリビジョン番号が振られる。このため、ファイルのリビジョン番号は常に偶数個の番号の組合せとなる。)
例えば、 gcc の x86-64-branch
ブランチ の内容は、
$ grep -r x86-64-branch: gcc-cvs/gcc/* | head
gcc-cvs/gcc/Attic/config.if,v: x86-64-branch:1.7
gcc-cvs/gcc/COPYING,v: x86-64-branch:1.4
gcc-cvs/gcc/COPYING.LIB,v: x86-64-branch:1.4
gcc-cvs/gcc/ChangeLog,v: x86-64-branch:1.467.2.10
gcc-cvs/gcc/INSTALL/README,v: x86-64-branch:1.7
gcc-cvs/gcc/MAINTAINERS,v: x86-64-branch:1.205.2.1
gcc-cvs/gcc/Makefile.in,v: x86-64-branch:1.93.2.2
gcc-cvs/gcc/README,v: x86-64-branch:1.5
gcc-cvs/gcc/boehm-gc/allchblk.c,v: x86-64-branch:1.9
gcc-cvs/gcc/boehm-gc/doc/README.environment,v: x86-64-branch:1.4
(後略)
COPYING
ファイルであればリビジョン 1.4
、 ChangeLog
ファイルであればリビジョン 1.467.2.10
の内容を採用すれば良いことがわかる。
対して、git(や、Subversion)はディレクトリツリー全体をバージョン管理の対象としているため、リポジトリを変換するには "ある時刻時点でのリポジトリの状態" を 全てのファイルをスキャンした上で 再現しなければならない。この制約があるため、cvs-fast-exportはリポジトリに含まれるコミットの情報全てを一旦メモリに読んだ上で処理する方式を取っている。
CVSは当初RCSのフロントエンドとして作られた経緯があるため、ファイルの履歴はRCSの ,v
ファイルに記録される。例えば、 MAINTAINERS
ファイルの履歴は MAINTAINERS,v
ファイルに記録されていて、
1.406
date 2005.03.30.20.00.27; author gerald; state Exp;
branches;
next 1.405;
のようにdiffの日付/時刻とauthorの情報が含まれている。このファイルには
grep ^date MAINTAINERS,v |wc -l
845
845個の変更履歴が含まれているので、これを全ファイルについて時刻順にソートして変更時刻順に並べれば良いように思えるが そうは問屋が卸さない 。
例えば、 gcc/boehm-gc/Attic/Makefile,v
ファイルを見てみると、
1.2
date 99.04.07.08.01.28; author tromey; state dead;
branches;
next 1.1;
1.1
date 99.04.07.14.56.06; author tromey; state Exp;
branches
1.1.1.1;
next ;
なんと 1.1
(7月14日)よりも 1.2
(7月8日)の方が古い。 タイムパラドックスだ! (ついでにこの日付フォーマットには2000年問題もある)
CVSは歴史的事情で時刻はクライアントの自己申告に頼っており、オペミスやその他の要因でタイムパラドックスを含むリポジトリを作成できてしまう。Subversionやより後年のVCSでは、ディレクトリツリー全体を管理する思想によってこのような問題を防いでいる。
一応、最近(2004年)のcvsでは commitid
という複数ファイルへの編集を一意に識別するためのIDが導入されていて複数ファイルに跨がるコミットを付番できるようになっているが、現役でCVSを使っているような限られたプロジェクトくらいしか恩恵が無い。(特に、gccは2005年にはSubversionに移行してしまったため1つも無い)