CVS/Subversionを使ったバージョン管理(中編:CVSを使ったバージョン管理)

 本特集ではバージョン管理システムの基本的な考え方や用語を解説するとともに、SubversionやCVSを使ったバージョン管理方法について説明していく。前編ではバージョン管理システムの基本事項について解説を行ったが、中編となる本記事では、 CVS(Concurrent Versions System)を使ったバージョン管理について解説する。

 CVSはオープンソースソフトウェアの開発において、広く使われているバージョン管理システムだ。CVSはソースコードなどのリソースやその変更履歴がCVSサーバー側で管理される中央集権型のバージョン管理システムで、CVSサーバーに対してチェックアウトやコミットといった操作を行うことで開発作業を進めていく。

 なお、CVSはネットワーク対応のバージョン管理システムとしては歴史が長いが、 RCS(Revision Control System)という古典的なバージョン管理システムをベースに開発されていることもあり、さまざまな制約がある。そのため、もし現在バージョン管理システムを新規に利用したいのならば、SubversionやGit、Mercurialといった、より後発のバージョン管理システムの検討をおすすめする。もちろん、CVSを利用しているプロジェクトに参加するにはCVSの操作法を身につけておく必要があるので、ここで覚えておいても損はない。

CVSサーバー/クライアントの導入

 現在、CVSの開発はSavannah(GNUが提供する開発システム)上で行われており、各種UNIX/LinuxやWindowsといったプラットフォーム向けバイナリおよびソースコードが公開されている(ダウンロードディレクトリ)。また、多くのLinuxディストリビューションやFreeBSD、NetBSD、OpenBSD、Cygwinなどでは、パッケージマネージャからのインストールが可能だ。インストールからサーバー周りの設定までを自動で行えるので、パッケージが利用できる環境ではそちらを利用することをおすすめする。

 なお、CVSの開発サイトからダウンロードできるWindows向け公式バイナリは、Windows環境からCVSサーバーにアクセスするクライアントとしては利用できるが、サーバーとしてはそのままでは利用できない。Windows環境でCVSサーバーを動かしたい場合、 CVSNT というWindows用CVSサーバーを検討するとよいだろう。日本語ファイル名への対応が追加された CVSNT SJIS版 も公開されている。

 そのほか、Windows環境ではエクスプローラからGUIでCVSの各種操作が行える TortoiseCVS というCVSクライアントもある。Eclipseを利用しているなら、標準で搭載されているCVSクライアント機能を利用するのもよいだろう。なお、以下では標準のCVSクライアントである「cvs」コマンドを利用したCVSリポジトリへのアクセスについて解説しているが、そのほかのクライアントを使用する場合でも、基本的な用法は変わらない。

CVSが参照する環境変数を設定する

 CVSを利用する際には、事前に各種環境変数を設定しておく必要がある。CVSを利用する際に必須となる設定は、下記の3点だ。

  • CVSのルートディレクトリ
  • 利用するプロトコル
  • 利用するリモートシェル

 CVSでは、リポジトリへのアクセス方法(プロトコル)として「local」および「ext」、「pserver」の3種類が用意されている(表1)。localはネットワークを使用せず、ローカルのファイルシステム上にリポジトリを作成して利用するもの、extはSSH/RSHを利用してリモートのリポジトリにアクセスするもので、pserverはCVS独自の専用通信プロトコルだ。ただし、pserverを利用したアクセスは認証過程や通信内容が暗号化されないため、特にインターネット経由でリポジトリにアクセスする際など、認証情報や通信内容が漏えいする可能性がある。そのため、現在ではSSHを利用するextプロトコルが一般的に利用され、pserverを利用したアクセスについては匿名でのリポジトリアクセスなどに限定して利用されていることが多い。

