CVS

はじめに

動いてるプログラムに手を加える前に元のソースファイルをコピーして「~.bak」というファイルを作った。今までにそんな経験はないでしょうか。そういったファイルはほとんどの場合少しも活用されずに埋もれてしまう運命にあります。
CVSを使えばこういう状況を劇的に改善することが出来ます。

CVSとは

CVS(Concurrent Versions System)とはバージョン管理システムの一つで、ファイルの変更情報を管理するソフトウェアです。その管理下に入ったファイルは、任意の時点での内容を取り出したり、いつどんな変更が加えられたかを調べたりといったことが出来ます。

CVS以外のバージョン管理システムとしてはMicrosoft Visual SourceSafeやSubversionが有名ですが、動作するプラットフォームや実績を考えるとCVSが頭一つ抜き出ています。CVSで管理されているオープンソースソフトウェアも数多く、習得しておいて損をするということはないと思います。

CVSを使うにはいくつかのやり方がありますが、ここでは一番応用の利くcvsコマンドを使う方法を取り上げます。
またコマンドのオプションの意味をすべて説明していくと非常に長くなってしまうため必要最低限のものだけを解説します。

CVSの基礎知識

概念

実際に利用する前に、その概念を把握しておくと理解しやすいでしょう。特徴的なキーワードを通して解説します。

リポジトリ
履歴情報を管理するために、CVS は全てのファイルとディレクトリをリポジトリと呼ばれるところに保存します。
ワーキング(作業)コピー
リポジトリに情報を保存するといいましたがユーザーは直接リポジトリを触るということはありません。編集を始める前にリポジトリから作業用にファイルをコピーしてきてそのコピーに対して作業を行い、作業が終わったら変更した内容をリポジトリに反映するという形をとります。そのコピーの事をワーキングコピーと呼びます。
チェックアウト
リポジトリからコピーを取ってくる作業をチェックアウトといいます。
コミット
ワーキングコピーでの作業内容をリポジトリに反映させる作業をコミットといいます。

CVSの基本的な使い方

コマンドの導入と初期設定

Linuxならば多くの場合標準でインストールされていると思いますが、Windowsでは自分で導入する必要があります。
ここではCygwinのCVSパッケージを利用することをおすすめします。http://www.cygwin.com/からsetup.exeを落としてきてウィザードに従って進めるだけなので、ここでは特にインストールの仕方には触れません。

コマンドのインストールが終わったらcvsコマンドを便利に使うために設定ファイルを用意します。
以下のテキストを~/.cvsrcとして保存してください。

cvs -z5 -q
diff -u
checkout -P
update -d -P
status -v

リポジトリを作成する

環境が整ったら早速使ってみましょう。一番最初に行うのはリポジトリの作成です。
シェルで以下のコマンドを実行してください(「$」はプロンプトの開始を意味しているだけなので実際には打たないでください)。

$ cvs -d ~/cvsroot init

これでホームディレクトリ下にcvsrootディレクトリがリポジトリとして作成されました。

ファイルを登録する

管理したいファイルを登録する作業を行います。例として、以下のようなディレクトリ構成のfooプロジェクトを登録するものとします。

~
`-- foo/
   |-- Main.java
   `-- Sub.java

Main.javaの内容は次のようになっています。

public class Main {

    public static void main(String[] args) {
        System.out.println("Helo, World!");
        return;
    }

}

Sub.javaの内容は次のようになっています。

public class Sub {

}

まずプロジェクトのディレクトリに移って

$ cd ~/foo

importします。

$ cvs -d ~/cvsroot import -m "test" foo somebody foo_v_0
N foo/Main.java
N foo/Sub.java

No conflicts created by this import

fooのところにはプロジェクト名(CVSではモジュール名といいます)を、somebodyのところにはあなたの
名前をいれてください。

これで登録が終わりました。既存のプロジェクトディレクトリはいらないので削除しておきます。

$ cd
$ rm -rf foo

チェックアウトする

概念の項で説明したように、実際にファイルを編集するにはワーキングコピーを作成する必要があります。

$ cd
$ ls -F
cvsroot/

$ cvs -d ~/cvsroot checkout foo
U foo/Main.java
U foo/Sub.java

$ ls -F
cvsroot/  foo/

fooディレクトリが作成されたことが確認できました。ディレクトリの中身も見てみます。

$ cd foo
$ ls -F
CVS/  Main.java  Sub.java

Main.java、Sub.javaがちゃんとあります。CVSディレクトリはCVSが使うので触らないようにしてください。

