[目次に戻る] [入門CVSのサポートページに戻る] [みかままのページに戻る]
[前へ] [次へ]

Last-updated: $Date: 2001/05/28 06:54:55 $ GMT

第5章 他の人と一緒に使う場合のお話

この章では、CVSのリポジトリを他の人と共有し、一緒に使う場合に考えないといけないこと、使えると便利な機能について説明します。ただし、とても沢山の場合があって、全てについては説明できませんから、この本では項目をしぼって説明します。それでも、入門編としては難しいかもしれません。なるべく、わかりやすく実例をあげて説明しようと思いますので、おつきあいください。使えると本当に便利ですよ。


アクセス権について考える

何かを人と共有して使おうとすると、必ず「権限」ということを考えなければならなくなります。CVSの場合には、「ファイルへアクセス権」が大事です。「ファイルのアクセス権」というのは、どのファイルは読み書き(+実行)して良くて、どのファイルはそうしてはいけないのか、ということです。読んでもいいけど、書いてはダメだよ、という場合もあります。CVSでそれを制御するのには、UNIX(または他OS)のアクセス制御機構とCVS自体のアクセス制御機構の2つの機構が関わってくるので、話が複雑になっています。それぞれの機構に分けて話をしましょう。前者についてはUNIXの場合についてのみ説明します。NTがリポジトリサーバをしている場合でも、おおまかには同じだと思います。

UNIXのアクセス制御機構での調整

共有する場合に問題になるのは、新規にモジュールを作る場合のリポジト リ(この場合には/home/cvsroot)への読み書き許可、各モジュール(リポジトリの下のディレクトリ)への読み書き許可、および、管理ファイルhistoryへの読み書き許可の三つです。最後の管理ファイルhistoryは最近のcvsでは最初から777に設定されています【図X】。なっていなければchmod 777 historyとでもしておいて下さい。

図X CVSROOT/historyのアクセス権を眺める

% ls -la /home/cvsroot/CVSROOT/history

-rw-rw-rw-    1 mika     users        9261 Feb 15 10:42 /home/cvsroot/CVSROOT/history

この本の最初の方で、リポジトリを/home/cvsrootとして作り、それを説明に使ってきました。このリポジトリの所有者は何になっているでしょうか。あの時は、「とりあえず、自分の書き込み許可権だけでも出す」ように指示したと思います。この指示に従っていれば、自分が所有者になっているはずです【図X】。この状態でリポジトリに読み書きできるのは、自分(mika)とusersグループに所属する他のユーザだけです。

最近のCVSは自動的に、各モジュールについてはimport実行時に、管理ファイルhistoryについてはinitコマンド実行時に、グループの読み書き許可を出すようになってるようです。リポジトリ自体については、リポジトリの作成者が気をつけておくしかありません。もちろんモジュールの新規登録は他の人にはできないようにするというのも一つのポリシーとしてあり得ますが、登録を許すのであれば、chmod g+w してグループが書き込めるようにしておきましょう。

グループの他のメンバーに読み書きされるのがイヤであれば、chmodでディレクトリのモードを変更する必要があります。自分だけのグループを作って、chgrpでそのグループに変えておくという手もあります。と、これは共有でなくす方の話でしたね。共有する場合の話をしましょう。

図X リポジトリのアクセス権を眺める
% ls -la /home/cvsroot/CVSROOT/history

-rw-rw-rw-    1 mika     users        9261 Feb 15 10:42 /home/cvsroot/CVSROOT/history

UNIXサーバ上にアカウントがあるユーザ間で共有する場合

この場合には、その人たちで構成されるグループを作って、リポジトリのグループをそのグループにするのが簡単です。例えば、開発グループ、moonがあって、メンバーがmika, nobu, usakoの3人であるとします。 まず、/etc/groupにmoonグループを追加します【図X】。 /etc/groupは1行毎にグループの指定をします。コロン(:)で区切られたフィールドの1番目がグループ名、2番目がパスワード、3番目がグループの番号、4番目がメンバーでメンバーはカンマ(,)で区切って並べます。グループの番号は適当に割り振られてないものを使用して下さい。

図X /etc/groupへ追加する行
moon:x:9000:mika,nobu,usako

その後、リポジトリのグループを moonに変更します【図X】。念のため、ディレクトリの下の下までグループを変更するように、再帰(-R)を指定してchgrpを実行します。

図X グループを変更する
% chgrp -R moon /home/cvsroot

リポジトリはどんなふうになったでしょうか?ls -laで見てみましょう。

図X リポジトリのグループは変わったかな?
% ls -la /home/cvsroot

total 28

drwxr-xr-x    7 mika     moon         4096 Feb 14 21:00 .

drwxr-xr-x    8 root     root         4096 Dec 31 11:15 ..

drwxrwxr-x    3 mika     moon         4096 Feb 15 10:11 CVSROOT

drwxrwxr-x    2 mika     moon         4096 Feb 15 10:37 bintest

drwxrwxr-x    3 mika     moon         4096 Feb 15 22:13 cvstest

drwxrwxr-x    6 mika     moon         4096 Jan  1 21:15 cvstest2

drwxrwxr-x    2 mika     moon         4096 Dec 25 03:43 newmodule

本当に読み書きできるようになったかどうか試してみましょう。ちょっと、usakoさんに、cvstestを取り出して、test.txtを編集・コミットしてもらいました。リポジトリのcvstestモジュールの状態を見てみると【図X】、test.txt,vだけ所有者がusakoに(また、グループがanimalに)変わっています。グループについては、ユーザの第一グループ(/etc/passwdに記述されたグループ)になってしまうので、新しくディレクトリを作成する場合にはリポジトリ内のディレクトリのグループを変更しておく必要があるでしょう。

図X cvstestモジュールはどうなったかな?
% ls -l /home/cvsroot/cvstest

total 48

drwxrwxr-x    2 mika     moon         4096 Jan 26 07:21 Attic/

-r--r--r--    1 mika     moon          829 Feb  1 10:44 difftest.txt,v

-r--r--r--    1 mika     moon         7952 Jan 26 07:27 fish.jpg,v

-r--r--r--    1 mika     moon         4980 Jan 26 07:27 fish2.jpg,v

-r--r--r--    1 mika     moon         1746 Jan 26 07:27 rcskeytest.txt,v

-r--r--r--    1 mika     moon         1412 Jan 27 09:38 rcskeytest2.txt,v

-r--r--r--    1 mika     moon          495 Jan 30 22:19 tagtest.txt,v

-r--r--r--    1 mika     moon          380 Jan 26 07:27 test.bin,v

-r--r--r--    1 usako    animal        753 Feb 16 11:05 test.txt,v

-r--r--r--    1 mika     moon          999 Jan 26 07:27 test2.txt,v

ちなみに、所属するグループが違うユーザがディレクトリを作った場合、そのグループに属さないユーザがどのコマンドを実行しようとしてもCVSがロックファイルが作れないということで実行できなくなってしまいます。リポジトリ内のディレクトリの所属グループを変えてupdateコマンドを実行してみた例が図Xです。cvstest2/dir3というディレクトリが他のユーザとグループになってしまっているので【6行目】、cvstest2/dir3を更新しようとしたとき【18行目】にロックファイルが作れないとCVSがエラーを吐いて【19〜20行目】止まってしまいます【21行目】。ロックファイルを作らないように-pオプションを使って標準出力に出させるという手もありますが、めんどうくさいです。

図X 違うグループのディレクトリが含まれるとき
% ls -l /home/cvsroot/cvstest2

total 36

drwxrwxr-x    2 mika     moon         4096 Dec 25 03:39 Attic

drwxrwxr-x    3 mika     moon         4096 Feb 18 10:04 dir1

drwxrwxr-x    2 usako    moon         4096 Feb 18 10:04 dir2

drwxrwxr-x    2 usako    animal       4096 Feb 18 09:44 dir3

drwxrwxr-x    2 mika     moon         4096 Feb 18 09:44 newdir

drwxrwxr-x    2 mika     moon         4096 Feb 18 09:44 newdir2

drwxrwxr-x    2 mika     users        4096 Feb 18 09:45 newdir3

-r--r--r--    1 mika     moon          507 Dec 26 12:42 test2.txt,v

-r--r--r--    1 mika     moon          601 Dec 26 12:19 tmp2.txt,v

% cvs update

cvs update: Updating .

cvs update: Updating dir1

cvs update: Updating dir1/dir2

cvs update: Updating dir2

? dir2/test.txt

cvs update: Updating dir3

cvs update: failed to create lock directory in repository `/home/cvsroot/cvstest2/dir3': Permission denied

cvs update: failed to obtain dir lock in repository `/home/cvsroot/cvstest2/dir3'

cvs [update aborted]: read lock failed - giving up

ユーザにディレクトリ追加後はちゃんと設定するように注意しておいても、ついうっかり忘れることはあるでしょう。 このようなことが起こらないようにするのに一番簡単な手段は、/home/cvsrootもしくは共有することがわかっているディレクトリを共有グループにした上で set-group-ID をあらかじめ設定しておくことです。ごちゃごちゃ言ってもわかりませんね。 実際にやってみましょう。 まず、set-group-IDを設定するには chmod コマンドに g+s を指定して実行します。図Xの1行目がそうです。2行目で ls -la してみると、. (カレントディレクトリ)のモードが "drwxr-xr-x" だったのが、"drwxr-sr-x"とグループのモードの実行許可のところがxからsに変わっているのがわかると思います。

図X set-group-IDの設定
% chmod g+s /home/cvsroot

% ls -la /home/cvsroot

total 32

drwxr-sr-x    8 mika     moon         4096 Feb 18 10:19 .

drwxr-xr-x   10 root     root         4096 Feb 16 11:02 ..

drwxrwxr-x    3 mika     moon         4096 Feb 15 10:11 CVSROOT

drwxrwxr-x    2 mika     moon         4096 Feb 15 10:37 bintest

drwxrwxr-x    3 mika     moon         4096 Feb 16 11:05 cvstest

drwxrwxr-x    9 mika     moon         4096 Feb 18 10:16 cvstest2

drwxrwxr-x    2 mika     moon         4096 Dec 25 03:43 newmodule

試しに、modetestモジュールを新しく登録してみます【図X】。 modetestモジュールはファイルを一つとサブディレクトリを一つ持った単純なモジュールです。サブディレクトリの中にもファイルが一つ入っています。 登録後、ls -la で/home/cvsrootを眺めてみる【8行目】と、modetest だけ、set-group-ID が設定されています【16行目】。さらに、modetestの中身を眺めてみると、サブディレクトリにも set-group-ID が設定されています。そして各ディレクトリの中のファイルがグループmoonになっていて万々歳です。

図X modetestモジュールを新規に登録してみる

% cvs -d /home/cvsroot import -m "Mode test" modetest mikamama MODETEST1

N modetest/test.txt

cvs import: Importing /home/cvsroot/modetest/dir1

N modetest/dir1/test.txt

No conflicts created by this import

% ls -la /home/cvsroot

total 32

drwxr-sr-x    8 mika     moon         4096 Feb 18 10:19 .

drwxr-xr-x   10 root     root         4096 Feb 16 11:02 ..

drwxrwxr-x    3 mika     moon         4096 Feb 15 10:11 CVSROOT

drwxrwxr-x    2 mika     moon         4096 Feb 15 10:37 bintest

drwxrwxr-x    3 mika     moon         4096 Feb 16 11:05 cvstest

drwxrwxr-x    9 mika     moon         4096 Feb 18 10:16 cvstest2

drwxrwsr-x    3 mika     moon         4096 Feb 18 10:19 modetest

drwxrwxr-x    2 mika     moon         4096 Dec 25 03:43 newmodule

% ls -laR /home/cvsroot/modetest

/home/cvsroot/modetest:

total 16

drwxrwsr-x    3 mika     moon         4096 Feb 18 10:19 .

drwxr-sr-x    8 mika     moon         4096 Feb 18 10:19 ..

drwxrwsr-x    2 mika     moon         4096 Feb 18 10:19 dir1

-r--r--r--    1 mika     moon          391 Feb 18 10:19 test.txt,v

/home/cvsroot/modetest/dir1:

total 12

drwxrwsr-x    2 mika     moon         4096 Feb 18 10:19 .

drwxrwsr-x    3 mika     moon         4096 Feb 18 10:19 ..

-r--r--r--    1 mika     moon          394 Feb 18 10:19 test.txt,v

ということで、異なるグループに属するユーザ間でリポジトリを共有する場合には、共通のグループを作成し、リポジトリをそのグループに設定し、さらにそのリポジトリにset-group-IDを設定しておくとよい、ということになります。ちょっと大変ですね。

UNIXサーバ上にアカウントがないユーザ間で共有する場合

例えば、開発者としえ参加して欲しいけど、サーバにログインする権限は出したくないなぁ、ということがあります。こういう時には、少々話が複雑になります。NFSでも、sambaでも、pserverでもどんな共有機構を使用しても、最終的にはシステム内に代理のアカウントを作成して、そのユーザの権限で読み書きをするということになります。システム内に個別のユーザを作成しないというポリシーがある場合には、NFSやsambaなどのファイル共有機構ではユーザを共有することになって、作業履歴が混乱するかもしれません。ログインすることのできない仮のアカウントを個別に作成して割り当てれば、混乱は避けれるでしょう。が、個別アカウントを作るというのは、考えないといけないことが増えて色々と大変です。

一つのアカウントのみを使い、CVS上での作業は区別できるようにするためには、pserverを利用するのが一番簡単だと思います。pserverが認証に用いるpasswdファイルにこのための機能があります。どうするのでしょうか。例えば、代理アカウントをfruitとし、外部開発者に渡すログイン名をそれぞれ、apple, orange, grapeとすることにしましょう。まず、図Xのように実際にはログインできないユーザを作成します。これは、RedHatLinuxでのコマンドを利用しているので、各環境に合わせて適当にログインできないユーザを作成してください。3行目のように/etc/passwdファイルにエントリができていれば成功です。ここでは、前のグループmoonを利用していますが、システムのユーザと共有しない場合にはやはり代理のグループを作成して、リポジトリもそのグループにして構いません。pserverはこの代理ユーザでしか使用しないという約束にしておけば、pserver自体を代理ユーザの権限で動かすこともできます(inetd, xinetdで設定します)。要は運用のポリシーということです。

図X 代理ユーザfruitの作成。
% /usr/sbin/useradd -u 8000 -g moon -d /tmp -s /bin/false -c "Proxy user for CVS" fruit

% grep fruit /etc/passwd

fruit:x:8000:9000:Proxy user for CVS:/tmp:/bin/false

このとき、管理ファイル passwd には図Xのように設定します。どのユーザもパスワード生成とpasswdファイルの扱いについては前の章を参考にしてください。passwdファイルは登録時には自動的に再構築されない(されるようにするには、checkoutlistに加える必要があるが注意が必要)ので、直接変更するか、上書きコピーしておいてください。