プロトコル 説明
local ネットワークを使用せず、ローカルにリポジトリを作成して利用する
ext SSH経由でリモートのリポジトリにアクセスする
pserver 専用プロトコルでアクセスを行う
表1 CVSで利用できるプロトコル

 利用するプロトコルは、「CVSROOT」環境変数でリポジトリのルートディレクトリとともに「:<使用するプロトコル>:<ユーザー名>@<ホスト名>:<リポジトリのルートディレクトリ>」という形で指定する。たとえば、下記のような条件でCVSリポジトリにアクセスするとしよう。

  • 使用するプロトコル:ext(SSH経由)
  • ユーザー名:john
  • ホスト名:cvs.sourceforge.jp
  • リポジトリのルートディレクトリ:/cvsroot/sample1

 この場合、CVSROOT環境変数は「:ext:john@cvs.sourceforge.jp:/cvsroot/sample1」となる。この環境変数を設定するには、以下のようにコマンドを実行すればよい。

 export CVSROOT=:ext:john@cvs.sourceforge.jp:/cvsroot/sample1  (sh系シェルの場合)
 setenv CVSROOT :ext:john@cvs.sourceforge.jp:/cvsroot/sample1  (csh系シェルの場合)
 set CVSROOT=:ext:john@cvs.sourceforge.jp:/cvsroot/sample1     (Windowsの場合)

 なお、CVSROOT環境変数を設定する代わりに、cvsコマンドの実行時に「-d」オプションでリポジトリや使用するプロトコルを指定することも可能だ。

 また、プロトコルとしてextを利用する場合は、あらかじめ使用するリモートシェルをCVS_RSH環境変数で選択しておく。リモートシェルはRSHもしくはSSHが利用できるが、セキュリティの観点からSSHを利用するのが一般的だ。SSHを利用する場合、CVS_RSH環境変数に「ssh」と設定すればよい。

 export CVS_RSH=ssh  (sh系シェルの場合)
 setenv CVS_RSH ssh  (csh系シェルの場合)
 set CVS_RSH=ssh     (Windowsの場合)
CVSのルートディレクトリ

 CVSを利用する際に、CVSの履歴データベースや登録したファイルなどを格納するディレクトリをCVSのルートディレクトリと呼ぶ。CVSのルートディレクトリには任意のディレクトリが利用可能だ。ただし、そのパーミッションには注意が必要だ。

 プロトコルとしてlocal、もしくはextを使用する場合、CVSのルートディレクトリおよびその配下のディレクトリには、cvsコマンドを実行したユーザー権限でアクセスが行われる。また、プロトコルとしてpserverを使用する場合、CVS pserverを実行しているユーザーの権限でCVSのルートディレクトリ以下にアクセスが行われる。そのため、少なくともCVSのルートディレクトリは、これらのユーザーによる読み込みが可能なようにパーミッションを設定しておく必要がある。

リポジトリの新規作成

 新たにCVSリポジトリを作成する手順は、以下のようになる。なお、この作業はすでにCVSリポジトリが用意されている場合は不要だ。

  1. CVSサーバーとなるマシンにCVSのルートディレクトリを作成する
  2. CVSクライアントとなるマシンでCVSROOT環境変数およびCVS_RSH環境変数を設定する
  3. CVSクライアントとなるマシンで「cvs init」コマンドを実行する

 「cvs init」コマンドは、CVSの初期化を行うコマンドだ。これを実行することにより、指定したCVSのルートディレクトリに「CVSROOT」というディレクトリが作成され、そこにCVSの管理データベースが保存される。

リポジトリに新たにファイル一式を登録する

 リポジトリに新たにディレクトリを作成し、そこにファイルを登録する(インポートする)には、cvsの「import」コマンドを使用する。importコマンドの書式は次のとおりだ。

 cvs import <登録先ディレクトリ> <ベンダータグ> <リリースタグ>

 これを実行すると、カレントディレクトリ内のファイルがCVSサーバー内の「/」以下に登録される。「ベンダータグ」は作者を表す文字列、「リリースタグ」は登録したファイル群の現時点のバージョンを表す文字列で、それぞれ任意の名称を利用できる。特に開発コミュニティなどで明確にルールが定められていなければ、ベンダータグとリリースタグは適当な名称でよい(ただし、ベンダータグとリリースタグを完全に同一の文字列にすることはできない)。

 たとえば、CVSリポジトリ以下の「foobar」というディレクトリに、カレントディレクトリ内のファイルを新たに登録するには、次のようにする。