ファイルの編集~リポジトリへの登録の流れ

ここまでで準備が終わりました。後は普段通りファイルを編集してください。

update(up)コマンド

長時間編集を続けていると自分がどのファイルに変更を加えたか分からなくなってくることがあります。
そういうときにはupdateコマンドを使うと便利です。なお、これ以降のcvsコマンドは必ずワーキングコピーのディレクトリ内で実行する必要があります。

$ cd ~/foo
$ cvs update

まだ変更していないので、当然ですが何も出力されません。

この例ではMain.javaに出力する文字列が間違っているというミスがあるのでそれを直すことにします。
修正が終わったらコンパイル、実行して意図通りの挙動になっているか確認します。

$ javac Main.java
$ java Main
Hello, World!

問題ないようですね。ここでもう一回updateコマンドを使います。毎回updateと打つのは面倒なのでupという略で実行しましょう。

$ cvs up
M Main.java
? Main.class

一行目はMain.javaが変更された(Modified)ということを示しています。手を加えていないSub.javaの情報は出力されません。
二行目はCVSの管理下にないMain.classというファイルがあるということを示しています。Main.classはMain.javaから生成できるのでバージョン管理する必要はありません。無視して次のステップへ進みます。

diff(di)コマンド

updateコマンドでどのファイルを変更したのかは分かりました。しかしどういった変更をしたのかまでは分かりません。
それを調べるために使われるのがdiffコマンドです。

