[目次へ][6章へ][6.2へ][6.4へ][7章へ] 最終更新:$Date: 2002/10/23 06:26:50 $

なんか、modulesの挙動が大いに前と違うような…。

しかたがないので全面改訂(しくしくしくしく…。


■■6.3 外部プログラムを呼び出してみよう

■■■ 6.3.1 テスト用コードを準備しよう
■■■ 6.3.2 cvswrappersを設定しておこう (checkout, commit, update, export)
■■■ 6.3.3 modulesファイルを設定しておこう(checkout, commit, update, rtag, export)
■■■ 6.3.4 commitinfo, verifymsg, loginfoファイルを設定しておこう(commit)
■■■ 6.3.5 taginfoファイルを設定しておこう(tag, rtag)
■■■ 6.3.6 notifyファイルを設定しておこう(edit, unedit(release), commit)
■■■ 6.3.7 コマンドを実行してみよう


■■6.3 外部プログラムを呼び出してみよう

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

ただし、wrappersの-f/-tオプションは、オリジナルの配布では無効にされています。ここでは漢字コード変換パッチがあたっている(1.11.1p1まで)もしくはコンパイル時に有効にしたもの(任意)と仮定して話を進めていきます。どちらもしていない場合にはwrappersに関する記述は飛ばしてください。

【表6.3.1】トリガコマンドとプログラム指定をおこなう管理ファイル
トリガコマンド プログラム指定をおこなう管理ファイル 対象の指定法
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ファイルだけ、ファイルパターンで指定するのはcvswrappersファイルだけです。その他のファイルでは、ディレクトリパターンで指定します。

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

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

例えば、「^module$」というパターンは先頭がmで始まって、oduleと続き、その後は何もない、つまりmoduleというディレクトリ名にのみ合致します。kmoduleもmoduleaも合致しません。なお、ディレクトリ名はリポジトリからの相対パスを指定します。つまり、/home/cvsroot/moduleがmoduleになります。

ちなみに、このパターン「^module$」は、module/moduleにもその他の任意のサブディレクトリにも合致しません。例えば、commitinfoだと、サブディレクトリでのcommitでは外部プログラムは呼び出されません。サブディレクトリ以下に合致したときにも外部プログラムを実行したければ、「^module/」のように書いてみれば良さそうです。しかし今度は、moduleには合致しなくなります。なお、両方のパターンを並べて書いておけば、どちらの場合でも合致してプログラムが実行されます。

自分の必要とする動作を考えてパターンを設定してください。似たようなモジュールがなければ、「^module」だけでもいいんですけどね。後から、moduleが頭についたモジュール(modulerとか)を追加すると、意図せずにそのモジュールに対してもプログラムが実行されてしまうことがあるということには注意しておいて下さい。

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

■■■6.3.1 テスト用コードを準備しよう

テスト用のコードを【図6.3.1】のように用意します。このコードはサポートページにも置いておきますので、適宜ダウンロードしてお使いください[commandlog.pl]。

中身について簡単に説明しておきます。なお、行頭の2文字は説明のための行番号です。01行目から10行目まではコメントです。11行目の変数$LOGFILEがログを記録するためのファイルの指定です。適宜変更してください。19行目から34行目までが、コマンドの実行の様子を指定されたファイルに記録するためのサブルーチンです。引数としては、コマンドを区別するためのコマンド名を第1引数に(20行目)、ログファイル名を第2引数に(21行目)とります。実際に呼び出すときログファイルを変えたくなったら、第2引数に指定すればよいわけです。指定がなければ、11行目で指定されたファイルに記録します。

22行目と23行目では、実行時刻と実行しているディレクトリをそれぞれ取得しています。これらは記録に組み込まれます。24行目で記録先ファイルをオープンし、時刻、コマンド名、実行ディレクトリをこのファイルに記録しています(25行目)。26行目から28行目では引数があれば、それをあるだけ取ってきて、ファイルに記録しています。

これで一応記録すべきものは記録してしまったので、ファイルをクローズして(30行目)、終わります(31行目)。

ちなみに、ファイル最後の「1;」(33行目)というのは、このファイルを別のファイルが読み込めるようにするためのおまじないです。

【図6.3.1】commandlog.pl
▼
01 # filename: commandlog.pl
02 #   This is a command logging script for testing external command envoking.
03 # usage:
04 #   #!/usr/bin/perl
05 #   reguire "commandlog.pl";
06 #   ...
07 #   &commandlog("COMNAME"[, "LOGFILENAME"]);
08 #
09 # author: Mika Ohtsuki (mika@mikamama.com)
10 # $Id$
11 $LOGFILE = "/home/cvsuser/bin/logfile";
12 
13 #####
14 # method: commandlog
15 #   command logging subroutine.
16 # usage:
17 #   &commandlog("COMNAME"[, "LOGFILENAME"]);
18 #####
19 sub commandlog {
20     my $commandname = $_[0];
21     my $logfile = $_[1] ? $_[1] : $LOGFILE;
22     my $date=`date`; chomp($date);
23     my $pwd=`pwd`; chomp($pwd);
24     open(LOG,">>$logfile") || die "cannot open logfile: $logfile\n";
25     print LOG "$date $commandname($pwd): ";
26     foreach $arg (@ARGV) {
27 	       print LOG "$arg, ";
28     }
29     print LOG "\n";
30     close($logfile);
31 }
32 
33 1;
▲

以下では、このプログラムを利用して、コマンド実行時のログを記録していきます。このファイルを読み込むには、絶対パスを指定するか、perlに-Iでファイルのインクルードパスを指定する必要があります。まぁ、絶対パスの指定の方が簡単でしょうかね…。

■■■6.3.2 cvswrappersを設定しておこう (checkout, commit, update, export)

管理ファイルcvswrappersによって、外部プログラムを呼び出すことができます。ただし、この機能は上でも書いた通り、標準では組み込まれません。

cvswrappersでの設定法は、【図6.3.2】のようになります(図では折り返されていますが実際は改行せずに入力します)。

【図6.3.2】cvswrappersでの設定法
▼
ファイルの種類 -f 'プログラムの絶対パス [引数] %s' -t 'プログラムの絶対パス [引数] %s %s'
▲

cvswrappersは基本的にファイル毎にパターンに合致するかをチェックしてプログラムを実行します。cvswrappersに指定したプログラムはファイルの出入りのあるときは、いつでも実行されます。-fがリポジトリ「から」出ていくときに呼び出されるフィルタ(ここではfromフィルタと呼びます)を指定するオプションで、-tがリポジトリ「へ」入っていくときに呼び出されるフィルタ(ここではtoフィルタと呼びます)を指定するオプションです。

オプションの後の引数を'(シングルクォーテーション)で囲むことと、その内部にプログラムの絶対パス、それから引数にあたる文字列を空白文字で区切って書いておくことを忘れないでください。引数にあたる文字列は-fオプションがひとつで「%s」、-tオプションが2つで「%s %s」のようになります。これらのフィルタプログラムが実際に実行されるときに、「%s」に対してファイル名が渡されます。

toフィルタは引数を少なくとも2つ取ることを期待されています。この2つの引数は、入力ファイルと出力ファイルで、-tで指定する「%s %s」が渡されます。toフィルタは2番目の%sで指定された出力ファイルを作成しなければなりません。もし作成されない場合には、CVSは出力ファイルがないと文句を言って処理を中断してしまいます。

そこで、fromフィルタとして、単にログを記録するだけのプログラムを用意します【図6.3.3】。6.3.1項で紹介したログ記録のサブルーチンを使用しています。【図6.3.3 2行目】のrequire文がサブルーチンのファイルを読み込むためのもので、【図6.3.3 4行目】で実際にサブルーチンを呼んでいます。

【図6.3.3】cvswrappers用テストプログラム(/home/cvsuser/bin/from_filter.pl)[filter_from.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("WRAP_FROM");
▲

また、toフィルタとして、「そのままコピーする」というプログラムを用意します【図6.3.4】。ログを記録する他に、【図6.3.4 7行目】の「system("cp $ARGV[0] $ARGV[1]");」でファイルをコピーしています。

【図6.3.4】toフィルタ(/home/cvsuser/bin/filter_to.pl)
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

unless ($ARGV[0]&&$ARGV[1]) {
    die "Usage: to_filter.pl infile outfile\n";
}
system("cp $ARGV[0] $ARGV[1]");

&commandlog("WRAP_TO");
▲

用意ができたら、これらのフィルタをcvswrappersに、【図6.3.5】のように設定しておきます。

【図6.3.5】cvswrappersに実際に設定する
▼
*.txt -f '/home/cvsuser/bin/filter_from.pl %s' -t '/home/cvsuser/bin/filter_to.pl %s %s'
▲

実際のテストは最後にします。先に、他の設定もしていきましょう。

■■■6.3.3 modulesファイルを設定しておこう(checkout, commit, update, rtag, export)

管理ファイルmodulesによって、外部プログラムを呼び出せるコマンドは、checkout, commit, update, rtag, exportの5つです。このうち、commitについては、commitinfo, verifymsg, loginfoの使用が推奨されています。modulesで指定するのはモジュールで、モジュールにcheckoutが実行された場合にそのモジュールに指定されたプログラムを実行します。

modulesでの設定法は、【図6.3.6】のようになります。

【図6.3.6】modulesでのプログラム呼び出しの設定法
▼
モジュール名 [-o checkoutプログラム] [-i commitプログラム] [-u updateプログラム] [-t rtagプログラム] [-e exportプログラム] モジュールディレクトリ
▲

最初にモジュール名(実際にはキーワード)を指定し、オプション-o, -i, -u, -t, -eで、それぞれcheckout, commit, update, rtag, export時に呼び出すプログラムを指定します。プログラムの指定は絶対パスである必要があり、また、ユーザが独自に引数を渡すことはできません。CVSが指定されたプログラムを実際に呼び出すときには、対象となったモジュール名を引数として渡します。

最後に、モジュールのリポジトリ内でのディレクトリ名を書いておかなくてはいけません。これは、modulesというファイルが、元々モジュールに別名をつけるためのものであることに由来します。つけないとエラーになります。

ここで、【図6.3.7】〜【図6.3.11】のようなテストプログラムを用意して、挙動をチェックしてみます。これらのperlプログラムはコマンド名が変わっている以外は、同じものです。これらのperlプログラムを/home/cvsuser/binという場所に、それぞれ、checkouttest.pl, committest.pl, updatetest.pl, rtagtest.pl, exporttest.plという名前で保存します。

【図6.3.7】checkout用テストプログラム(/home/cvsuser/bin/checkouttest.pl)[checkouttest.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("CHECKOUT");
▲
【図6.3.8】commit用テストプログラム(/home/cvsuser/bin/committest.pl)[committest.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("COMMIT");
▲
【図6.3.9】update用テストプログラム(/home/cvsuser/bin/updatetest.pl)[updatetest.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("UPDATE");
▲
【図6.3.10】rtag用テストプログラム(/home/cvsuser/bin/rtagtest.pl)[rtagtest.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("RTAG");
▲
【図6.3.11】export用テストプログラム(/home/cvsuser/bin/exporttest.pl)[exporttest.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("EXPORT");
▲

設置が終わったら、modulesファイルを【図6.3.12】のように設定しておきましょう。図では改行していますが、最後まで1行で書く必要があります。

【図6.3.12】modulesファイルに設定しておく
▼
test -o /home/cvsuser/bin/checkouttest.pl -i /home/cvsuser/bin/committest.pl -u /home/cvsuser/bin/updatetest.pl  -t /home/cvsuser/bin/rtagtest.pl -e /home/cvsuser/bin/exporttest.pl test
▲

後で例をあげて説明しますが、一挙に設定したのには理由があります。以前はそうではなかったと思うのですが、この設定を書き換えるとモジュールに対応した作業コピーをリリースしてチェックアウトしなおす必要があるからです。これに気がつかなくて、結構はまりました…ええ(しくしく)。

■■■6.3.4 commitinfo, verifymsg, loginfoファイルを設定しておこう(commit)

commitコマンドは非常に重要なコマンドであるため、開始時、ログ解析時、終了時のそれぞれでプログラムが実行できるようになっています。開始時に呼び出されるプログラムを設定できるのがcommitinfoファイル、ログ解析時に解析プログラムを設定できるのがverifymsg、終了時に後処理を行うプログラムを設定できるのがloginfoファイルです。このうち、loginfoファイルが終了通知などでもっともよく使用されています。

順番に詳しく見ていきましょう。

■■■■@commitinfo

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

設定方法は、【図6.3.13】のようになります。

【図6.3.13】commitinfoでの設定法
▼
ディレクトリパターン プログラムの絶対パス [引数]
▲

なお、ここでは各ファイルのチェックはおこなわず、単に実行時の状態と引数をログファイルに出力するだけのテストプログラムを用意します【図6.3.14】。

【図6.3.14】commitinfo用テストプログラム(/home/cvsuser/bin/commitinfotest.pl)[commitinfotest.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("COMMITINFO");
▲

これを、/home/cvsuser/bin/commitinfotest.plとして、【図6.3.15】のように設定します。

【図6.3.15】commitinfoでの設定例
▼
^test /home/cvsuser/bin/commitinfotest.pl
▲

■■■■Cverifymsg

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

管理ファイルverifymsgでの設定法は【図6.3.16】のようになります。

【図6.3.16】verifymsgでの設定法
▼
ディレクトリパターン プログラムの絶対パス [引数]
▲

ここで使うテストプログラム【図6.3.17】はそうしたチェックはおこなわず実行時の状態と引数を出力するだけの他のテストプログラムと変わらないものです。

【図6.3.18】verifymsg用テストプログラム(/home/cvsuser/bin/verifymsgtest.pl)[verifymsgtest.pl]
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("VERIFYMSG");
▲

このテストプログラムを/home/cvsuser/bin/verifymsgtest.plとして、【図6.3.19】のように設定します。

【図6.3.19】verifymsgでの設定例
▼
^test /home/cvsuser/bin/verifymsgtest.pl
▲

■■■■Dloginfo

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

【図6.3.20】はloginfoの設定方法です。

【図6.3.20】loginfoでの設定法
▼
ディレクトリパターン プログラムの絶対パス [引数] 展開フォーマット
▲

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

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

【図6.3.21】loginfo用テストプログラム(/home/cvsuser/bin/loginfotest.pl)
▼
#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("LOGINFO");
▲

このプログラムをloginfoファイルに【図6.3.22】のように設定します。展開フォーマットとしては、最も情報が多くなるように3文字を%{sVv}という形で指定します。

【図6.3.22】loginfoで実際に設定してみる
▼
^test /home/cvsuser/bin/loginfotest.pl %{sVv}
▲

なお、1.11.1以降、loginfoの書式が拡張され、かなり複雑なものが書けるようになりました。具体的には、シングルコーテーション「'」で囲まれていたものを、ダブルコーテーション「"」で囲むようにし、中に、ドル記号「$」、バッククォート(`)、バックスラッシュ(あるいは円記号で表示されるコード)「\」およびバックスラッシュ「\」でエスケープした「"」を含めるようにできるようになったということです。これにより、シェルに複雑な処理をさせるコードを書いてそのまま実行してもらえるようになりました。

■■■6.3.5 taginfoファイルを設定しておこう(tag, rtag)

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

taginfoの書式は【図6.3.23】のようになります。

【図6.3.23】taginfoでの書式
▼
ディレクトリパターン プログラムの絶対パス [引数]
▲

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

まず、taginfoテストプログラムを【図6.3.24】のように用意します。[taginfotest.pl]

【図6.3.24】taginfo用テストプログラム(/home/cvsuser/bin/taginfotest.pl)
▼
#!/usr/bin/perl

require "/home/cvsuser/bin/commandlog.pl";

&commandlog("TAGINFO");
▲

その後、【図6.3.25】のようにtaginfoに設定してみます。

【図6.3.25】taginfoでの設定例
▼
^test /home/cvsuser/bin/taginfotest.pl
▲

■■■6.3.6 notifyファイルを設定しておこう(edit, unedit(release), commit)

notifyファイルで外部プログラムを呼び出すことが可能ですが、これは複数人での作業と深く関連しています。このため、トリガーコマンドのうちのeditとuneditについてはまだ説明していません。これらは、checkoutやupdateが実行されてからcommitされるまでの間に、具体的にどのファイルが編集されているかを知るための仕組みの一部です。このコマンドが有効に働くためには、watchコマンドで編集状態に入ったときに、知らせて欲しいファイルを指定しておく必要があります。

具体的には、bmikaがtest.txtと、test2.txtを編集していて、「bmika以外の人がこれらのファイルを編集するときには知りたいなぁ」と思ったとします。この時、bmikaはあらかじめ、

▼
% cvs watch add test.txt test2.txt
▲

のように指定しておく必要があります。このように設定しておけば、mika以外の人がedit、unedit、commitの操作をおこなったときに、CVSは管理ファイルnotifyに設定された外部プログラムを呼び出します。watchコマンドの詳細は、後で改めてします。ここではnotifyへの設定方法とプログラムの呼び出され方を見てみます。

notifyで外部プログラムを呼び出すように設定するには、【図6.3.26】のようにします。

【図6.3.26】notifyでの設定法
▼
パターン プログラムの絶対パス [引数] %s
▲

最初に来るのが、リポジトリからの相対パスにマッチさせるためのパターンです。次に、呼び出すプログラムの絶対パス、最後の%sは観察している人(この例ではcvsuserになります)を引数として渡すためのおまじないです。CVSは特に追加の引数は渡しません。プログラムに渡されるのはここで指定した分だけです。notifyからプログラムに渡せる引数が、観察している人だけとあまりに少なくて面白くないですね。実はユーザ名と対象になったファイルなど細かい情報は標準入力を介して、【図6.3.x】のようなフォーマットで渡されます。

【図6.3.27】送られてくるメールのフォーマット
▼
対象モジュール名 対象ファイル名
---
Triggered 操作種類 watch on モジュールのリポジトリ内のフルパス
By ユーザ名
▲

テスト用のプログラムを、【図6.3.28】のように用意します。commandlogサブルーチンは、この標準入力を介して渡されたデータを出力するようになっていないので、【図6.3.28 5-9行目】でログファイルに内容を書き出すようなコードを追加しています。

【図6.3.28】notify用テストプログラム(/home/cvsuser/bin/notifytest.pl)[notifytest.pl]
▼#!/usr/bin/perl
require "/home/cvsuser/bin/commandlog.pl";

&commandlog("NOTIFY");
open (LOG, ">>$LOGFILE") || die "Cannot open $LOGFILE\n";
while () {
  print LOG $_;
}
close(LOG);
▲

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

【図6.3.29】notifyでの設定例
▼
^test /home/cvsuser/bin/notifytest.pl %s
▲

■■■6.3.7 コマンドを実行してみよう

ここまで、設定が終わったら、お待ちかね、各コマンドを実行してみます。テストには、test.txt、test2.txt、test3.txtという3つのファイルを含むtestというモジュールを使用します。なおここでは、後でのnotify作業などチェックのために、pserverの仮想ユーザをbmika, smikaという名前で作成して【図6.3.30】、ログインしておきます【図6.3.31】。

【図6.3.30】仮想ユーザを作成しておこう(/home/cvsroot/CVSROOT/passwd)
▼
bmika:*************:cvsuser
smika:*************:cvsuser
▲
【図6.3.31】ログインしておこう
▼
% cvs -d :pserver:bmika@localhost:/home/cvsroot login
Logging in to :pserver:bmika@localhost:2401/home/cvsroot
CVS password:
% cvs -d :pserver:smika@localhost:/home/cvsroot login
Logging in to :pserver:smika@localhost:2401/home/cvsroot
CVS password:
▲

さらに、後のnotifyファイルでの挙動確認のために監視を開始しておきます【図6.3.32】。

【図6.3.32】監視を開始しておく
▼
% cvs -d :pserver:bmika@localhost:/home/cvsroot watch on
▲

特にメッセージは現れませんが、今後取り出すファイルはすべて書き込み禁止で取り出されるようになります。

※というわけでもないみたい?>書き込み禁止

■■■■ checkoutコマンドを実行してみる

まず、作業コピーがないとしようがないので、チェックアウトをしておきます【図6.3.33】。bmikaというディレクトリでチェックアウトしてみます。以下そのディレクトリでの作業は[bmika]%で示します。

【図6.3.33】checkoutコマンドの実行結果
▼
[bmika]% cvs -d :pserver:bmika@localhost:/home/cvsroot checkout test
cvs server: Updating test
U test/test.txt
U test/test2.txt
U test/test3.txt
cvs server: Executing ''/home/cvsuser/bin/checkouttest.pl' 'test''
▲

このとき、modulesに設定されたプログラムを実行するメッセージが出力されます【図6.3.33 6行目】。これをみると、引数は1つで、モジュール名が渡されていることがわかりますね。

さて、ログを見てみましょう【図6.3.34】。

【図6.3.34】/home/cvsuser/bin/logfileに記録されたログ
▼
Wed Aug 28 13:25:57 JST 2002 WRAP_FROM(/tmp/cvs-serv11246/test): test.txt,
Wed Aug 28 13:25:57 JST 2002 WRAP_FROM(/tmp/cvs-serv11246/test): test2.txt,
Wed Aug 28 13:25:57 JST 2002 WRAP_FROM(/tmp/cvs-serv11246/test): test3.txt,
Wed Aug 28 13:25:57 JST 2002 CHECKOUT(/tmp/cvs-serv11246): test,
▲

チェックアウト時に、パターンにマッチしたファイルすべてに対して、fromフィルタがそのファイル名を引数に実行され【図6.3.34 1-3行目】、最後にmodulesで指定したチェックアウト用のプログラムがモジュール名を引数に実行されているのがわかります。ちなみに、コマンドの実行ディレクトリが/tmp/cvs-serv11246/testになっているのは、このリポジトリがpserver経由だからです。ローカルの場合は作業コピーで実行されますが、遠隔の場合にはサーバ側に作業場所が作成され、そこで実行されます。

ここで、とってきた作業コピーのCVSディレクトリを覗くと【図6.3.35】、興味深い状態になっていることがわかります。

【図6.3.35】おや?見慣れないファイルが増えてる
▼
[bmika]% ls test/CVS/
Checkin.prog  Entries  Repository  Root  Update.prog
▲

Checkin.progとUpdate.progという見慣れないファイルが増えており、その中身は以下のように、modulesファイルで指定したプログラム名が書かれています。

test/CVS/Checkin.prog
▼
/home/cvsuser/bin/committest.pl
▲
test/CVS/Update.prog
▼
/home/cvsuser/bin/updatetest.pl
▲

コミットや更新のときに、ここに保存された情報が利用されるようです。このファイルはチェックアウトの時にしか作られないようです。このため、modulesで設定変更をするといちいちリリースとチェックアウトをしなければいけないということになっています。多分リポジトリが違う作業コピーが入り混じったときのための処置だと思いますが、ちょっと面倒くさいですね。

■■■■ コミット前の準備

commitコマンドを実行する前に、smikaとしてtestをチェックアウトしておきましょう【図6.3.36】。作業内容は、smikaという別のディレクトリで行うほかは、上と一緒です。こちらのディレクトリでの作業は[smika]%で示すこととします。この作業コピーは、次のコミットでnotifyフィルタの動作を見るほか、更新のときにも使用します。

【図6.3.36】smikaの分をとっておこう
▼
[smika]% cvs -d :pserver:smika@localhost:/home/cvsroot checkout test
cvs server: Updating test
U test/test.txt
U test/test2.txt
U test/test3.txt
cvs server: Executing ''/home/cvsuser/bin/checkouttest.pl' 'test''
▲

notifyフィルタの動作を見るためには、観察者になっておく必要があります。ここでは、test.txtに対してsmikaとbmikaが、test2.txtに対してsmikaが観察者(watcher)になっているものとします。観察者になるコマンドはwatch addです。smikaとbmikaのそれぞれの作業コピーで対象ファイルに対して、watch addを実行しておきます【図6.3.37】【図6.3.38】。

【図6.3.37】smikaの観察開始
▼
[smika]% cvs watch add test.txt test2.txt
▲
【図6.3.38】bmikaの観察開始
▼
[bmika]% cvs watch add test.txt
▲

ここで、たとえばbmikaの作業コピーでwatchersコマンドで観察者を調べてみると、【図6.3.39】のようになっているはずです。このへんの詳細は第7章で説明することにして、作業を進めましょう。

【図6.3.39】観察者の状況
▼
[bmika]% cvs watchers
test.txt        smika   edit    unedit  commit
        bmika   edit    unedit  commit
test2.txt       smika   edit    unedit  commit
▲

■■■■ editコマンドを実行してみよう

観察者の設定が済んだら、bmikaの作業コピーでコミットするための変更を設定します。ここでは「test.txtの削除」、「test1.txtの追加」、「test2.txtの編集」とします。削除と追加は特に問題ないと思いますが、編集については、監視モードになっているため、一旦editでこれを通知してやる必要があります。 実行してみましょう。

【図6.3.40】editコマンドの実行
▼
[bmika]% cvs edit test2.txt
▲

別に何のメッセージも返ってきませんが、書き込み禁止の場合にはそれが解除されています。また、ログファイルには【図6.3.41】のように記録されます。

【図6.3.41】editによるログの状況
▼
Wed Aug 28 13:39:58 JST 2002 NOTIFY(/tmp/cvs-serv11345): smika,
test test2.txt
---
Triggered edit watch on /home/cvsroot/test
By bmika
▲

引数に自分以外の観察者名渡され、標準入力にモジュール名とファイル名、/home/cvsroot/testを監視していてeditコマンドに引き起こされたことと、それがbmikaによるものというメッセージが渡されているのがわかります。ということで、ここでtest2.txtをおもむろに編集しておきます。

■■■■ updateコマンドでの確認

現在の状況は、updateコマンドで見てみると、【図6.3.42】のような感じに見えるはずです。「test.txtの削除」、「test1.txtの追加」、「test2.txtの編集」になっていることを確認しておいてください。

【図6.3.42】updateコマンドで眺めてみる
▼
( cd bmika/test/ ; cvs update)
cvs server: Updating .
R test.txt
A test1.txt
M test2.txt
cvs server: Executing ''/home/cvsuser/bin/updatetest.pl' '/home/cvsroot/test''
▲

モジュールに登録されたプログラムが実行されているのがわかります。一方、ログファイルを眺めてみる【図6.3.43】と、その前にcvswrappersのtoフィルタが実行されているのがわかります。どうも、編集したファイルは比較のためにかあちらに転送しているようです。

【図6.3.43】updateコマンドのログ
▼
Wed Aug 28 13:44:32 JST 2002 WRAP_TO(/tmp/cvs-serv11367): test2.txt, /tmp/cvsIpl9hj,
Wed Aug 28 13:44:32 JST 2002 UPDATE(/tmp/cvs-serv11367): /home/cvsroot/test,
▲

■■■■ commitコマンドを実行してみよう

次にようやくコミットします。もう、さっさと実行してしまいましょう。えい!【図6.3.44】

【図6.3.44】commitを実行!
▼
[bmika]% cvs commit -m "Commit test"
cvs commit: Examining .
Removing test.txt;
/home/cvsroot/test/test.txt,v  <--  test.txt
new revision: delete; previous revision: 1.2
done
RCS file: /home/cvsroot/test/test1.txt,v
done
Checking in test1.txt;
/home/cvsroot/test/test1.txt,v  <--  test1.txt
initial revision: 1.1
done
Checking in test2.txt;
/home/cvsroot/test/test2.txt,v  <--  test2.txt
new revision: 1.3; previous revision: 1.2
done
cvs server: Executing ''/home/cvsuser/bin/committest.pl' '/home/cvsroot/test''
▲

最後の行を見ると、modulesに設定したプログラムは無事動いているようです。このとき、ログは【図6.3.45】のようになります。

【図6.3.45】commitコマンド実行によるログ
▼
Wed Aug 28 16:45:35 JST 2002 WRAP_TO(/tmp/cvs-serv11539): test2.txt, /tmp/cvsxOR9A9,
Wed Aug 28 16:45:35 JST 2002 COMMITINFO(/tmp/cvs-serv11539): /home/cvsroot/test, test.txt, test1.txt, test2.txt,
Wed Aug 28 16:45:35 JST 2002 VERIFYMSG(/tmp/cvs-serv11539): /tmp/cvseXNV9c,
Wed Aug 28 16:45:36 JST 2002 NOTIFY(/tmp/cvs-serv11539): smika,
test test.txt
---
Triggered commit watch on /home/cvsroot/test
By bmika
Wed Aug 28 16:45:36 JST 2002 WRAP_TO(/tmp/cvs-serv11539): test1.txt, /tmp/cvs0jGOjS,
Wed Aug 28 16:45:36 JST 2002 WRAP_TO(/tmp/cvs-serv11539): test1.txt, /tmp/cvsow5Gin,
Wed Aug 28 16:45:36 JST 2002 WRAP_FROM(/tmp/cvs-serv11539): test1.txt,
Wed Aug 28 16:45:36 JST 2002 WRAP_TO(/tmp/cvs-serv11539): test2.txt, /tmp/cvsClp5ib,
Wed Aug 28 16:45:36 JST 2002 WRAP_FROM(/tmp/cvs-serv11539): test2.txt,
Wed Aug 28 16:45:36 JST 2002 NOTIFY(/tmp/cvs-serv11539): smika,
test test2.txt
---
Triggered commit watch on /home/cvsroot/test
By bmika
Wed Aug 28 16:45:36 JST 2002 LOGINFO(/tmp/cvs-serv11539): test test1.txt,NONE,1.1 test2.txt,1.2,1.3 test.txt,1.2,NONE,
Wed Aug 28 16:45:36 JST 2002 COMMIT(/tmp/cvs-serv11539): /home/cvsroot/test,
▲

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

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

さらに、verifymsgに設定されたプログラムが実行され、ログメッセージを保存した/tmp/cvseXNV9cというファイル名を渡されています【図6.3.45 3行目】。 ここまではまだ、前準備です。ここから、各ファイルについて実際のコミット動作がおこなわれます。

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

次にtest1.txtに対して、toフィルタが格納のために再度実行された後、作業コピー側に格納された結果を反映させるために、fromフィルタが呼ばれています【図6.3.45 9-11行目】。同様に、test2.txtは先ほど一度toフィルタを介した後、fromフィルタを通して作業コピーの方へ戻ってきています【図6.3.45 12-13行目】。

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

これで、各ファイルのリポジトリへの格納と作業ファイルへの反映が終了します。

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

リビジョンが存在しない場合には、NONEが入ります。追加の場合には旧リビジョンが、削除の場合には新リビジョンが存在しないので、それぞれNONEと記述されていることが、この例で見て取れます。

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

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

■■■■ updateコマンドを実行してみよう

smikaの作業コピーで、updateコマンドを実行すると、【図6.3.46】のようになります。

【図6.3.46】実行結果
▼
[smika]% cvs update
cvs server: Updating .
cvs server: test.txt is no longer in the repository
U test1.txt
P test2.txt
cvs server: Executing ''/home/cvsuser/bin/updatetest.pl' '/home/cvsroot/test''
▲

modulesに設定したプログラムを実行した旨のメッセージが出力されます【図6.3.46 6行目】。引数は1つで、リポジトリ内のモジュールの絶対パスが渡されていることがわかります。ログファイルはどうなったでしょうか【図6.3.47】。

【図6.3.47】/home/cvsuser/bin/logfileに記録されたupdateのログ
▼
Wed Aug 28 17:09:09 JST 2002 WRAP_FROM(/tmp/cvs-serv11601): test1.txt,
Wed Aug 28 17:09:09 JST 2002 UPDATE(/tmp/cvs-serv11601): /home/cvsroot/test,
▲

cvswrappersのfromフィルタがtest1.txtに対して呼び出された【図6.3.47 1行目】あと、modulesに設定されたプログラムが呼び出されています【図6.3.47 2行目】。差分については関係ないようです。

■■■■ tagコマンドを実行してみる

タグの設定【図6.3.48 1-5行目】、移動【図6.3.48 7-9行目】、削除【図6.3.48 10-14行目】をおこなってみます。設定から移動の間にtest1.txtを編集してコミットしています。よって、タグの移動対象はtest1.txtだけです。

【図6.3.48】実行結果
▼
[bmika]% cvs tag TAGTEST
cvs server: Tagging .
T test1.txt
T test2.txt
T test3.txt
(test1.txtを編集、コミット)
[bmika]% cvs tag -F TAGTEST
cvs server: Tagging .
T test1.txt
[bmika]% cvs tag -d TAGTEST
cvs server: Untagging .
D test1.txt
D test2.txt
D test3.txt
▲

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

【図6.3.49】/home/cvsuser/bin/logfileに記録されたログ
▼
Wed Aug 28 17:46:47 JST 2002 TAGINFO(/tmp/cvs-serv11640): TAGTEST, add, /home/cvsroot/test, test1.txt, 1.1, test2.txt, 1.3, test3.txt, 1.1.1.1,
(コミット関連略)
Wed Aug 28 17:49:31 JST 2002 TAGINFO(/tmp/cvs-serv11673): TAGTEST, mov, /home/cvsroot/test, test1.txt, 1.2,
Wed Aug 28 17:50:00 JST 2002 TAGINFO(/tmp/cvs-serv11679): TAGTEST, del, /home/cvsroot/test, test1.txt, 1.2, test2.txt, 1.3, test3.txt, 1.1.1.1,

■■■■ rtagコマンドを実行してみよう

rtagの実行時にもtaginfoに設定されたプログラムは呼び出されます。タグの設定【図6.3.50 1-3行目】、移動【図6.3.50 5-7行目】、削除【図6.3.50 8-10行目】をおこなってみます。tagのときと同様に、設定から移動の間にtest1.txtの編集とコミットをおこなっています。

【図6.3.50】実行結果
▼
% cvs -d :pserver:bmika@localhost:/home/cvsroot rtag RTAGTEST test
cvs server: Tagging test
cvs server: Executing ''/home/cvsuser/bin/rtagtest.pl' 'test' 'RTAGTEST''
(test1.txtにを編集、コミット)
% cvs -d :pserver:bmika@localhost:/home/cvsroot rtag -F RTAGTEST test
cvs server: Tagging test
cvs server: Executing ''/home/cvsuser/bin/rtagtest.pl' 'test' 'RTAGTEST''
% cvs -d :pserver:bmika@localhost:/home/cvsroot rtag -d RTAGTEST test
cvs server: Untagging test
cvs server: Executing ''/home/cvsuser/bin/rtagtest.pl' 'test' 'RTAGTEST''
▲

今度は、tagの実行時と違って、それぞれの実行時に

▼
Executing ''/home/cvsuser/bin/rtagtest.pl' 'test' 'RTAGTEST''
(''/home/cvsuser/bin/rtagtest.pl' 'test' 'RTAGTEST''を実行しています)
▲

というメッセージが最後に表示されます【3/7/10行目】。つまり、modulesで指定したプログラムが実行されていることがわかります。引数には、確かにモジュール名testとタグ名RTAGTESTが渡されています。

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

【図6.3.51】/home/cvsuser/bin/logfileに記録されたログ
▼
Wed Aug 28 17:53:18 JST 2002 TAGINFO(/home/cvsroot/test): RTAGTEST, add, /home/cvsroot/test, test1.txt, 1.2, test2.txt, 1.3, test3.txt, 1.1.1.1,
Wed Aug 28 17:53:18 JST 2002 RTAG(/tmp/cvs-serv11694): test, RTAGTEST,
(コミット関連略)
Wed Aug 28 17:54:19 JST 2002 TAGINFO(/home/cvsroot/test): RTAGTEST, mov, /home/cvsroot/test, test1.txt, 1.3,
Wed Aug 28 17:54:20 JST 2002 RTAG(/tmp/cvs-serv11730): test, RTAGTEST,
Wed Aug 28 17:55:12 JST 2002 TAGINFO(/home/cvsroot/test): RTAGTEST, del, /home/cvsroot/test, test1.txt, 1.3, test2.txt, 1.3, test3.txt, 1.1.1.1,
Wed Aug 28 17:55:13 JST 2002 RTAG(/tmp/cvs-serv11741): test, RTAGTEST,
▲

■■■■ uneditコマンドを実行してみる

uneditの通知は、あらかじめeditをしておかないとおこなわれません。また、コミットされると、edit状態が解除されてしまうので、コミットしないで変更を破棄する場合にしか使用しないコマンドです。なお、releaseコマンドは実行時にuneditと同様に扱われます。動作を見てみるために、test2.txtに対して、edit、編集、uneditを行ってみます【図6.3.52】。

【図6.3.52】uneditのテスト
▼
[bmika]% cvs edit test2.txt
(test2.txtを編集)
[bmika]% cvs unedit test2.txt
test2.txt has been modified; revert changes? yes
▲

編集してuneditすると、test2.txt has been modified; revert changes?(test2.txtは変更されていますが、変更を元に戻しますか?)と聞かれます。ここでは yes(はい)と答えておきます。

このとき、ログの出力を見てみる【図6.3.53】と、editのメッセージのeditだったところが、uneditになっただけのメッセージが記録されているのがわかります。

【図6.3.53】/home/cvsuser/bin/logfileに記録されたログ
▼
Wed Aug 28 17:57:29 JST 2002 NOTIFY(/tmp/cvs-serv11752): smika,
test test2.txt
---
Triggered edit watch on /home/cvsroot/test
By bmika
Wed Aug 28 17:58:55 JST 2002 NOTIFY(/tmp/cvs-serv11759): smika,
test test2.txt
---
Triggered unedit watch on /home/cvsroot/test
By bmika
▲

■■■■ exportコマンド実行してみよう

さて、最後はexportです。exportはCVSディレクトリを除いた形でモジュールのコピーを取り出すためのコマンドでした。このコマンドの実行時に何を自動実行させる必要があるのかピンときませんが、とにかくmodulesに設定すれば実行されるように設定できます。また、cvswrappersに設定しておけば、ファイルを取り出すときのフィルタもcheckoutと同様に実行されます。

exportコマンドに-Dオプションで日付「tommorow(明日)」を指定することで最新版を取り出してみます【図6.3.54】。最後にexporttest.plが、引数にモジュール名「test」を渡されて実行されたというメッセージが表示されます。

【図6.3.54】exportの実行結果
▼
% 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/bin/exporttest.pl' 'test''
▲

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

【図6.3.55】/home/cvsuser/bin/logfileに記録されたログ
▼
Wed Aug 28 18:01:12 JST 2002 WRAP_FROM(/home/mika/test/test): test1.txt,
Wed Aug 28 18:01:12 JST 2002 WRAP_FROM(/home/mika/test/test): test2.txt,
Wed Aug 28 18:01:12 JST 2002 WRAP_FROM(/home/mika/test/test): test3.txt,
Wed Aug 28 18:01:13 JST 2002 EXPORT(/home/mika/test): test,
▲

■■■■ おわりに

以上、各コマンドについて、ざっとテストプラグラムを設定して実験してみましたが、なんとなくわかりましか。具体的に何か仕事をさせないと、良くわからないかもしれませんね。6.4節と6.5節で、実際に作業コピーの自動更新と、漢字コード変換を具体例として紹介してみます。