$ cvs import foobar john foobar001

 この例では、ベンダー名を「john」、リリースタグ名を「foobar001」と指定している。

リポジトリ内のファイルを取り出す

 CVSリポジトリからファイルを取得する(チェックアウトを行う)には、cvsの「checkout」(co)コマンドを使用する。ただし、使用するプロトコルによって手順が若干異なるので注意してほしい。

 リポジトリに対してpserverプロトコルで接続する場合、まずloginコマンドでCVSサーバーにログインし、続けてcheckoutコマンドを実行する。

$ cvs login
$ CVS password:      ←パスワードを入力
$ cvs checkout <取得したいディレクトリ>

 いっぽう、extもしくはlocalプロトコルで接続する場合はloginコマンドは不要で、直接checkoutコマンドを実行すればよい。

$ cvs checkout <取得したいディレクトリ>
SourceForge.JPのCVSリポジトリの利用

 SourceForge.JPでは、匿名でチェックアウトする場合のみpserverプロトコルを使用し、それ以外の場合はSSH経由でのチェックアウトを利用する。匿名でチェックアウトする場合、次のようにすればよい。

$ export CVSROOT=:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/<プロジェクト名>
$ cvs login
 CVS password:      ←何も入力せずEnterキーを押す
$ cvs checkout <取得したいディレクトリ>

 SourceForge.JPでは匿名でのコミットは許されていないため、匿名でチェックアウトしたファイルに修正を加えた際、そのままではコミットを行えない。もし修正したファイルをコミットする可能性があるなら、次のようにextプロトコルを利用し、SSH経由での非匿名チェックアウトを使用する。

$ export CVSROOT=ext:@cvs.sourceforge.jp:/cvsroot/<プロジェクト名>
$ export CVS_RSH=ssh
$ cvs checkout <取得したいディレクトリ>

上記の例はsh系シェルでの実行例なので、環境に応じて適宜環境変数の設定行を読み替えてほしい

ローカルで加えた変更をリポジトリに保存する

 チェックアウトしたファイルに変更を加えた後、その変更をリポジトリに保存する(コミットする)には、変更を加えたファイルがあるディレクトリで「cvs commit」コマンドを使用する。引数なしで「cvs commit」コマンドを実行すると、カレントディレクトリ以下のすべてのファイルがコミット対象となる。特定のファイルのみをコミットしたい場合、コミットするファイルを引数で指定する。

$ cvs commit <ファイル1> <ファイル2> ...

 このコマンドを実行すると、変更点の概要といったコメントを入力するためのエディタが起動する。コメントを入力後、保存してエディタを閉じると必要に応じて認証が行われ、認証に成功すればファイルがリポジトリに登録される。

 なお、コメントを入力するエディタはEDITOR環境変数もしくはCVSEDITOR環境変数で指定できる。これらの環境変数が設定されていない場合、コミットに失敗するので、あらかじめこれらの環境変数を指定しておくか、もしくは「-m」オプションでコメントを指定しよう。たとえば、「add xxx function」というコメントを付けてコミットするには下記のようにすればよい。

$ cvs commit -m "add xxx function"    ←「-m」オプションに続けてコメントを入力する

他者がリポジトリに加えた修正をローカルのファイルに反映させる

 他者がリポジトリに加えた修正をローカルのファイルに反映させるには、該当するファイルが存在するフォルダで「cvs update」コマンドを実行する。

$ cvs update

 また、アップデートするファイルを引数で個別に指定することもできる。