図X CVSROOT/passwd
apple:hoxOeYoHeWHnc:fruit

orange:miuEwiC3VUyjo:fruit

grape:buTMkghEXwLhI:fruit

本当にappleとして作業できるのか、試してみましょう【図X】。まず、appleとしてpserverにログインする必要があります【1行目】。次に先ほど作成したmodetestモジュールをチェックアウトしてみます【4行目】。lsで眺めてみる【9〜15行目】と、ファイルの所有者は自分です。しかし、cvs historyでCVSでのイベントを見てみる【16行目】と、ちゃんとappleがチェックアウトしたことになっています【17行目】。

図X appleになって確認してみる
% cvs -d :pserver:apple@localhost:/home/cvsroot login

(Logging in to apple@localhost)

CVS password:

% cvs -d :pserver:apple@localhost:/home/cvsroot checkout modetest

cvs server: Updating modetest

U modetest/test.txt

cvs server: Updating modetest/dir1

U modetest/dir1/test.txt

% ls -la modetest

total 20

drwxr-xr-x    4 mika     users        4096 Feb 18 12:23 .

drwxr-xr-x    8 mika     users        4096 Feb 18 12:23 ..

drwxr-xr-x    2 mika     users        4096 Feb 18 12:23 CVS

drwxr-xr-x    3 mika     users        4096 Feb 18 11:56 dir1

-rw-r--r--    1 mika     users          11 Feb 18 10:19 test.txt

% cvs history

