動いてるプログラムに手を加える前に元のソースファイルをコピーして「~.bak」というファイルを作った。今までにそんな経験はないでしょうか。そういったファイルはほとんどの場合少しも活用されずに埋もれてしまう運命にあります。
CVSを使えばこういう状況を劇的に改善することが出来ます。
CVS(Concurrent Versions System)とはバージョン管理システムの一つで、ファイルの変更情報を管理するソフトウェアです。その管理下に入ったファイルは、任意の時点での内容を取り出したり、いつどんな変更が加えられたかを調べたりといったことが出来ます。
CVS以外のバージョン管理システムとしてはMicrosoft Visual SourceSafeやSubversionが有名ですが、動作するプラットフォームや実績を考えると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コマンドを使うと便利です。なお、これ以降の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から生成できるのでバージョン管理する必要はありません。無視して次のステップへ進みます。
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とを比較して
差分を出力せよということを表しています。リビジョンという聞き慣れない単語が出てきましたがこれについては
後で説明します。
さて、編集作業も一段落つきました。これまでの作業内容をリポジトリに反映させる必要がありますが、その作業をコミットといいます(概念の項でも説明しました)。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コマンドを使うと、ファイルのある箇所が最後に更新されたのがいつかを調べることが出来ます。
$ 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 up ? Main.class
これまでMain.classは単純に無視してきましたが、ここでは明示的に除外してみます。
以下の内容で.cvsignoreというファイルを作成してください。
Main.class
updateコマンドを実行します。
$ cvs up ? .cvsignore
Main.classに関する情報は表示されなくなり、今度は新しく加えた.cvsignoreが管理下にないファイルだと示されました。
.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に興味を持ったら他の資料をあたって勉強することをおすすめします。参考までにいくつか挙げておきます。