$ cvs update  <アップデートするファイル1> <アップデートするファイル2> ...

競合(コンフリクト)への対処

 CVSではコミットを行う際、自分がコミットしようとしたファイルが他人によって変更され、先にコミットされていないかどうか、常にチェックを行っている。これは、同一のリビジョンのファイルに対し、複数の異なる修正が加えられるという「競合(コンフリクト)」が発生しないようにするためだ。

 もし自分がコミットしたファイルがすでに他人によって変更され、先にコミットされていた場合、cvsはコミット時にメッセージを表示し、競合したファイルについてはコミットを行わない。もし競合が発生したら、この競合を解決してコミットを行う必要がある。

 競合を解決するには、まずcvs updateコマンドを使用して競合したファイルの最新リビジョンをリポジトリから取り出す。すると、それぞれの開発者が加えた変更点をcvsが自動的にチェックし、可能な限り自動でその変更をマージしてくれる。ただし、同一の個所に異なる変更が加えられた場合など、自動でのマージに失敗する場合がある。その場合、ファイルには競合している行が「<<<<<<<」や「=======」、「>>>>>>>」といった記号とともに併記される。この記号を目印に競合している行を編集し、編集後のファイルを再度コミットすればよい。なお、自分が変更を加えたオリジナルのファイルは「.#<ファイル名>.<リビジョン>」という名前にリネームされて保存される。

 たとえば、下記はCVSのマニュアルに記述されている競合の例だ。

    if (nerr == 0)
        gencode();
    else
        fprintf(stderr, "No code generated.\n");
<<<<<<< driver.c
    exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
=======
    exit(!!nerr);
>>>>>>> 1.6

 ここでは、「<<<<<<< driver.c」と「=======」で囲まれている部分が自分が変更を加えた個所、「=======」から「>>>>>>> 1.6」までがリポジトリ内にある最新リビジョンを表している。これらの個所を状況に応じて編集し、コミットすればよい。

たとえば、自分が加えた変更を破棄し、先に他者が行っていた変更を残すには「<<<<<<< driver.c」から「=======」までの行と、「>>>>>>> 1.6」の行をを削除する。他者が行った変更を削除するには「<<<<<<< driver.c」の行と、「=======」から「>>>>>>> 1.6」までの行を削除すればよい。また、どちらの変更も採用せず、まったく異なる変更を加えるには「<<<<<<< driver.c」から「>>>>>>> 1.6」までの行を全削除し、新たなコードで置き換えればよい。

 なお、以上の例はテキストファイルの場合で、バイナリファイルについてはCVSでは一切自動的なマージが行われない。

リポジトリにファイルやフォルダを追加する

 リポジトリ内のファイルやフォルダを追加するには、「cvs add」コマンドを使用する。

$ cvs add <追加するファイル>

 また、バイナリファイルを登録する場合は「-kb」オプションを付けて実行する。

$ cvs add -kb <追加するファイル>

 cvs addコマンドでは、ファイルだけでなくフォルダを追加することも可能だ。フォルダを指定すると、自動的にそのフォルダ内のファイルもリポジトリに追加される。なお、cvs addコマンドを実行した時点では、ファイルはリポジトリにコピーされない。cvs addの実行後、cvs commitを実行してコミットを行った時点でファイルはリポジトリにコピーされる。

リポジトリ内のファイルやフォルダを削除する

 リポジトリ内のファイルやフォルダを削除するには、削除するファイルを指定して「cvs remove」コマンドを使用する。

$ cvs remove -f <削除するファイル>

 cvs delコマンドはリポジトリ内のファイルを削除するように指示を発行するだけで、これだけではローカルのファイルは削除されない。実際にリポジトリ内のファイルが削除されるのはcvs addコマンドの場合と同様、コミットの実行時となる。

 また、ワーキングコピー内のファイルを先に削除した場合、「cvs remove」コマンドを引数なしで実行することで、削除したファイルをリポジトリからも削除できる。