$ cvs diff
Index: Main.java
===================================================================
RCS file: /home/somebody/cvsroot/foo/Main.java,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 Main.java
--- Main.java   23 Sep 2004 02:51:12 -0000      1.1.1.1
+++ Main.java   23 Sep 2004 02:51:46 -0000
@@ -1,7 +1,7 @@
 public class Main {

     public static void main(String[] args) {
-        System.out.println("Helo, World!");
+        System.out.println("Hello, World!");
         return;
     }

長いメッセージが出力されましたが、後半を見ればどういう変更が行われたかが分かると思います。

この中でもう一つ注目して欲しいのが

diff -u -r1.1.1.1 Main.java

という行です。これは手元にあるMain.javaと(リポジトリにある)リビジョン1.1.1.1のMain.javaとを比較して
差分を出力せよということを表しています。リビジョンという聞き慣れない単語が出てきましたがこれについては
後で説明します。

commit(ci)コマンド

さて、編集作業も一段落つきました。これまでの作業内容をリポジトリに反映させる必要がありますが、その作業をコミットといいます(概念の項でも説明しました)。CVSを使わない場合における「編集を行う前にバックアップを取っておく」作業に相当します。

コミットするにはcommitコマンドを使います。

$ cvs commit -m "fixed output message." Main.java
Checking in Main.java;
/home/somebody/cvsroot/foo/Main.java,v  <--  Main.java
new revision: 1.2; previous revision: 1.1
done

出力するメッセージを直したというログを含めてコミットを行いました。

出力をよく見るとまたリビジョンという語が出てきたことに気づくと思います。
リビジョンとはファイルの各履歴につく通し番号のようなもので、コミットするたびに増加していきます。これまで編集してきたMain.javaは1.2というリビジョン番号がついたことになります。今後Main.javaをどんなに変更したとしてもリポジトリからリビジョン1.2のMain.javaを持ってくれば、現時点のMain.javaを復元できることになります。

コミットした結果updateコマンドの結果はどのようになったのでしょうか。

$ cvs up
? Main.class

変更が取り込まれたため、Main.javaのMマークが無くなりました。

これで基本的な流れはつかめたと思います。今後は「ファイルの編集~リポジトリへの登録の流れ」を繰り返し行うことになります。

履歴の活用の仕方

CVSはため込んだ履歴を効果的に使うことが出来ます。

任意のリビジョンのファイルを手に入れる

ありがちなのは編集を進めるうちに詰まってしまって、とりあえず直前のリビジョンに戻したいというケースでしょう。
こんな時はupdateコマンドに-Cオプションを付けて実行します。

$ cvs up
M Main.java
? Main.class

$ cvs up -C Main.java
(Locally modified Main.java moved to .#Main.java.1.2)
U Main.java

直前ではなく、もっと古いリビジョンのものが必要なときには-r[リビジョン番号]を付けます。リビジョン1.1のMain.javaが
欲しいときには次のようにします。

$ cvs up -r1.1 Main.java
U Main.java

$ cat Main.java
public class Main {

    public static void main(String[] args) {
        System.out.println("Helo, World!");
        return;
    }

}

ログを見る

過去のある時点でのリビジョン番号を覚えているということはほとんど無いので、普通はログを見て特定することになります。

$ cvs log Main.java

RCS file: /home/somebody/cvsroot/foo/Main.java,v
Working file: Main.java
head: 1.2
branch:
locks: strict
access list:
symbolic names:
        foo_v_0: 1.1.1.1
        somebody: 1.1.1
keyword substitution: kv
total revisions: 3;     selected revisions: 3
description:
----------------------------
revision 1.2
date: 2004/09/23 04:23:09;  author: somebody;  state: Exp;  lines: +1 -1
fix output message.
----------------------------
revision 1.1
date: 2004/09/23 02:51:12;  author: somebody;  state: Exp;
branches:  1.1.1;
Initial revision
----------------------------
revision 1.1.1.1
date: 2004/09/23 02:51:12;  author: somebody;  state: Exp;  lines: +0 -0
test
=============================================================================

このときに役に立つのがコミット時に指定するログメッセージです。分かりやすいログメッセージを残すようにしましょう。

annotate(ann)コマンド

annotateコマンドを使うと、ファイルのある箇所が最後に更新されたのがいつかを調べることが出来ます。

$ cvs annotate Main.java

Annotations for Main.java
***************
1.1          (somebody   23-Sep-04): public class Main {
1.1          (somebody   23-Sep-04):
1.1          (somebody   23-Sep-04):     public static void main(String[] args) {
1.2          (somebody   23-Sep-04):         System.out.println("Hello, World!");
1.1          (somebody   23-Sep-04):         return;
1.1          (somebody   23-Sep-04):     }
1.1          (somebody   23-Sep-04):
1.1          (somebody   23-Sep-04): }
1.1          (somebody   23-Sep-04):

このコマンドを使うとソースを調べるのが非常に楽になります。

たとえばリビジョン1.2で更新された行が気になったとしましょう。
ログを見れば(「ログを見る」の項参照)、この行が1.2になって新たに追加されたわけではなくprintlnメソッドに与えられている
引数が変更されたんだろうと推測できます。実際にどんな変更が行われたか見るのも簡単です。

$ cvs di -r1.1 -r1.2 Main.java
Index: Main.java
===================================================================
RCS file: /home/somebody/cvsroot/foo/Main.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- Main.java   23 Sep 2004 02:51:12 -0000      1.1
+++ Main.java   23 Sep 2004 04:23:09 -0000      1.2
@@ -1,7 +1,7 @@
 public class Main {

     public static void main(String[] args) {
-        System.out.println("Helo, World!");
+        System.out.println("Hello, World!");
         return;
     }

CVSの少し高度な使い方

.cvsignoreによるファイルの明示的な除外

$ cvs up
? Main.class

これまでMain.classは単純に無視してきましたが、ここでは明示的に除外してみます。

.cvsignoreファイルの作成

以下の内容で.cvsignoreというファイルを作成してください。

Main.class

updateコマンドを実行します。

$ cvs up
? .cvsignore

Main.classに関する情報は表示されなくなり、今度は新しく加えた.cvsignoreが管理下にないファイルだと示されました。

addコマンド

.cvsignoreはバージョン管理していく方が良さそうです。リポジトリに新しくファイルを追加するにはaddコマンドを使います。

$ cvs add .cvsignore
cvs add: use 'cvs commit' to add this file permanently

ファイルを永久に追加するには「cvs commit」を使えというメッセージが出ていますが、ひとまず置いておいて現時点での状態をupdateコマンドで確認します。

$ cvs up
A .cvsignore

「?」が「A」に変わり追加待ちとなっています。コミットしてリポジトリに反映します。

$ cvs ci -m "new file." .cvsignore
RCS file: /home/somebody/cvsroot/foo/.cvsignore,v
done
Checking in .cvsignore;
/home/somebody/cvsroot/foo/.cvsignore,v  <--  .cvsignore
initial revision: 1.1
done

これで期待通りの結果が得られます。

$ ls -aF
./  ../  .cvsignore  CVS/  Main.class  Main.java  Sub.java

$ cvs up

ところで.cvsignoreで指定するパターンにはワイルドカードが使えます。今後classファイルが増えることも考えられるので、Main.classではなく*.classを指定した方がよいでしょう。

終わりに

ここに記したのはCVSの機能のごく一部に過ぎず、内容も不正確な部分があります。CVSに興味を持ったら他の資料をあたって勉強することをおすすめします。参考までにいくつか挙げておきます。