O 02/18 02:56 +0000 apple modetest =modetest= /*

この作業コピー上で作業している限り、全てappleが作業したことになります。そして、リポジトリ側ではそのファイルの作業者は代理アカウントになります。ちょっと、ファイルを追加した場合について見てみましょう。図Xでまず、test2.txtというファイルを作成し【1〜2行目】、それをadd【3行目】、commit【6行目】します。 ここで、リポジトリにあるtest2.txt,vファイルを眺めてみる【13行目】と、所有者は代理アカウントのfruitになっていることがわかります【14行目】。しかし、historyコマンド(とオプション-e)を使ってcvsのイベントを見てみる【15行目】と、appleが追加したことになっています【17行目】。

図X appleとして追加したファイルのユーザはどうなったかな?
% cat > test2.txt

Test file by apple.

% cvs add -m "Test file by apple" test2.txt

cvs server: scheduling file `test2.txt' for addition

cvs server: use 'cvs commit' to add this file permanently

% cvs commit -m "New file addition test by external user" test2.txt

RCS file: /home/cvsroot/modetest/test2.txt,v

done

Checking in test2.txt;

/home/cvsroot/modetest/test2.txt,v  <--  test2.txt

initial revision: 1.1

done

% ls -l /home/cvsroot/modetest/test2.txt,v

-r--r--r--    1 fruit    moon          221 Feb 18 12:28 /home/cvsroot/modetest/test2.txt,v

% cvs history -e

O 02/18 02:56 +0000 apple modetest =modetest= /*

A 02/18 03:28 +0000 apple 1.1 test2.txt modetest == 

このように、システムとCVSとのユーザが入り混じって複雑ですが、良く理解して自分の環境にあったアクセス権を設定するようにしてください。

pserverでアクセス権を制御する

上のような場合に、全て同じ代理アカウントを使用して作業するような場合には、ユーザ毎にアクセス権を制御するのが難しくなります。つまり、あるユーザには書き込み許可を出したくない、というようなことがあるかもしれません。別の代理アカウントを作って、システム側で書き込み権限を許可したりするのは大変ですし、ロックファイル作成にやはりひっかかりそうです。こんなときには、管理ファイルのreadersかwritersを使用します。

例えば、orangeには書き込み許可を出したくないとします。readersでも、writersでもどちらを使ってもよいのですが、まずreadersを使う場合について見てみましょう。orangeだけ仲間外れに(書き込めなく)するには、図XのようにCVSROOT/readersにorangeと書くだけです。

図X CVSROOT/readers
orange

初期状態ではこのファイルはないので、図Xのように登録しましょう。管理ファイルとしてCVSは知っているので、checkoutlistに加える必要はありません。

図X readers を追加する
% cvs add -m "Reader file" readers

cvs add: scheduling file `readers' for addition

cvs add: use 'cvs commit' to add this file permanently

% cvs commit -m "Reader file test" readers

RCS file: /home/cvsroot/CVSROOT/readers,v

done

Checking in readers;

/home/cvsroot/CVSROOT/readers,v  <--  readers

initial revision: 1.1

done

cvs commit: Rebuilding administrative file database

次に本当に書けなくなったのか、orangeになって作業してみます。login、checkoutをorangeとして実行した【1〜9行目】後、チェックアウトしたモジュールの作業コピー内に降りて【10行目】、test.txtを編集したとします。これをcommitしようとすると、確かに怒られました。

図X orange として作業
% cvs -d :pserver:orange@localhost:/home/cvsroot login

(Logging in to orange@localhost)

CVS password:

% cvs -d :pserver:orange@localhost:/home/cvsroot checkout modetest

cvs server: Updating modetest

U modetest/test.txt

U modetest/test2.txt

cvs server: Updating modetest/dir1

U modetest/dir1/test.txt

% cd modetest

(test.txtを編集)

% cvs commit -m "Modifing test by orange" test.txt

cvs [server aborted]: "commit" requires write access to the repository

同じ効果を、管理ファイルwritersの方で実現することができます。こちらは、書くことができるユーザだけを書いておくことで、それ以外のユーザの書き込みをできなくします。例えば、writersに図Xのように書くと今度は、appleとgrapeが書き込めなくなります。

図X CVSROOT/writers
orange

ここでreadersを残したままだと、readersの方が優先されてしまい、この場合orangeも書き込めなくなります。次に、writersファイルのせいで、orange以外の人が書けなくなりますので、結局だれ一人として書き込めなくなります。困りましたね。こういう場合には、図Xの様にreadersファイルを削除しましょう。ここで、注意して欲しいのは、removeコマンドだけでは、/home/cvsroot/CVSROOTにできたreadersは削除されないということです。システムのrmコマンドを使って削除しておきましょう【9行目】。

図X readers を削除する
% cvs remove -f readers

cvs remove: scheduling `readers' for removal

cvs remove: use 'cvs commit' to remove this file permanently

% cvs commit -m "Readers file removed"

cvs commit: Examining .

Removing readers;

/home/cvsroot/CVSROOT/readers,v  <--  readers

new revision: delete; previous revision: 1.1

% rm /home/cvsroot/CVSROOT/readers

rm: remove write-protected file `/home/cvsroot/CVSROOT/readers'? y

このように、readersとwritersを両方使おうとするとややこしくなるので、どちらかを使うようにしておいた方がいいでしょう。どちらが少ないかということもありますが、readersの方が優先度が高いので、readersを使った方が間違いが少ないかもしれません。例えば、anonymou(匿名)アクセスを設定する場合には、readersに匿名ユーザを書くようにしておくようにしましょう。

管理ファイルが誰でも書き換え可能なの知ってる?

さて、今のように管理ファイルをごそごそ書き換えていろいろやってるわけですが、共有するようにして特に何もしないと、上で書き込み可能に設定されたユーザであれば誰でも書き換えられるようになります。これって、よく考えると結構怖いと思いませんか?あとで紹介するように、管理ファイルのいくつかを設定すれば、外部プログラムを実行することができるので、しようと思えばいろいろなことができちゃいます。開発者として登録した人に悪気がなくても、その人のシステムがクラックされてしまうかもしれませんしね。用心したことに越したことはありません。

管理ファイルを特定のユーザにしかいじれないようにするのは、システムのグループを利用するしかないようです。リポジトリ所有者だけが書き換えられるようにするには簡単です。管理ファイルのディレクトリCVSROOTから、chmod g-wでグループの書き込み許可を剥奪してください。それで、所有者以外のユーザは書き込めなくなり、ロックファイルが作成できなくなるので、チェックアウトも困難になります。それでも、読むだけは不可能ではないのですが、なんにしろコミットはできません。

図X CVSROOTからグループの書き込み許可を剥奪
% chmod g-w /home/cvsroot/CVSROOT

(他のユーザになる)

% cvs -d /home/cvsroot checkout CVSROOT

cvs checkout: Updating CVSROOT

cvs checkout: failed to create lock directory in repository `/home/cvsroot/CVSROOT': Permission denied

cvs checkout: failed to obtain dir lock in repository `/home/cvsroot/CVSROOT'

cvs [checkout aborted]: read lock failed - giving up

管理者が複数いる場合には、その人たちをメンバーとするグループを別途作成して、CVSROOTをそのグループの所有とするのがいいでしょう。

管理コマンドの実行ユーザを制限しておこう

この本では-kオプション以外説明しませんでしたが、CVSのコマンドadminは、実はリポジトリの状態を自由に変えられる凶悪なコマンドだったりします。共有環境ではこのコマンドの実行は必ず制限しておきましょう。この制限のためには、システムにcvsadminというグループを作ります。adminコマンドの使い方がよくわかってないなら、このグループには誰も含まないようにしておきましょう。そうすると、adminコマンドは-kオプションを除いて誰も使えなくなります。-kオプションはcvsadminグループに入っていなくても誰でも使えますので心配しないでください。

つまり、図Xのような行を/etc/groupに追加しておくことをお勧めします。 グループIDはなんでもいいので適当に選んでつけてください。

図X /etc/groupに追加する行
cvsadmin:x:9001:

さて、確かに制限されたかどうか、試してみましょう。ここでは、失敗しても害の少ないState(状態)の書き換えで試してみます【図X】。cvsadminグループのメンバーに制限されている旨のメッセージが出て、失敗しました【2行目】。

図X 制限されたかどうか試してみる
% cvs admin -s 'Stab' cvstest

cvs [admin aborted]: usage is restricted to members of the group cvsadmin

adminコマンドで実際に何ができるのか知りたい人は、CVSの付属マニュアルを読むと良いでしょう。ただし、かなりのオプションは昔との互換性をのこすためだけで、実用的ではありませんけれど(凶悪なのはタグとロック関係くらいです)。


ブランチについて考える

タグのところでちょっと出てきましたが、ブランチタグという特殊なタグをつけることでブランチ(枝)を作ることができます。ブランチというのは、トランク(幹)としてメインのリビジョンに対して、分岐してできたリビジョンのことです。イメージ的には図Xのようになります。この図ではBTAGというブランチタグをつけて、ブランチを作ったあと、ブランチとトランクが並行してリビジョンを進めていく様子を表しています。分岐した後はお互いの変更は全く影響しません。

図X ブランチのイメージ図

と言われてもぴんとこないかと思いますので、もうちょっと具体的に説明してみます。

ブランチを作ると何がうれしいの?

プログラム開発をしているときは嬉しいです。ソースコード以外の文書ではあまり嬉しさはないかもしれません。どういうことかというと、プログラムを開発していると、ときおりすばらしいことを思いついて組み込みたくなります。あるいは、顧客からの要求で新しい機能の追加を強いられることがあるかもしれません。何にしろ、今動いているプログラムに新しい機能を組み込むときは、一旦プログラムが不安定になってしまうというのが普通です。つまり、新しいコードが他の部分となじむまでに時間がかかるのです(なじまないと、バグという形で色々な不快な現象が起こります)。一方、今動いているプログラムは安定してはいるものの、やはりちょっとした不都合が残っていて、修正の必要があるかもしれません。新しい機能を追加したプログラムの方でその不都合を解決しても、それが安定して提供されるまで待っていたら、その恩恵を受けるまでに時間がかかりすぎでしまいます。

このため、なじんでプログラムが安定動作するまで、新しいソースコード群と、多くの人が使っている安定版のソースコード群とは分離され、かつ並行して開発が続けられなければなりません。そして、新しいプログラムが安定したら、バグ取りが続けられてきた安定版のソースコードと合わせて、新しい安定版のプログラムとして提供されることになります。

ブランチはこのようなときに、安定版のソースコード群から、開発用のソースコード群を分岐するのに用います。通常は、安定版をトランクとし、新機能追加が必要になったときに、トランクから分岐されます。ブランチもトランクもそれぞれ並行にバージョン管理されます。実際には一つの,vファイルで管理されているので、ブランチのリビジョン番号とトランクのリビジョン番号が,vファイルの中に入り混じることになるのですが。

安定したら、そのブランチをトランクにマージして、そのブランチは見捨てるという方法と、開発用のブランチとして利用しつつ適当なタイミングでトランクにマージするという方法の2つがあります。トランクへのマージが繰り返されない分、前者の方が後者より簡単です。この他ブランチの運用方法については、より詳しい文献(「CVSバージョン管理システム」など)をあたると良いでしょう。

ブランチを作るには

tagコマンドあるいはrtagコマンドの-bオプションを使用します。rtagの場合リポジトリの指定と、どのリビジョンあるいはどの時点でブランチタグをつけるのか指定する必要があります。tagでも前のリビジョンや日付につけたい場合には、そのリビジョン・日付を指定することができます。

図X ブランチ作成の書式(tagを使った場合)
cvs tag [-r リビジョン| -D 日付] -b ブランチタグ名 ファイル群...

図X ブランチ作成の書式(rtagを使った場合)
cvs -d リポジトリ rtag -r リビジョン(-D 日付) -b ブランチタグ名 ファイル群...

簡単な例を使って説明してみましょう。branchtestという3つのファイルからなるモジュールを作って登録してみます【図X】。

図X branchtest の登録
% ls

b1.txt  b2.txt  b3.txt

% cvs -d /home/cvsroot import -m "Branch test module." branchtest mikamama BRANCHTEST_1

N branchtest/b1.txt

N branchtest/b2.txt

N branchtest/b3.txt



No conflicts created by this import

このモジュールのファイルに対して、何回か編集・コミットを繰り返してみます。タグをつける時点でb1.txt、b2.txt、b3.txtのリビジョンはそれぞれ1.3、1.2、1.4になっているとします。この作業コピーの上で、tagコマンドを利用してブランチを作ってみます。ブランチタグ名は「TESTBRANCH-1」とつけることにします。

まず、ブランチを作る時点に通常のタグをつけます。これは分岐点を後で取り出して比べたりしたくなったり、やりなおしたりしたくなることがあるためです。名前は何でもいいのですが、分岐点であるということがわかる名前をつけるのがいいと思います。ここでは、root-of-をブランチタグの頭につけることにしましょう(受け売りです)。ブランチタグ名をTESTBRANCH-1とすることにしましたので、分岐点のタグはroot-of-TASEBRABCH-1とします。図Xの1行目がその分岐点のタグ付けをしています。

次に、tagコマンドに-bオプションをつけてブランチタグをつけます。図Xの6行目でコマンドを実行しています。これだけでは、どんなタグがついたかわからないので、statusコマンドの-vオプションで各ファイルについたタグを眺めてみましょう【11〜最終行】。なんだか、Existing Tagsに沢山のタグが出てきましたね。はじめの2つが今つけたタグで、後の2つはimport実行時につけたベンダータグ名(mikamama)と、リリースタグ名(BRANCHTEST_1)です。後の2つは外部のベンダーからソースコードを提供されている場合に、新しく提供されたバージョンのソースコードと手元で変更したソースコードをマージしたいというような時に使われます。このタグの使用法について詳しく知りたい場合は、「CVSバージョン管理システム」を読むと良いでしょう。

図X ブランチ作成
% cvs tag root-of-TESTBRANCH-1

cvs tag: Tagging .

T b1.txt

T b2.txt

T b3.txt

% cvs tag -b TESTBRANCH-1

cvs tag: Tagging .

T b1.txt

T b2.txt

T b3.txt



% cvs status -v

cvs status: Examining .

===================================================================

File: b1.txt            Status: Up-to-date



   Working revision:    1.3     Tue Feb 20 11:08:15 2001

   Repository revision: 1.3     /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          (none)

   Sticky Date:         (none)

   Sticky Options:      (none)



   Existing Tags:

        TESTBRANCH-1                    (branch: 1.3.2)

        root-of-TESTBRANCH-1            (revision: 1.3)

        BRANCHTEST_1                    (revision: 1.1.1.1)

        mikamama                        (branch: 1.1.1)

===================================================================

File: b2.txt            Status: Up-to-date



   Working revision:    1.2     Tue Feb 20 11:08:05 2001

   Repository revision: 1.2     /home/cvsroot/branchtest/b2.txt,v

   Sticky Tag:          (none)

   Sticky Date:         (none)

   Sticky Options:      (none)



   Existing Tags:

        TESTBRANCH-1                    (branch: 1.2.2)

        root-of-TESTBRANCH-1            (revision: 1.2)

        BRANCHTEST_1                    (revision: 1.1.1.1)

        mikamama                        (branch: 1.1.1)



===================================================================

File: b3.txt            Status: Up-to-date



   Working revision:    1.4     Tue Feb 20 11:51:26 2001

   Repository revision: 1.4     /home/cvsroot/branchtest/b3.txt,v

   Sticky Tag:          (none)

   Sticky Date:         (none)

   Sticky Options:      (none)



   Existing Tags:

        TESTBRANCH-1                    (branch: 1.4.2)

        root-of-TESTBRANCH-1            (revision: 1.4)

        BRANCHTEST_1                    (revision: 1.1.1.1)

        mikamama                        (branch: 1.1.1)

ここでは自分のつけた、分岐点タグroot-of-TESTBRANCH-1とブランチタグTESTBRANCH-1について見てみます。分岐点タグは通常のタグで、各ファイルのリビジョン1.3、1.2、1.4に対してついています。おもしろいのは、ブランチタグに割り振られた1.3.2、1.2.2、1.4.2という番号です。このようにブランチは3つの番号から成ります。そして後で見るように、ブランチ上のリビジョンは4つの数字で表されます。つまり、次に変更をコミットするとそれぞれリビジョンは、1.3.2.1、1.4.2.1、1.2.2.1のようになります。

ブランチを取り出すには

ブランチを取り出してその上で作業を開始するには、checkoutコマンド実行時に-rオプションを使ってブランチタグ名を指定します。

図X ブランチを取り出すための書式
cvs -d リポジトリ checkout -r ブランチタグ名 モジュール名

なお、環境変数CVSROOTを設定していれば-dは必要ありません。図Xで取り出してみた後、statusコマンドで状態を見てみると、Stickyタグにブランチタグがついていることがわかります。以前Stickyタグが設定されているとコミットできなくなると言いましたが、ブランチタグの場合にはそういうことはありません。この場合のStickyタグはブランチだということを表すために設定されているだけで、普通通りにコミットすることができます。

図X ブランチを取り出す
[ブランチ]% cvs -d /home/cvsroot checkout -r TESTBRANCH-1 branchtest

cvs checkout: Updating branchtest

U branchtest/b1.txt

U branchtest/b2.txt

U branchtest/b3.txt

[ブランチ]% cvs status -v

cvs status: Examining .

cvs status: Examining branchtest

===================================================================

File: b1.txt            Status: Up-to-date



   Working revision:    1.3     Tue Feb 20 11:42:31 2001

   Repository revision: 1.3     /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          TESTBRANCH-1 (branch: 1.3.2)

   Sticky Date:         (none)

   Sticky Options:      (none)



   Existing Tags:

        TESTBRANCH-1                    (branch: 1.3.2)

        root-of-TESTBRANCH-1            (revision: 1.3)

        BRANCHTEST_1                    (revision: 1.1.1.1)

        mikamama                        (branch: 1.1.1)



===================================================================

File: b2.txt            Status: Up-to-date

(後略)

これまでの作業過程と各ファイルの変化を図で表すと、図Xのようになります。

図X 分岐するまでの作業の様子

 

ブランチで作業するには

ブランチをとってきてしまえば、後はその作業コピー上で作業している限りはほとんど、トランクでの作業と変わりません。ややこしいのはマージする時です。マージについては次で説明します。ここでは、簡単にb1.txtをちょっと編集してコミットしたという場合について考えてみましょう。図Xの1行目のように、いつもと変わりなくコミットします。いつもと違うのは、新しいリビジョンの番号です。上でちょっと述べたように、ブランチ上のリビジョンは4つの数字で表されます。その前の3つの数字はブランチに割り振られた番号です。図Xの5行目で1.3から1.3.2.1に上がったというメッセージがありますし、statusコマンドで見るとリビジョンが1.3.2.1でブランチの番号が1.3.2であることがわかります。

図X ブランチの方で変更をコミットする
[ブランチ]% cvs commit -m "Branch first commit"

cvs commit: Examining .

Checking in b1.txt;

/home/cvsroot/branchtest/b1.txt,v  <--  b1.txt

new revision: 1.3.2.1; previous revision: 1.3

done

[ブランチ]% cvs status b1.txt

===================================================================

File: b1.txt            Status: Up-to-date



   Working revision:    1.3.2.1 Tue Feb 20 12:24:26 2001

   Repository revision: 1.3.2.1 /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          TESTBRANCH-1 (branch: 1.3.2)

   Sticky Date:         (none)

   Sticky Options:      (none)

ここで、他の場所に改めてこのブランチの作業コピーをとるとどうなるのか、見てみます。

図X 別の場所でブランチを取り出してみる
[ブランチ2]% cvs -d /home/cvsroot checkout -r TESTBRANCH-1 branchtest

cvs checkout: Updating branchtest

U branchtest/b1.txt

U branchtest/b2.txt

U branchtest/b3.txt

[ブランチ2]% cd branchtest

[ブランチ2]% cvs status b1.txt

===================================================================

File: b1.txt            Status: Up-to-date



   Working revision:    1.3.2.1 Tue Feb 20 12:24:44 2001

   Repository revision: 1.3.2.1 /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          TESTBRANCH-1 (branch: 1.3.2)

   Sticky Date:         (none)

   Sticky Options:      (none)



Working revisionが1.3.2.1になっているということで、ブランチの最新バージョンが取り出されてくるようです。他の人が開発に加わった場合にもあまり気にしないで大丈夫ということですね。

マージしてみる

ブランチはいつかはマージされる必要があります。マージしないでずっと独立して開発を続けていると、何か別のものになってしまいます。普通はあまりかけ離れてしまわないうちに、当初の目的である機能の組み込みが安定したらトランクに変更を組み込んで、ブランチは放棄します。放棄しない場合には、トランクに対して行われた変更を自分の方に組み込んで一旦同じ状態にし、それから新しい機能の組み込みに挑戦するというようにします。前者の一番の欠点はトランクに旧ブランチがマージされて、新しい作業用ブランチができるまで、ブランチ側の開発者が暇だということです。後者の場合には、マージされている間もブランチ上で作業を続けることができます。しかし、トランク側でのマージが終わった後、そのトランクでの変更をブランチに反映させるのにちょっと手間がかかりますし、気をつけないとおかしなことになったりします。ここでは、簡単な例を使って、まずブランチからトランクへのマージと、そのあとのトランクからブランチへのマージについて眺めてみましょう。

マージには時間がかかります。なぜかというと、コンフリクト(conflict=競合)が発生することがあるからです。1章でちょっと話しましたが、コンフリクトというのは、同じファイルの同じ場所を違うように編集した場合に発生します。このような場合、CVSでは解決できないので、「コンフリクトが発生したので解決してね」、とメッセージとコンフリクトした結果のファイルを開発者に残します。このコンフリクトを解決できるのは人間だけです。通常は、プログラム全体の行く末を把握している人が、コンフリクトを起こした部分を書いた開発者と話し合いながら、うまくいくように書き直します。

コンフリクトが生じるもの凄く簡単な例を考えてみます。分岐時点でのb1.txtが図Xのようであったとします。これをブランチ側で、図Xのように、トランク側で図Xのように書き換えたとしましょう。図Xと図Xの3行目がお互いに異なっています。

図X 分岐時点でのb1.txt
This is a branch test file No.1 b1.txt.

Second commit.


図X ブランチ側b1.txt
This is a branch test file No.1 b1.txt.

Second commit.

Branch first commit.


図X トランク側b1.txt
This is a branch test file No.1 b1.txt.

Second commit.

Sixth commit.

まず、トランク側でコンフリクトを起こすための変更をコミットしておきます。リビジョンは1.3から1.4に上がりました。

図X トランクの方で変更をコミットする
[トランク]% cvs commit -m "Sixth commit." b1.txt

Checking in b1.txt;

/home/cvsroot/branchtest/b1.txt,v  <--  b1.txt

new revision: 1.4; previous revision: 1.3

done

[トランク]% cvs status b1.txt

===================================================================

File: b1.txt            Status: Up-to-date



   Working revision:    1.4     Tue Feb 20 12:27:54 2001

   Repository revision: 1.4     /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          (none)

   Sticky Date:         (none)

   Sticky Options:      (none)

ここで、トランク側にマージしようと思います。マージする前に、トランクでの修正を後からブランチに反映させることができるように、まずブランチにタグを設定しておきます。次の開発期間に入ったということで、一つ数字をインクリメントしてタグ名をTESTBRANCH-2としましょう【図X】。statusコマンドで見てみると、ブランチのリビジョンにTESTBRANCH-2がついていることがわかります。

図X マージされる直前にブランチにタグをつける
[ブランチ]% cvs tag TESTBRANCH-2

cvs tag: Tagging .

T b1.txt

T b2.txt

T b3.txt

[ブランチ]% cvs status -v b1.txt

===================================================================

File: b1.txt            Status: Up-to-date



   Working revision:    1.3.2.1 Thu Feb 22 01:47:34 2001

   Repository revision: 1.3.2.1 /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          TESTBRANCH-1 (branch: 1.3.2)

   Sticky Date:         (none)

   Sticky Options:      (none)



   Existing Tags:

        TESTBRANCH-2                    (revision: 1.3.2.1)

        merged-TESTBRANCH-1             (revision: 1.5)

        TESTBRANCH-1                    (branch: 1.3.2)

        root-of-TESTBRANCH-1            (revision: 1.3)

        BRANCHTEST_1                    (revision: 1.1.1.1)

        mikamama                        (branch: 1.1.1)

マージするには-j(joinの意味)オプションを使い、マージしたいタグを指定します。ブランチタグの場合にはブランチの最新状態がマージされます。

図X トランクへブランチをマージする
[トランク]% cvs update -j TESTBRANCH-1

cvs update: Updating .

RCS file: /home/cvsroot/branchtest/b1.txt,v

retrieving revision 1.3

retrieving revision 1.3.2.1

Merging differences between 1.3 and 1.3.2.1 into b1.txt

rcsmerge: warning: conflicts during merge

[トランク]% cvs status b1.txt

===================================================================

File: b1.txt            Status: File had conflicts on merge



   Working revision:    1.4     Result of merge

   Repository revision: 1.4     /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          (none)

   Sticky Date:         (none)

   Sticky Options:      (none)

6行目に1.3と1.3.2.1をマージしてb1.txtにしましたという意味のメッセージが表示されています。次の、7行目に警告(warning)が出ていることに注意して下さい。statusコマンドでb1.txtの現在の様子を眺めてみる【8行目】と、Statusのとこにマージのときのコンフリクトがあるということと、Working revisionのところにマージの結果であるということが書かれています。

なんで、1.3なのかすごく不思議ですけど、マージされた結果のb1.txtは図のように、リビジョン1.4と1.3.2.1の間のコンフリクトを含んでします。2行目の「<<<<<<< b1.txt」と8行目の「>>>>>>> 1.3.2.1」に囲まれた領域がコンフリクトを示しています。途中「=======」に区切られて、上と下にコンフリクトしたそれぞれの内容が書かれています。

図X コンフリクトが発生している状態のb1.txt
This is a branch test file No.1 b1.txt.

<<<<<<< b1.txt

Second commit.

Sixth commit.

=======

Second commit.

Branch first commit.

>>>>>>> 1.3.2.1

開発者はこのような競合を眺め、通常ならば関わった開発者と良く相談しながら修正しなければなりません。ブランチを使った並行開発を行う場合には、開発者相互の意志疎通が緊密に行われる必要があります。ここでは簡単に両方足したような文に書き換えることにします【図X】。

図X 修正されたb1.txt
This is a branch test file No.1 b1.txt.

Second commit.

Sixth and branch first commit.

そして、このb1.txtを安定したバージョンとしてコミットしてしまいます。そして、マージされた状態であることを示すために全体にタグ付けしておきます。

図X 修正されたb1.txtを「安定バージョン」としてコミットする

% cvs commit -m "Stable version of b1.txt" b1.txt

Checking in b1.txt;

/home/cvsroot/branchtest/b1.txt,v  <--  b1.txt

new revision: 1.5; previous revision: 1.4

done

% cvs status b1.txt

===================================================================

File: b1.txt            Status: Up-to-date



   Working revision:    1.5     Thu Feb 22 01:13:30 2001

   Repository revision: 1.5     /home/cvsroot/branchtest/b1.txt,v

   Sticky Tag:          (none)

   Sticky Date:         (none)

   Sticky Options:      (none)



% cvs tag merged-TESTBRANCH-1

cvs tag: Tagging .

T b1.txt

T b2.txt

T b3.txt

今までの作業過程と各ファイルの変化を図で表すと図Xのようになります。

図X トランクへのマージまでの作業の様子

一方、ブランチ側はトランク側でなされた変更を自分の方へ取り込む必要があります。マージしてその後特にトランク側に変更がなければ、マージ時点のタグmerged-TESTBRANCH-1を使用しても構いませんが、トランク側で何か変更があった場合には、HEADという特別なタグを使用します。また、ブランチ側は、さっき新しくつけたタグTESTBRANCH-2を使用します。このへんは今は変更が少ないのであんまり意味はないのですが。なんだかややこしいですね。それを実行したのが図Xの1行目です。4〜5行目の出力を見る限りでは、ブランチ側の最新リビジョン1.3.2.1とトランク側の最新リビジョン1.5をマージしようとしていますから大丈夫なようです。

結果はどうなったでしょうか。b1.txtについてdiffをとってみます【7行目】。出力結果を見ると良いようです。

図X ブランチへトランクをマージする
[ブランチ]% cvs update -j TESTBRANCH-2 -j HEAD

cvs update: Updating .

RCS file: /home/cvsroot/branchtest/b1.txt,v

retrieving revision 1.3.2.1

retrieving revision 1.5

Merging differences between 1.3.2.1 and 1.5 into b1.txt

[ブランチ]% cvs diff -c b1.txt

Index: b1.txt

===================================================================

RCS file: /home/cvsroot/branchtest/b1.txt,v

retrieving revision 1.3.2.1

diff -c -r1.3.2.1 b1.txt

*** b1.txt      2001/02/20 12:24:44     1.3.2.1

--- b1.txt      2001/02/22 02:03:00

***************

*** 1,3 ****

  This is a branch test file No.1 b1.txt.

  Second commit.

! Branch first commit.

--- 1,3 ----

  This is a branch test file No.1 b1.txt.

  Second commit.

! Sixth and branch first commit.

後はこれをコミットして、何か適当なタグをつけておけば他の人が使えて幸せになれます【図X】。

図X トランクをマージした状態をコミットしてタグをつける

[ブランチ]% cvs commit -m "Merged from trunk: b1.txt" b1.txt



Checking in b1.txt;

/home/cvsroot/branchtest/b1.txt,v  <--  b1.txt

new revision: 1.3.2.2; previous revision: 1.3.2.1

done

[ブランチ]% cvs tag trunk-merged-TESTBRABCH-2

これまでの作業過程と各ファイルの変化を図示すると、図Xのようになります。

図X ブランチへのマージまでの作業の様子

ということで、ごく簡単なブランチの使い方を説明してみましたが、それでも頭がごちゃごちゃしてしまったかもしれませんね。筆者はブランチを使っていると良く頭が混乱します。タグを指定し間違えたりして、違うリビジョンをマージしてしまうことも良くあります。そういう状態になってしまったら、一旦マージされてできたファイルを削除して、updateで最新版をとってきて、改めてマージするようにするといいです。色々と自分で試行錯誤してみてください。

注意:RedHatLinuxの6.1J、7.0Jとcvs-1.10.8(with kanjiwrap)の組み合わせで、マージ時に/tmpファイルにできる一時ファイルの名前にゴミがついてしまって、結果マージに失敗するという症状に遭遇しました。メーリングリストに問い合わせて検討した結果、RedHatLinuxのメモリ確保関数とknjwrp patchとの関係で問題が生じることがほぼ判明しました。じきにこの問題に対処した形のknjwrp patchが提供されると思いますので、この環境の人は注意してknjwrp patchの更新に対応して下さい。この本のはじめの方でSRPMから作った人で、この本のサポートページからパッチをダウンロードした人、またRPMパッケージを同ページからダウンロードした人は、おそらく大丈夫だと思います。@木津さんへ:後ほど対応します


コマンド実行時に外部プログラムを実行することについて考える

自分一人で使ってるときでも、あるコマンドを実行したときに外部プログラムを自動的に呼び出せると便利です。ですが、特に複数人で作業するようになると、特にこの機能を活用する必要が出てきます。例えば、誰かの更新時に共有領域が自動的に更新されるように、とか、最も使われるのは作業内容をメールで共同作業者に伝えるということでしょう。他の文献だと設定ファイルや実際にトリガとなって外部プログラムを実行できるコマンドについての説明が散らばっていて分かりにくいので、表Xのようにまとめてみます。

トリガコマンド プログラム指定を行う管理ファイル 対象の指定法
checkout
modules/-oオプション
modules/-uオプション(上書きの場合)
wrappers/-fオプション(ファイル種類)
モジュール名
モジュール名
ファイルパターン
update
modules/-uオプション
wrappers/-fオプション
モジュール名
ファイルパターン
edit
notify
ディレクトリパターン
unedit(release)
notify
ディレクトリパターン
tag
taginfo
ディレクトリパターン
rtag
modules/-tオプション
taginfo
モジュール名
ディレクトリパターン
commit
modules/-iオプション
wrappers/-f&-tオプション
commitinfo
verifymsg
loginfo
notify
モジュール名
ファイルパターン
ディレクトリパターン
ディレクトリパターン
ディレクトリパターン
ディレクトリパターン
export
modules/-eオプション
wrappers/-fオプション
モジュール名
ファイルパターン

ここで、「対象の指定法」という名前の列であげた、モジュール名、ディレクトリパターン、ファイルパターンというのは、CVSがコマンド実行時に対象ファイルまたはディレクトリがプログラムを呼び出すように指定されているかチェックするための指定法の種類です。モジュール名で指定するのはmodulesファイルだけ、ファイルパターンで指定するのはwrappersファイルだけです。その他のファイルでは、ディレクトリパターンで指定します。

ファイルパターンは、CVSのコマンドが実行されたファイルに対して合致しているかチェックされます。パターンとして指定できるのは、ファイル名とワイルドカード(「*」と「?」)を使った表現だけです。正規表現は使えません。例えば、「*.txt」というパターンを指定していると、a.txtやhoge.txtなど、おしりに「.txt」がついているファイルが合致します。「?.txt」にすると何か1文字だけであとは.txtであるファイルに合致します。つまり、a.txtには合致しますが、hoge.txtには合致しません。wrappersではモジュール名やディレクトリはチェックされません。

ディレクトリパターンは、CVSのコマンドが実行されたディレクトリに対して合致しているかチェックされます。パターンはごく一般的な正規表現を使って書きます。正確な文法の定義を探しきらなかったのですが、「^」(前に何もない)、「$」(後ろに何もない)、「.」(任意の一文字に合致)、「+」(1回以上繰り返す)、「*」(0回以上繰り返す)は使えるようです。また、固有の予約語として、全てを意味するALLと、他のパターンが合致しないときを意味するDEFAULTがあります。例えば、「^module$」というパターンは先頭がmで始まって、oduleと続き、その後は何もない、つまりmoduleというディレクトリ名にのみ合致します。kmoduleもmoduleaも合致しません。なお、ディレクトリ名はリポジトリからの相対パスです。つまり、/home/cvsroot/moduleがmoduleになります。

ちなみに、このパターン「^module$」は、module/moduleにもその他の任意のサブディレクトリにも合致しません。例えば、commitinfoだと、サブディレクトリでのコミットでは外部プログラムは呼び出されません。もし、サブディレクトリ以下に合致したときにも外部プログラムを実行したいのであれば、「^module/」のように書いてみれば良さそうです。ですが今度は、moduleには合致しなくなってしまったりします。例えば、commitinfoだと、一番上でのコミットでは外部プログラムは呼び出されなくなります。なお、両方のパターンを並べて書いておけば、どちらの場合でも合致してプログラムが実行されます。自分の必要とする動作を考えてパターンを設定して下さい。まぁ似たようなモジュールがなければ、「^module」だけでもいいんですけどね。後から、moduleが頭についたモジュール(modulerとか)を追加すると、意図せずにそのモジュールに対してもプログラムが実行されてしまうというだけです。

筆書の知る範囲では、トリガとして外部プログラムを呼び出すことができるコマンドは、表に上げたコマンド、checkout, update, edit, unedit(release), tag, rtag, commit, exportの8つ(releaseを入れると9つ)だけです。以下で、これらについてもう少し詳しく見てみましょう。

checkoutコマンド実行時に外部プログラムを呼び出すには

管理ファイルmodulesを利用する方法とcvswrappersを利用する方法の2つがあります。それぞれ、指定する対象が違うので注意して下さい。modulesで指定するのはモジュールで、モジュールにcheckoutが実行された場合にそのモジュールに指定されたプログラムを実行します。一方、cvswrappersは基本的にファイル毎にパターンに合致するかをチェックしてプログラムを実行します。

modulesでの設定法は、図Xのようになります。 最初にモジュール名(実際にはキーワード)を指定し、オプション-oでcheckout時に呼び出すプログラムを指定します。プログラムにはユーザが独自に引数を渡すことはできません。プログラムが実際に呼び出されるときには、CVSが対象となったモジュール名をプログラムに引数として渡します。最後が、モジュールかモジュール以下の任意のディレクトリです。元々modulesはディレクトリに別名をつけて取り出せるようにするための管理ファイルですので、これは必ず指定しなくてはいけません。

図X modules での設定法
モジュール名 -o プログラムへの完全パス モジュールディレクトリ

一方、cvswrappersでの書式は、図Xのようになります。-fの後の引数を'(シングルクオーテーション)で囲むことと、その内部にプログラムへの完全パスと"%s"という文字列を空白文字で区切って書いておくことを忘れないで下さい。このプログラムが実際に実行されるときに、"%s"のところに対象となるファイル名が渡されるようになります。プログラムと%sの間(後でも良い)にプログラムに渡す他の引数を書いておいても構いません。どこに何を書くかは呼び出すプログラム次第です。

図X cvswrappers での書式
ファイルの種類 -f 'プログラムへの完全パス [引数] %s'

ここで、図Xのようなテストプログラムを用意して、挙動をチェックしてみます。このテストプログラムは要するに、日付と実行時のディレクトリおよび渡された引数をチェックアウトされたというメッセージとともに/home/cvsuser/logfileというログファイルに書き出すperlプログラムです。これを/home/cvsuser/checkout_prog.pl という名前にしたとしましょう。

図X modules 用テストプログラム(/home/cvsuser/checkout_prog.pl)

#!/usr/bin/perl

$logfile = "/home/cvsuser/logfile";

$date=`date`; chomp($date);

$pwd=`pwd`; chomp($pwd);

open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";

print LOG "$date CHECKOUT($pwd): $ARGV[0] $ARGV[1]\n";

close($logfile);

同じく、cvswrappers用にも同様の動作をするテストプログラムを作成しておきます【図X】。これを、/home/cvsuser/from_filter.plという名前にしたとします。

図X cvswrappers 用テストプログラム(/home/cvsuser/from_filter.pl)
#!/usr/bin/perl

$logfile = "/home/cvsuser/logfile";

$date=`date`; chomp($date);

$pwd=`pwd`; chomp($pwd);

open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";

print LOG "$date WRAP_FROM($pwd):";

foreach $arg (@ARGV) {

  print LOG " $arg";

}

print LOG "\n";

close($logfile);

これらをそれぞれ実行するように、modulesとcvswrappersに設定します【図X、図X】。cvswrappersでは、モジュール単位の指定はできないので、モジュール内に含まれるファイルのパターンを指定しています。

図X modules での設定例
test -o /home/cvsuser/checkout_prog.pl test

図X cvswrappers での設定例
*.txt -f '/home/cvsuser/from_filter.pl %s'

この状態で、test.txt、test2.txt、test3.txtという3つのファイルを含むtestというモジュールをチェックアウトしてみます【図X】。このとき、modulesに設定されたプログラムを実行するメッセージが出力されます【最終行】。これをみると、引数は一つで、モジュール名が渡されていることがわかりますね。一方、cvswrappersに設定されたプログラムが実行されたかどうかはわかりません。

図X 実行結果
cvs -d :pserver:mika@localhost:/home/cvsroot checkout test

cvs server: Checkout test

U test/test.txt

U test/test2.txt

U test/test3.txt

cvs server: Executing ''/home/cvsuser/checkout_prog.pl' 'test''

しかし、ログファイルを見てみると、図Xのようなログにcvswrappersで実行されたプログラムのログも残されていることを確認できます。図Xで%sとして指定した部分には、パターンに合致したファイルが入っていることがこれからわかります。さらに、まずcvswrappersに設定されたプログラム(fromフィルタ)が実行され【1〜3行目】、そのあとmodulesに設定されたプログラムが実行される【4行目】ということがわかります。ちなみに、pserverで実行すると、/tmpの下に暫定的なディレクトリが作られて、その下で作業されるということもこのログからわかって興味深いです。このテストプログラムを書き換えて環境変数がどうなっているかなどを調べてみるのも面白いでしょう(例えば、図Xのようなコードを変数を出力させたあとに追加すれば一覧が出力されます)。

図X /home/cvsuser/logfileに記録されたログ
Sun Feb  4 04:09:42 JST 2001 WRAP_FROM(/tmp/cvs-serv32543/test): test.txt

Sun Feb  4 04:09:42 JST 2001 WRAP_FROM(/tmp/cvs-serv32543/test): test2.txt

Sun Feb  4 04:09:42 JST 2001 WRAP_FROM(/tmp/cvs-serv32543/test): test3.txt

Sun Feb  4 04:09:43 JST 2001 CHECKOUT(/tmp/cvs-serv32543): test


図X 環境変数一覧を出力するための追加コード


foreach $env (keys %ENV) {

  print LOG " $env = $ENV{$env}\n";

}

updateコマンド実行時に外部プログラムを呼び出すには

やはり、modulesとcvswrappersで設定することができます。modulesでの設定法は変わりますが、cvswrappersの設定法は変わりません。要するにcvswrappersはファイルの出入りのあるときはいつでも実行されるからです。modulesでupdate時に呼び出されるプログラムを指定するには-uオプションを使用します。プログラムに引数が渡せないのはcheckoutのときと同じです。CVSはプログラム実行時にリポジトリ内でのモジュールの完全パスを渡します。

図X modules での設定法
モジュール名 -u プログラムへの完全パス モジュールディレクトリ

checkoutでのmodules用テストプログラムをちょっとだけ書き換えた(CHECKOUTという部分をUPDATEにした)テストプログラムを図Xのように用意します。/home/cvsuser/update_prog.plという名前にしたとしましょう。そして、図Xのように設定します。

図X modules 用テストプログラム(/home/cvsuser/update_prog.pl)

#!/usr/bin/perl

$logfile = "/home/cvsuser/logfile";

$date=`date`; chomp($date);

$pwd=`pwd`; chomp($pwd);

open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";

print LOG "$date UPDATE($pwd): $ARGV[0] $ARGV[1]\n";

close($logfile);

図X modules での設定例
test -u /home/cvsuser/update_prog.pl test

cvswrappersのテストプログラムおよび設定例はcheckoutでのものと同じなので省略します。この状態でupdateを実行すると、図Xのようになります。checkoutの時と同様に、modulesに設定したプログラムを実行した旨のメッセージが出力されます【最終行】。これをみると、引数は一つで、リポジトリ内のモジュールへの完全パスである「/home/cvsroot/test」が渡されていることがわかります。

図X 実行結果
% cvs update

cvs update: Updating .

U test.txt

U test2.txt

U test3.txt

cvs update: Executing ''/home/cvsuser/update_prog.pl' '/home/cvsroot/test''

ログファイルはどうなったでしょうか。cvswrappersのfromフィルタがまずそれぞれのファイルに対して呼び出された【1〜3行目】あと、modulesに設定されたプログラムが呼び出されています【4行目】。

図X /home/cvsuser/logfileに記録されたログ
Sun Feb  4 19:57:07 JST 2001 WRAP_FROM(/home/cvsuser/tmp/test): test.txt

Sun Feb  4 19:57:07 JST 2001 WRAP_FROM(/home/cvsuser/tmp/test): test2.txt

Sun Feb  4 19:57:07 JST 2001 WRAP_FROM(/home/cvsuser/tmp/test): test3.txt

Sun Feb  4 19:57:07 JST 2001 UPDATE(/home/cvsuser/tmp/test): /home/cvsroot/test

editコマンド実行時に外部プログラムを呼び出すには

editと次に出てくるuneditについてはまだ説明していませんでした。これらは、checkoutやupdateが実行されてからcommitされるまでの間に、具体的にどのファイルが編集されているかを知るための仕組みの一部です。このコマンドが有効に働くためには、あらかじめwatchコマンドで編集状態に入ったときに知らせて欲しいファイルを指定しておく必要があります。具体的には、mikaがtest.txtとtest2.txtは自分も編集していて、他の人が編集をするときには知りたいなぁ、と思ったとします。これが通知されるようにするには、mikaはあらかじめ、cvs watch add test.txt test2.txtのように指定しておく必要があります。この設定をしていると、他の人がedit, unedit, commitの操作を行ったときに、管理ファイルnotifyに設定された外部プログラムを呼び出すようになります。watchコマンドの細かい話は、また後で改めてします。ここでは、notifyへの設定方とその呼び出され方だけ見てみます。

notifyで外部プログラムを呼び出すように設定するには、図Xの様にします。最初に来るのが、リポジトリからの相対パスにマッチさせるためのパターンで、次に呼び出すプログラムへの完全パス、最後の%sは観察している人(この例ではcvsuserになります)を引数として渡すためのおまじないです。CVSは特に追加の引数は渡しません。プログラムに渡されるのはここで指定した分だけです。

図X notify での設定法
パターン プログラムへの完全パス [引数] %s

notifyからプログラムに渡せる引数が、観察している人だけとあまりに少なくて面白くないですね。実はユーザ名と対象になったファイルなど細かい情報は標準入力を介して、次のようなフォーマットで渡されます。

図X 送られてくるメールのフォーマット

対象モジュール名 対象ファイル名

---

Triggered 操作種類 watch on モジュールのリポジトリ内の完全パス

By ユーザ名

この標準入力を介して渡されたデータを出力するようにテストプログラムを改造してみました【図X】。

図X notify 用テストプログラム(/home/cvsuser/notify_prog.pl)

#!/usr/bin/perl

$logfile = "/home/cvsuser/logfile";

$date=`date`; chomp($date);

$pwd=`pwd`; chomp($pwd);

open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";

print LOG "$date NOTIFY($pwd):";

foreach $arg (@ARGV) {

  print LOG " $arg";

}

print LOG "\n";

while () {

  print LOG $_;

}

close($logfile);

これを、図Xのように設定してみました。この例では簡単にするために、最初にtestという文字列があるディレクトリに合致するようにしています。

図X notify での設定例
^test   /home/cvsuser/notify_prog.pl %s

で、図Xのように、watchersコマンドでtest.txt、test2.txtについて観察者がmikaとして設定されているのを確認【1〜3行目】の上、friendlyというユーザとしてeditコマンドを実行してみました【4行目】。ここでは、(watch onしてないかぎり)別に何も起こりません。

図X 実行結果
% cvs watchers

test.txt        mika    edit    unedit  commit

test2.txt       mika    edit    unedit  commit

% cvs edit test.txt



ログを見てみると、図Xのようになっていました。引数の宛先がmikaになっていて、標準入力を介して与えられたデータが続いています。モジュール名test、対象ファイル名test2.txt、モジュールのリポジトリ内の完全パス/home/cvsroot/test、引き金となったユーザの名前friendlyがその中に含まれています。ちなみに、friendlyはpserverのユーザなのですが、ちゃんとその名前が記述されています。pserverを介して実行しても、編集を開始しユーザと対象となったファイルなどの情報はわかるようになっています。perlを使えば、この情報をメール以外にも流用できそうですね。

図X /home/cvsuser/logfileに記録されたログ
Sat Feb 24 16:30:46 JST 2001 NOTIFY(/tmp/cvs-serv13397): mika

test test2.txt

---

Triggered edit watch on /home/cvsroot/test

By friendly

 

unedit(release)コマンド実行時に外部プログラムを呼び出すには

notifyでの設定はeditと同じなので省略します。uneditの通知は、あらかじめeditをしておかないと行われません。なお、releaseコマンドは実行時にuneditと同様に扱われます。

図X 実行結果
% cvs unedit test.txt

ログの出力はeditだったところが、uneditになっただけで後は変わりません【図X】。

図X /home/cvsuser/logfileに記録されたログ
Sat Feb 24 16:41:27 JST 2001 NOTIFY(/tmp/cvs-serv13414): mika

test test2.txt

---

Triggered unedit watch on /home/cvsroot/test

By friendly

 

tagコマンド実行時に外部プログラムを呼び出すには

タグをつけたときにプログラムを実行したいことがあるかもしれません。今一つ私には思い浮かばないんですが、一区切りついたときや、暫定リリースだといったときに、クリーンビルドしてみるとかexportして配布パッケージを作るとかいうことがあるのかもしれません。タグをつけるコマンドはtagとrtagがありますが、tagコマンド実行時に外部プログラムを呼び出すように設定できるのはtaginfoだけです。modulesでは、rtagコマンドに対してのみ設定が可能で、tagコマンドの実行時には関係ありません。

taginfoの書式は図Xのようになります。まず、最初にディレクトリパターンがきます。ディレクトリパターンは上で述べたように正規表現です。次に呼び出すプログラムで、プログラムにユーザが任意の引数を渡すことができます。CVSはこのプログラム実行時には、更に引数として、タグ名、対象となったディレクトリ、対象となったファイルとそのリビジョンの組をその数だけ渡します。

図X taginfo での書式
ディレクトリパターン プログラムへの完全パス [引数]

taginfoテストプログラムを図Xのように用意し、図Xのようにtaginfoに設定してみます。modules用と違って、引数をあるだけ出力するようにしてあります【7〜9行目】。

図X taginfo 用テストプログラム(/home/cvsuser/taginfo_prog.pl)

#!/usr/bin/perl

$logfile = "/home/cvsuser/logfile";

$date=`date`; chomp($date);

$pwd=`pwd`; chomp($pwd);

open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";

print LOG "$date TAGINFO($pwd):";

foreach $arg (@ARGV) {

  print LOG " $arg";

}

print LOG "\n";

close($logfile);


図X taginfo での設定例
^test /home/cvsuser/taginfo_prog.pl

この状態で、タグの設定【図X 1〜5行目】、移動【図X 12〜14行目】、削除【図X 15〜19行目】を行ってみます。タグの移動については説明していなかったと思いますが、例えばあるファイル(test.txt)を編集・コミット【図X 6〜12行目】して、その新しいリビジョンの方に以前のリビジョンについているタグを設定したいというようなときに、-Fオプションをつけて以前のリビジョンのタグをそのファイルに移動します。タグ移動のイメージ図を図Xに示しておきます。

図X 実行結果
% cvs tag TAGTEST

cvs server: Tagging .

T test.txt

T test2.txt

T test3.txt

(test.txtを編集)

% cvs commit -m "Tag move test" test.txt

Checking in test.txt;

/home/cvsroot/test/test.txt,v  <--  test.txt

new revision: 1.27; previous revision: 1.26

done

% cvs tag -F TAGTEST

cvs server: Tagging .

T test.txt

% cvs tag -d TAGTEST

cvs server: Untagging .

D test.txt

D test2.txt

D test3.txt


図X タグの移動のイメージ図


このとき、ログファイルを見てみると、taginfoで呼び出されるプログラムには沢山の引数が渡されているのがわかります。まず、タグの名前であるTAGTESTが来て、次に操作(設定にはadd、移動にはmov、削除にはdel)、そして対象となったファイルの含まれるディレクトリ(/home/cvsroot/test)、対象となったファイルとそのリビジョンの組(test.txtと1.26など)がファイルの数だけ並べられているのが確認できます。プログラムで色々加工すると面白そうですね。

図X /home/cvsuser/logfileに記録されたログ
Sun Feb 18 17:34:19 JST 2001 TAGINFO(/tmp/cvs-serv8566): TAGTEST add /home/cvsroot/test test.txt 1.26 test2.txt 1.11 test3.txt 1.4



Sun Feb 18 17:38:04 JST 2001 TAGINFO(/tmp/cvs-serv8617): TAGTEST mov /home/cvsroot/test test.txt 1.27

Sun Feb 18 17:40:11 JST 2001 TAGINFO(/tmp/cvs-serv8627): TAGTEST del /home/cvsroot/test test.txt 1.27 test2.txt 1.11 test3.txt 1.4

 

rtagコマンド実行時に外部プログラムを呼び出すには

rtagの実行時にもtaginfoに設定されたプログラムは呼び出されますが、同時にmodulesに-tオプションで設定されたプログラムも呼び出されます。taginfoは上の設定をそのまま使うものとして、ここではmodulesの設定について見てみます。設定方法は図Xのようになります。オプション-tを使う以外は他のときと同じです。CVSが追加引数として渡すのは、モジュール名とタグ名です。

図X modules での書式
モジュール名 -t プログラムへの完全パス モジュールディレクトリ

このためのテストプログラムを図Xのように用意します。このプログラムを/home/cvsuser/rtag_prog.plとして、図Xのように設定します。

図X modules 用テストプログラム(/home/cvsuser/rtag_prog.pl)
#!/usr/bin/perl

$logfile = "/home/cvsuser/logfile";

$date=`date`; chomp($date);

$pwd=`pwd`; chomp($pwd);

open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";

print LOG "$date RTAG: $ARGV[0] $ARGV[1]\n";

close($logfile);

図X modules での設定例
test -t /home/cvsuser/rtag_prog.pl test

この状態で、tagのときと同じようにタグの設定【図X 1〜3行目】、移動【図X 4〜6行目】、削除【図X 7〜9行目】を行ってみます。今度は、tagの実行時と違って、各実行時に「Executing ''/home/cvsuser/rtag_prog.pl' 'test' 'RTAGTEST''( ''/home/cvsuser/rtag_prog.pl' 'test' 'RTAGTEST''を実行しています)というメッセージが最後に出ています【3、6、9行目】。つまり、modulesで指定したプログラムが実行されていることがわかります。引数には、確かにモジュール名testとタグ名RTAGTESTが渡されています。

図X 実行結果
% cvs -d :pserver:mika@localhost:/home/cvsroot rtag RTAGTEST test

cvs server: Tagging test

cvs server: Executing ''/home/cvsuser/rtag_prog.pl' 'test' 'RTAGTEST''

% cvs -d :pserver:mika@localhost:/home/cvsroot rtag -F RTAGTEST test

cvs server: Tagging test

cvs server: Executing ''/home/cvsuser/rtag_prog.pl' 'test' 'RTAGTEST''

% cvs -d :pserver:mika@localhost:/home/cvsroot rtag -d RTAGTEST test

cvs server: Untagging test

cvs server: Executing ''/home/cvsuser/rtag_prog.pl' 'test' 'RTAGTEST''

このときログファイルには図Xのようなログが記録されています。taginfoの出力は基本的にさっきと同じです。rtagが呼ばれる度に、まずtaginfoで設定されたプログラムが呼ばれ、その後modulesに設定したプログラムが呼ばれているのがわかります。modulesに設定されたプログラムには、tagが設定されたのか、移動されたのか、削除されたのかという情報は渡されていませんし、対象になったファイルもわかりません。これを見る限りでは、taginfoの方が便利そうです。

図X /home/cvsuser/logfileに記録されたログ
Sun Feb 18 17:46:56 JST 2001 TAGINFO(/home/cvsroot/test): RTAGTEST add /home/cvsroot/test test.txt 1.27 test2.txt 1.11 test3.txt 1.4

Sun Feb 18 17:46:56 JST 2001 RTAG: test RTAGTEST



Sun Feb 18 17:48:18 JST 2001 TAGINFO(/home/cvsroot/test): RTAGTEST mov /home/cvsroot/test test.txt 1.28

Sun Feb 18 17:48:18 JST 2001 RTAG: test RTAGTEST

Sun Feb 18 17:49:42 JST 2001 TAGINFO(/home/cvsroot/test): RTAGTEST del /home/cvsroot/test test.txt 1.27 test2.txt 1.11 test3.txt 1.4

Sun Feb 18 17:49:42 JST 2001 RTAG: test RTAGTEST

commitコマンド実行時に外部プログラムを呼び出すには

commitはとても大事な操作なため、さまざまなタイミングと処理対象に対して外部プログラムが呼び出せるようになっています。commit時のプログラム呼び出しを設定できる管理ファイルは、modules、cvswrappers、commitinfo、verifymsg、loginfo、notifyの6つです。 以下でそれぞれの性質について検討してみます。そして、その後に実際にテストプログラムを走らせてみて、何が起こっているかを確認してみます。

  1. modules

    modulesでの設定法は、図Xのようになります。commit用のオプションは-iです。プログラム実行時の追加引数は、モジュールのリポジトリ内での完全パスです。上でcheckoutなどで使用したテストプログラムをちょっといじった(COMMITとしただけです)プログラムを/home/cvsuser/commit_prog.plという名前で作成し【図X】、設定します。

    図X modules での書式
    モジュール名 -i プログラムへの完全パス モジュールディレクトリ

    図X modules 用テストプログラム(/home/cvsuser/commit_prog.pl)

    #!/usr/bin/perl
    
    $logfile = "/home/cvsuser/logfile";
    
    $date=`date`; chomp($date);
    
    $pwd=`pwd`; chomp($pwd);
    
    open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";
    
    print LOG "$date COMMIT($pwd): $ARGV[0] $ARGV[1]\n";
    
    close($logfile);

    図X modules での設定例
    test -i /home/cvsuser/commit_prog.pl test
  2. cvswrappers
  3. cvswrappersでの設定法は、図Xのようになります。commit時にはファイルが入ってくるばかりでなく、出て行きもするので、両方のプログラムを呼び出すことが可能です。出て行くほうの設定法はcheckoutのときと同じです。それに加えて、-tオプションで入ってくるファイルに対するフィルタプログラムを指定します。 出ていく方のフィルタプログラム(toフィルタと、この本では呼んでいます)は引数を少なくとも2つ取ることを期待されています。この2つの引数は、入力ファイルと出力ファイルで、図Xでは「%s %s」のように並べられています。toフィルタは2番目の%sで指定された出力ファイルを作成しなければなりません。もし作成されない場合には、CVSが出力ファイルがないと文句を言って処理をやめてしまいます。

    図X cvswrappers での設定法
    ファイルの種類 -f 'プログラムへの完全パス [引数] %s' -t 'プログラムへの完全パス [引数] %s %s'

    ですので、 出力用のフィルタプログラムとして、ここではそのままコピーするというプログラムを用意します。コピーを行っているのは、7行目の「system("cp $ARGV[0] $ARGV[1]");」という部分です。 他の部分は他のテストプログラムと大して変わりません。WRAP_TOであるということと、カレントディレクトリ、および引数を出力するようになっています。

    図X 出力用フィルタプログラム(/home/cvsuser/to_filter.pl)
    #!/usr/bin/perl
    
    
    
    $logfile = "/home/cvsuser/logfile";
    
    
    
    unless ($ARGV[0]&&$ARGV[1]) {
    
        die "Usage: to_filter.pl infile outfile\n";
    
    }
    
    system("cp $ARGV[0] $ARGV[1]");
    
    $date=`date`; chomp($date);
    
    $pwd=`pwd`; chomp($pwd);
    
    open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";
    
    print LOG "$date WRAP_TO($pwd):";
    
    foreach $arg (@ARGV) {
    
      print LOG " $arg";
    
    }
    
    print LOG "\n";
    
    close($logfile);

    このファイルを図Xのように設定します。コミット作業の最中には入力用フィルタ(fromフィルタ)も呼び出されるので、前の方で使用したものを設定しておきます。fromフィルタが呼び出されるのは、リポジトリ登録後に結果を作業コピーの方へ反映するからです。

    図X cvswrappers での設定例
    *.txt -f '/home/cvsuser/from_filter.pl %s' -t '/home/cvsuser/to_filter.pl %s %s'

     

  4. commitinfo
  5. 設定法は、図Xのようになります。

    図X commitinfo での設定法
    ディレクトリパターン プログラムへの完全パス [引数]


    commitinfoに設定されたプログラムはコミットが開始された時点で呼び出され、追加引数として、モジュールのリポジトリ内の完全パスと対象となる各ファイルの名前が渡されます。このプログラムの終了ステータスはCVSによってチェックされていて、もし0以外だとコミットが中止されます。つまり、コミットの本作業に入る前に各ファイルをチェックして、もし何らかの問題がある場合には、まだ影響の出ていない状態でやめることができるように設計されているのです。

    なお、ここで使用するテストプログラムは図Xのようなもので、各ファイルのチェックは行いません。単に実行時の状態と引数をログファイルに出力するだけの毎度のテストプログラムです。これを、/home/cvsuser/commitinfo_prog.plとして、図Xのように設定します。

    図X commitinfo 用テストプログラム(/home/cvsuser/commitinfo_prog.pl)
    #!/usr/bin/perl
    
    
    
    $logfile = "/home/cvsuser/logfile";
    
    
    
    $date=`date`; chomp($date);
    
    $pwd=`pwd`; chomp($pwd);
    
    open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";
    
    print LOG "$date COMMITINFO($pwd):";
    
    foreach $arg (@ARGV) {
    
      print LOG " $arg";
    
    }
    
    print LOG "\n";
    
    close($logfile);
    図X commitinfo での設定例
    ^test /home/cvsuser/commitinfo_prog.pl
  6. verifymsg
  7. verifymsgでの設定法は図Xのようになります。

    図X verifymsg での設定法
    ディレクトリパターン プログラムへの完全パス [引数]

    verifymesgに設定されたプログラムはcommitinfoのプログラムが実行された後、呼び出されます。このプログラムには、ユーザが入力したログメッセージをチェックすることが期待されています。そのため、追加引数としてログメッセージを暫定的に保存しているファイルの完全パスが渡されます。commitinfoのものと同様に、このプログラムの終了ステータスもpCVSによってチェックされていて、もし0以外だとコミットがアボートされます。つまり、ユーザの入力したログメッセージが要求を満たしているかを、コミットの本作業に入る前にチェックして、満たしていない場合は影響のない状態でやめることができるように設計されているのです。

    ここで使うテストプログラム【図X】はそうしたチェックは行わず実行時の状態と引数を出力するだけの他のテストプログラムと変わらないものです。このテストプログラムを/home/cvsuser/verifymsg_prog.plとして、図Xのように設定します。

    図X verifymsg 用テストプログラム(/home/cvsuser/verifymsg_prog.pl)
    #!/usr/bin/perl
    
    
    
    $logfile = "/home/cvsuser/logfile";
    
    
    
    $date=`date`; chomp($date);
    
    $pwd=`pwd`; chomp($pwd);
    
    open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";
    
    print LOG "$date VERIFYMSG($pwd):";
    
    foreach $arg (@ARGV) {
    
      print LOG " $arg";
    
    }
    
    print LOG "\n";
    
    close($logfile);
    図X verifymsg での設定例
    ^test /home/cvsuser/verifymsg_prog.pl

     

  8. loginfo
  9. 図Xはloginfoの設定法です。loginfo ではプログラムへの完全パスに加えて、プログラムに渡される追加引数の展開フォーマットを指定する必要があります。展開フォーマットは、%に続けたs、V、vの3文字で指定します。それぞれの文字の意味は、sが対象となったファイル名で、Vがそのファイルの前のリビジョン、vが新規につけられたリビジョンです。複数指定する場合には、%{sV}のように{}でくくります。%sだと「test.txt」のように%{sV}だと「test.txt,1.5」のように、%{sVv}だと「test.txt,1.5,1.6」のように各要素が「,」で区切られた形になります。なお、その形で渡されるファイルとリビジョンの組以外に、一番始めに対象となったモジュールの名前が渡されます。ちなみに、%{}という形で指定すると、モジュール名だけが渡されます。

    図X loginfo での設定法
    ディレクトリパターン プログラムへの完全パス [引数] 展開フォーマット

    loginfoに設定されたプログラムは、実際の格納作業が終了したあとで呼び出されます。そのため、一般に完了通知を行うためのプログラムを指定します。あるいは、何かの後処理が必要な場合も、その処理を行うためのプログラムはここで設定することができます。なお、この後にmodulesに設定したプログラムも実行されます。modulesでは渡される追加引数が、モジュールのリポジトリ内での完全パスだけとちょっと使い勝手が悪いですが、モジュールを指定するのは簡単です。どちらを使うかはそのあたりのトレードオフということになるかと思います。

    ここでは何が起こっているかを見るためだけに図Xのようなプログラムファイルを、/home/cvsuser/loginfo_prog.plとしてloginfoファイルに図Xのように設定します。 展開フォーマットとしては、最も情報が多くなるように3文字を%{sVv}という形で指定します。

    図X loginfo 用テストプログラム(/home/cvsuser/loginfo_prog.pl)
    #!/usr/bin/perl
    
    
    
    $logfile = "/home/cvsuser/logfile";
    
    
    
    $date=`date`; chomp($date);
    
    $pwd=`pwd`; chomp($pwd);
    
    open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";
    
    print LOG "$date LOGINFO($pwd):";
    
    foreach $arg (@ARGV) {
    
      print LOG " $arg";
    
    }
    
    print LOG "\n";
    
    close($logfile);
    図X loginfo での設定例
    ^test /home/cvsuser/loginfo_prog.pl %{sVv}
  10. notify

    notifyの設定法はeditのものと同じですのでここでは書きません。同様のテストプログラムが同様に設定されているものとします。


テストモジュールを用いてコミットを実行してみます【図X】。なお、コミットする変更は「test.txtの削除」「test3.txtの復帰」「test1.txt、test2.txtの編集」です。また、test.txtに対してmikaとfriendlyが、test2.txtに対してmikaが観察者(watcher)になっているものとします。

図X 実行結果
% cvs commit -m "I hope this will be final commit"

cvs commit: Examining .

Removing test.txt;

/home/cvsroot/test/test.txt,v  <--  test.txt

new revision: delete; previous revision: 1.29

done

Checking in test1.txt;

/home/cvsroot/test/test1.txt,v  <--  test1.txt

new revision: 1.2; previous revision: 1.1

done

Checking in test2.txt;

/home/cvsroot/test/test2.txt,v  <--  test2.txt

new revision: 1.13; previous revision: 1.12

done

Checking in test3.txt;

/home/cvsroot/test/test3.txt,v  <--  test3.txt

new revision: 1.6; previous revision: 1.5

done

cvs commit: Executing ''/home/cvsuser/commit_prog.pl' '/home/cvsroot/test''

このとき、ログは図Xのようになります。notify_prog.plの出力情報はうっとおしいので、ここでは省略してあります。

まず、普通の編集であるtest1.txtとtest2.txtが(パターンに合致しているので)cvswrappersのtoフィルタを介して暫定ファイル(/tmp/cvsUzibIf、/tmp/cvsclfdmo)にコピーされています【1〜2行目】。

次に、commitinfoに設定されたプログラムが実行されています【3行目】。引数は確かに、モジュールのリポジトリ内の完全パスである、/home/cvsroot/testと対象となった各ファイル名test.txt、test1.txt、test2.txt、test3.txtになっています。

さらに、verifymsgに設定されたプログラムが実行され、ログメッセージを保存した/tmp/cvsIxST6zというファイル名を渡されています【4行目】。

ここから、各ファイルについて実際のコミット動作が行われます。

まず、test.txtには観察者がいるため、test.txtの削除に対してnotifyに設定されたプログラムが実行されています。引数はtest.txtを観察しているユーザの名前、つまりmikaまたはfriendlyです。このプログラムは観察者毎に実行されています【5〜8行目】。

次にtest1.txtとtest2.txtに対して、toフィルタが格納のために再度実行されたあと作業コピー側に格納された結果を反映させるために、連続してfromフィルタが呼ばれています【9〜12行目】。

ここで、test2.txtには観察者がいるため、コミットに対してnotifyに設定されたプログラムが実行され、test2.txtの観察者であるmikaというユーザ名が渡されています【13〜14行目】。

さらに、復帰したファイルであるtest3.txtについて同じようにtoフィルタとfromフィルタが実行されます【15〜16行目】。これで、各ファイルのリポジトリへの格納と作業ファイルへの反映はこれで終了です。

この格納反映作業が無事成功した状態で、loginfoに設定されたプログラムが呼び出されます【17行目】。引数は、対象となったモジュールの名前testがます最初に来て、続いて各対象ファイルが上で指定した通りに「ファイル名,旧リビジョン,新リビジョン」の形に展開されて並んでいることがわかります。

そして、最後の最後にmodulesに設定されたプログラムが、モジュールのリポジトリ内での完全パス/home/cvsroot/testを引数に呼び出されています【18行目】。

図X /home/cvsuser/logfileに記録されたログ
Fri Feb 23 20:22:04 JST 2001 WRAP_TO(/home/cvsuser/tmp/test): test1.txt /tmp/cvsUzibIf

Fri Feb 23 20:22:04 JST 2001 WRAP_TO(/home/cvsuser/tmp/test): test2.txt /tmp/cvsclfdmo

Fri Feb 23 20:22:04 JST 2001 COMMITINFO(/home/cvsuser/tmp/test): /home/cvsroot/test test.txt test1.txt test2.txt test3.txt

Fri Feb 23 20:22:04 JST 2001 VERIFYMSG(/home/cvsuser/tmp/test): /tmp/cvsIxST6z

Fri Feb 23 20:22:04 JST 2001 NOTIFY(/home/cvsuser/tmp/test): mika

(出力略)

Fri Feb 23 20:22:04 JST 2001 NOTIFY(/home/cvsuser/tmp/test): friendly

(出力略)

Fri Feb 23 20:22:04 JST 2001 WRAP_TO(/home/cvsuser/tmp/test): test1.txt /tmp/cvssPgkhm

Fri Feb 23 20:22:04 JST 2001 WRAP_FROM(/home/cvsuser/tmp/test): test1.txt

Fri Feb 23 20:22:04 JST 2001 WRAP_TO(/home/cvsuser/tmp/test): test2.txt /tmp/cvsofCohw

Fri Feb 23 20:22:04 JST 2001 WRAP_FROM(/home/cvsuser/tmp/test): test2.txt

Fri Feb 23 20:22:04 JST 2001 NOTIFY(/home/cvsuser/tmp/test): mika

(出力略)

Fri Feb 23 20:22:04 JST 2001 WRAP_TO(/home/cvsuser/tmp/test): test3.txt /tmp/cvsW2LFX2

Fri Feb 23 20:22:04 JST 2001 WRAP_FROM(/home/cvsuser/tmp/test): test3.txt

Fri Feb 23 20:22:04 JST 2001 LOGINFO(/home/cvsuser/tmp/test): test test3.txt,1.5,1.6 test1.txt,1.1,1.2 test2.txt,1.12,1.13 test.txt,1.29,NONE

Fri Feb 23 20:22:04 JST 2001 COMMIT(/home/cvsuser/tmp/test): /home/cvsroot/test

このように、各ファイルに設定したプログラムが呼び出されるタイミングや、その引数、終了ステータスでの中止可能性といった性質を良く理解すれば、自分の目的とする動作をどのファイルを使ってどのように設定して実現させるかということは自ずから明らかになるかと思います。

exportコマンド実行時に外部プログラムを呼び出すには

さて、最後はexportです。exportはCVSディレクトリを除いた形でモジュールのコピーを取り出すためのコマンドでした。このコマンドの実行時に何を自動実行させる必要があるのかぴんと来ませんが、とにかくmodulesに設定すれば実行されるようにはできます。また、cvswrappersに設定しておけば、ファイルを取り出すときのフィルタもcheckoutと同様に実行されます。modulesでの設定法は図Xのようになります。オプションが-eになった以外はmodulesでの一般的な指定法です。cvswrappersでの設定法はcheckout、updateと同じですので書きません。

図X modules での設定法
モジュール名 -e プログラムへの完全パス モジュールディレクトリ

modules用のテストプログラムを図Xの様に用意して、これを/home/cvsuser/export_prog.plとして図Xのように設定します。cvswrappersはcheckout、updateの場合と同じように設定されているものとします。追加引数としては対象となったモジュール名が渡されます。

図X modules 用テストプログラム(/home/cvsuser/export_prog.pl)

#!/usr/bin/perl

$logfile = "/home/cvsuser/logfile";

$date=`date`; chomp($date);

$pwd=`pwd`; chomp($pwd);

open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";

print LOG "$date EXPORT($pwd): $ARGV[0] $ARGV[1]\n";

close($logfile);


図X modules での設定例
test -e /home/cvsuser/export_prog.pl test

この状態で、exportに-Dオプションで日付を明日を指定することで最新版を取り出してみます【図X】。最後にexport_prog.plが引数にモジュール名testを渡されて実行されたというメッセージが出ています。

図X 実行結果
% cvs -d /home/cvsroot export -D tomorrow test

cvs export: Updating test

U test/test1.txt

U test/test2.txt

U test/test3.txt

cvs export: Executing ''/home/cvsuser/export_prog.pl' 'test''

ログの方を見てみると、*.txtに合致したファイル(この場合全てですが)毎にcvswrappersに指定されたfromフィルタが実行され、最後にmodulesのプログラムが呼び出されていることがわかります。

図X /home/cvsuser/logfileに記録されたログ
Fri Feb 23 23:02:48 JST 2001 WRAP_FROM(/home/cvsuser/tmp/exp/test): test1.txt

Fri Feb 23 23:02:48 JST 2001 WRAP_FROM(/home/cvsuser/tmp/exp/test): test2.txt

Fri Feb 23 23:02:48 JST 2001 WRAP_FROM(/home/cvsuser/tmp/exp/test): test3.txt

Fri Feb 23 23:02:48 JST 2001 EXPORT(/home/cvsuser/tmp/exp): test

以上、各コマンドについて、ざっとテストプラグラムを設定して実験してみましたが、なんとなくわかったでしょうか。具体的に何か仕事をさせないと、良くわからないかもしれませんね。以下では、いくつか例をあげてやってみましょう。作業コピーの自動更新と、電子メールでのコミットの通知、およびnotifyの使い方について少し具体的に紹介してみます。


作業コピーを自動更新するには

誰かがコミットした時に、どこかに置いてある作業コピーを自動更新したいときがあります。作業コピーはウェブホームページかもしれませんし、コンパイルできるかチェックするためのものかもしれません。コミット作業のメイン部分が完了時に外部プログラムを呼び出す場合の設定はloginfoかmodulesでするということを上で説明しました。loginfoを使うと色々な処理が可能ですが、この目的ではそこまでの処理は必要ありません。サブディレクトリで動作を変えたいとか、コミットされたファイルをチェックして何か処理をさせたいとかいう場合には、loginfoを使うべきです。対象となるファイルひとつひとつ、サブディレクトリひとつひとつに細かい制御が必要でないのなら、modulesを使うのが、サブディレクトリへのパターンマッチなどを考える必要がない分簡単です。ということで、ここでは、modulesでの設定例を紹介します。

前準備

まず、自動更新させるための作業コピーを用意します。そうですね、例えばウェブホームページをCVS管理下におき、公開領域に作業コピーを置くと想定してみましょう。ホームページディレクトリは/home/pozzy/public_htmlだとします。もし既にいくらかホームページを作ってしまっているなら、暫定的な作業スペースを用意して、public_htmlにあったファイルをコピーしてきて整理します。中には管理下におきたくないファイルもあるかもしれません。また、シンボリックリンクは後で張ることにして、削除してしまいましょう。

整理がすんだら、そのディレクトリ内でモジュールを登録します。GIFファイルやJPEGファイルなどバイナリファイルが含まれている場合には、-Wで指定するのを忘れないようにしましょう(cvswrappersで既に指定されている場合には必要ありません)。モジュール名はなんでもいいですが、ひとまずpozzyhpとでもしましょうか。登録がすんだら、public_htmlを一旦退避し、pozzyhpをチェックアウトしてきてpublic_htmlに改名します。そして、シンボリックリンクや管理下に置かなかったファイルを足したりする後処理をします。

図X 前準備
% cd /home/pozzy; mkdir tmp; cd tmp

% (cd /home/pozzy/public_html; tar cpf - .) | tar xpf -

(整理する)

% cvs -d /home/cvsroot import -W "*.gif -k'b'" -W "*.GIF -k'b'" -m "Pozzy's homepage" pozzyhp pozzy POZZYHP_1

% cd /home/pozzy

N pozzyhp/index.html

N pozzyhp/intro.html

cvs import: Importing /home/cvsroot/pozzyhp/images

N pozzyhp/images/BACK403.GIF

N pozzyhp/images/myphoto.gif



No conflicts created by this import



% mv public_html public_html.bak

% cvs -d /home/cvsroot checkout pozzyhp

cvs checkout: Updating pozzyhp

U pozzyhp/index.html

U pozzyhp/intro.html

cvs checkout: Updating pozzyhp/images

U pozzyhp/images/BACK403.GIF

U pozzyhp/images/myphoto.gif

% mv pozzyhp public_html

(後処理をする)

これで、ひとまず前準備はすみました。次は、この作業コピーがコミット時に自動更新されるように設定します。

自動更新スクリプトの設置

この手の自動更新は良く使いそうなので、図Xのようなperlスクリプトを用意してみました。どこかにはもっといいスクリプトがあるかもしれませんし、わかれば簡単なので自分で作ってもいいでしょう。一番ひっかかる可能性があるのは、updateコマンドをバックグラウンド実行しなければいけないというところです。このプログラムが呼び出されるコミットの実行中にはディレクトリにロックがかかっていて、updateコマンドを実行することはできません。実行しようとすると、ロック解除を待って止まってしまうのですが、コミット動作そのものもそこで止まってしまい、結果永遠に待ち続けることになります。これを避けるためには、updateをバックグラウンドで実行することでプログラムの実行から切り離し、プログラム本体が終了した後にupdateが実行されるようにするのが簡単です。これが、11行目で最後に「&」がつけられている意味です。11行目自体は「作業コピーのディレクトリに移動して、cvs updateコマンドをバックグラウンド実行」という意味の文字列です。実際の実行は次の12行目でsystemという関数を呼び出すことで行っています。

プログラムの他の部分は、最初の4行が環境および変数の設定、6〜8行目が引数の処理、14〜17行目までが最後のメッセージ出力です。環境および引数の設定については、このスクリプトを使用する場合は、環境に合わせて変更して下さい。第一引数は作業コピーのディレクトリでなければなりません。これは必須で、無い場合は実行されません。第二引数はあってもなくてもいいのですが、更新後に作業コピーのファイル属性を変える必要がある場合には、指定すれば再帰的に変更されます。この例程度では指定する必要はないでしょう。最後のメッセージ部分は、もっと改良すると楽しいかもしれません。例えば、引数にメールアドレスを指定できるようにして、自動更新を通知するようにするということもできるでしょう。

図X CVSROOT/auto_update.pl
#!/usr/bin/perl

# cvs path

$cvs = '/usr/bin/cvs';

$usage = "usage: auto_update.pl WORKDIR [MODE]";



# process arguments

die $usage, "\n" unless $workdir = $ARGV[0];

$chmod = $ARGV[1] ? "; chmod -R $ARGV[1]" : "";



# main update process which must be execute in background.

$cmd = "(cd $workdir; $cvs update $chmod) &";

system($cmd);



# last message.

$date = `date`; chomp($date);

$mesg = "$workdir automatically updated at $date";

print "$mesg\n";

このスクリプトはどこに置いても構わないのですが、単なる趣味で、CVSROOTに管理ファイルとして登録します。以前、4章で漢字変換スクリプトとして作成したwrapnkf2というファイルをやはり管理ファイルとして登録しましたが、同じ様にcheckoutlistにこのファイルを加えて、CVSROOTに追加登録します【図X】。lsしてauto_update.plができていれば成功です。なお登録する前に、実行権がついていることを確認して、なかったら設定しておいて下さい。この後で使います。

図X auto_update.plを管理ファイルとして追加する
% cat >> checkoutlist

auto_update.pl Auto updater auto_update.pl cannot be retrieved

% cvs add -m "Auto updater" auto_update.pl

cvs add: scheduling file `auto_update.pl' for addition

cvs add: use 'cvs commit' to add this file permanently

% cvs commit -m "Add auto_update.pl" auto_update.pl checkou

tlist

RCS file: /home/cvsroot/CVSROOT/auto_update.pl,v

done

Checking in auto_update.pl;

/home/cvsroot/CVSROOT/auto_update.pl,v  <--  auto_update.pl

initial revision: 1.1

done

Checking in checkoutlist;

/home/cvsroot/CVSROOT/checkoutlist,v  <--  checkoutlist

new revision: 1.5; previous revision: 1.4

done

cvs commit: Rebuilding administrative file database

% ls /home/cvsroot/CVSROOT

Attic             commitinfo     editinfo    modules,v  rcsinfo,v    wrapnkf2

Emptydir          commitinfo,v   editinfo,v  notify     taginfo      wrapnkf2,v

auto_update.pl    config         history     notify,v   taginfo,v    writers

auto_update.pl,v  config,v       loginfo     passwd     val-tags

checkoutlist      cvswrappers    loginfo,v   passwd,v   verifymsg

checkoutlist,v    cvswrappers,v  modules     rcsinfo    verifymsg,v

なお、modulesを使って外部プログラムとして呼び出す場合には、外部プログラムに引数を渡すことができません。なので、図Xのようなラッパー(wrapper=包むもの)を作成して、これをプログラムとして指定することにします。これは、このホームページ専用のシェルスクリプトなので、ホームページの一部に組み込んで、バージョン管理にも一緒に登録しておくといいでしょう。シンボリックリンクの張り直しなど、固有の動作もここで実行するように書くこともできると思います。

図X modules登録用ラッパー (/home/pozzy/public_html/bin/update.sh)
#!/bin/sh

/home/cvsroot/CVSROOT/auto_update.pl /home/pozzy/public_html

なお、ここではローカルなリポジトリを考えて上のような構成としましたが、遠隔にあるリポジトリへの自動更新も、自動更新スクリプトをCGIとして作れば、リポジトリ側からそのCGIを呼び出して実行することで自動更新を行わせることが可能かと思います。そうすることができれば、例えばプロバイダの容量制限がある場合にどんどん増殖していくリポジトリ分を他へ移すことができるわけですから嬉しいですよね。それについては現在検討中です。そのうちホームページにでも書くかもしれませんので、お楽しみに。

さて、このラッパーをmodulesに登録します【図X】。これをCVSROOTにコミットすれば、設定は終わりです。次は本当に動いているかテストしてみましょう。

図X modules に設定する
test -i /home/pozzy/public_html/bin/update.sh test
テストしてみる

どこでもいいですから別の所(遠隔でも構いません、別のところであれば)に、pozzyhpの作業コピーを取り出します【図X 1行目】。そして、適当に作業して、commitします【4行目】。途中の出力はどうでもいいので略しますが、8行目に/home/pozzy/public_html/bin/update.shを実行したというメッセージが出ていることに注目して下さい。次にスクリプトの最後で出力するようにしていたメッセージが出力されています【9行目】。これで最後のはずですが、プロンプトが出てきた後で、何も入力していないのに、updateが実行された時のメッセージが出力されるはずです【10〜14行目】。これがバックグラウンドで実行されて、ロックが解除されるのを待っていたupdate実行プロセスが、commitが終了するやいなや処理を再開したというわけです。

図X 自動更新のテスト
% cvs -d /home/cvsroot checkout pozzyhp

% cd pozzyhp

(編集)

% cvs commit -m "fix bug in index.html and add mailto to intro.html"

cvs commit: Examining .

(中略)

done

cvs commit: Executing ''/home/pozzy/public_html/bin/update.sh' '/home/cvsroot/pozzyhp''

/home/pozzy/public_html automatically updated at 金  2月 23 23:42:33 JST 2001

% cvs update: Updating .

U index.html

U intro.html

cvs update: Updating bin

cvs update: Updating images

ということで、このような風にしてコミット時の更新が自動化できることがわかりました。ぜひ活用して下さい。


電子メールでコミットを通知するには

コミット時に他の開発者へメールを送ることをコミットメールと言ったりもします。やはり、コミットの完了時点でメールしたいでしょうから、loginfoもしくはmodulesを使用するのがよいでしょう。しかし、modulesへ渡される情報は少なすぎてメールをもらっても嬉しくなさそうです。そこで、そういう場合にはloginfoでメールするように設定します。良く使われている機能ですので、このためのログを解析してメールするためのプログラムとして良いプログラムが当然のように提供されています。/usr/lib/cvs/contribにlogというプログラムが入っているはずです。knjwrpをあてた場合には、ログメッセージをJISに変換してメールするように書き換えられたlog_jpというプログラムも入っているはずです。これらは、perlスクリプトですので、興味のある人は覗いてみて下さい。ログに日本語を使用している場合には、log_jpにならって、メールを転送する前にJISコードに変換しておくのがよいでしょう。log_jpではlogの85行目を図Xのように、nkfフィルタで変換するように書き換えています。ここを書き換えるついでにMailになっているメールコマンドを好きなやつに変えても構いません。特になければこのままにしておいて下さい。

図X log の85行目を書き換える
# $mailcmd = "| Mail -s 'CVS update: $modulepath'";

$JMAIL = "nkf -j | Mail"; # japanese mail

$mailcmd = "| $JMAIL -s 'CVS update: $modulepath'";

このプログラムをloginfoに設定します。このプログラムの先頭部分に説明がついているので読んでもらうといいのですが(英語ですけれど)、プログラムの使い方は図Xのようになります。-fが必須で、他のオプションは必須ではありません。-mでのユーザ(メールアドレス)指定は複数回繰り返すことができます。-sオプションはメッセージにstatus -vの出力を加えないようにします。-sを指定しない場合にはメッセージにstatus -vの出力がついてきます。

図X log の使い方
log [[-m ユーザ(メールアドレス)] ...] [-s] -f ログファイル 'ディレクトリ名 ファイル ...'

では、実際に設定してみましょう。上でずっと使ってきた、testモジュールがコミットされたときに、CVSROOTの下のcommitlogというファイルにログをとりつつ、developpers@hoge.comという開発者のメーリングリストに転送するというような状況を想定してみます。この場合、図Xのように設定します。なお、テストするときにはメールアドレスには自分のメールアドレスを指定するようにして下さいね。

図X loginfo での設定例
^test /usr/lib/cvs/contrib/log %s -f $CVSROOT/CVSROOT/commitlog -m developers@hoge.com

この設定をリポジトリに反映させたら、実際にいじってコミットしてみます。ここでは、test2.txtとtest3.txtをいじってみたとします。図Xのようにcommitコマンドを実行します。

図X コミットしてみる
% cvs commit -m "Loginfo test 3"

cvs commit: Examining .

Checking in test2.txt;

/home/cvsroot/test/test2.txt,v  <--  test2.txt

new revision: 1.16; previous revision: 1.15

done

Checking in test3.txt;

/home/cvsroot/test/test3.txt,v  <--  test3.txt

new revision: 1.8; previous revision: 1.7

done

cvs commit: Executing ''/home/cvsuser/commit_prog.pl' '/home/cvsroot/test''

さて、どんなメールが届いたでしょうか。上の想定だと以下のようなメールがdeveloppers@hoge.comに登録されたユーザに届いているはずです。題名には、testというモジュールが更新されたと書いてあります。内容には、日付とその変更をした人(cvsuser)につづいてモジュールのリポジトリ内での完全パス、実際に作業を行った場所の情報、対象となったファイル、それらに対してstatus -vコマンドを実行した結果がずらずらと並んでいます。また、この内容と同じものが、/home/cvsroot/CVSROOT/commitlogにも同じ内容のログが記録されているはずです。確かめてみてください。

図X 届いたメール
Subject: CVS update: test

From: CVS User <cvsuser@xxx.xxx>

To: developers@hoge.com

Date: Sat, 24 Feb 2001 13:16:27 +0900



Date:   Saturday February 24, 2001 @ 13:16

Author: cvsuser



Update of /home/cvsroot/test

In directory xxx.xxx:/home/cvsuser/tmp/test



Modified Files:

        test2.txt test3.txt

Log Message:

Loginfo test 3

===================================================================

File: test2.txt         Status: Up-to-date



   Working revision:    1.16    Sat Feb 24 04:16:26 2001

   Repository revision: 1.16    /home/cvsroot/test/test2.txt,v



   Existing Tags:

        TAGINFO_TEST                    (revision: 1.10)



===================================================================

File: test3.txt         Status: Up-to-date



   Working revision:    1.8     Sat Feb 24 04:16:27 2001

   Repository revision: 1.8     /home/cvsroot/test/test3.txt,v



   Existing Tags:

        No Tags Exist

このように、コミットメールを使うようになると、コミットログの内容が詳細になるそうです。ただし、仲間内での愚痴とかも書かれやすくなるため、気をつけないと他の人が読むと眉をひそめるような内容のログになることもあるようです。後から誰からでも参照されるものだということを念頭において書きましょうということだそうです。


編集状態を細かく通知するには

コミットメールは非常に便利なのですが、コミットされた時にしかメールがこないと不便なこともあります。筆者はあまりそういう状態になったことはないのですが、非常に複雑なコードを複数人で編集していてお互いの変更が頻繁に入り乱れるような場合、更新をしばらく忘れたりするとマージが非常に大変になってしまったりします。そうした場合に、編集されるたびに自分に通知がくるようにするにはどうしたいいかという話をちょっとだけ詳しくします。通知を行うプログラムを呼び出すように設定する管理ファイルはnotifyファイルです。上のeditでその設定法については説明しました。そして、このプログラムはwatchコマンドを使って別途観察している人を指定しておかなければ呼び出されないということも言いました。ここではまず、watchコマンドと関連コマンドであるedit、unedit(release)、commit、watchers、editorsについてその機能と関わり合いについて説明してみようと思います。そして、一般的な設定例について紹介します。

watchに関連するコマンドたち

  1. watch
  2. watchの書式は図Xのようになります。watchにはon、off、add、removeという4つのサブコマンドがあり、これで動作を切り替えます。-aオプションで指定するのは操作種別で、edit、unedit、commitの3種類が指定できます。それぞれ、edit、unedit(release)、commitに対応しています。-aは複数回指定できます。なお、指定しなければ、3種類全てという指定になります。最後に対象となるファイルを複数個していすることができます。省略した場合には、そのディレクトリ(以下)が対象になります。

    図X watchコマンドの書式
    cvs watch [on|off|add|remove] [-lR] [[-a 操作]...] [ファイル群...]
    
    


    watchコマンドには、2つの全く異なる機能があります。一つはあるファイルに対して観察者を指定および指定解除する機能で、サブコマンドのうちのadd(指定)とremove(指定解除)で行います。もう一方は、対象ファイルを取り出すと必ず読み取りのみになるように設定および設定解除する機能で、残りのサブコマンドon(設定)とoff(設定解除)で行います。後者の機能は、editコマンドを実行して編集を開始するように他の人に強いることを目的としています。なぜなら、普通に書き込める状態ではeditコマンドを実行しないでも編集できてしまうため、わざわざeditコマンドを実行してから編集を開始しようとはあまり思わないためです(よほど律儀な人なら別ですけど、筆者はずぼらなのでまずしません)。

  3. edit
  4. 編集を開始することをそのファイルの観察者に通知するためのコマンドです。なお、このコマンドを実行すると自分も観察者のリストに入れられます。editコマンドの書式は図Xのようになります。特別なオプションは何もありません。

    図X editコマンドの書式
    cvs edit [-lR] [ファイル群...]
    
    
  5. unedit(release)
  6. 編集をやめたことをそのファイル(とunedit)の観察者に通知するためのコマンドです。なお、このコマンドを実行するとそれまでに加えた変更は捨てられてしまいます。そして観察者のリストから除かれます。uneditコマンドの書式は図Xのようになります。特別なオプションは何もありません。また、releaseコマンド実行もunedit実行と同じ取り扱いになります。

    図X uneditコマンドの書式
    cvs unedit [-lR] [ファイル群...]
    
    
  7. commit
  8. いわずと知れたcommitコマンドです。編集結果をリポジトリに反映するためのコマンドですので、関わってきています。commitコマンド実行時にはファイルのcommitを観察している人に通知が行われます。

  9. watchers
  10. このコマンドは、あるファイルを観察している人の一覧を見るためのコマンドです。

    図X watchersコマンドの書式
    cvs unedit [-lR] [ファイル群...]
    
    
  11. editors

    watchersコマンドに似ていますが、こちらのコマンドは、あるファイルを編集している人の一覧を見るためのコマンドです。

    図X editorsコマンドの書式
    cvs unedit [-lR] [ファイル群...]
    
    
管理ファイルについて

管理ファイルで関係してくるのはnotifyとuserの2つです。notifyについては上で説明しましたが、usersファイルというのは何でしょうか。これは、pserver経由で接続している場合に、pserverでのユーザ名ではメールが送れないということを解決するための管理ファイルです。つまり、usersファイルにpserverのユーザ名に対応するメールアドレスを書いておくと、そのメールアドレスに通知が行われるようになります。usersに設定してないと、pserverのユーザににメールを送ろうとして失敗して、CVSが文句を言います。

usersの書式は図Xのようになります。ただし、このファイルはまだCVSの正式な管理ファイルにはなっていない(cvs-1.10.8時点)ので、checkoutlistに自分で加えなくてはいけません。

図X CVSROOT/usersの書式
ユーザ名:メールアドレス

以下に一般的な設定例について紹介します。全てのモジュールでこの機能を使いたい場合にはnotifyファイルを図Xのように設定します。さらに、usersを使用して、pserverのユーザには必ず電子メールアドレスを指定します。例えば、orangeにはpozzy@hoge.com、appleにはpie@hage.com、grapeにはjuice@moge.comというアドレスがある場合には、図Xのように設定します。設定後、図Xのような行をcheckoutlistに追加してusersを管理ファイルとして扱うようにするのを忘れないでください。

図X notifyの設定例
ALL mail %s -s "CVS notification"

図X usersの設定例
orange:pozzy@hoge.com

apple:pie@hage.com

grape:juice@moge.com

図X usersをcheckoutlistに追加するための行(例)
users Users file checkout failed

テストしてみる

ここで、うまく設定できたか試してみるのと同時に、上にあげたコマンドを実際に使ってみることにします。以下に、test1.txt、tset2.txt、test3.txtという3つのファイルをもつnotifytestモジュールを利用したテスト例を示します。

まず、orangeとしてcvs watch addを実行し、モジュールに含まれる各ファイルの観察者になります。ついでに、他の人がeditを実行するのを忘れないように、cvs watch onコマンドを実行しておきます。

図X orangeがnotifytestモジュールの観察を開始する

orange% cvs watch add

orange% cvs watchers

test1.txt       orange  edit    unedit  commit

test2.txt       orange  edit    unedit  commit

test3.txt       orange  edit    unedit  commit

orange% cvs watch on

次に、grapeになってnotifytestモジュールをチェックアウトしたとしましょう。この状態でnotifytestに含まれるファイルを見てみると、いつもと違ってファイルに書き込み許可が出ていません【1〜6行目】。よく知らない人がこういう状態になってしまったらビックリしますよね。こういうときには、いきなりchmod u+w などで書き込み許可を出してしまわないで、cvs watchersを実行してみてください。そのファイルに観察者がいたら、きっとその人が犯人に違いありません(たまに良く分かっていない初心者が試してみて設定されてしまうこともあります)。あわてずに、「ああ、cvs watch onしたんだな」と思って、cvs editを使って下さい。どうしても知らせたくないと思ったらchmod u+wでもいいんですけどね…。編集者がchmodを使って書き込み許可を出してしまうのを止めることまではできません。この仕組みはあくまでもわかった人が自分の意志で使うためのものです。

さて、ということでcvs editでtest1.txtの編集を開始すると宣言しましょう【7行目】。コマンド実行後、ファイル一覧を見てみると、test1.txtに書き込み許可が出て、編集可能状態になったことがわかります【11行目】。

図X grapeがeditをtest1.txtに対して実行する


grape% ls -l

total 16

drwxr-xr-x    2 juice    users        4096 Feb 24 17:03 CVS

-r--r--r--    1 juice    users           7 Feb 24 15:19 test1.txt

-r--r--r--    1 juice    users           7 Feb 24 15:19 test2.txt

-r--r--r--    1 juice    users           7 Feb 24 15:19 test3.txt

grape% cvs edit test1.txt

grape% ls -l

total 16

drwxr-xr-x    3 juice    users        4096 Feb 24 17:05 CVS

-rw-r--r--    1 juice    users           7 Feb 24 15:19 test1.txt

-r--r--r--    1 juice    users           7 Feb 24 15:19 test2.txt

-r--r--r--    1 juice    users           7 Feb 24 15:19 test3.txt



さて、観察者であるorangeにはどんなメールが送られたのでしょうか。図Xのようなメールが送られているはずです。usersに指定したorangeのメールアドレスpozzy@hoge.comに送られます。ちゃんと設定が効いているようですね。メール本体にはgrapeがmotifytestモジュールのtest1.txtに対してeditを実行(つまり編集を開始)したことなどが書かれています。

図X orangeに送られたメール

Subject: CVS notification

From: Proxy user for CVS <fruit@xxx.xxx>

To: pozzy@hoge.com

Date: Sat, 24 Feb 2001 17:05:47 +0900





notifytest test1.txt

---

Triggered edit watch on /home/cvsroot/notifytest

By grape

さて、ここで図Xのようにcvs watchersを実行して観察者の状態を眺めてみると、面白いことがわかります。さっきは、orangeだけだったtext1.txtの観察者にgrapeが加えられています。実は、editで編集を開始したことをCVSに教えると、CVSはこの編集者を準観察者とみなし、観察者のリストに加えてくれるのです。ちなみに、真の観察者とは各操作種別の前にtがついていることで区別できます。確かに、grapeの列には、tedit、tunedit、tcommitというtのついた操作種別が並んでいます。観察者リストに加えられている間は、他の人がここに並んでいる操作をするたびに、orangeが受け取るのと同じメールを受け取ることになります。grapeは、uneditやcommitをした時点で観察者リストから取り除かれます。

図X 観察者の状態を眺めてみる

grape% cvs watchers



test1.txt       orange  edit    unedit  commit

        grape   tedit   tunedit tcommit

test2.txt       orange  edit    unedit  commit

test3.txt       orange  edit    unedit  commit

暫定的な観察者でも誰が編集をしているのかわかりますけれど、もっとちゃんと編集者について調べるにはcvs editorsを使います。この例では図Xのような結果になります。test1.txtに対して今、grapeが編集中であること、それを開始した時刻、および作業を行っているホスト名や作業しているディレクトリまでわかります。面白いですね。編集者が複数いれば、ずらずらと並んで表示されます。

図X 編集者の状態を眺めてみる

grape% cvs editors

test1.txt       grape   Sat Feb 24 08:05:47 2001 GMT    ara.moge.com     /home/juice/tmp/notifytest

なお、uneditやcommitを実行すると、ファイルから書き込み許可がなくなります。これは、また次に編集するときはeditを実行してね、という意味です。

図X 編集をコミットしてみる

grape% cvs commit -m "Commit test for notification" test1.txt



Checking in test1.txt;

/home/cvsroot/notifytest/test1.txt,v  <--  test1.txt

new revision: 1.2; previous revision: 1.1

done

grape% ls -l

total 16

drwxr-xr-x    3 juice    users        4096 Feb 24 17:44 CVS

-r--r--r--    1 juice    users          15 Feb 24 17:43 test1.txt

-r--r--r--    1 juice    users           7 Feb 24 15:19 test2.txt

-r--r--r--    1 juice    users           7 Feb 24 15:19 test3.txt

なお、いくらかいじったファイルにuneditを実行しようとすると、「ファイルは変更されています。変更を元に戻しますか?」と聞かれます【図X 4行目】。これに「y」と答えると、それまでの変更がすべて捨てられて、前の状態に戻されてしまいますので気をつけてください。それが嫌な場合は、nと答えて、commitコマンドで変更を反映するかどうにか、いいようにして下さい。

図X uneditしてみるとどうなるのかな?

grape% cvs edit test2.txt



(編集)

grape% cvs unedit test2.txt

test2.txt has been modified; revert changes? y

最後に、観察をやめるというのを試してみます。orangeがtest1.txtに興味を無くしたものとします。そこで、観察をやめることにしました。観察をやめるには、cvs watch removeを使用します。図Xのように実行【1行目】後、watchersコマンドでtest1.txtのリストから自分が削除されていることを確認します【2〜5行目】。たまたま、このときgrapeが編集を開始していたようで、grapeだけ残ってしまいました。そのうち消えるでしょう。なお、他の人に迷惑なので、cvs watch offをtest1.txtに実行して、書き込み許可が出されるようにしておきます【6行目】。これで、次にgrapeがコミットしたときにはtest1.txtには書き込み許可が出るようになりましたが、grapeは気がつかないかもしれませんね。

図X uneditしてみるとどうなるのかな?

orange% cvs watch remove test1.txt

orange% cvs watchers

test1.txt       grape   tedit   tunedit tcommit

test2.txt       orange  edit    unedit  commit

test3.txt       orange  edit    unedit  commit

oarnge% cvs watch off test1.txt

以上、簡単に使い方を紹介してみました。おわかり頂けたでしょうか。この機能はあくまでも、トラブルが生じそうな状態をなるべく早く知るのを支援しているにすぎませんし、editを使うことを厳しく他の人に強いることもできません。この機能の使用においては、開発者間の意志疎通が十分なされていることが必要条件となってきます。orangeは前もって、watch add、watch onする前に他の開発者にそうするよと話しておくべきです。勝手に導入されてしまうと、他の開発者はむっとしてしまうかもしれませんし、気がつかずにchmodで許可を出して編集を続けるかもしれません。そうしたお互いの意志疎通を大事にした上で、それを支援するためにこうした機能を使っていってください。うまく使えば、心強い味方となりますのでがんばって下さいね。