$ rm <削除するファイル>
$ cvs remove

リポジトリ内のファイルやディレクトリの名前を変更/移動する

 CVSでは、ファイル/ディレクトリの移動や名前変更を行う機能は用意されていない。そのため、ファイル/ディレクトリの名前を変えたい場合は対象のファイル/ディレクトリを削除し、続けて別の名前でそのファイル/ディレクトリを登録する、といった手順を踏む。

$ mv <変更前ファイル名> <変更後ファイル名>
$ cvs remove
$ cvs add <変更後ファイル名>
$ cvs commit

 なお、これを行うと古いファイルの変更履歴やリビジョン情報は消えてしまうので注意してほしい。

現在のリビジョンのファイル群に名前(タグ)を付ける

 CVSでは、特定のリビジョンのファイルツリーに名前(タグ)を付ける機能がある。たとえばファイルをリリースする場合や、多数のソースコードに対して大きな変更を加えようとする前にタグを付けておくことで、あとから簡単にタグをつけたリビジョンのファイルを取り出すことができるようになる。

 タグを付けるには、「cvs tag」コマンドを使用する。

$ cvs tag <タグ名>

 また、タグをつけたリビジョンのファイルを取り出すには、タグ名を「-r」オプションで指定してcvs checkoutコマンドを実行すればよい。

$ cvs checkout -r <タグ名>

ブランチを作る

 ブランチを作るには、「-b」オプション付きでcvs tagコマンドを実行する。

$cvs tag -b <タグ名> <取得したいディレクトリ>

 また、特定のブランチのファイルを取り出すには、ブランチ名を「-r」オプションで指定してcvs checkoutコマンドを実行する。

$ cvs checkout -r <ブランチ名>

 ブランチで加えた修正を分岐元のツリーに反映させる(マージする)には、分岐元のツリーをチェックアウトし、そこで「-j <ブランチ名>」オプションを付けてcvs updateコマンドを実行すればよい。

$ cvs checkout <取得したいディレクトリ>
$ cvs update -j <ブランチ名>
タグとブランチの違い

 CVSにおいて、タグの付加やブランチの作成はともに「cvs tag」コマンドを利用する。どちらも、名前を付加した特定のリビジョンのファイルにアクセスできる、という点では似ているが、取り出したファイルに変更を加えてコミットを行う際の挙動は大きく異なる。

 まず、タグを指定してチェックアウトしたファイルは、そのままではコミットできないことが多い。そのチェックアウトしたファイルよりもリビジョンが大きいファイルがリポジトリ内に存在する可能性が高いからだ。この場合、アップデートを行って変更点をマージしたり、競合を解決した後にコミットを行うことになる(図A)。

図A
図A タグを付けられたファイルは、修正やアップデートを行ってコミットすると最新のリビジョンとして保存される

 一方、ブランチを指定してチェックアウトしたファイルは、分岐元のファイルとは異なるリビジョンが設定されており、そのままコミットを行うことができる(図B)。

図B
図B ブランチを行った場合、リビジョンは本流のツリーとは異なる系列が付けられ平行管理できる

リポジトリの状態や履歴を確認する

 リポジトリの状態やバージョン管理の履歴を確認するには、「cvs status」や「cvs log」、「cvs history」、「cvs diff」、コマンドなどが利用できる。それぞれの利用方法については表2のとおりだ。

コマンド 説明
cvs status カレントディレクトリ以下のワーキングコピーの状態やリビジョン、更新状態などを表示する
cvs log コミット時のログを表示する
cvs history 各種CVSコマンドの実行履歴を表示する
cvs diff -r <比較元リビジョン> -r <比較先リビジョン> <比較するファイル> 指定したファイルの、リビジョン間の差分を表示する
表2 リポジトリの状態や履歴を確認するコマンド

(後編に続く…)