Image of the CVS Fish

Open Source Development with CVS 日本語訳

著者 Karl Fogel, 翻訳 竹内かほり

Original: http://cvsbook.red-bean.com/cvsbook.html
Latest: http://cvsbook.red-bean.com/cvsbook.html

Copyright © 1999 Karl Fogel <kfogel@red-bean.com>

This document is free software; you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version.

This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

This manual describes how to use and administer CVS (Concurrent Versions System). It is part of a larger work entitled Open Source Development With CVS; please see the introduction for details.

This is version 1.12 of this manual.

Table of Contents


Node:Top, Next:, Up:(dir)

Top


Node:Translation, Next:, Previous:Top, Up:Top

日本語訳について

これをお読みのかたへ。

こんにちは。日本語訳を作成中のたけうちかほりです。

このドキュメントは Open Software Development with CVS という本の free chapters を訳している最中のものです。原書については http://cvsbook.red-bean.com/ を御参照ください。

現時点、この訳は校正や用語統一なんて程遠い、読み返しすらしてないようなひどい訳ですので、書いてあることが正しいかどうか御自分でお確かめくださいましね。まちがってたら即わたくし take-k@secom-sis.co.jp までお知らせください。もとの英語はとても素直でわかりやすいものなので、日本語があやしかったら迷わず原文英語を参照してください。 http://cvsbook.red-bean.com/

にあります。訳を配るときはいままでメールにつけてたんですけれど、だんだん大きくなってきたので仕方なく web に置きました。間違い満載の、迷惑千万な訳だと思いますが、間違いを見つけたかたはわたくし たけうちかほり take-k@secom-sis.co.jp までお知らせください。

下訳が済んでいるのは introduction と 2, 4, 6, 8, 9, 10 章です。それ以外(ライセンス等)は英文のままです。

たけうちかほり <take-k@secom-sis.co.jp>

この日本語下訳の配布条件

原英文の配布条件に準じます。

以下に原文についている許諾文を付けます。GPL については GNU General Public License を参照してください。

Copyright © 1999 Karl Fogel <kfogel@red-bean.com>

This document is free software; you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version.

This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.


Node:Introduction, Next:, Previous:Translation, Up:Top

Introduction

この文書は、共同作業及びバージョン管理に CVS (Concurrent Versions System) を使うことについての、無料でオンライン提供された章です。 CVS のインストールと基本コンセプトから、進んだ使い方、管理まで全てをカバーしています。CVS を使う人、使おうとしている人向けです。

これらの章は Open Source Development With CVS (published by The Coriolis Group, ISBN1-57610-490-7) からの抜粋です。この文書に収容されていない部分(第1, 3, 5, 7章)には、CVS を使ったオープンソースプロジェクトを実行する際の問題や考え方について述べてあります。

ここにある無料の章だけでも CVS の本として完結してはいますが、あなたがこれらを気に入って、本を買ってくれればよいなあと思います。 http://www.coriolis.com/bookstore/bookdetail.cfm?id=1576104907 で出版社に直接注文できます。

これらの章は GNU General Public License のもとにリリースされています。フリーソフトウェアについての情報一般は、http://www.gnu.org/ を見て下さい。特に http://www.gnu.org/philosophy/free-sw.html

この文書についてのコメント、あるいは訂正があれば、bug-cvsb ook@red-bean.com までメールでお知らせ下さい。お知らせや更新情報は http://cvsbook.red-bean.com/ をご覧ください。


Node:An Overview of CVS, Next:, Previous:Introduction, Up:Top

An Overview of CVS

I can't imagine programming without it... that would be like parachuting without a parachute!

-Brian Fitzpatrick on CVS

この章では CVS の基礎を紹介したあと、日常での CVS の使い方を詳しい案内付きで見ていきます。概念が順々に示されますので、CVS が初めての人はこの章を最初から最後まで飛ばさずに読破するのが一番いいでしょう。


Node:Basic Concepts, Next:, Up:An Overview of CVS

Basic Concepts

CVS や他のバージョン管理システムを使ったことがない場合、基本的な仮定がわからないために足を取られてしまうのは目に見えています。CVS を使い始める時に最初に混乱するのはだいたいの場合、CVS の使用目的が2つあって(レコード保持と共同作業)、その2つが明らかに関連がないから、のように思えます。 結果的にその2つの機能は密接に結びついてしまっているのですが。

レコード保持は必須の機能です。プログラムの現在の状態を、以前は同じところがどのようであったか比べたいと思う人が多いからです。例えば、新しい機能を実装しようとすると通常、開発者はプログラムを全く動かない壊れた状態にしてしまい、その機能を大概実装し終わる頃までは壊れたままになるものです。そういう時に限って、以前リリースしたバージョンのバグレポートがやってきます。そのバグ(今いじっているバージョンのソースにも多分存在するんでしょうね)をどうにかするためには、そのプログラムを使える状態にまで戻してやらなければならないのです。

ソースコードの履歴を CVS で管理していれば、その状態を元に戻すのに何の苦労も要りません。実際、開発者は単に「3週間前の状態のそのプログラムをよこしたまえ」、あるいは「最近のリリース時の状態のプログラムをよこしたまえ」と言いさえすればいいのです。あなたがもし、履歴へのアクセスをこういう風に便利な方法でやったことがないなら、これを使うようになった時そのあまりの素早さに驚くと思います。わたしも今コーディング中のプロジェクトでリビジョン管理をいつも使っていて、何度も救われました。

共同作業を容易にするために何が必要か理解するためには、ひとつのプロジェクト上で多数の人々が働けるよう CVS が提供している機構について、詳しく見ていく必要があるでしょう。まあでもその前に、CVS が提供していない(または少なくとも支援していない)機能をちょっと見ましょうか。それはファイルのロックです。他のバージョン管理システムを使ったことがあるなら、「ロック-更新ロック解除」開発モデルはおなじみだと思います。開発者はまず編集したいファイルの排他的書込みアクセス(ロック)を取得し、次にそれを変更、そしてロックを解除して他の開発者がそのファイルにアクセスできるようにします。既に誰かがファイルをロックしていれば、あなたがそのファイルを変更する前にまずロックを「解除」してもらわなければなりません。(いくつかの実装ではロックを「盗む」ことができますが、これは時々盗まれた側の悲鳴が上がることになりますね、よくない慣習です)

このようなシステムは、開発者がお互いをよく知っており、任意の時刻に誰が何をしようとしているか知っており、アクセスの競合が起こって誰かが作業できない時には素早く連絡できるような状況であればうまく動きます。しかし開発グループが大きくなり、散らばってくると、ロックのことばかりが時間を取り始め、コーディングする時間を削っていきます。こうなると混乱が定常状態となり、人々から本来の仕事をやる気を削いでしまいます。

CVS はもう少し成熟したアプローチを取ります。衝突しないよう開発者自身に調整させるのではなくて、CVS は開発者が同時に編集できるようにし、変更全てを統合する仕事を引き受け、衝突を追跡します。この処理には「コピー変更マージ」モデルを用い、次のように動作します:

  1. 開発者Aは CVS から作業コピー(プロジェクトを構成するファイルを含むディレクトリツリー)を取得します。これは作業コピーを「チェックアウト」するとも言います。図書館から本を借り出す(チェックアウト)ようだからです。
  2. 開発者Aは自分の作業コピーを自由に編集します。その頃、別の開発者は各自の作業コピーにて忙しく仕事をしています。それぞれに別のコピーを持っているので衝突はありません。それはあたかも、開発者全員が図書館の同じ本のコピーをそれぞれ持っていて、それぞれ独立にそれの余白にコメントを書き込んだり、あるページを書き換えたりしている様子のようです。
  3. 開発者Aは変更を終え、その変更の性質と目的を説明する「ログメッセージ」とともに CVS へ変更をコミットします。これは、本の何を変更したか、及びその理由を図書館に知らせることにたとえられると思います。図書館はこれらの変更を「マスタ」コピーへ受け入れ、それを永久に記録します。
  4. 一方、他の開発者は最近マスタコピーが変更されたかどうかを図書館に問い合わせることができます。変更があれば、CVS は自動的に作業コピーをアップデートします。(ここが魅力のある素晴らしいところです、あなたもここを評価するといいなあ。実際の本もこういう風になっていたら世界はどんなに違うでしょうね!)

CVS のもとでは、あるプロジェクト上の全ての開発者が平等です。いつアップデートするか、いつコミットするかを決定するのは主に個人の好みまたはプロジェクトのポリシーです。コーディングプロジェクトでよく使われるやり方の一つは、大きな作業を始める前にアップデートを行い、変更が完了してテストしたときだけコミットするというもので、こうするとマスタコピーはいつも「動く」状態に保たれます。

たぶんあなたは、「開発者AとBが、それぞれの作業コピーの同じところで違う変更を施し、両者が変更をコミットしたらどうなるの?」と思っているんじゃないかと思います。これは コンフリクト (conflict, 衝突) と呼ばれるもので、開発者Bは変更をコミットしようとした時点で CVS からコンフリクトを知らされます。開発者Bは次に進む前に、CVS から、コンフリクトを検出したことと、作業コピーのコンフリクトの起こった箇所にコンフリクトマーカー(見てすぐに分かるテキストのフラグ)を挿入したことを知らされます。そこには両者の変更が示されており、比較しやすいようになっています。開発者Bはそれを全て整頓して、コンフリクトを解消した新しいリビジョンをコミットしなければなりません。開発者2人はこの問題を解決するために話す必要があるでしょう。CVS はコンフリクトが存在することを開発者に警告するだけです。 実際に解消するのは人間の役目です。

マスタコピーっていうのは何なのかって? 公式の CVS の用語では、それはプロジェクトのリポジトリと呼びます。リポジトリというのは単に、中央のサーバにあるファイルツリーです。その構造の詳しいところはまあ置いておいて(それは Repository Administration を見てね)、チェックアウト-コミットアップデートのサイクルの要件を満たすためにリポジトリが何をしなければならないかを見ていきましょう。次のシナリオについて考えてみて下さい:

  1. 開発者が2人(AとBとします)、プロジェクトの作業コピーを同時にチェックアウトしたとします。プロジェクトは開始したばかりで、誰も変更をコミットしておらず、ファイルは全部オリジナルの状態のままです。
  2. 開発者Aはすぐに作業を始め、変更のひとまとまりをコミットします。
  3. その頃、開発者Bはテレビを見ています。
  4. 開発者Aはまるで明日がないかのようにハッキングしまくり、2回目のコミットを実行します。この時点で、リポジトリの履歴にはオリジナル、次にAの初回変更、その次に今回の変更が記録されています。
  5. その頃、開発者Bはテレビゲームをしています。
  6. ここで突然、開発者Cがプロジェクトに加わり、リポジトリから作業コピーをチェックアウトします。開発者Cの作業コピーにはAの2回分の変更が反映されています。チェックアウトした時にはその変更はもうリポジトリにあったからです。
  7. 開発者Aは何かに憑かれたかのようにコーディングを続け、完了して3回目のコミットを行います。
  8. 開発者Bは、例の狂ったような活動にも気づかないまま(幸せなヤツだ)、ついに「そろそろ始めるか」と決めたようです。作業コピーをわざわざアップデートするような面倒なことはやらずに、すぐファイルを編集し始めます。そのなかにはAが作業したファイルもいくつかあるかもしれません。そして開発者Bは最初の変更をコミットします。

この時点で、次のうちいずれかになります。A が編集したファイルをBが一切編集しなかったとしたら、コミットは成功します。しかし、B のファイルがリポジトリの最新に追いついていなくて、しかも B がそれらのファイルを編集していることを CVS が認識したら、CVS は B に対し、コミットする前にアップデートしなくてはならない、と知らせます。

B がアップデートをかけると、CVS は A の変更をBの作業コピーにマージします。Aの作業分は、Bのまだコミットしていない作業分とコンフリクトするものもあるし、しないものもあるでしょう。コンフリクトしない分については B の作業コピーに適用されてそれで終わりです。しかしコンフリクトしている分については、コミットする前に B がコンフリクトを解消しなければなりません。

ここで開発者 C がアップデートを行ったとすると、リポジトリから変更をいろいろと受け取ることになるでしょう。A の3回目のコミット分と、B の初回コミットの成功した分です(ホントは2回目にコミットしようとした時のやつですね、初回にコンフリクトがあって失敗してるとしたら)。

いろいろな程度に最新に同期していない作業コピーを持っている開発者に対し、CVS が正しい順序で変更を提供するためには、リポジトリはプロジェクトの最初から全てのコミットを保存しておく必要があります。実際には、CVS リポジトリは連続的に diff を取ってそれを保存しています。ですから、とても古い作業コピーがあったとしても、それとリポジトリの現状の違いを計算できますし、実際その作業コピーを最新にすることもできます。これにより、開発者は任意の時点のプロジェクト履歴を見ることができ、非常に古い作業コピーを生き返らせることができるのです。

厳密にはリポジトリは別の手段で同様の結果を出せたかもしれないですが、実際、diff を保存するというのは必要な機能を実装するにはシンプルで直感的な方法です。

この処理により、patch をうまく使えば、CVS はいつのファイルツリーでも再構築できて、ある作業コピーの状態を任意の別の状態にすることができる、というおまけもついています。任意の特定の時刻のプロジェクトの状態をチェックアウトすることができる、ということです。他の人の作業コピーに影響を与えずに、任意の2つの状態の違いを diff のフォーマットで見ることもできます。

つまり、プロジェクト履歴にアクセスしやすくするために必要な機能そのものが、分散していて調整しきれないけれど能力のある開発者チームがプロジェクトで共同作業するためにも役立っているというわけです。

今はリポジトリのセットアップやユーザアクセス管理、CVS 特有のファイル形式の詳しいところは省いていきます(それはRepository Administrationで述べます)。ここでは作業コピーを変更するときの方法に集中しましょう。

まずは用語だけさっと説明しますね:


Node:A Day With CVS, Next:, Previous:Basic Concepts, Up:An Overview of CVS

A Day With CVS

この節では CVS の基本的な操作を説明したあと、よくある CVS の使い方をカバーするような例を示します。ツアーが進むにつれて、 CVS が内部的にどう動いているか見ていくことにします。

CVS を使うだけなら CVS の実装の細かいところまで全部知っている必要はないのですが、どう動いているか基本的なところを知っていると、したいことを実現するために一番良い方法を選ぶ際、役に立ちます。動作機構が全部丸見えだという点で、CVS は自動車より自転車に似ています。自転車のようにすぐ飛び乗れますし。でも、ちょっと勉強してギアがどう動いているかわかれば、もっと効率よく乗れるのです。(CVS の場合、その丸見えなところが熟考の末の設計決定なのか、たまたまそうなだけなのかわからないですが、フリーのプログラムはよくそうなっています。外から見えるような実装というのはそのシステムが内部的にどう動いているか最初からさらすことになり、ユーザが開発者になって貢献してくれるようになりやすいという利点があります。)

ツアーの各パートは、それ以前のパートで得た知識を使うことになります。初めて読むかたは最初から始めて、飛ばさずに順番に読んでいくことをお勧めします。下のメニューは繰り返し読む時の便宜のためにあるので、前のほうの章が分かっていないあいだに興味のある章へ飛ぶのに使ったりしないほうがいいと思います。


Node:Conventions Used In This Tour, Next:, Up:A Day With CVS

Conventions Used In This Tour

ツアーの舞台は Unix 環境です。CVS は Windows や Macintosh でも動きますし、Ice Engineering の Tim Endres によって書かれた Java のクライアントもありますから Java の動くところならどこででも動きます。しかしここでは、現時点及び潜在的な CVS ユーザの大部分が Unix のコマンドライン環境で作業していると仮定します、少々乱暴かもしれませんが。あなたがもしそうでなかったとしても、ツアーのなかの例は簡単に他のインタフェースに読み替えることができると思います。コンセプトさえ理解すればどんな CVS フロントエンドでも使いこなせると思いますよ。(信じてください、わたしは何度もやってきたんです)

ツアー中の例はプログラミングのプロジェクトを追跡するために CVS を使う人を対象に書きましたが、CVS の操作はソースコードだけでなくテキストドキュメントを扱う際にも適用できます。

また、すでに CVS がインストールされていて(フリーの Unix システムにはたいがいデフォルトで入っているので、あなたの知らないうちにインストールされていることが多いでしょう)、リポジトリにアクセスできると仮定しています。環境が整っていなくても、読むだけでも学ぶことは多いと思います。Repository Administration を読めば CVS のインストールとリポジトリのセットアップについて勉強できます。

CVS がインストール済みとして、オンラインマニュアルを探してみて下さい。著者の Per Cederqvist にちなんで「Cederqvist」として親しまれているマニュアルはソースディストリビューションに付属していて、普通だいたい最新のリファレンスがあります。Texinfo 形式で書かれていて、Unix では Info ドキュメントの構造のが読めると思います。コマンドラインの info プログラムで読めますし、

floss$ info cvs

Emacs のなかで Ctrl+H のあとに "i" をタイプしても読めます。どっちも動かない場合はあなたのまわりの Unix グルに相談してください(または Repository Administration を参照してください、インストールについて書いてあります)。 CVS をよく使うようになりたいなら、Cederqvist に詳しくなりたいと思うに違いありません。


Node:Invoking CVS, Next:, Previous:Conventions Used In This Tour, Up:A Day With CVS

Invoking CVS

CVS はひとつのプログラムですが、様々な動作をします: アップデート、コミット、ブランチ、diff 取り、などなど。CVS を起動する時はどの動作をさせるか指定します。起動時の形式は次の通りです:

floss$ cvs command

例えば

floss$ cvs update
floss$ cvs diff
floss$ cvs commit

などなど。(まだ上のコマンドを実行しちゃいけませんよ、作業コピーの中じゃなくちゃ意味ありませんから。すぐに出てきますからガマンしてください)

CVS もコマンドもオプションが書けます。CVS の振舞いに影響のあるオプション(コマンドの動作とは独立)は「グローバルオプション」と呼ばれます。コマンド用のオプションは「コマンドオプション」と呼ばれます。グローバルオプションは常にコマンドより左側に書かれ、コマンドオプションはコマンドの右側に書かれます。つまり

floss$ cvs -Q update -p

-Q はグローバルオプションで、-p はコマンドオプションですね。(好奇心旺盛なアナタのために: -Q は「quietly」という意味で、お知らせ出力を抑制し、何らかの理由でコマンドが完了しなかった場合のエラーメッセージのみを表示します。-p は update の結果をファイルではなく標準出力に送るという意味です)


Node:Accessing A Repository, Next:, Previous:Invoking CVS, Up:A Day With CVS

Accessing A Repository

CVS にはアクセスしたいリポジトリを前もって知らせてやらなければなりません。もうチェックアウトしたんなら関係ないです - 作業コピーはすべて、自分がどのリポジトリからチェックアウトされたものかがわかっていますから、CVS はその作業コピーのリポジトリを自動的に推定します。ここではとりあえずまだ作業コピーを作っていないと仮定しましょう、そうすると CVS に明示的にどこを見に行けばいいか指定する必要があるのです。これはグローバルオプション -d で指定できます(-d は directory を意味します、歴史的経緯があってこの略称なのですが、「repository」の -r のほうがよかったと思いますよね)。そのあとにリポジトリのパスを書きます。リポジトリがローカルの /usr/local/cvs (標準的な場所です)にあるとすると、こうです:

floss$ cvs -d /usr/local/cvs command

しかし、リポジトリはネットワーク越しの別のマシン上にあることが多いです。CVS ではネットワーク経由でアクセスする方法を選択できます。どれを使えばいいかはリポジトリマシン(以降「サーバ」と呼びます)がどの程度セキュリティを必要としているかによります。サーバのいろいろなリモートアクセス方法を設定するについては Repository Administration に述べてあります。 ここではクライアント側についてだけ話しましょう。

幸い、リモートアクセスを起動するにはすべて共通の文法を使います。ローカルのリポジトリでなくリモートのリポジトリを指定するには、長めのリポジトリパスを使えばよいのです。まずアクセス方法の名前をコロンで囲んだものを書き、次にユーザ名とサーバ名を @ でつなげて書きます。またコロンを書き、最後にサーバ上のリポジトリのパスを書きます。

pserver (password-authenticated server) アクセスについてみてみましょう。

floss$ cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs login
(Logging in to jrandom@cvs.foobar.com)
CVS password: (enter your CVS password here)
floss$

-d に続く長いリポジトリパスは、「pserver アクセスを使って、ユーザ名 jrandom、サーバは cvs.foobar.com で /usr/local/cvs というリポジトリを持っているからね」ということをCVS に知らせています。ホスト名は別に "cvs.something.com" である必要はありません、ただの慣習です。but it could just as easily have been:

floss$ cvs -d :pserver:jrandom@fish.foobar.org:/usr/local/cvs command

このコマンドはログインを実行し、あなたがこのリポジトリで作業する権限があるかどうか確認します。パスワードプロンプトを出し、次にパスワードが正しいかどうかサーバにたずねます。Unix の慣習に従い、ログインが成功したら何のメッセージもなしに終わります。失敗したら(たとえばパスワードが間違っているなどの理由で)、エラーメッセージを表示します。

ある CVS サーバに対しては、ログインは一度しかする必要がありません。ログインが成功すると、CVS はホームディレクトリの .cvspass というファイルにパスワードを保存します。pserver メソッドを経由してリポジトリにアクセスする際にはそのファイルからパスワードを持ってくるので、初回 CVS サーバにアクセスする時のみログインすればよいのです(各クライアント毎)。もちろんパスワードが変更になった時にはいつでも再度 cvs login を走らせることができます。

Note: pserver はこのような初回ログインが必要な唯一のアクセス方法です。他の方法は普通の CVS コマンドを即実行することができます。

いったん .cvspass に認証情報を保存すれば、他の CVS コマンドも同じ文法で動きます:

floss$ cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs command

Windows で pserver を使うには手順をもう一つ踏みます。Windows ではホームディレクトリの概念がないため、CVS はどこに .cvspass を置いてよいか分かりません。場所を教えてあげましょう。C: ドライブのルートを指定するのが普通です:

C:\WINDOWS> set HOME=C:
C:\WINDOWS> cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs login
(Logging in to jrandom@cvs.foobar.com)
CVS password: (enter password here)
C:\WINDOWS>

ファイルシステム中のどこでも構いません。ネットワークドライブを使うのは避けたいと思うかもしれませんね、そのドライブにアクセスできる人に .cvspass を見られてしまいますから。

CVS では pserver のほかに ext (外部接続プログラム利用)、kserver(ケルベロスセキュリティシステムバージョン4利用)、gserver(GSSAPI(Generic Security Services API、ケルベロスバージョン5以上を扱う)利用)の各方法をサポートしています。これらは pserver と同じように使えますが、それぞれに特質があります。

このなかでは ext が最もよく使われている方法でしょう。サーバに rsh か ssh でログインできるなら、ext が使えます。次のようにテストできます:

floss$ rsh -l jrandom cvs.foobar.com
Password: enter your login password here

はい、rsh でログインログアウトができるとします。クライアントマシンに戻って次をどうぞ:

floss$ CVS_RSH=rsh; export CVS_RSH
floss$ cvs -d :ext:jrandom@cvs.foobar.com:/usr/local/cvs command

最初の行は(Unix のボーンシェルで書いてあります)、CVS_RSH 環境変数に rsh を設定して、接続に rsh を使うことを CVS に指示します。2番目の行は任意の CVS コマンドが書けます。パスワードを入力するよう促され、CVS はサーバにログインします。

Cシェルを使っている人はこれをやってみて下さい:

floss% setenv CVS_RSH rsh

and for Windows, try this:

C:\WINDOWS> set CVS_RSH=rsh

これ以降ツアーではボーンシェルで書きますので、あなたの環境に合わせて読み替えて下さい。

rsh のかわりに ssh (セキュアシェル)を使う場合、 CVS_RSH を適切に設定するだけです:

floss$ CVS_RSH=ssh; export CVS_RSH

設定する値が ssh にもかかわらず変数名が CVS_RSH だというのを見過ごさないように。歴史的な理由でこうなっているのです(Unix ではこれさえ言えば何でも許されるんですよネ)。CVS_RSH には、リモートサーバにログインできて、コマンドを走らせることができて、出力を受け取ることができるプログラムなら何でも指定できます。rsh 以降、この手のプログラムはほかにもありますが、ssh が最もポピュラーです。注意点として、このプログラムはデータストリームを書き換えてはならないということが挙げられます。この点で Windows NT の rsh は不合格です。DOS と Unix の改行コードを変換してしまうからです。 Windows 用のほかの rsh を使うか、その他のアクセス方法を使って下さい。

gserver と kserver は他に比べてあまり使われませんのでここでは説明しません。今までに説明した方法とよく似ています。詳しくは Cederqvist を参照のこと。

ひとつのリポジトリしか使わないのなら毎回 -d リポジトリ とか打つのはイヤでしょう、CVSROOT 環境変数を設定してください(これも CVSREPOS という名前のほうがよかったと思いますが、今となってはもう遅いです):

floss$ CVSROOT=/usr/local/cvs
floss$ export CVSROOT
floss$ echo $CVSROOT
/usr/local/cvs
floss$

またはこんな感じです:

floss$ CVSROOT=:pserver:jrandom@cvs.foobar.com:/usr/local/cvs
floss$ export CVSROOT
floss$ echo $CVSROOT
:pserver:jrandom@cvs.foobar.com:/usr/local/cvs
floss$

以降では CVSROOT にリポジトリの場所を指定していると仮定しますので、例には -d オプションは書きません。いろいろなリポジトリを使う場合は、CVSROOT を設定せずに -d リポジトリ と指定して下さい。


Node:Starting A New Project, Next:, Previous:Accessing A Repository, Up:A Day With CVS

Starting A New Project

既に CVS の管理下にあるプロジェクト(もうそのプロジェクトがリポジトリのどこかにあるということです)で作業するために CVS を勉強中のあなたはきっと、この節を飛ばして次の「Checking Out A Working Copy」を読みたいだろうと思います。この節は、ソースコードがあって、それを CVS の管理下に置きたいあなたにぴったりです。既にリポジトリにアクセスできると仮定して進めます、リポジトリ自体の設定をするには Repository Administration を参照して下さい。

CVS に新しいプロジェクトを入れるのは インポート(import) といいます。 CVS コマンドは、今あなたが考えた通り、こうです:

floss$ cvs import

コマンドが成功するためにはもう少しオプションが必要ですけれど(あと、正しい場所で実行する必要があります)。さて、まずあなたのプロジェクトのトップレベルディレクトリに移って下さい:

floss$ cd myproj
floss$ ls
README.txt  a-subdir/   b-subdir/   hello.c
floss$

プロジェクトには、トップレベルにファイルが2つ - README.txt と hello.c - と、サブディレクトリが2つ - a-subdir と b-subdir - と、それらの下のファイルがいくつか(例には示されていませんが)あります。プロジェクトをインポートする時、CVS はカレントディレクトリから始めて、ツリーのなかの全てをインポートします。ですから、プロジェクトのパーツになるファイルだけがツリーのなかにあることを確認して下さい。バックアップファイルとか走り書きのファイルとかは全部掃除しておいてください。

import コマンドの一般的な書き方はこうです:

floss$ cvs import -m "log msg" projname vendortag releasetag

-m フラグ(message)にはそのインポートを説明する短いメッセージを指定します。プロジェクト全体の最初のログメッセージになります。以降のコミット毎にそれぞれログメッセージが追加されます。これらのメッセージは必須です。- m フラグを指定しない場合 CVS は自動的にエディタを立上げて(EDITOR 環境変数を見ます)、ログメッセージをタイプさせられます。ログメッセージを保存してエディタを抜けてから import は続行します。

次の引数はプロジェクトの名前です(ここでは "myproj" を使います)。チェックアウトする時に、この名前でもってリポジトリからプロジェクトをチェックアウトします。(実際に何が起こるかというと、リポジトリの中にこの名前のディレクトリが作成されるのですが、詳しくは Repository Administration を参照のこと) カレントディレクトリと同じ名前である必要はありません。まあ、そうするのが普通みたいですけども。

vendortag と releasetag 引数は CVS の図書管理に必要なのですが、今はきにしないで下さい。あなたが使うにはほとんど関係ありませんから。これらが重要になる情況について(ほとんどないですが)は Advanced CVS を読んで下さい。いまはとりあえず、この引数にはユーザ名と "start" を使うことにします。

さて、import を起動する準備ができました:

floss$ cvs import -m "initial import into CVS" myproj jrandom start
N myproj/hello.c
N myproj/README.txt
cvs import: Importing /usr/local/cvs/myproj/a-subdir
N myproj/a-subdir/whatever.c
cvs import: Importing /usr/local/cvs/myproj/a-subdir/subsubdir
N myproj/a-subdir/subsubdir/fish.c
cvs import: Importing /usr/local/cvs/myproj/b-subdir
N myproj/b-subdir/random.c

No conflicts created by this import
floss$

おめでとう! このコマンドを走らせたことで、リポジトリに実際に影響のあることをついになしとげたことになるわけです。

import コマンドの出力を読むと、CVS がファイル名の前に何か1文字を出力していることに気づきますね。この場合、"N" は「新しいファイル (new file)」という意味です。左側に1文字つけてステータスを表すのは、CVS の出力では一般的なパターンです。あとで、チェックアウトとアップデートのときにも見ることになると思います。

たぶんあなたはこう考えるでしょう、さてプロジェクトをインポートしたわけだ、すぐ作業を始めてもいいんだよね、と。いえいえ違うんです、ハズレ。カレントディレクトリはまだ CVS の作業コピーではありません。これがインポートの元になったのは事実ですが、インポートされただけで CVS の作業コピーにヘンシーン、するわけではないのです。作業コピーを手に入れるためにはリポジトリからチェックアウトする必要があります。

でも、まずは今のそのプロジェクトツリーを保存しておきたいんじゃないかと思います。いったん CVS にソースを入れたら、バージョン管理していないコピーを間違えて編集してしまって混乱するのはイヤでしょうからね(そういう変更はプロジェクト履歴に格納されませんから)。現時点以降の編集は全部作業コピーでやりたいだろうと思います。しかしリポジトリにちゃんと入っているかどうかを確認もせずに、インポートしたツリーをいきなり削除するのは不安でしょう。もちろん 99.999% 確実だとは思うけれども(だって import コマンドはエラーも返さなかったし)、だからって危ない橋をわざわざ渡らなくてもいいですよね。注意しすぎても損はない、というのは、どんなプログラマだって知っていることです。こういう風にしてください:

floss$ ls
README.txt  a-subdir/   b-subdir/   hello.c
floss$ cd ..
floss$ ls
myproj/
floss$ mv myproj was_myproj
floss$ ls
was_myproj/
floss$

はい、これでどうでしょう。オリジナルのファイルは保存されているし、もう使われないバージョンだというのが名前から明らかに分かりますから作業コピーと間違えることもないでしょう。これでチェックアウトの用意ができました。


Node:Checking Out A Working Copy, Next:, Previous:Starting A New Project, Up:A Day With CVS

Checking Out A Working Copy

プロジェクトをチェックアウトするコマンドは、そう、今あなたが考えているので合っています:

floss$ cvs checkout myproj
cvs checkout: Updating myproj
U myproj/README.txt
U myproj/hello.c
cvs checkout: Updating myproj/a-subdir
U myproj/a-subdir/whatever.c
cvs checkout: Updating myproj/a-subdir/subsubdir
U myproj/a-subdir/subsubdir/fish.c
cvs checkout: Updating myproj/b-subdir
U myproj/b-subdir/random.c

floss$ ls
myproj/      was_myproj/
floss$ cd myproj
floss$ ls
CVS/        README.txt  a-subdir/   b-subdir/   hello.c
floss$

ほら、初めての作業コピーですよ! 中身はインポートした時と全く同じ、ただし CVS という名前のサブディレクトリができています。CVS がバージョン管理情報を格納しているのです。実際、プロジェクト中の各ディレクトリがそれぞれ CVS サブディレクトリを持っています:

floss$ ls a-subdir
CVS/        subsubdir/  whatever.c
floss$ ls a-subdir/subsubdir/
CVS/    fish.c
floss$ ls b-subdir
CVS/      random.c

CVS が CVS という名前のサブディレクトリの中にリビジョン情報を格納しているということは、プロジェクトの中に CVS という名前のサブディレクトリを含めることができないということです。実用上これが問題になったという話は聞いたことがありません。

ファイルを編集する前に、ブラックボックスの中身を覗いてみましょう:

floss$ cd CVS
floss$ ls
Entries     Repository  Root
floss$ cat Root
/usr/local/cvs
floss$ cat Repository
myproj
floss$

ナゾなことはなにもありませんね。Root ファイルはリポジトリの場所を示し、 Repository ファイルはプロジェクトがリポジトリ内のどこにあるかを示しています。ちょっと混乱するかもしれません、説明します。

CVS の用語はもう長いこと混乱しています。「リポジトリ」という語は違う2つのものを指すのに使われます。ある時はリポジトリのルートディレクトリ(例えば /usr/locla/cvs)を意味します。これはたくさんのプロジェクトを含んでいます。Root ファイルはこちらを指しています。しかし他の場合は、リポジトリルート内にある、特定のプロジェクトのサブディレクトリ(例えば /u sr/local/cvs/myproj, /usr/local/cvs/yourproj, /usr/local/cvs/fish)を意味することもあります。CVS サブディレクトリ内の Repository ファイルは後者の意味をとるわけです。

この本で「リポジトリ」というとき、普通は Root(トップレベルリポジトリ) を意味しますが、時々はプロジェクトのサブディレクトリという意味で使う時もあります。文脈からその意味が読み取れない場合には、文章で明らかにします。 Repository ファイルに書かれているパスは時々、相対パスではなくプロジェクトの絶対パスになっていることがあるので注意して下さい。この場合、Root ファイルが少し冗長になります:

floss$ cd CVS
floss$ cat Root
:pserver:jrandom@cvs.foobar.com:/usr/local/cvs
floss$ cat Repository
/usr/local/cvs/myproj
floss$

Entries ファイルはプロジェクト内の各ファイルについての情報を保持しています。1行につき1ファイルで、直下のファイルとサブディレクトリの情報だけが書いてあります。myproj にある CVS/Entries ファイルを示します:

floss$ cat Entries
/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
/hello.c/1.1.1.1/Sun Apr 18 18:18:22 1999//
D/a-subdir////
D/b-subdir////

各行のフォーマットはこうです:

/filename/revision number/last modification date//

ディレクトリの行は最初に "D" とあります。(CVS はディレクトリの変更履歴は保存しないので、その行のリビジョン番号とタイムスタンプは空になります)

タイムスタンプは最終更新の日付と時刻を記録します(地方時ではなく Universal Time)。CVS はこれのおかげで、最後のチェックアウト、アップデート、またはコミットの時点以降に、あるファイルが更新されたかどうかをすぐ知らせられるわけです。ファイルシステム中のタイムスタンプが CVS/Entries ファイル中のタイムスタンプと違っていれば(わざわざリポジトリを見に行かなくとも)、そのファイルが更新されたんだろうというのがわかるのです。

サブディレクトリ中の CVS/* ファイルを見てみましょう。

floss$ cd a-subdir/CVS
floss$ cat Root
/usr/local/cvs
floss$ cat Repository
myproj/a-subdir
floss$ cat Entries
/whatever.c/1.1.1.1/Sun Apr 18 18:18:22 1999//
D/subsubdir////
floss$

ルートリポジトリは変わっていませんが、Repository ファイルにはプロジェクトのこのサブディレクトリの場所が書いてあって、Entries ファイルの内容も違うのが分かります。

インポートの直後は、プロジェクト中のどのファイルのリビジョン番号も全部 1.1.1.1 です。最初のリビジョン番号はちょっと特殊なので、そのへんはあとにしましょう、変更してコミットしてみてからリビジョン番号についてみていく予定です。


Node:Version Versus Revision, Next:, Previous:Checking Out A Working Copy, Up:A Day With CVS

Version Versus Revision

CVS が各ファイル用に保持している内部リビジョン番号は、そのファイルが構成するソフトウェアのバージョン番号とは関係がありません。例えばあなたのプロジェクトにファイルが3つあるとして、その内部リビジョン番号は 1999/5/3 の時点で 1.2, 1.7, 2.48 だとします。その日、そのソフトをパッケージングして SlickoSoft バージョン3としてリリースします。これは純粋にマーケティングの決定であり、CVS のリビジョンには全く影響しません。CVS のリビジョン番号はお客様には見えないものです(リポジトリを見せない限り)。公に見える番号は「バージョン3」の「3」だけです。CVS に関してのみ言えば、それをたとえばバージョン1729と呼んだって構わないわけです。バージョン番号(リリース番号でもいいですが)は CVS の内部の変更追跡には何の影響もありません。

混乱を避けるため、「リビジョン」という単語は CVS 管理下にあるファイルの内部リビジョン番号だけを指すために使います。ああ、CVS のことは「バージョン管理システム」と呼ぶかもしれません、「リビジョン管理システム」っていうのはなんだかかっこわるいですからね。


Node:Making A Change, Next:, Previous:Version Versus Revision, Up:A Day With CVS

Making A Change

このプロジェクトは特にたいしたことはできません。hello.c の内容を示します:

floss$ cat hello.c
#include <stdio.h>

void
main ()
{
   printf ("Hello, world!\n");
}

プロジェクトをインポートして以来、初の変更を加えましょう。次の行を加えます:

printf ("Goodbye, world!\n");

Hello, world! のあとにです。お好みのエディタを起動して変更してください:

floss$ emacs hello.c
  ...

今回のこれはしごく単純な変更ですからまあ忘れたりしないと思います。でも、もっと大きくて複雑なプロジェクトになると、ファイルを編集したあとに他のことにジャマされて、数日後に戻ってきた時にはもう何をやったか思い出せないでしょうし、ひょっとすると、何も変更してないと思ってしまうかもしれません。このときにリポジトリと作業コピーを比べてみて初めて、「CVS は命の恩人です」という状況になるのです。


Node:Finding Out What You (And Others) Did -- update And diff, Next:, Previous:Making A Change, Up:A Day With CVS

Finding Out What You (And Others) Did - update And diff

前に、リポジトリから作業コピーへ変更を持ってくる方法として、アップデートのことを述べました。これは他の人の変更を取得する方法です。でもアップデートというのは本当はもう少し複雑なことをしています: 作業コピーの状態全てを、リポジトリ内のプロジェクトの状態と比較します。チェックアウト時以降、リポジトリに何も変更がなくても作業コピーが変更されていれば、アップデートはそれも表示します:

floss$ cvs update
cvs update: Updating .
M hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir

hello.c の隣にある「M」は、最後のチェックアウト以降このファイルが変更されました、そしてその変更はまだリポジトリへはコミットされていません、という意味です。

どのファイルを編集したんだったかをただ知りたいなと思うだけのこともあるでしょう。しかしどんな変更を施したのか詳しく見たいときには、diff 形式のフルレポートを取得することもできます。diff コマンドは作業ファイル中の変更されたであろうファイルと、対応するリポジトリ中のファイルを比較し、全ての相違を表示します:

floss$ cvs diff
cvs diff: Diffing .
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.1.1.1
diff -r1.1.1.1 hello.c
6a7
>   printf ("Goodbye, world!\n");
cvs diff: Diffing a-subdir
cvs diff: Diffing a-subdir/subsubdir
cvs diff: Diffing b-subdir

That's helpful, if a bit obscure, but there's still a lot of cruft in the output. ビギナーの人は最初の数行は無視して構いません。リポジトリ内のファイル名と、最後にチェックインされたリビジョンの番号が書かれています。 他の状況では役に立つ情報なんですが(あとで少し詳しく見ていきます)、作業コピーにどんな変更があったかを知りたいだけなら必要のないものです。

diff を読む時にもっと障害になっているのは、CVS がアップデート中に各ディレクトリに入ったことを知らせている部分です。そのコマンドがどのくらい長くかかったかわかるので、大きいプロジェクトの長いアップデートでなら役に立ちますが、今回の場合、ただ diff を読みにくくしているだけです。-Q グローバルオプションで CVS に静かに仕事しろと言ってみましょう。

floss$ cvs -Q diff
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.1.1.1
diff -r1.1.1.1 hello.c
6a7
>   printf ("Goodbye, world!\n");

いいカンジ、少なくとも cruft はいくつかなくなりました。でも、この diff はまだ見にくいですね。6行目に新しい行が追加されて(7行目になって)、内容は次のようです:

printf ("Goodbye, world!\n");

diff の最初の「>」は、この行は新しいほうのバージョンにあって、古いほうにはない、ということを示します。

でも、このフォーマットはもう少し読みやすいようにできるんじゃないでしょうか。多くの人はコンテキスト diff のほうが読みやすいというのを知っていると思います。あれは変更の周りの文脈を数行示してくれますからね。コンテキスト diff は diff コマンドに -c フラグを渡せば生成できます:

floss$ cvs -Q diff -c
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 hello.c
*** hello.c     1999/04/18 18:18:22     1.1.1.1
--- hello.c     1999/04/19 02:17:07
***************
*** 4,7 ****
---4,8 --
  main ()
  {
    printf ("Hello, world!\n");
+   printf ("Goodbye, world!\n");
  }

やっと分かり易くなった! コンテキスト diff を読み慣れていなくてもこの出力を見れば一目で何が起こったか分かると思います。新しい行が(最初の行の + は追加行を示します)、Hello, world! と最後の中括弧の間に追加されたのです。

コンテキスト diff を完璧に読みこなす必要はありませんが(それは patch コマンドがやることです)、そのフォーマットにちょっと親しむだけの時間を取ったって少なくとも損はないでしょう。cruft は飛ばして、最初の2行は

*** hello.c     1999/04/18 18:18:22     1.1.1.1
--- hello.c     1999/04/19 02:17:07

何と何の diff を取ったかを書いてあります。この場合は hello.c のリビジョン 1.1.1.1 と、同じファイルの変更されたバージョンです(2行目のほうにはリビジョン番号はありませんが、これは作業ファイルだけに施された変更であってリポジトリにはまだコミットされていないからです) 。これ以降 diff 内に出てくるアスタリスクとダッシュの行はセクションを識別しています。行番号範囲を埋め込んであるアスタリスクの行はオリジナルファイルのセクションを示します。ダッシュの行、さっきとは違う行範囲が埋め込んであると思いますが、これは変更されたファイルのセクションを示します。これらのセクションは対比されて「hunk」というペアになり、一方は古いファイル、他方は新しいファイルになります。

今回の diff には hunk がひとつだけあります:

***************
*** 4,7 ****
--- 4,8 --
  main ()
  {
    printf ("Hello, world!\n");
+   printf ("Goodbye, world!\n");
  }

hunk の最初のセクションは空で、オリジナルのファイルからは何も削除されていないことを意味します。2番目のセクションは、新しいファイルの対応する場所に1行追加されたことを示します。「+」という印がつけてあります。(diff がファイルから抜粋をする時は、最初の2カラムは「+」とかの特別なコードのために空けてあります。そのため、ただ抜粋しているだけの行は空白2つでインデントされているように見えます。この余分なインデントは diff が適用される時には削除されます、当たり前ですけど)

行番号範囲は、その hunk がカバーする範囲です(コンテキストを示す行を含む)。オリジナルファイルではその hunk は4行目から7行目までだったのに対し、新しいファイルでは4行目から8行目になっています(1行追加されましたからね)。オリジナルファイルから何も削除されていない場合、diff はオリジナルファイルの行を出力する必要がないことに注意して下さい。行範囲と hunk の後半からわかることです。

わたしの実際のプロジェクトから、他のコンテキスト diff をお見せしましょう:

floss$ cvs -Q diff -c
Index: cvs2cl.pl
===================================================================
RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v
retrieving revision 1.76
diff -c -r1.76 cvs2cl.pl
*** cvs2cl.pl   1999/04/13 22:29:44     1.76
--- cvs2cl.pl   1999/04/19 05:41:37
***************
*** 212,218 ****
          # can contain uppercase and lowercase letters, digits, '-',
          # and '_'. However, it's not our place to enforce that, so
          # we'll allow anything CVS hands us to be a tag:
!         /^\s([^:]+): ([0=9.]+)$/;
          push (@{$symbolic_names{$2}}, $1);
        }
      }
-- 212,218 --
          # can contain uppercase and lowercase letters, digits, '-',
          # and '_'. However, it's not our place to enforce that, so
          # we'll allow anything CVS hands us to be a tag:
!         /^\s([^:]+): ([\d.]+)$/;
          push (@{$symbolic_names{$2}}, $1);
        }
      }

びっくりマーク(「!」)は、その行が古いファイルと新しいファイルで違うことを示します。「+」も「-」もないことから、ファイルの行数は変わらなかったことが分かります。

同じプロジェクトからもう一つ別のコンテキスト diff を。今回はもう少し複雑です:

floss$ cvs -Q diff -c
Index: cvs2cl.pl
===================================================================
RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v
retrieving revision 1.76
diff -c -r1.76 cvs2cl.pl
*** cvs2cl.pl   1999/04/13 22:29:44     1.76
--- cvs2cl.pl   1999/04/19 05:58:51
***************
*** 207,217 ****
}
        else    # we're looking at a tag name, so parse & store it
        {
-         # According to the Cederqvist manual, in node "Tags", "Tag
-         # names must start with an uppercase or lowercase letter and
-         # can contain uppercase and lowercase letters, digits, '-',
-         # and '_'. However, it's not our place to enforce that, so
-         # we'll allow anything CVS hands us to be a tag:
          /^\s([^:]+): ([0-9.]+)$/;
          push (@{$symbolic_names{$2}}, $1);
        }
- 207,212 --
***************
*** 223,228 ****
--- 218,225 --
      if (/^revision (\d\.[0-9.]+)$/) {
        $revision = "$1";
      }
+
+     # This line was added, I admit, solely for the sake of a diff example.

      # If have file name but not time and author, and see date or
      # author, then grab them:

この diff には hunk が2つあります。最初のやつは5行削除です(これらの行は hunk の最初のセクションだけに示されていて、2番目のセクションの行番号は5行少なくなっています)。途切れていないアスタリスクの行は hunk の区切りで、 2番目の hunk では2行追加されたことが分かります。空行ひとつと無意味なコメントが1行ですね。一つ前の hunk を受けて行番号がどう変わっているか、注意して下さい。オリジナルファイルにおいては2番目の hunk は223行目から228行目、最初の hunk で5行削除されたので新しいファイルでは218から225行目になっています。

おめでとう、これでもうあなたも diff を読むことにかけてはエキスパートですね。


Node:CVS And Implied Arguments, Next:, Previous:Finding Out What You (And Others) Did -- update And diff, Up:A Day With CVS

CVS And Implied Arguments

今まで述べてきた各 CVS コマンドでは、コマンドラインでファイルの指定をしていないことに気づいたと思います。例えば

floss$ cvs diff

を走らせましたね、

floss$ cvs diff hello.c

ではなくて。また、

floss$ cvs update

を走らせましたよね、

floss$ cvs update hello.c

ではなくて。ここでの原則は、ファイルを指定しない場合、CVS はそのコマンドで適用できる限りの全てのファイルに対して動作する、ということです。この原則はカレントディレクトリ以下のサブディレクトリ内のファイルも含みます。 CVS はカレントディレクトリ以下のツリーを自動的に降りていきます。例えば b-subdir/random.c と a-subdir/subsubdir/fish.c を変更したとすると、結果は次のようになるでしょう:

floss$ cvs update
cvs update: Updating .
M hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
M a-subdir/subsubdir/fish.c
cvs update: Updating b-subdir
M b-subdir/random.c
floss$

いや、こっちのほうがいいかな:

floss$ cvs -q update
M hello.c
M a-subdir/subsubdir/fish.c
M b-subdir/random.c
floss$

-q は -Q のちょっと弱いヤツです。もし -Q を使ったとしたら何も出力されないでしょう。変更情報は必須でないメッセージだとみなされてしまうからです。小文字の -q を使うと制限が弱まります。要らないと思うようなメッセージは抑制されて、確かで役に立ちそうなメッセージは出力されます。

アップデートでファイルを指定することもできます:

floss$ cvs update hello.c b-subdir/random.c
M hello.c
M b-subdir/random.c
floss$

こうすると CVS は指定されたファイルだけを調べて、他のは無視します。

実際のところはファイルを限定しないでコマンドを走らせるほうが普通です。ほとんどの場合、ディレクトリツリー全体を一度にアップデートしたいことが多いです。ここでやっているアップデートは、ローカルで変更されたファイルを表示するだけであることを思い出して下さい。リポジトリにはまだ何の変更も加えられていないですからね。プロジェクトで他の人と一緒に作業している場合には、適宜アップデートを走らせてリポジトリの変更を自分の作業コピーに取り入れていくわけですが、その場合にはアップデートしたいファイル名を指定するというのは少しは役に立つでしょう。

同じ原則が CVS のほかのコマンドにもあてはまります。例えば diff ですが、ひとつのファイルの変更だけ見るということができます。

floss$ cvs diff -c b-subdir/random.c
Index: b-subdir/random.c
===================================================================
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 random.c
*** b-subdir/random.c   1999/04/18 18:18:22     1.1.1.1
--- b-subdir/random.c   1999/04/19 06:09:48
***************
*** 1 ****
! /* A completely empty C file. */
--- 1,8 --
! /* Print out a random number. */
!
! #include <stdio.h>
!
! void main ()
! {
!   printf ("a random number\n");
! }

また、全ての変更を一度に見るというのもできます(ちょっと大きい diff だけど、席から離れないで):

floss$ cvs -Q diff -c
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 hello.c
*** hello.c     1999/04/18 18:18:22     1.1.1.1
--- hello.c     1999/04/19 02:17:07
***************
*** 4,7 ****
--- 4,8 --
  main ()
  {
    printf ("Hello, world!\n");
+   printf ("Goodbye, world!\n");
  }
Index: a-subdir/subsubdir/fish.c
===================================================================
RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 fish.c
*** a-subdir/subsubdir/fish.c   1999/04/18 18:18:22     1.1.1.1
--- a-subdir/subsubdir/fish.c   1999/04/19 06:08:50
***************
*** 1 ****
! /* A completely empty C file. */
--- 1,8 --
! #include <stdio.h>
!
! void main ()
! {
!   while (1) {
!     printf ("fish\n");
!   }
! }
Index: b-subdir/random.c
===================================================================
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 random.c
*** b-subdir/random.c   1999/04/18 18:18:22     1.1.1.1
--- b-subdir/random.c   1999/04/19 06:09:48
***************
*** 1 ****
! /* A completely empty C file. */
--- 1,8 --
! /* Print out a random number. */
!
! #include <stdio.h>
!
! void main ()
! {
!   printf ("a random number\n");
! }

とにかく、diff を見てわかるように、このプロジェクトは明らかに prime time の準備ができました。リポジトリに変更をコミットしてみましょう。


Node:Committing, Next:, Previous:CVS And Implied Arguments, Up:A Day With CVS

Committing

commit コマンドは、リポジトリに変更を送ります。ファイルを指定しなければ、変更の全てがリポジトリに送られます。それが嫌なら、1つかそれ以上のファイル名を指定してコミットすることもできます(その場合他のファイルは無視されます)。

ここでは、1つのファイルを指定してコミット、2つのファイルを推測させてコミットしてみます:

floss$ cvs commit -m "print goodbye too" hello.c
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <--  hello.c
new revision: 1.2; previous revision: 1.1
done
floss$ cvs commit -m "filled out C code"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in a-subdir/subsubdir/fish.c;
/usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v  <--  fish.c
new revision: 1.2; previous revision: 1.1
done
Checking in b-subdir/random.c;
/usr/local/cvs/myproj/b-subdir/random.c,v  <--  random.c
new revision: 1.2; previous revision: 1.1
done
floss$

ちょっと時間を取って、出力を注意して読んで下さい。ほとんどが自己説明的です。リビジョン番号がインクリメントされていることに気づくと思います(思った通りだ)。でもオリジナルのリビジョンは 1.1 になっていて、以前 Entries ファイルで見た 1.1.1.1 ではないですね。

ここでこの食い違いについて説明しますが、まああまり重要なことではありません。これは、CVS が 1.1.1.1 に特別な意味を持たせていることに関係しています。多くの場合、「ファイルはインポート時にリビジョン番号1.1を受け取る」といっても構わないのですが、初回コミットが起こるまで、Entries ファイルにはリビジョン番号 1.1.1.1 が示されています(その理由は CVS のみぞ知る)。


Node:Revision Numbers, Next:, Previous:Committing, Up:A Day With CVS

Revision Numbers

プロジェクト中の各ファイルはそれぞれリビジョン番号というのを持っています。ファイルがコミットされるとリビジョン番号の最後のところが1増えます。 従って、プロジェクトを構成するいろいろなファイルは、任意の時点でそれぞれ全然違うリビジョン番号を持つことになります。これはただ、あるファイルは他のファイルよりも多く変更され(コミットされ)た、ということを意味するだけです。

(あなたはきっと、変更のたびに小数点の右側が変わるとすると、じゃあ左側の部分は何なんだろう、と思うことでしょう。実際、CVS が左側の数字を自動的に増やすことはなく、ユーザのリクエストによって増やすことになります。 ほとんど使われない機能なのでこのツアーでは説明しません。)

ここまで使ってきた例のプロジェクトで、3つのファイルの変更をコミットしたばかりです。それらのファイルのリビジョンは今 1.2 ですが、プロジェクトの他のファイルはまだ 1.1 です。プロジェクトをチェックアウトする時には、各ファイルのリビジョン番号の一番高いものを取ってくることになります。 qsmith が今 myproj をチェックアウトしたとすると、トップレベルディレクトリのリビジョン番号は次のようになっているでしょう:

paste$ cvs -q -d :pserver:qsmith@cvs.foobar.com:/usr/local/cvs co myproj
U myproj/README.txt
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
paste$ cd myproj/CVS
paste$ cat Entries
/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
/hello.c/1.2/Mon Apr 19 06:35:15 1999//
D/a-subdir////
D/b-subdir////
paste$

hello.c ファイル(他のファイルにうもれていますが)は今リビジョン 1.2 で、 README.txt はまだ最初のリビジョンのままです。(リビジョン 1.1.1.1 ですが、 1.1 でもあります)

彼が hello.c に

printf ("between hello and goodbye\n");

このような行を付け加えてコミットしたとすると、リビジョン番号はもう一度インクリメントされます:

paste$ cvs ci -m "added new middle line"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <--  hello.c
new revision: 1.3; previous revision: 1.2
done
paste$

hello.c はリビジョン 1.3 になりました。fish.c と random.c はリビジョン 1.2 のままで、その他のファイルは全部リビジョン 1.1 です。

cvs commit のかわりに cvs ci というコマンドを使ったことに注意して下さい。 CVS のコマンドはほとんど、タイプしやすいように短い形式を持っています。 checkout, update, commit の省略形はそれぞれ、co, up, ci です。省略形の一覧を見たければ cvs --help-synonyms を走らせてみましょう。

Entries ファイルを見るのがリビジョン番号を知るための唯一の方法ではありません。 status コマンドも使えます。

paste$ cvs status hello.c
===================================================================
File: hello.c           Status: Up-to-date

   Working revision:    1.3     Tue Apr 20 02:34:42 1999
   Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

ファイル名を指定しないで起動すると、プロジェクト内の全ファイルのステータスを表示します:

paste$ cvs status
cvs status: Examining.
===================================================================
File: README.txt        Status: Up-to-date

   Working revision:    1.1.1.1 Sun Apr 18 18:18:22 1999
   Repository revision: 1.1.1.1 /usr/local/cvs/myproj/README.txt,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

===================================================================
File: hello.c           Status: Up-to-date

   Working revision:    1.3     Tue Apr 20 02:34:42 1999
   Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

cvs status: Examining a-subdir
===================================================================
File: whatever.c        Status: Up-to-date

   Working revision:    1.1.1.1 Sun Apr 18 18:18:22 1999
   Repository revision: 1.1.1.1 /usr/local/cvs/myproj/a-subdir/whatever.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

cvs status: Examining a-subdir/subsubdir
===================================================================
File: fish.c            Status: Up-to-date

   Working revision:    1.2     Mon Apr 19 06:35:27 1999
   Repository revision: 1.2     /usr/local/cvs/myproj/
                                a-subdir/subsubdir/fish.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

cvs status: Examining b-subdir
===================================================================
File: random.c          Status: Up-to-date

   Working revision:    1.2     Mon Apr 19 06:35:27 1999
   Repository revision: 1.2     /usr/local/cvs/myproj/b-subdir/random.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

paste$

よくわからないところは無視して下さい。実際これは CVS に関してはよいアドバイスなんです。あなたが探しているちょっとした情報に、全然関係ない情報がぞろぞろついてきてわけがわからない、ということがよくあるのです。というかそれが普通です。必要あるところだけ取り出して、残りは気にしないことです。

前の例で、気にしないといけないところは各ファイルのステータス出力の最初の 3行です(空行は数えないで)。最初の行は一番重要です。ファイル名と作業コピーのステータスが書いてあります。現在、ファイルは全てリポジトリと同期しているので Up-to-date となっています。もし random.c を変更してまだコミットしていないとすると、次のようになるでしょう:

===================================================================
File: random.c          Status: Locally Modified

   Working revision:    1.2     Mon Apr 19 06:35:27 1999
   Repository revision: 1.2     /usr/local/cvs/myproj/b-subdir/random.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

Working revision と Repository revision を見ると、ファイルがリポジトリと同期していないかどうかがわかります。オリジナル作業コピーに戻って(jrandom の作業コピーはまだ hello.c の変更を知りません)、ステータスを見てみましょう。

floss$ cvs status hello.c
===================================================================
File: hello.c           Status: Needs Patch

   Working revision:    1.2     Mon Apr 19 02:17:07 1999
   Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

floss$

これは、誰かが hello.c の変更をコミットしてリポジトリのリビジョンを 1.3 に上げたのに、この作業コピーはまだ 1.2 のままであることを示しています。Status: Needs Patch の意味は、次のアップデートでリポジトリのその変更を見て、作業コピーに "patch" を当てる、ということです。

ちょっと、qsmith が hello.c を変更したのを知らないつもりになってみましょう。status も update も走らせません。そのかわりそのファイルを編集して、同じところを変更してみましょう。こうやると、初めてのコンフリクトにお目にかかれますよ。


Node:Detecting And Resolving Conflicts, Next:, Previous:Revision Numbers, Up:A Day With CVS

Detecting And Resolving Conflicts

コンフリクトを発見するのは簡単です。 CVS は update を実行する前に、間違えようのない言葉で「コンフリクトがあるよ」と知らせてくれます。まずコンフリクトを作ってみましょう。hello.c を編集して、次のような行を追加して下さい:

printf ("this change will conflict\n");

qsmith がこういう行をコミットしたその場所にです:

printf ("between hello and goodbye\n");

この時点で、作業コピーの hello.c のステータスは次のようになります

floss$ cvs status hello.c
===================================================================
File: hello.c           Status: Needs Merge

   Working revision:    1.2     Mon Apr 19 02:17:07 1999
   Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

floss$

リポジトリも作業コピーも変更されていて、それらの変更をマージしなければならない、という意味です。(CVS は変更がコンフリクトしていることはまだ気づいてません、update をまだ実行していないですからね) update を走らせた時にはこうなります:

floss$ cvs update hello.c
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.2
retrieving revision 1.3
Merging differences between 1.2 and 1.3 into hello.c
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in hello.c
C hello.c
floss$

最後の行は giveaway 。ファイル名の横にある C は変更がマージされたけれどもコンフリクトした、ということを示します。 hello.c の内容には両方の変更が示されています:

#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
<<<<<<< hello.c
  printf ("this change will conflict\n");
=======
  printf ("between hello and goodbye\n");
>>>>>>> 1.3
  printf ("Goodbye, world!\n");
}

コンフリクトはつねにコンフリクトマーカで区切られ、次の形式で示されます:

<<<<<<< (filename)
  作業コピーの未コミットの変更
  blah blah blah =======
  リポジトリからきた新しい変更
  blah blah blah
  などなど (リポジトリの最新リビジョン番号など)
>>>>>>> (latest revision number in the repository)

Entries ファイルには、ファイルが現在中途半端な状態になっていることが書いてあります。

floss$ cat CVS/Entries
/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
D/a-subdir////
D/b-subdir////
/hello.c/1.3/Result of merge+Tue Apr 20 03:59:09 1999//
floss$

コンフリクトを解消するには、ファイルを編集して、あるべき姿にし、コンフリクトマーカを取り除き、そしてコミットします。必ずしも変更のうちどちらかを選んでもう片方を捨てたりする必要はありません、どちらの変更もいまいちだと思えば、コンフリクトしているところ(ファイル全部でもかまわないんですが)をすっかり書き換えてしまってもいいのです。今回は最初の変更に合わせることにして、でもキャピタライズと句読点の打ちかたを少しだけ変えておくことにしましょう。

floss$ emacs hello.c
  (make the edits...)
floss$ cat hello.c
#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
  printf ("BETWEEN HELLO AND GOODBYE.\n");
  printf ("Goodbye, world!\n");
}
floss$ cvs ci -m "adjusted middle line"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <-  hello.c
new revision: 1.4; previous revision: 1.3
done
floss$


Node:Finding Out Who Did What (Browsing Log Messages), Next:, Previous:Detecting And Resolving Conflicts, Up:A Day With CVS

Finding Out Who Did What (Browsing Log Messages)

ここまで、今回のこのプロジェクトはいくつかの変更を経験しました。いままでに起こったことをざっと見ようと思ったとき、diff を全部詳しく調べたりする必要はありません。ログメッセージを見るのが理想的ですね、log コマンドを使えば見ることができます:

floss$ cvs log
(pages upon pages of output omitted)

ログ出力は繁雑になりがちです。1つのファイルのログメッセージだけを見ましょう:

floss$ cvs log hello.c
RCS file: /usr/local/cvs/myproj/hello.c,v
Working file: hello.c
head: 1.4
branch:
locks: strict
access list:
symbolic names:
        start: 1.1.1.1
        jrandom: 1.1.1
keyword substitution: kv
total revisions: 5;     selected revisions: 5
description:
--------------
revision 1.4
date: 1999/04/20 04:14:37;  author: jrandom;  state: Exp;  lines: +1 -1
adjusted middle line
--------------
revision 1.3
date: 1999/04/20 02:30:05;  author: qsmith;  state: Exp;  lines: +1 -0
added new middle line
--------------
revision 1.2
date: 1999/04/19 06:35:15;  author: jrandom;  state: Exp;  lines: +1 -0
print goodbye too
--------------
revision 1.1
date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
branches:  1.1.1;
Initial revision
--------------
revision 1.1.1.1
date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
initial import into CVS
=========================================================================
floss$

いつものとおり、一番上になにかたくさん情報があるようですが、無視しましょう。ダッシュの行の次に肝心なところが、読んでわかるフォーマットで書いてあります。

1つのコミットでたくさんのファイルが送られるとき、それらのファイルは同じメッセージを共有します。変更を追跡するとき役に立ちます。たとえば、fish.c と random.c を同時にコミットしたときのことを思いだしてみてください。こんな風にコミットしましたよね:

floss$ cvs commit -m "filled out C code"
Checking in a-subdir/subsubdir/fish.c;
/usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v  <-  fish.c
new revision: 1.2; previous revision: 1.1
done
Checking in b-subdir/random.c;
/usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
new revision: 1.2; previous revision: 1.1
done
floss$

ここでやったことは、同じログメッセージ「C のコードをふくらませた」で両方のファイルをコミットするということです(ここではたまたまどちらのファイルもリビジョン番号が 1.1 から 1.2 になっていますが、それは偶然一致しただけです。もし random.c が 1.29 だったら、このコミットで 1.30 になって、 fish.c のリビジョン1.2と同じログメッセージを共有することになります)。

cvs log を実行すると、共有ログメッセージが見えます:

floss$ cvs log a-subdir/subsubdir/fish.c b-subdir/random.c

RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v
Working file: a-subdir/subsubdir/fish.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
        start: 1.1.1.1
        jrandom: 1.1.1
keyword substitution: kv
total revisions: 3;     selected revisions: 3
description:
--------------
revision 1.2
date: 1999/04/19 06:35:27;  author: jrandom;  state: Exp;  lines: +8 -1
filled out C code
--------------
revision 1.1
date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
branches:  1.1.1;
Initial revision
--------------
revision 1.1.1.1
date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
initial import into CVS
=========================================================================
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
Working file: b-subdir/random.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
        start: 1.1.1.1
        jrandom: 1.1.1
keyword substitution: kv
total revisions: 3;     selected revisions: 3
description:
--------------
revision 1.2
date: 1999/04/19 06:35:27;  author: jrandom;  state: Exp;  lines: +8 -1
filled out C code
--------------
revision 1.1
date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
branches:  1.1.1;
Initial revision
--------------
revision 1.1.1.1
date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
initial import into CVS
=========================================================================
floss$

この出力を見れば、この2つのリビジョンが同じコミットだったというのがわかります(2つのリビジョンのタイムスタンプが同一あるいは直近だ、というのよりはるかにわかりやすいですね)。

ログメッセージを読むというのは、あるプロジェクトにどのようなことが起こってきたかをさっとつかんだり、ある時刻に特定のファイルに何が起こったか知るには良い方法です。生の cvs log の出力をもっと簡明で読みやすいかたち(GNU の ChangeLog のスタイルみたいな)に整形するフリーのツールもあります。そういうツールはこのツアーではカバーしませんが、Third-Party Tools で紹介します。


Node:Examining And Reverting Changes, Next:, Previous:Finding Out Who Did What (Browsing Log Messages), Up:A Day With CVS

Examining And Reverting Changes

qsmith がログを読んでいて、jrandom が hello.c に最近ほどこした変更を見たとします:

revision 1.4
date: 1999/04/20 04:14:37;  author: jrandom;  state: Exp;  lines: +1 -1
adjusted middle line

彼は「jrandom は一体何をしとんねん」と思いました。qsmith がたずねた質問を正確な言葉で言うと、「hello.c のわたしのリビジョン(1.3)と、そのすぐあとの jrandom のリビジョン(1.4)の違いは何なのでしょう」ですね。これは diff コマンドでわかることですが、今回は過去の2つのリビジョンを比べるので、 -r コマンドオプションを使ってそれらを指定します:

paste$ cvs diff -c -r 1.3 -r 1.4 hello.c
Index: hello.c
===========================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -c -r1.3 -r1.4
*** hello.c     1999/04/20 02:30:05     1.3
--- hello.c     1999/04/20 04:14:37     1.4
***************
*** 4,9 ****
  main ()
  {
    printf ("Hello, world!\n");
!   printf ("between hello and goodbye\n");
    printf ("Goodbye, world!\n");
  }
--- 4,9 --
  main ()
  {
    printf ("Hello, world!\n");
!   printf ("BETWEEN HELLO AND GOODBYE.\n");
    printf ("Goodbye, world!\n");
  }
paste$

このように見ると変更点は明らかです。リビジョン番号を時系列順に指定したので(普通はこれでいいです)、diff もその順で示されます。リビジョン番号を1つだけ指定すると、CVS は現在の作業コピーを比較対象にします。

qsmith はこの変更を見てすぐ、自分のやりかたの方がいいから、「アンドゥー」つまりリビジョンをひとつ戻して解決するんだ、と決めました。

しかし、彼はリビジョン1.4を捨てたいというわけではありません。ですが、ただ技術的な問題としてどうかというと、CVS ではそういうことも可能です、たいがいそんなことをする理由はないですが。リビジョン1.4をそのままにしておいて、1.3 にそっくりな新しいリビジョン1.5を作るほうがましです。こうすると、アンドゥーそのものもそのファイルの履歴に残ります。

残るはどうやってリビジョン1.3を取ってきてそれを1.5とするか、という疑問だけです。

この場合に限って言うと、とてもシンプルな変更なので qsmith が 1.3 をうつすよう手でファイルを編集して、それをコミットすれば済みます。でも、変更がもっと複雑になったら(実際のプロジェクトでは普通そうでしょう)、古いリビジョンを手でもう一回作るというのはどう考えても間違えそうです。ですから、 qsmith は CVS を使って古いリビジョンを取ってきて、それを再コミットするべきです。

これを実現するために、同じくらい良い方法が2つあります。ゆっくりチマチマやる方法と、速くてカッコイイ方法です。まずはゆっくりチマチマやる方法を先に見ましょう。


Node:The Slow Method Of Reverting, Next:, Previous:Examining And Reverting Changes, Up:A Day With CVS

The Slow Method Of Reverting

この方法では update に -p フラグと -r フラグを同時に渡します。-p オプションは指定したリビジョン番号の内容を標準出力に送ります。それだけではこのオプションは全然役に立ちません。ファイル内容がディスプレイ上を流れるだけ、作業コピーはそのままです。しかしファイルにリダイレクトすれば、そのファイルの内容は古いリビジョンになるのです。手で編集してその状態にしたかのようになります。

しかしまず qsmith はリポジトリの最新に追いついておく必要があります:

paste$ cvs update
cvs update: Updating .
U hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
paste$ cat hello.c
#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
  printf ("BETWEEN HELLO AND GOODBYE.\n");
  printf ("Goodbye, world!\n");
}
paste$

次に update -p を走らせてリビジョン 1.3 が本当に彼の欲しいものかどうか確認します:

paste$ cvs update -p -r 1.3 hello.c
===================================================================
Checking out hello.c
RCS:  /usr/local/cvs/myproj/hello.c,v
VERS: 1.3
***************
#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
  printf ("between hello and goodbye\n");
  printf ("Goodbye, world!\n");
}

おっと、最初の何行かが cruft ですね。これらは実際は標準出力ではなくて標準エラー出力に送られているので害はありません。どちらにしろ出力が読みにくくなるのは確かなので -Q で抑制します:

paste$ cvs -Q update -p -r 1.3 hello.c
#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
  printf ("between hello and goodbye\n");
  printf ("Goodbye, world!\n");
}
paste$

どうでしょう、これは qsmith の欲しかったものですね。次はこれを作業コピーのファイルに置きかえます、Unix のリダイレクトを使いましょう(">" がそれです):

paste$ cvs -Q update -p -r 1.3 hello.c > hello.c
paste$ cvs update
cvs update: Updating .
M hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
paste$

update を走らせると変更ファイルとしてリストされました。これは内容が変わっているということです。はっきり言うと、これは古いリビジョン1.3の内容と同じです(CVS はこれが以前のリビジョンと同一だということは知りません、ただファイルが変更されたことだけがわかっています)。qsmith が特に確認したいと思えば、diff をとってチェックできます:

paste$ cvs -Q diff -c
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.4
diff -c -r1.4 hello.c
*** hello.c     1999/04/20 04:14:37     1.4
--- hello.c     1999/04/20 06:02:25
***************
*** 4,9 ****
  main ()
  {
    printf ("Hello, world!\n");
!   printf ("BETWEEN HELLO AND GOODBYE.\n");
    printf ("Goodbye, world!\n");
  }
--- 4,9 --
  main ()
  {
    printf ("Hello, world!\n");
!   printf ("between hello and goodbye\n");
    printf ("Goodbye, world!\n");
  }
paste$

はい、彼のしたかった復帰ができました。実際、これは以前取った diff の逆です。満足して彼はコミットをかけます:

paste$ cvs ci -m "reverted to 1.3 code"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <-  hello.c
new revision: 1.5; previous revision: 1.4
done
paste$


Node:The Fast Method Of Reverting, Previous:The Slow Method Of Reverting, Up:A Day With CVS

The Fast Method Of Reverting

元に戻すのに速くてカッコイイ方法というのは、update に -j (join)フラグを渡すやりかたです。このフラグはリビジョン番号をとるという点で -r に似ていて、同時に2つまでの -j フラグを取ることもできます。CVS は指定された2つのリビジョンの違いを計算し、問題のファイルにパッチとして適用する(だから、リビジョン番号を指定する順番にはくれぐれも気をつけて)。

qsmith の作業コピーが最新版だとしましょう、その場合こうします:

paste$ cvs update -j 1.4 -j 1.3 hello.c
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.4
retrieving revision 1.3
Merging differences between 1.4 and 1.3 into hello.c
paste$ cvs update
cvs update: Updating .
M hello.c
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
paste$ cvs ci -m "reverted to 1.3 code" hello.c
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <--  hello.c
new revision: 1.5; previous revision: 1.4
done
paste$

ファイルを1つだけ元に戻す場合なら、チマチマしようがすばやくしようが、そんなに違いがあるわけではないです。しかしあとで出てきますが、複数のファイルを一度に元に戻そうとしたときなんかには速い方法のほうがどんなに良いかわかると思います。とりあえずはやりやすい方法を使ってください。

Reverting Is Not A Substitute For Communication

たいがいの場合、qsmith が例でやったようなことというのはえらく無作法なやりかたです。実際のプロジェクトで他の人と一緒に作業しているときに、だれかが良くない変更をコミットしてるなと思ったら、まずはその人とそれについて話し合うのがよいでしょう。その変更にはもっともな理由があることもあるし、ただあんまりちゃんと考えていなかっただけのこともあります。どちらにしろ、いきなり元に戻したりするような理由はありません。起こったことはすべて CVS に永久に保存されているのですから、変更した人に相談してからもとに戻しても遅くはないのです。

あなたが納期目前のプロジェクトのメンテナだったりあるいは無条件に変更を元に戻す必要も権利もあると思うのなら、そうすればいいですが、元に戻された変更の主にはすぐにメールでフォローを入れて、あなたが何故そんなことをしたのか、何が原因で変更を再コミットする必要があったのかを説明してください。


Node:Other Useful CVS Commands, Next:, Previous:A Day With CVS, Up:An Overview of CVS

Other Useful CVS Commands

ここまでくれば、基本的なことなら気楽に CVS を使えるようになっていることと思います。ここからはツアー口調をやめて、役に立つコマンドをいくつか要約して紹介したいと思います。


Node:Adding Files, Next:, Up:Other Useful CVS Commands

Adding Files

ファイル追加には2ステップの処理をします。add コマンドを実行してからコミットします。ファイルはコミットを実行するまでリポジトリには入りません:

floss$ cvs add newfile.c
cvs add: scheduling file 'newfile.c' for addition
cvs add: use 'cvs commit' to add this file permanently
floss$ cvs ci -m "added newfile.c" newfile.c
RCS file: /usr/local/cvs/myproj/newfile.c,v
done
Checking in newfile.c;
/usr/local/cvs/myproj/newfile.c,v  <-  newfile.c
initial revision: 1.1
done
floss$


Node:Adding Directories, Next:, Previous:Adding Files, Up:Other Useful CVS Commands

Adding Directories

ファイルを追加する場合とは違い、ディレクトリを追加するのは1ステップです。コミットする必要はありません:

floss$ mkdir c-subdir
floss$ cvs add c-subdir
Directory /usr/local/cvs/myproj/c-subdir added to the repository
floss$

作業コピーの新しいディレクトリ内を見ると、add コマンドが CVS サブディレクトリを自動的に生成したのがわかります:

floss$ ls c-subdir
CVS/
floss$ ls c-subdir/CVS
Entries     Repository  Root
floss$

これで、作業ディレクトリ中のほかのディレクトリでやっているように、中にファイルや新しいディレクトリを追加したりできます。


Node:CVS And Binary Files, Next:, Previous:Adding Directories, Up:Other Useful CVS Commands

CVS And Binary Files

今まで黙っていましたが、CVS にはちょっとしたイヤな秘密があります。CVS はバイナリファイルをうまく扱えないのです(あー、ほかにもちょっとしたイヤな秘密はありますけど、これは一番イヤな秘密のうちのひとつに数えられるものなんです)。バイナリを全然扱えないというのではないんですが、いいところが全然ないんです。

いままで扱ってきたのは全てプレーンテキストファイルです。CVS はテキストファイル用の特別なトリックを使っています。たとえばリポジトリが Unix で作業コピーが Windows や Mac にある場合、改行コードをそれぞれの環境に合わせて適切に変換していたりします。改行コードというのは、Unix ではラインフィード(LF)のみに対し、Windowsではキャリッジリターン/ラインフィード(CRLF)になっています。従って Windows の作業コピー中のファイルは CRLF を使う一方で、同じプロジェクトの Unix マシン上の作業コピーは LF を使っています(リポジトリではいつも LF です)。

ほかに、CVS は RCS キーワードと呼ばれる特別な文字列を認識するトリックがあって、これはテキストファイルのその文字列を認識したら、リビジョンや他の便利な情報に置換するというものです。例えば、ファイルがこういう文字列を含んでいたとすると:

$Revision$

CVS はコミットのたびにリビジョン番号を含むようにこの文字列を展開します。こんな風に:

$Revision: 1.3 $

CVS はファイルが改良されるのに合わせてこの文字列を最新に保ちます。(Advanced CVSThird-Party Tools に、こういうキーワード文字列についていろいろと説明してあります)

文字列展開は、ファイルを編集しているときにリビジョン番号やほかの情報を見ることができたりするのでとても便利な機能です、テキストファイルについてはね。でもファイルが JPG の画像だったら? コンパイル済みの実行ファイルだったら? そういう種類のファイルで、CVS がキーワードを見つけて展開するようなことがあったら、深刻なダメージを与えるかもしれません。バイナリではそういう文字列が偶然現われることがあるからです。

ですから、バイナリファイルを追加するときには、CVS に対してキーワード展開と改行コード変換をやめるように教えてやる必要があります。その場合 -kb オプションを使ってください:

floss$ cvs add -kb filename
floss$ cvs ci -m "added blah" filename
  (etc)

また、ときどき(テキストファイル中に擬似キーワード文字列を含んでいるような場合など) キーワード展開をしたくない場合もあるでしょう。そういう場合は -ko オプションを使います:

floss$ cvs add -ko filename
floss$ cvs ci -m "added blah" filename
  (etc)

(実際、この章はそのようなドキュメントのひとつですね、ここでも例のなかに $Revision$ だとか書いてありますし)

バイナリファイルのリビジョン間で cvs diff を走らせても意味がないことに注意してください。diff はテキストを前提としたアルゴリズムを使っているので、バイナリファイルの場合はただ違っているということが報告されるだけで違いの内容まではわかりません。CVS の将来のバージョンではバイナリファイルの diff をとる方法も提供するかもしれません。


Node:Removing Files, Next:, Previous:CVS And Binary Files, Up:Other Useful CVS Commands

Removing Files

ファイルの削除は追加と同様、ひとつ余分な手順を踏みます。まずは作業コピーからそのファイルを削除しなければなりません:

floss$ rm newfile.c
floss$ cvs remove newfile.c
cvs remove: scheduling 'newfile.c' for removal
cvs remove: use 'cvs commit' to remove this file permanently
floss$ cvs ci -m "removed newfile.c" newfile.c
Removing newfile.c;
/usr/local/cvs/myproj/newfile.c,v  <-  newfile.c
new revision: delete; previous revision: 1.1
done
floss$

2つめと3つめのコマンドでは、作業コピーにすでに newfile.c が存在しないにもかかわらずファイル名を明示的に指定していることに注意してください。もちろん、コミット時にはファイル名を必ずしも指定する必要はありませんが、こうしておけば作業コピー中の他のファイルの変更をまきこんでコミットしてしまう心配がなくなります。


Node:Removing Directories, Next:, Previous:Removing Files, Up:Other Useful CVS Commands

Removing Directories

前にも言ったとおり、CVS はディレクトリのバージョン管理はしてくれません。そのかわりお手軽な代替手段として、ほとんどの場合に「正しい動作」をする、ちょっと変な動作を提供しています。空のディレクトリを特別扱いする、というのは、そういう変な動作のうちのひとつです。プロジェクトからディレクトリを削除するときには、まずそれの中身を全部削除しないといけません:

floss$ cd dir
floss$ rm file1 file2 file3
floss$ cvs remove file1 file2 file3
  (output omitted)
floss$ cvs ci -m "removed all files" file1 file2 file3
  (output omitted)

次に、ひとつ上のディレクトリで -P フラグ付きの update を 実行します:

floss$ cd ..
floss$ cvs update -P
  (output omitted)

-P オプションは空のディレクトリを刈り込む(prune)よう update に指示します。こうすると作業コピーから空のディレクトリが削除されます。それが終わって初めて、そのディレクトリは削除されたと言えます。中のファイルが削除され、ディレクトリ自身も削除されました(少なくとも作業コピーからは。リポジトリ内ではまだ空のディレクトリが存在したままですが)。

素の update を走らせた場合、CVSは新しいディレクトリをリポジトリから作業コピーへ自動的に持ってこないのですが、これは上記の動作と対になるおもしろい動作です。これには2つの理由があるんですが、ここで説明するような価値はないのでやめておきます。ときどき、リポジトリから新しいディレクトリを取ってくるよう -d フラグで指示して update を実行してみるとわかると思います。


Node:Renaming Files And Directories, Next:, Previous:Removing Directories, Up:Other Useful CVS Commands

Renaming Files And Directories

あるファイルの名前を変えるということは、新しい名前でファイルを作って、古いのを消すというのと等価です。 Unix で言うと次のようなコマンドです:

floss$ cp oldname newname
floss$ rm oldname

同じことを CVS で実行するとすると:

floss$ mv oldname newname
floss$ cvs remove oldname
  (output omitted)
floss$ cvs add newname
  (output omitted)
floss$ cvs ci -m "renamed oldname to newname" oldname newname
  (output omitted)
floss$

ファイルに関してはこれでおしまいです。ディレクトリの名前を変えるのもだいたい同じです。新しいディレクトリを作って、cvs add して、古いディレクトリから新しいディレクトリへファイルを全部移し、古いディレクトリでそれらを cvs remove してから新しいディレクトリで cvs add、cvs commit して実際にコミットしたら、cvs update -P で空のディレクトリを作業コピーからなくせばいいのです。

floss$ mkdir newdir
floss$ cvs add newdir
floss$ mv olddir/* newdir
mv: newdir/CVS: cannot overwrite directory
floss$ cd olddir
floss$ cvs rm foo.c bar.txt
floss$ cd ../newdir
floss$ cvs add foo.c bar.txt
floss$ cd ..
floss$ cvs commit -m "moved foo.c and bar.txt from olddir to newdir"
floss$ cvs update -P

3つめのコマンドの warning に注意してください。olddir の CVS/ サブディレクトリを newdir に移せない、という意味のことを言われます。同じ名前のディレクトリが newdir にありますからね。olddir に CVS/ サブディレクトリを置いたままにしておきたいでしょうから、これでいいんですけど。

見てのとおり、ディレクトリを移すのはちょっと面倒です。一番いいのは最初にプロジェクトをインポートする時点で適切な配置にしておくことです。そうすればそうそうディレクトリを移したりする必要もないでしょう。あとで、リポジトリ内のディレクトリを直接変えてディレクトリを移す思い切った方法を紹介しますが、その方法は緊急のとき以外やらないほうがいいと思います。なにを取り扱うのでも、できるかぎり作業コピーのなかで CVS の操作で行うのが一番いいのです。


Node:Avoiding Option Fatigue, Next:, Previous:Renaming Files And Directories, Up:Other Useful CVS Commands

Avoiding Option Fatigue

普通の人なら、コマンドを打つたびに同じオプションフラグをタイプするのは面倒くさいだろうと思います。いつも -Q グローバルオプションを指定するとか、 diff を取るときにはいつも -c を指定するというのがわかっているのに、なんで毎回タイプしなくちゃいけないんでしょう。

幸い、策はあります。CVS はホームディレクトリの .cvsrc を探します。そのファイルの中でデフォルトオプションを指定すれば、CVS の起動のたびにそれが適用されます。.cvsrc の例を示します:

diff -c
update -P
cvs -q

行の一番左の単語が CVS コマンド(省略形でないほう)に一致したら、その行のオプションがそのコマンドに毎回適用されます。グローバルオプションを指定するには cvs を使ってください(上記では最後の行)。この例では cvs diff の実行には毎回自動的に -c フラグがつきます。


Node:Getting Snapshots (Dates And Tagging), Next:, Previous:Avoiding Option Fatigue, Up:Other Useful CVS Commands

Getting Snapshots (Dates And Tagging)

バグレポートが舞い込んできて壊れた状態になったプログラムの話に戻りましょう。開発者は突然、最後のリリースの時点のプロジェクト全体にアクセスしなくてはなりません。ファイルはあれもこれも変更してあって、おまけにリビジョン番号がファイルによってバラバラであろうとも、です。ログメッセージを見て、ファイルごとにリリースのときのリビジョン番号はどれだったか探して、それぞれ -r でリビジョン番号指定して update 走らせて、なんて、考えただけで時間食いそうです。中規模から大規模のプロジェクト(数十から数百のファイルがあるような…)でそんなことをして、ふりまわされたくないでしょう。

そこで CVS はプロジェクト中の過去のリビジョンをひとまとめにアクセスできる方法を提供しています。実際には2つの方法が用意されています。ひとつは日付指定で、コミットされた時刻をもとにリビジョンを選択する方法。もうひとつはタグ指定で、過去にプロジェクトのスナップショットとしてマークをつけたものにアクセスする方法。

状況によってどちらを選択するかが決まってきます。日付指定アクセスは update に -D フラグを渡すことによって実行できます。-r に似ていますがリビジョン番号のかわりに日付を指定します:

floss$ cvs -q update -D "1999-04-19"
U hello.c
U a-subdir/subsubdir/fish.c
U b-subdir/random.c
floss$

-D オプションを指定すると、update は指定された日付のなかで一番大きいリビジョンのファイルを取ってきて、必要があれば作業コピー中のファイルをそのリビジョンで置き換えます。

日付だけでなく、時刻も指定できます(したほうがいいことも多いです)。たとえば上のコマンドでは全部リビジョン1.1を取ってきています(表示された3つのファイルだけが変更されていますが、それは他のファイルが全て1.1のままだからです)。hello.c のステータスを見て確認しましょう:

floss$ cvs -Q status hello.c
===================================================================
File: hello.c                 Status: Up-to-date
   Working revision:          1.1.1.1 Sat Apr 24 22:45:03 1999
   Repository revision:       1.1.1.1 /usr/local/cvs/myproj/hello.c,v
   Sticky Date:               99.04.19.05.00.00
floss$

でもこの章の最初のほうにちょっと戻ってログメッセージを見てみると、 hello.c のリビジョン1.2は確かに1999/4/19にコミットされているはずなのに。どうしてリビジョン1.2ではなく1.1を取ってきたのでしょう?

これは、"1999-04-19" という日付が「1999-04-19が始まる真夜中」、つまりその日の最初の瞬間、という意味に解釈されてしまうことが問題なのです。これはたぶん、望んだことではないですね。1.2 はその日のもうすこしあとでコミットされました。日付をもうすこし正確に指定して、リビジョン1.2を取ってきましょう:

floss$ cvs -q update -D "1999-04-19 23:59:59"
U hello.c
U a-subdir/subsubdir/fish.c
U b-subdir/random.c
floss$ cvs status hello.c
===================================================================
File: hello.c                 Status: Locally Modified
   Working revision:  1.2     Sat Apr 24 22:45:22 1999
   Repository revision:       1.2     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:                (none)
   Sticky Date:               99.04.20.04.59.59
   Sticky Options:    (none)
floss$

こんなもんでしょうか。Sticky Date の行の日付と時刻をよく見てみると、午前 4:59:59を表示しているように見えますね、コマンドでは 11:59 を指定したはずなのに(sticky というのが何なのかというのは、あとで説明します)。御推察の通り、このずれは地方時と Universal Coordinated Time (グリニッジ標準時)の差が原因です。リポジトリは日付を Universal Time で保存しますが、クライアント側の CVS は地方時を仮定します。今回の -D の場合、リポジトリ内の時刻を比較することに興味があって、手元のシステムの時刻のことは気にしていないので、少々運が悪かったですね。コマンド中に GMT を指定すれば回避できます:

floss$ cvs -q update -D "1999-04-19 23:59:59 GMT"
U hello.c
floss$ cvs -q status hello.c
===================================================================
File: hello.c                 Status: Up-to-date
   Working revision:  1.2     Sun Apr 25 22:38:53 1999
   Repository revision:       1.2     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:                (none)
   Sticky Date:               99.04.19.23.59.59
   Sticky Options:    (none)
floss$

いかがでしょう。こうすることで作業コピーは 4/19 の最終のコミットへと戻りました(その日の最後の1秒のあいだにコミットしたのなら別ですが、しなかったですから)。

今 update を走らせたらどうなるんでしょう?

floss$ cvs update
cvs update: Updating .
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
floss$

何も起きません。しかし、少なくとも3つのファイルに関してはもっと新しいバージョンがあったはずです。なぜそれらが作業コピーに入ってこないのでしょう。

ここで「sticky」の出番です。-D フラグでアップデート(「ダウンデート」かな?)すると、作業コピーは永続的にその日付以前に制限されることになります。CVS 用語で言うと、その作業コピーは「スティッキーデート」が設定された、ということになります。作業コピーが一度スティッキーになると、そうでなくなるように指示されるまでスティッキーになったままです。ですから、続いて update を実行しても自動的に最新のリビジョンを取ってきたりはしないのです。スティッキーかどうかは、cvs status を実行するか、CVS/Entries ファイルを調べればわかります:

floss$ cvs -q update -D "1999-04-19 23:59:59 GMT"
U hello.c
floss$ cat CVS/Entries
D/a-subdir////
D/b-subdir////
D/c-subdir////
/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//D99.04.19.23.59.59
/hello.c/1.2/Sun Apr 25 23:07:29 1999//D99.04.19.23.59.59
floss$

ここで hello.c を変更してコミットしようとすると

floss$ cvs update
M hello.c
floss$ cvs ci -m "trying to change the past"
cvs commit: cannot commit with sticky date for file 'hello.c'
cvs [commit aborted]: correct above errors first!
floss$

CVS はコミットさせてくれません。それは時間を遡って過去を変えようとするようなものだからです。CVS はあらゆる記録をとろうとし、その結果、その操作を許可しないのです。

しかしこれは CVS がその日以来コミットされてきたリビジョンを知らないという意味ではありません。スティッキーデートの設定された作業コピーも、未来のリビジョンを含めて比較できます。

floss$ cvs -q diff -c -r 1.5 hello.c
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.5
diff -c -r1.5 hello.c
*** hello.c   1999/04/24 22:09:27     1.5
--- hello.c   1999/04/25 00:08:44
***************
*** 3,9 ****
  void
  main ()
  {
    printf ("Hello, world!\n");
-   printf ("between hello and goodbye\n");
    printf ("Goodbye, world!\n");
  }
--- 3,9 --
  void
  main ()
  {
+   /* this line was added to a downdated working copy */
    printf ("Hello, world!\n");
    printf ("Goodbye, world!\n");
  }

diff を見ると、1999/4/19 現在において hello の行と gooodbye の行の間の行はまだ追加されていなかったということがわかります。作業コピーに加えた変更も表示されていますね(コード断片の前にあるコメントを追加しました)。

スティッキーデート(やほかのスティッキー)を取り除くこともできます。update で -A を指定してください(-A はリセットという意味です、理由は聞かないでください)、作業コピーが最新のリビジョンに戻ります:

floss$ cvs -q update -A
U hello.c
floss$ cvs status hello.c
===================================================================
File: hello.c                 Status: Up-to-date
   Working revision:  1.5     Sun Apr 25 22:50:27 1999
   Repository revision:       1.5     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:                (none)
   Sticky Date:               (none)
   Sticky Options:    (none)
floss$


Node:Acceptable Date Formats, Next:, Previous:Getting Snapshots (Dates And Tagging), Up:Other Useful CVS Commands

Acceptable Date Formats

CVS は日付の指定の形式については幅広く受け入れます。前の例で使った形式は ISO 8601 のフォーマット(International Standards Organization standard #8601 のこと、www.saqqara.demon.co.uk/datefmt.htm も参照のこと)なのですが、これを使えばうまくいかないことはないでしょう。電子メールの日付フォーマット(RFC 822 と RFC 1123、 www.rfc-editor.org/rfc/ を参照のこと)も使えます。現在の日付から相対的に日付を指定する曖昧な英単語すら使えます。

全てのフォーマットを使える必要はないですが、CVS が何を受け付けるか理解するためにいくつかの例を示します:

floss$ cvs update -D "19 Apr 1999"
floss$ cvs update -D "19 Apr 1999 20:05"
floss$ cvs update -D "19/04/1999"
floss$ cvs update -D "3 days ago"
floss$ cvs update -D "5 years ago"
floss$ cvs update -D "19 Apr 1999 23:59:59 GMT"
floss$ cvs update -D "19 Apr"

日付を囲むダブルクオートは、日付が空白を含んでいても、Unix シェルがそれをひとつの引数として扱うようにするためにつけています。空白を含んでいなくても害はないのでいつも使うようにするのがよいでしょう。


Node:Marking A Moment In Time (Tags), Previous:Acceptable Date Formats, Up:Other Useful CVS Commands

Marking A Moment In Time (Tags)

日付を指定してアクセスする方法は、単なる時間の経過が主な関心事であれば便利かもしれません。しかし、本当は特定のイベントが起こった時点でのプロジェクトの状態にアクセスしたい、ということのほうが多いと思います。それはたとえばリリースの時点であったり、ソフトウェア開発が安定したある時点であったり、主要な機能を追加または削除した時点であったりするわけです。

そのイベントが起こった日付を思い出そうとしたり、ログメッセージを読んでその日付を推測したりするのは、さぞかし退屈な作業でしょう。おそらく、そのようなイベントは重要なのでしょうから、リビジョン履歴のなかにそのようにきちんと記録されています。CVS でそのようなマークをつける方法は タグ付け(tagging) といいます。

タグはコミットとは違い、ファイルの特定の変更を記録するわけではなくて、開発者のファイルへの姿勢に変更があることを記録します。タグとは、ある開発者の作業コピーで表わされる、リビジョンの集合につけられたラベルです(通常、そのような作業コピーは完全に最新なので、タグ名はリポジトリ内の「最新で最良の」リビジョンにつけられます)。

タグをセットするのは簡単です、こんな感じ:

floss$ cvs -q tag Release-1999_05_01
T README.txt
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
floss$

このコマンドで、この作業コピーで表されるスナップショットにシンボル名 "Release-1999_05_01" を関連づけます。きちんと定義すると、スナップショットとは、プロジェクトのファイルと関連づけられたリビジョン番号の集合です。これらのリビジョン番号はファイル同士で同じである必要はなく、実際、違うことのほうが多いです。たとえば、この章でずっと使っている myproj ディレクトリでタグをつけて、その作業コピーが完全に最新だったと仮定すると、シンボル名 "Release-1999_05_01" は hello.c はリビジョン1.5、fish.c はリビジョン1.2、random.c はリビジョン1.2、その他はリビジョン1.1につきます。

タグを線で表わしてプロジェクト内のファイルのいろいろなリビジョンをつないで可視化するとわかりやすいと思います。Figure 2.1 では、あるプロジェクト内でタグ付けされた各ファイルのリビジョン番号を線でつないでみました。

     File A      File B      File C      File D      File E
     ------      ------      ------      ------      ------
     1.1         1.1         1.1         1.1         1.1
 ----1.2-.       1.2         1.2         1.2         1.2
     1.3 |       1.3         1.3         1.3         1.3
          \      1.4       .-1.4-.       1.4         1.4
           \     1.5      /  1.5  \      1.5         1.5
            \    1.6     /   1.6   |     1.6         1.6
             \   1.7    /          |     1.7         1.7
              \  1.8   /           |     1.8       .-1.8------->
               \ 1.9  /            |     1.9      /  1.9
                `1.10'             |     1.10    /   1.10
                 1.11              |     1.11    |
                                   |     1.12    |
                                   |     1.13    |
                                    \    1.14    |
                                     \   1.15   /
                                      \  1.16  /
                                       `-1.17-'

[Figure 2.1: How a tag might stand in relation to files's revisions.]

線をひっぱってピンとさせて、それに沿って見ると、そのプロジェクトの履歴中の特定の時点が見えてきます。すなわち、その時点でタグがセットされたのです(Figure 2.2)。

     File A      File B      File C      File D      File E
     ------      ------      ------      ------      ------
                                         1.1
                                         1.2
                                         1.3
                                         1.4
                                         1.5
                                         1.6
                                         1.7
                 1.1                     1.8
                 1.2                     1.9
                 1.3                     1.10        1.1
                 1.4                     1.11        1.2
                 1.5                     1.12        1.3
                 1.6                     1.13        1.4
                 1.7         1.1         1.14        1.5
                 1.8         1.2         1.15        1.6
     1.1         1.9         1.3         1.16        1.7
 ----1.2---------1.10--------1.4---------1.17--------1.8------->
     1.3         1.11        1.5         1.17        1.9
                             1.6         1.17        1.10

[Figure 2.2: The same tag as a "straight sight" through the revision history.]

続けてファイルを編集し、コミットしても、タグはリビジョン番号が増えるにつれて動いたりしません。固定したまま、タグづけられた時点での各ファイルのリビジョン番号にくっついています(スティッキー)。

タグは記述子として重要性があるにもかかわらず、ログメッセージにタグのことが含まれなかったり、that the tags themselves can't be full paragraphs of prose というのは少し不幸です。前の例ではタグ自身が、そのプロジェクトがある日付でのリリース状態であることを明白に説明しています。しかし、もう少し複雑な状態のスナップショットを作りたいこともあるでしょう、そうするとタグ名はこんなに見苦しくなってしまいます:

floss$ cvs tag testing-release-3_pre-19990525-public-release

一般的な規則として、タグ名はできるだけ簡潔に、そして記録しようとしているイベントについての情報を必要十分に含んでいるよう心がけるべきです。迷った時は、説明しすぎるほうへ振っておいたほうがいいでしょう。あとになって、その時の状況を正確に記述した冗長なタグ名から何かわかって、嬉しいこともあるでしょう。

タグ名にピリオドやスペースが使われていないのに気づいたと思います。CVS では有効なタグ名を構成するものについてはちょっと厳しいのです。英字で始まり、英数字、ハイフン("-")、アンダスコア("_")で構成される、というのがそのルールです。スペースやピリオド、コロン、カンマ、記号は使えません。

タグ名でスナップショットにアクセスするには、タグ名をリビジョン番号のように使えばいいのです。スナップショットへのアクセスには2通りの方法があります: あるタグを指定して新しい作業コピーをチェックアウトするか、またはタグを指定して既存の作業コピーに上書きするか、です。どちらの方法でも、作業コピー中のファイルは指定したタグのリビジョンになっています。

たいがいの場合やりたいことというのは、そのスナップショットの時点でプロジェクトがどんなだったかちょっと見たい、というくらいのことだと思います。そのような場合だと、自分が今作業していて、コミットしていない変更があったり何か便利な状態が構築してあったりするようなメインの作業コピーでそんなことはあんまりしたくないでしょうから、タグを指定して別の作業コピーをチェックアウトしたいんだということにしましょう。このようにします(これは今ある作業コピーとは別の場所、1つ上のディレクトリとか、に居ることを確認してから実行してくださいね!)

floss$ cvs checkout -r Release-1999_05_01 myproj
cvs checkout: Updating myproj
U myproj/README.txt
U myproj/hello.c
cvs checkout: Updating myproj/a-subdir
U myproj/a-subdir/whatever.c
cvs checkout: Updating myproj/a-subdir/subsubdir
U myproj/a-subdir/subsubdir/fish.c
cvs checkout: Updating myproj/b-subdir
U myproj/b-subdir/random.c
cvs checkout: Updating myproj/c-subdir

update コマンドで -r オプションを見てきました、あれはそのあとにリビジョン番号をつけましたよね。多くの場合、タグはリビジョン番号のように使えます。それは、各ファイルにとってみればタグというのは単に、対応するひとつのリビジョン番号をさしているだけだからです(ひとつのプロジェクトで同じ名前のタグをふたつ持つというのは違反ですし、一般には不可能です)。実際、CVS でリビジョン番号を使うようなところではどこでも、かわりにタグ名が使えます(タグがセットされていさえすれば)。あるファイルについて、現状と最後のリリース時点間の diff を取りたければこのようにします:

floss$ cvs diff -c -r Release-1999_05_01 hello.c

一時的にそのリビジョンに戻したければこのようにします:

floss$ cvs update -r Release-1999_05_01 hello.c

タグとリビジョンのこの変換可能性から、有効なタグ名の厳しいルールの理由が説明できます。タグ名にピリオドを許したらどうなりますか? 実際のリビジョン番号"1.47"に"1.3"というタグ名をつけることができてしまいますよね。その後にこういうコマンドを実行したとすると

floss$ cvs update -r 1.3 hello.c

これが "1.3" というタグを指定しているのか、もっと前のリビジョン1.3を指定しているのか、CVS はどうやって判断すればよいのでしょう。このせいでタグ名が制限してあって、CVS はタグ名とリビジョン番号の区別を容易に判断できるようになっています。リビジョン番号にはピリオドがあって、タグ名にはありません。(ほかの制限も同じ理由によります、CVS がタグ名を認識しやすいようになっているのです)

さてここで、そろそろだろうなと思ったことと思いますが、スナップショットにアクセスする2つめの方法を紹介します。既存の作業コピーをタグ付けされたリビジョンに切り替えるやつです、これも update でやります:

floss$ cvs update -r Release-1999_05_01
cvs update: Updating .
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
cvs update: Updating c-subdir
floss$

上記のコマンドは、以前 hello.c を Release-1999_05_01 に戻すときに使ったものとほとんど同じです。ファイル名の指定がないところだけが違います、今回はプロジェクト全体を元に戻したいですからね。(もしやりたければプロジェクトのサブツリーだけをタグの時点に戻すこともできます。上のコマンドをトップレベルではなくてサブツリー内で実行してください。あんまりそうしたい機会が多いとも思えませんけど)

アップデートの時点では、どのファイルも変更されていないように見えることに注意してください。作業コピーはタグ付けされた時点では最新でしたし、それ以来変更はコミットされていません。

しかしこれは何も変更されていないということを意味しません。作業コピーはこれがタグ付けされたリビジョンであることを知っています。ここで変更を加えてコミットしようとすると(hello.c を変更したとしましょう):

floss$ cvs -q update
M hello.c
floss$ cvs -q ci -m "trying to commit from a working copy on a tag"
cvs commit: sticky tag 'Release-1999_05_01' for file 'hello.c' is not a branch
cvs [commit aborted]: correct above errors first!
floss$

CVS はコミットを許しません。(エラメッセージの正確な意味についてはいまのところ放っておいていいです、ブランチについては次で説明します) これはタグで指定されたこの作業コピーがチェックアウトされたものかアップデートされたものかには関係ありません。いったんタグで指定したら、CVS はその作業コピーを履歴のある一時点でのスタティックなスナップショットだと見なし、履歴を変更させなくなります。少なくとも簡単にはさせてくれません。cvs status を実行するか、CVS/Entries ファイルを見ると、スティッキータグが各ファイルに設定されているのがわかります。たとえばトップレベルの Entries ファイルはこうなっています:

floss$ cat CVS/Entries
D/a-subdir////
D/b-subdir////
D/c-subdir////
/README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//TRelease-1999_05_01
/hello.c/1.5/Tue Apr 20 07:24:10 1999//TRelease-1999_05_01
floss$

ほかのスティッキーと同じように、タグも -A フラグつきの update を実行すれば削除できます:

floss$ cvs -q update -A
M hello.c
floss$

hello.c に加えた変更は失われません、CVS is still aware that the file changed with respect to the repository:

floss$ cvs -q diff -c hello.c
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.5
diff -c -r1.5 hello.c
*** hello.c   1999/04/20 06:12:56     1.5
--- hello.c   1999/05/04 20:09:17
***************
*** 6,9 ****
--- 6,10 --
    printf ("Hello, world!\n");
    printf ("between hello and goodbye\n");
    printf ("Goodbye, world!\n");
+   /* a comment on the last line */
  }
floss$

update でリセットしたので、CVS はコミットさせてくれます:

floss$ cvs ci -m "added comment to end of main function"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
cvs commit: Examining c-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <-  hello.c
new revision: 1.6; previous revision: 1.5
done
floss$

もちろん、Release-1999_05_01 というタグはリビジョン1.5についたままです。タグ以前と以降のリビジョンのステータスを比べてみてください:

floss$ cvs -q status hello.c
===================================================================
File: hello.c                 Status: Up-to-date
   Working revision:  1.6     Tue May  4 20:09:17 1999
   Repository revision:       1.6     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:                (none)
   Sticky Date:               (none)
   Sticky Options:            (none)
floss$ cvs -q update -r Release-1999_05_01
U hello.c
floss$ cvs -q status hello.c
===================================================================
File: hello.c                 Status: Up-to-date
   Working revision:  1.5     Tue May  4 20:21:12 1999
   Repository revision:       1.5     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:                Release-1999_05_01 (revision: 1.5)
   Sticky Date:               (none)
   Sticky Options:            (none)
floss$

CVS は歴史を変えさせてくれない、と言いました。さて、今から歴史を変える方法を教えます。


Node:Branches, Previous:Other Useful CVS Commands, Up:An Overview of CVS

Branches

ここまで、CVS をインテリジェントで整備された図書館の一種のように見てきました。しかし、CVS はタイムマシンであると考えることもできます(このたとえは Jim Blandy によります)。今までは CVS で過去に影響は与えずに調べる方法だけを見てきました。CVS ではすてきなタイムマシンのように、時を遡って過去を変えることもできます。そうするとどうなるでしょう? SF ファンならその答えを知っていますよね: わたしたちの世界と並行するもうひとつの世界が、変えた過去のその時点から分岐するのです。CVS のブランチはプロジェクトの開発を並行する別々の歴史に分ける働きがあります。あるブランチに加えた変更は別のブランチには影響しません。


Node:Branching Basics, Next:, Up:Branches

Branching Basics

ブランチは何故便利なのでしょう?

ここであのシナリオに戻りましょう。プログラムの新しいバージョンを作っている最中の開発者が、ひとつ前のリリースのバグレポートを受けとったところです。開発者が問題を解決したとしても、顧客にそのバグフィクス版を届ける方法がわかりません。いえ、プログラムの古いコピーを取ってきて、CVS の知らないところでそれを直して出荷する、というのは難しいことではありません。でもこれでは、何をしたか記録が残らないのです。CVS は直しに気づかないまま。もしあとでそのパッチによくないところが発見されても、誰にも問題を再生するスタート地点さえわかりません。

現在の不安定なバージョンのソースのバグを直して顧客に出荷する、というのはもっと悪いアドバイスですね。そりゃ確かにレポートされたバグについては直っているかもしれませんが、その他のところは実装途中でテストもされていない状態です。動きはするでしょうが、but it's certainly not ready for prime time.

最後のリリースバージョンは、そのバグを除いては安定しているのですから、理想的な解決策は、時間を戻してその古いリリースのバグを直すことです。つまり、最後のリリースがこのバグフィクスを取り込むような、もうひとつの世界を作るということです。

ここでブランチの登場です。開発者は、開発のメインライン(トランク(幹))の、最新リビジョンでなく最後のリリースのところに根を下ろすブランチを作ります。彼女はこのブランチの作業コピーをチェックアウトして、バグフィクスに必要な変更を加え、それらをブランチにコミットします。こうすればバグフィクスの記録ができるというわけです。これで、ブランチに基づく暫定リリースをパッケージにして、顧客に出荷できます。

彼女の変更はトランクにあるコードに何の影響も与えませんし、同じバグフィクスがトランクのほうにも必要かどうか見つけだすことなしにはそれを望まないでしょう。もし必要なら、ブランチの変更をトランクにマージすることもできます。CVS は分岐点からブランチの先端(最新の状態のところ)までに加えられた変更を計算し、その違いをプロジェクトのトランクの先端に適用します。ブランチの根と先端の相違分のマージはもちろんうまくいって、バグフィクスになります。

マージはアップデートの特別な場合と考えることもできます。マージでの相違分というのは、作業コピーとリポジトリを比較するかわりに、ブランチの根と先端を比較することによって算出されるものです。

アップデートの動作というのは、その変更のパッチを作者から直接受け取って、それを手でパッチするのと同じです。事実アップデートを実行する際、CVS は作業コピーとリポジトリの相違分を計算して(相違分というのは diff プログラムがやるわけですが)、その diff を作業コピーに適用します、patch プログラムがやるのと同じようにです。これは、開発者が外の世界からの変更を取りいれるやりかた、パッチを作った人からパッチファイルをもらって手でパッチを当てるというやつですが、それを真似ています。

バグフィクスブランチをトランクにマージするというのは、外のコントリビュータのパッチを当ててバグを直すのと似ています。コントリビュータは最新のリリースバージョンへのパッチを作ります。ブランチの変更がそのバージョンに対してなされるのと同じです。現在のソースコードのその領域が最後のリリースからそう変わっていなければ、マージは問題なく成功するでしょう。しかし、コードがとても変わってしまっていたら、マージはコンフリクトを起こして失敗に終わるでしょう(パッチがリジェクトされるでしょうという意味です)、手で直接ゴソゴソやる必要があるわけです。これは通常、コンフリクト領域を読み、手で必要な変更を施し、そしてコミットすればいいのです。Figure 2.3 はブランチとマージで何が起こるかを示した図です。

             (branch on which bug was fixed)
           .---------------->---------------.
          /                                 |
         /                                  |
        /                                   |
       /                                    |
      /                                     V (<------ point of merge)
 ====*===================================================================>
                (main line of development)


[Figure 2.3: A branch and then a merge.  Time flows left to right.]

**************************************************************** We'll now walk through the steps necessary to make this picture happen. **************************************************************** 図で左から右へ流れているのは実際の時間ではなく、リビジョン履歴です。ブランチはリリースの時点で作られたわけではなく、リリースのリビジョンに根を下ろすように後で作られるものです。

プロジェクト中のファイルは Release-1999_05_01 とタグ付けされてからもたくさんリビジョンを増やして、ファイルも追加された、と仮定しましょう。古いリリースに関するバグレポートが舞い込んできたときに最初にしたいのは、例の古いリリース、Release-1999_05_01 というタグをつけたところに根を下ろすブランチを作ることです。

ひとつの方法として、まずそのタグに基づいた作業コピーをチェックアウトして、それから -b (ブランチ, branch) オプションで再度タグづけしてブランチを作ります。

floss$ cd ..
floss$ ls
myproj/
floss$ cvs -q checkout -d myproj_old_release -r Release-1999_05_01 myproj
U myproj_old_release/README.txt
U myproj_old_release/hello.c
U myproj_old_release/a-subdir/whatever.c
U myproj_old_release/a-subdir/subsubdir/fish.c
U myproj_old_release/b-subdir/random.c
floss$ ls
myproj/      myproj_old_release/
floss$ cd myproj_old_release
floss$ ls
CVS/      README.txt  a-subdir/   b-subdir/   hello.c
floss$ cvs -q tag -b Release-1999_05_01-bugfixes
T README.txt
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
floss$

最後のコマンドを良く見てください。ブランチを作るのに使うタグは適当でいいように見えるかもしれませんが、これにはちゃんと理由があります: このブランチをあとでアクセスするためのラベルとしてこのタグ名は使われます。ブランチ用のタグはブランチでないタグと変わらないように見えますし、同じネーミング制限に従っています。ブランチのタグ名には必ず「branch」という単語を居れるようにしている人もいます(たとえばRelease-1999_05_01-bugfix-branch のように)。こうするとブランチタグとほかの種類のタグを区別できます。よくタグ名をまちがってアクセスしてしまうようなら、そういう風にするのもよいでしょう。

(あと、最初のコマンドでチェックアウトに -d myproj_old_release というオプションがつけてあることに注意してください。これはチェックアウトのときに、作業コピーを myproj_old_release という名前のディレクトリに置くことを指示するもので、こうしておくと myproj にある現在のバージョンと混同してしまうこともありません。グローバルオプションの -d や、update の -d と混同しないようにしてください。)

もちろん、単に tag コマンドを実行したからといってこの作業コピーがブランチに切り替わってしまったりするわけではありません。タグ付けは作業コピーには影響ありません。作業コピーのリビジョンにあとでアクセスできるように、リポジトリ内にちょっとした情報を記録するだけです(履歴内の1コマとして、あるいはブランチとして、場合によります)。

2つのうちどちらかの方法でアクセスできます(もうこのフレーズはそろそろ耳タコかなあ)。ブランチ上の新しい作業コピーをチェックアウトしましょう:

floss$ pwd
/home/whatever
floss$ cvs co -d myproj_branch -r Release-1999_05_01-bugfixes myproj

あるいは、今ある作業コピーをそっちに切り替えますか:

floss$ pwd
/home/whatever/myproj
floss$ cvs update -r Release-1999_05_01-bugfixes

結果は同じです(新しい作業コピーのトップレベルディレクトリの名前は違いますが、CVS に関してはあまり重要ではないですね)。現在の作業コピーに未コミットの変更があれば、ブランチにアクセスするのに update ではなくて checkout を使いたいだろうと思います。でないと、ブランチに切り替えようとした際、作業コピーに対して変更をマージしようとしてしまいます。この場合、コンフリクトが起こるかもしれませんし、起こらなくても純粋でないブランチになります。これでは、作業コピー中のいくつかのファイルは変更されたままの状態なので、プログラムは指定されたタグをちゃんと反映していないことになります。

とにかく、どちらかの方法でお望みのブランチの作業コピーを取得できたとしましょう:

floss$ cvs -q status hello.c
===================================================================
File: hello.c                 Status: Up-to-date
   Working revision:  1.5     Tue Apr 20 06:12:56 1999
   Repository revision:       1.5     /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:                Release-1999_05_01-bugfixes
(branch: 1.5.2)
   Sticky Date:               (none)
   Sticky Options:            (none)
floss$ cvs -q status b-subdir/random.c
===================================================================
File: random.c                Status: Up-to-date
   Working revision:  1.2     Mon Apr 19 06:35:27 1999
   Repository revision:       1.2 /usr/local/cvs/myproj/b-subdir/random.c,v
   Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.2.2)
   Sticky Date:               (none)
   Sticky Options:            (none)
floss$

(これらの Sticky Tag 行の内容を手短に説明します)hello.c と random.c を変更したら、コミットをかけます

floss$ cvs -q update
M hello.c
M b-subdir/random.c
floss$ cvs ci -m "fixed old punctuation bugs"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <-  hello.c
new revision: 1.5.2.1; previous revision: 1.5
done
Checking in b-subdir/random.c;
/usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
new revision: 1.2.2.1; previous revision: 1.2
done
floss$

リビジョン番号を見ると、なんだかおもしろいことが起こっている様子なのに気づきませんか:

floss$ cvs -q status hello.c b-subdir/random.c
===================================================================
File: hello.c                 Status: Up-to-date
   Working revision:  1.5.2.1 Wed May  5 00:13:58 1999
   Repository revision:       1.5.2.1 /usr/local/cvs/myproj/hello.c,v
   Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.5.2)
   Sticky Date:               (none)
   Sticky Options:            (none)
===================================================================
File: random.c                Status: Up-to-date
   Working revision:  1.2.2.1 Wed May  5 00:14:25 1999
   Repository revision:       1.2.2.1 /usr/local/cvs/myproj/b-subdir/random.c,v
   Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.2.2)
   Sticky Date:               (none)
   Sticky Options:            (none)
floss$

数字が2つではなくて、4つになっています!

よく見ると、各ファイルのリビジョン番号はブランチ時の番号( Sticky Tag 行に示されています)の最後に余分な数字をつけ加えてあるようですね:

いま見ているのは CVS の内部作業の片鱗です。ほとんどいつもは、プロジェクト全体の分岐をマークするためにブランチを使いますが、CVS は実際はファイル毎にブランチを記録しています。このプロジェクトには、このブランチの分岐時に5つのファイルがあったので、5つの個別のブランチが作成され、みな同じタグ名がつけられました(Release-1999_05_01-bugfixes)。

CVS の実装のうち、このようなファイルごとのやりかたは、少々エレガントでないという意見が多いです。これは RCS の遺物なのです、RCS ではファイルをまとめてプロジェクトにしたりできませんでした。CVS は、RCS のブランチを扱うところのコードを受け継いでいるのでこうなっているのです。

通常は CVS がファイル等を内部的にどのように追跡しているかなんてあまり考える必要はありませんが、今回の場合は、ブランチ番号とリビジョン番号の関係について理解する助けになります。hello.c を見ていきましょう、これから hello.c に関して言うことは、ブランチの他のファイルについてもあてはまります(リビジョン/ブランチ番号は適当に読み替えてください)

hello.c のリビジョンは、ブランチが根を下ろした点で1.5でした。ブランチを作成した時、その端には番号がつけられてそれがブランチ番号になります(CVS は、まだ使われていない非ゼロの偶数整数値をひとつ選んでつけます)。そうするとこの場合、ブランチ番号は 1.5.2 となります。ブランチ番号そのものはリビジョン番号ではないですが、ブランチ番号はそのブランチ上の hello.c のリビジョン番号の根(プレフィクス)になります。

しかし、ブランチ後の作業コピーで status コマンドを実行すると、hello.c のリビジョン番号は単に 1.5 となっていて、1.5.2.0 とかではないようです。これは、ブランチ上の最初のリビジョン番号というのは、枝分かれした点のトランク上のリビジョン番号とつねに同じだからです。ですから、そのファイルがブランチ上で変更されずにトランク上のと同じあいだは、CVS は status の出力にトランクのリビジョン番号を出力します。

hello.c の新しいリビジョンをコミットすれば、hello.c はトランク上とブランチ上で違ってしまいます。ブランチ上のファイルは変わってしまいますが、トランク上のファイルは変わりませんから。従って、ブランチ上の hello.c には最初のブランチリビジョン番号が割り当てられます。コミットしたあとに status の出力を見れば、リビジョン番号が 1.5.2.1 になったことがわかります。

random.c ファイルについても同じことが言えます。ブランチ時点でのリビジョン番号は 1.2 ですから最初のブランチは 1.2.2、random.c をブランチ上に最初にコミットした時には 1.2.2.1 となります。

1.5.2.11.2.2.1 の間に数字的なつながりはなにもありません。同じ時にブランチしたものだということすらわかりません。両者に Release-1999_05_01-bugfixes というタグがあって、そのタグはそれぞれ 1.5.21.2.2 というブランチ番号についている、ということ以外には。従って、プロジェクト全体にわたるものとしてブランチを指定するためにはタグ名を使うしかありません。リビジョン番号を直接指定してファイルをブランチ上のものにすることは可能ですが、

floss$ cvs update -r 1.5.2.1 hello.c
U hello.c
floss$

これはよくないやりかたです。ブランチリビジョンのファイルを1つ、他のブランチでないリビジョンのファイルと混ぜてしまうことになります。その結果どんな損失があるかわかりません。ブランチにアクセスするときはブランチタグを使ってすべてのファイルに一度にアクセスすることです。特定のファイルだけ指定してはいけません。そうすれば、各ファイルの実際のブランチリビジョン番号について知らなくても気にしなくてもかまいません。

ブランチを持つブランチを作ることもでき、不合理なくらいのレベルさえ可能です。たとえば、あるファイルがリビジョン番号 1.5.4.37.2.3 の場合、次の図のような履歴になっていると思います:

                  1.1
                   |
                  1.2
                   |
                  1.3
                   |
                  1.4
                   |
                  1.5
                 /   \
                /     \
               /       \
           (1.5.2)   (1.5.4)         <--- (these are branch numbers)
             /           \
         1.5.2.1        1.5.4.1
            |              |
         1.5.2.2        1.5.4.2
            |              |
          (etc)          (...)       <--- (collapsed 34 revisions for brevity)
                           |
                        1.5.4.37
                          /
                         /
                   (1.5.4.37.2)      <--- (this is also a branch number)
                       /
                      /
               1.5.4.37.2.1
                     |
               1.5.4.37.2.2
                     |
               1.5.4.37.2.3

[Figure 2.4: An unusually high degree of branching.  Time flows downward.]

こんなに深いブランチを作るような状況になることはめったにありませんが、したいと思ったときにはいつでもできるのです。普通のブランチと同じように、ネストしたブランチを作成することもできます。ブランチ N の作業コピーをチェックアウトして、その中で cvs tag -b branchname を実行すれば、ブランチ N.M がリポジトリの中にできます(N は各ファイルのブランチリビジョン番号で(1.5.2.1など)、M はその番号で終わる次のブランチを表しています(2など))。


Node:Merging Changes From Branch To Trunk, Next:, Previous:Branching Basics, Up:Branches

Merging Changes From Branch To Trunk

さて、ブランチ上にバグフィクスがコミットされました。ここで作業コピーをトランクのリビジョンの一番大きいものに切り替えて、そっちでもそのバグフィクスをする必要があるかどうか見てみます。update -A を使って作業コピーをブランチから脱出させます(この点、ブランチタグはほかのスティッキーと同じです)。そのあと、いま離れてきたブランチと diff をとってみましょう:

floss$ cvs -q update -A
U hello.c
U b-subdir/random.c
floss$ cvs -q diff -c -r Release-1999_05_01-bugfixes
Index: hello.c
===================================================================
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.5.2.1
retrieving revision 1.6
diff -c -r1.5.2.1 -r1.6
*** hello.c   1999/05/05 00:15:07     1.5.2.1
--- hello.c   1999/05/04 20:19:16     1.6
***************
*** 4,9 ****
  main ()
  {
    printf ("Hello, world!\n");
!   printf ("between hello and good-bye\n");
    printf ("Goodbye, world!\n");
  }
--- 4,10 --
  main ()
  {
    printf ("Hello, world!\n");
!   printf ("between hello and goodbye\n");
    printf ("Goodbye, world!\n");
+   /* a comment on the last line */
  }
Index: b-subdir/random.c
===================================================================
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
retrieving revision 1.2.2.1
retrieving revision 1.2
diff -c -r1.2.2.1 -r1.2
*** b-subdir/random.c 1999/05/05 00:15:07     1.2.2.1
--- b-subdir/random.c 1999/04/19 06:35:27     1.2
***************
*** 4,8 ****
  void main ()
  {
!   printf ("A random number.\n");
  }
--- 4,8 --
  void main ()
  {
!   printf ("a random number\n");
  }
floss$

diff の結果では、ブランチリビジョンでは good-bye がハイフンつきになっていて、そのファイルのトランクリビジョンのほうには最後近くにブランチのほうにないコメントがついています。一方 random.c ですが、ブランチリビジョンのほうでは A がキャピタライズされてピリオドがついていますが、トランクのほうではそうなっていません。

ブランチを実際に現在の作業コピーにマージするには、update を -j フラグをつけて実行します(以前、古いリビジョンに戻すときに使った j と同じ、join という意味です):

floss$ cvs -q update -j Release-1999_05_01-bugfixes
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.5
retrieving revision 1.5.2.1
Merging differences between 1.5 and 1.5.2.1 into hello.c
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
retrieving revision 1.2
retrieving revision 1.2.2.1
Merging differences between 1.2 and 1.2.2.1 into random.c
floss$ cvs -q update
M hello.c
M b-subdir/random.c
floss$ cvs -q ci -m "merged from branch Release-1999_05_01-bugfixes"
Checking in hello.c;
/usr/local/cvs/myproj/hello.c,v  <-  hello.c
new revision: 1.7; previous revision: 1.6
done
Checking in b-subdir/random.c;
/usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
new revision: 1.3; previous revision: 1.2
done
floss$

こうすると、ブランチの根から先端までの変更を計算し、それを現在の作業コピーにマージします(その後に、まるでそのファイルを手で編集してその状態にしたかのように、変更を表示しています)。そして、作業コピーにマージしただけではリポジトリには変更は反映されないので、この変更をトランクにコミットします。

この例ではコンフリクトは起きませんでしたが、通常のマージでは起こりがちな(たぶん起こる)ことです。そうなったら、ほかのコンフリクトを解消するのと同じように解消作業をして、それからコミットしてください。


Node:Multiple Merges, Next:, Previous:Merging Changes From Branch To Trunk, Up:Branches

Multiple Merges

トランクにマージした後にも、ブランチ上での開発が続くことがあります。たとえば前のリリース版に2つめのバグが見つかった場合、それはブランチ上でバグフィクスされますね。random.c の冗談がわからない人がいたとしましょう、そうするとブランチ上でそれを説明する行をつけ加えなければいけません、

floss$ pwd
/home/whatever/myproj_branch
floss$ cat b-subdir/random.c
/* Print out a random number. */
#include <stdio.h>
void main ()
{
  printf ("A random number.\n");
  printf ("Get the joke?\n");
}
floss$

そしてコミットします。このバグフィクスもトランクにマージする必要がある場合、トランクの作業コピーに再マージするため、前回と同じように update コマンドを走らせようとしてしまうと思います:

floss$ cvs -q update -j Release-1999_05_01-bugfixes
RCS file: /usr/local/cvs/myproj/hello.c,v
retrieving revision 1.5
retrieving revision 1.5.2.1
Merging differences between 1.5 and 1.5.2.1 into hello.c
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
retrieving revision 1.2
retrieving revision 1.2.2.2
Merging differences between 1.2 and 1.2.2.2 into random.c
rcsmerge: warning: conflicts during merge
floss$

ごらんの通り、望んだ結果は得られません。あれからトランクには変更を加えていないからコンフリクトは起こらないはず、のような気がするのに、起こってしまいました。

この問題は update コマンドが説明した通りの動作をしたことによって起こりました: ブランチの根と先端の変更を計算して、現在の作業コピーにマージする、のです。問題はこれらの変更のうちのいくつかが既に作業コピーにマージされていた、ということにありました。だからコンフリクトしたのです:

floss$ pwd
/home/whatever/myproj
floss$ cat b-subdir/random.c
/* Print out a random number. */
#include <stdio.h
void main ()
{
<<<<<<< random.c
  printf ("A random number.\n");
=======
  printf ("A random number.\n");
  printf ("Get the joke?\n");
>>>>>>> 1.2.2.2
}
floss$

これらのコンフリクトを手で解消してもかまいません、各ファイルそれぞれやってね、と言うのは簡単なことです。まあでも最初っからコンフリクトしないようにするほうがいいですよね。-j フラグを1つではなく2つ渡すことによって、ブランチの根からでなく以前のマージのところから先端までの変更をマージすることができます。最初の -j はブランチ上の開始地点、2つめはただブランチ名だけを書けばいいです(これはブランチの先端という意味になります)。

問題は、どうやってブランチ上で以前のマージの点を指定すればいいか、ということです。ブランチタグ名に日付をつけて指定するのが1つの方法です。CVS はこのために特別な書きかたを用意しています:

floss$ cvs -q update -j "Release-1999_05_01-bugfixes:2 days ago" \
                     -j Release-1999_05_01-bugfixes
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
retrieving revision 1.2.2.1
retrieving revision 1.2.2.2
Merging differences between 1.2.2.1 and 1.2.2.2 into random.c
floss$

ブランチタグ名にコロンと日付をつなげると(普通の CVS の日付指定のやりかたならどれでもいいです)、CVS はその日付以降の変更を取ってきます。最初のバグフィクスが3日前にコミットされたのを知っていれば、上記のコマンドで2つめのバグフィクスだけをマージできます。

もう少し良い方法は、前もってやっとかないとだめなんですが、各バグフィクスの後にブランチにタグをつけておく方法です(普通のタグです、新しいブランチを分岐させたりするようなやつではなくて)。バグをフィクスしてコミットしたら、ブランチの作業コピーでこういうのを実行してください:

floss$ cvs -q tag Release-1999_05_01-bugfixes-fix-number-1
T README.txt
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
floss$

こうしておけば、2つめの変更をトランクにマージする時にはこのタグを使って、簡単に前のリビジョンと区別をつけられます。

floss$ cvs -q update -j Release-1999_05_01-bugfixes-fix-number-1 \
                     -j Release-1999_05_01-bugfixes
RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
retrieving revision 1.2.2.1
retrieving revision 1.2.2.2
Merging differences between 1.2.2.1 and 1.2.2.2 into random.c
floss$

変更を加えたのが何日前だったか、とか思い出さないといけない方法より、この方法のほうが断然いいのですが、この方法を使おうとするとトランクにマージするたびにブランチにタグをつけるのを忘れないようにせねばなりません。この話の教訓は、前もって、度々タグをつけよ、ということですね。タグは少なすぎるより多すぎるほうがマシです(説明的な名前をつけている限りにおいてはです)。たとえば上の例で言うと、ブランチ上の新しいタグ名はブランチタグと似ている必要はないわけです。ですから簡単に fix1 という名前をつけてもよかったのですが、Release-1999_05_01-bugfixes-fix-number-1 という名前にしました。他のブランチのタグと混同しないためには後者のほうがいいのです。(タグ名はブランチ内でユニークではなく、ファイル内でユニークであることを思い出してください。別のブランチだからといって、同じファイルに fix1 という名前のタグをふたつつけたりはできません。)


Node:Creating A Tag Or Branch Without A Working Copy, Previous:Multiple Merges, Up:Branches

Creating A Tag Or Branch Without A Working Copy

前にも述べたとおり、タグ付けというのは作業コピーでなくリポジトリに影響を与えます。では、タグをつけるときに作業コピーはいらないんじゃないか、という疑問が出てくると思います。作業コピーは、どのプロジェクトが対象で、各ファイルがどのリビジョンかというのを指定するために必要なだけです。作業コピーに関係なくプロジェクトとリビジョンを指定できたとしたら、作業コピーは必要ないのです。

こんな方法があります: rtag コマンドです("repository tag")。これは tag コマンドととても似たものなので、例を2つだけ示して使いかたの説明にかえます。最初のバグレポートが来たときに戻りましょう。最後のリリースに根を下ろすブランチを作らなければなりません。リリースのタグを指定して作業コピーをチェックアウトし、tag -b を実行しました:

floss$ cvs tag -b Release-1999_05_01-bugfixes

こうすると Release-1999_05_01 に根を下ろすブランチが作成されます。しかし、リリースのタグを知っているなら、それを使ってブランチの根の場所を指定して rtag コマンドを実行すればいいのです、作業コピーを作る必要はありません:

floss$ cvs rtag -b -r Release-1999_05_01 Release-1999_05_01-bugfixes myproj

これだけでおわりです。このコマンドは作業コピーの中でも外でも、どちらで実行してもしてもかまいません。ただし、CVSROOT 環境変数にリポジトリの場所を設定しておくか、-d グローバルオプションで指定する必要があります。このコマンドでブランチでないタグをつけることもできますが、各ファイルのリビジョン番号をひとつひとつ指定しなければならないので、あまり便利とは言えません。(それをタグで参照することも可能ですが、ということはもうタグがついているわけで、同じリビジョンに2つもタグをつけても仕方ないですよね)

さて、CVS でいろいろできるくらいに知識がつき、プロジェクトで他の人と作業できるくらいにはなったと思います。あまり重要でない機能いくつかと、ここまでに見てきた機能につける便利なオプションいくつかをまだ説明していません。このあと適切な章のなかで、どのように、どういう時につかえば良いかを実地例で説明します。よくわからない時には迷わず Cederqvist マニュアルを読んでみること、本気で CVS を使う人には不可欠なものですから。


Node:Repository Administration, Next:, Previous:An Overview of CVS, Up:Top

Repository Administration

An Overview of CVS ではプロジェクトの一参加者として CVS を使う方法を学びました。が、プロジェクト管理者になる場合にはそれに加え、CVS のインストールとリポジトリ管理の方法を知る必要があります。この章では、リポジトリがどういう構造になっていて CVS がそれをどう使っているかを、カーテンを取って詳しく見ます。また、アップデートやコミットの際に CVS が通過する主なステップと、その動作の変更のしかたを学びます。CVS がどのように動いているかを理解すれば、問題が起こった時に原因を調べて、保守性のよいやりかたで解決することもできます。

とても involved に聞こえるかもしれませんが、CVS は長生きすることは間違いなく、何年も使うことになるでしょう。今から勉強することは長い間役に立つことになるのです。CVS は使えば使うほど不可欠になります。何かに頼ろうとする時には(絶対そうなるって)、それを知る価値があるのです。

ということを心に留めておいて、始めましょう。まずは CVS をあなたのシステムに導入するところから。


Node:Getting And Installing CVS, Next:, Up:Repository Administration

Getting And Installing CVS

既にシステムに入っていて、わざわざ CVS を取りに行ったり必要はないことが多いです。主な Linux や FreeBSD のディストリビューションを使っている場合には、/usr/bin とかにインストールされていると思います。インストール済みでなくても、Red Hat Linux ユーザなら最新またはほとんど最新の CVS の RPM (Red Hat Package) がディストリビューションにあります。Debian ユーザなら以下のコマンドでインストールできます:

floss$ apt-get update
floss$ apt-get install cvs

CVS がマシン上にない場合はソースから作ります。Unix ユーザでない場合はその OS 用のバイナリが見つかると思います。幸い CVS は autoconfiscated で、GNU autoconf を使ってソースのコンパイルが非常に簡単になっています。


Node:Getting And Building CVS Under Unix, Next:, Up:Getting And Installing CVS

Getting And Building CVS Under Unix

これを書いている時点で、CVS の正規ダウンロードサイトが2つあります。ひとつはフリーソフトウェアファウンデーション(FSF)のFTPサイト ftp:/ /ftp.gnu.org/gnu/cvs/ で、公式な GNU ツールとして CVS を提供しています。Cyclic Software は CVS のメンテナ、じゃなくて「メンテナのメンテナ」で、ユーザと開発者向けにリポジトリサーバとダウンロードサイトを提供しています。http://download.cyclic.com/pub/ でリリースを配布しています。

どちらでもかまいません。下の例では Cyclic Software のサイトを利用しました。FTP クライアント(ウェブブラウザかもしれません)でそこへアクセスするとディレクトリ一覧が見えます、こんな感じ:

Index of /pub
    cvs-1.10.5/            18-Feb-99 21:36      -
    cvs-1.10.6/            17-May-99 10:34      -
    cvs-1.10/              09-Dec-98 17:26      -
    macintosh/             23-Feb-99 00:53      -
    os2/                   09-Dec-98 17:26      -
    packages/              09-Dec-98 17:26      -
    rcs/                   09-Dec-98 17:26      -
    tkcvs/                 09-Dec-98 17:26      -
    training/              09-Dec-98 17:26      -
    unix/                  09-Dec-98 17:26      -
    vms/                   09-Dec-98 17:26      -

「cvs-」で始まるディレクトリに注意して下さい(ほかのは無視して構いません)。cvs- ではじまるディレクトリは3つあって、ここで選択せねばなりません: 安定した(stable)リリースがいいか、新しい(けどテストの少ない)暫定バージョンを追いかけるか。安定したリリースは「cvs-1.10」のように小数点が1つだけで、暫定リリースは "1.10.5" のように最後にマイナーバージョンをつけてあってその数字が増えていきます。

GNU のサイトは通常メジャーリリースだけを提供して暫定のは提供しませんから、GNU のほうから CVS を取ってくる場合はこれら全部が見えるわけではありません。一般に、暫定リリースは大抵安全で、メジャーリリースで見つかったバグのバグフィクスが入っています。一番いいのは暫定リリースを追いかけて、問題があったら必要なだけ前のリリースに戻すというやりかたです。さきほどの例で一番新しい暫定リリースは cvs-1.10.6 です。ディレクトリに入ると

Index of /pub/cvs-1.10.6
    cvs-1.10.6.tar.gz      17-May-99 08:44   2.2M

というのが見えます。これが CVS のソースコードです。これをダウンロードし、コンパイルします。GNU ツールのコンパイルに慣れていれば何をするかわかるでしょうから、ここから Anatomy Of A CVS Distribution の節まで飛ばしてもかまいません。慣れていなければ何をしていいかわからないでしょうから、読みつづけて下さい…

ここからのコンパイル方法と例では、標準の Unix ディストリビューションを前提にしています。フリーの Unix (FreeBSD や Linux) や、主な商業 Unix (SunOS/Solaris, AIX, HP-UX, Ultrix など)ならば問題ないと思います。もし書いてある通りのやり方でうまくいかなくても諦めないで下さい。各 OS のコンパイルの詳しいところまではこの本の範囲ではありませんが、この章の最後に参考になるリソースへのポインタを示します。

とにかくコンパイルを進めましょう、まず GNU gunzip と tar を使って tar ファイルをほどきます(これらがインストールされていない場合、gunzip は ftp://ftp.gnu.org/gnu/gzip/ で、GNU の tar は ftp://ftp.gnu.org/gnu/tar/ で手に入ります)。

floss$ gunzip cvs-1.10.6.tar.gz
floss$ tar xvf cvs-1.10.6.tar

スクリーン上をファイル名がたくさん流れるのが見えると思います。

これで新しいディレクトリ cvs-1.10.6 ができ、その中に CVS のコードがあります。ディレクトリの中に移り、そこにある configure スクリプトで CVS をそのシステム用に合わせましょう:

floss$ cd cvs-1.10.6
floss$  ./configure
creating cache ./config.cache
checking for gcc... gcc
checking whether we are using GNU C... yes
checking whether gcc accepts -g... yes
checking how to run the C preprocessor... gcc -E
  (etc)

configure コマンドが終わると、ソースツリーはそのマシンでコンパイルするために必要なことをすべて知っている状態になります。次はこうします:

floss$ make

出力がたくさん流れるのが見えると思います。その次はこうです:

floss$ make install

(最後のステップではスーパユーザになる必要があります) また出力が流れます、これが終わると、CVS はマシンにインストールされています。

デフォルトでは CVS の実行ファイルは /usr/local/bin/cvs となります。それなりの make プログラムがシステムにインストールされていればの話です(持っていなければ GNU プロジェクトの make を ftp://ftp.gnu.org/gnu/make/ から持っていって下さい)

CVS を /usr/local/bin 以外の場所にインストールしたければ、最初のコンフィギュレーションのやりかたを変えます。例えば

floss$ ./configure --prefix=/usr

とすると、CVS は /usr/bin/cvs としてインストールされます(PREFIX/bin/cvs になるわけです)。デフォルトの prefix は /usr/local になっていて、たいていのインストールではこれでよいと思います。

今までのユーザのかたへ、以前のバージョンでは CVS は1つの実行ファイルになっておらず、また、RCS がインストールされていることに依存していましたが、バージョン 1.10 ではそうなっていません。ですから、cvs 以外のライブラリや実行ファイルのことを気にかけなくてもよくなりました。

リモートリポジトリにアクセスするためだけに CVS を使う場合は、ここまででおしまいです。リポジトリを提供しようとしている場合には、ステップがあといくつか残っています。この章のなかで、あとで説明します。


Node:Getting And Installing CVS Under Windows, Next:, Previous:Getting And Building CVS Under Unix, Up:Getting And Installing CVS

Getting And Installing CVS Under Windows

ソースコードから実行ファイルを作ることについてこだわっているのでなければ、 CVS を Windows 上でソースコードからコンパイルする必要はありません。 Unix とは違ってコンパイルツールがない場合が多いので、ソースから作ろうと思えばそのようなツールを持ってくることから始めなければならなくなります。そういうことはこの本の範囲ではありませんので、前もってコンパイルされた CVS バイナリをインストールする方法を教えます。

CVS の Windows バイナリディストリビューションは通常、メジャーリリースの分についてのみ作られることに注意して下さい、暫定リリースの分はありません。また、GNU FTP のほうには置いてありません。Cyclic Software のダウンロードサイトのメジャーバージョンディレクトリ http://download.cyclic.com/pub/cvs-1.10/ にアクセスしてください。そうすると特別なサブディレクトリが見えます:

Index of /pub/cvs-1.10
    cvs-1.10.tar.gz        14-Aug-98 09:35   2.4M
    windows/

この中には ZIP ファイルがあります:

Index of /pub/cvs-1.10/windows
    cvs-1.10-win.zip       14-Aug-98 10:10   589k

この ZIP ファイルは CVS のバイナリディストリビューションを含んでいます。その ZIP ファイルをダウンロードしてほどいてください:

floss$ unzip cvs-1.10-win.zip

Archive:  cvs-1.10-win.zip
  inflating: cvs.html
  inflating: cvs.exe
  inflating: README
  inflating: FAQ
  inflating: NEWS
  inflating: patch.exe
  inflating: win32gnu.dll

README に詳しいインストール方法が書いてあります。インストールはだいたい次のような手順になります: EXE と DLL ファイルを PATH の通ったディレクトリに置きます。リモートのリポジトリに pserver アクセスするつもりなら C:\AUTOEXEC.BAT に次のように書いてください:

set HOME=C:

これは .cvspass をどこに置くか指示するものです。そしてリブートします。

Windows 上で CVS を動かしても、現時点ではリモートのマシンにリポジトリを提供できません。クライアントになれる(リモートのリポジトリに接続できる)のと、ローカルモードで作業できる(同じマシンのリポジトリを使う)だけです。この本では大部分で Windows で動く CVS はクライアントだと仮定しています。しかし、この章の残りの Unix 向けのところを読めば、Windows でローカルリポジトリを設定することも難しくはありません。

リモートリポジトリにアクセスしているだけなら、CVS を走らせる必要さえありません。クライアントの機能を実装した WinCvs というツールがあります。 CVS そのものとは別に、それだけで配布されていますが、CVS と同様 GNU GPL ライセンス下で使用できます。詳しい情報は http://www.wincvs.org にてご覧ください。


Node:Getting And Installing CVS On A Macintosh, Next:, Previous:Getting And Installing CVS Under Windows, Up:Getting And Installing CVS

Getting And Installing CVS On A Macintosh

CVS は Macintosh でも利用できますが、メインのディストリビューションには入っていません。今のところ、それぞれ異なる3つの Macintosh 用 CVS クライアントが存在します:

正直に言うと、どれが一番いいかわからないです。全部使ってみて(上の順番通りに試す必要はありません)、いいと思うものを選んで下さい。MacCVS は WinCVS と同じホームページですし明らかに兄弟プロジェクトですね(これを書いている今、WinCVS のページに「MacCvs の開発はまもなく再開されます」という意味のお知らせがあります)。


Node:Limitations Of The Windows And Macintosh Versions, Previous:Getting And Installing CVS On A Macintosh, Up:Getting And Installing CVS

Limitations Of The Windows And Macintosh Versions

CVS の Windows 及び Macintosh ディストリビューションは機能に制限があります。これらはクライアントとしては動きます。つまりリポジトリサーバに接続して作業コピーを取得し、コミット、アップデートなどを実行できます。しかしリポジトリを提供することはできません。正しく設定すれば Windows 用のはローカルディスクリポジトリを使えますが、他のマシンへプロジェクトのリポジトリを提供することはできないのです。一般に、ネットワーク経由でアクセスできる CVS リポジトリを作りたい場合には Unix マシン上で CVS サーバを動かす必要があります。


Node:Anatomy Of A CVS Distribution, Next:, Previous:Getting And Installing CVS, Up:Repository Administration

Anatomy Of A CVS Distribution

ここまで説明してきた方法は、とにかく始められるよう、早く動かすことが目的でした。しかし、CVS のソースディストリビューションにはコードだけでなくいろいろ入っています。ここではソースツリーの早わかりロードマップをお見せします。便利なものがいろいろあるので無視できないと思いますよ。


Node:Informational Files, Next:, Up:Anatomy Of A CVS Distribution

Informational Files

ディストリビューションツリーのトップレベルにファイルがいくつかありますが、これには有用な情報が書いてあります(あるいはより詳しい情報へのポインタが書いてあります)。以下、重要な順に説明します:


Node:Subdirectories, Next:, Previous:Informational Files, Up:Anatomy Of A CVS Distribution

Subdirectories

CVS ディストリビューションにはたくさんのサブディレクトリがあります。通常のインストールではその中を見たりしませんが、それぞれが何なのか知っておいても良いでしょう。

contrib/
diff/
doc/
emx/
lib/
man/
os2/
src/
tools/
vms/
windows-NT/
zlib/

大半は無視してかまいません。emx/, os2/, vms/, windows-NT/ には OS 依存のコードがおさめてあって、CVS のコードレベルでデバッグするときくらいしか必要ないでしょう(あんまりありそうもない状況です、聞かないわけではないですが)。diff/, zlib/ サブディレクトリはそれぞれ CVS の内部実装の diff プログラムとGNU gzip 圧縮ライブラリ部分です。(ネットワーク越しにリモートリポジトリにアクセスする際にビット数を減らすため、後者を使っています)

contrib/, tools/ サブディレクトリに CVS とともに使用するフリーのサードパーティーソフトウェアをおさめてあります。contrib/ には特定の用途に専門化された小さなシェルスクリプトの一群があります(contrib/README に何をするものか書いてあります)。tools/ サブディレクトリにはコントリビュートされたソフトウェアがあって、README にはこうあります:

以前このサブディレクトリには CVS とともに使用するツールがおさめてあり
ました。特に pcl-cvs バージョン1.xがおさめてありました。pcl-cvs は CVS
の Emacs インタフェースです。

pcl-cvs をお探しの場合、こちらにある pcl-cvs バージョン2.xをお勧めします:

    ftp://ftp.weird.com/pub/local/

ここで述べられている PCL-CVS パッケージはとても使いやすいです、のちほど Third-Party Tools でもう少し説明します。

src/, lib/ サブディレクトリには CVS の内部を含むソースコード一式がおさめてあります。主なデータ構造とコマンドの実装は src/ にあって、lib/ には CVS が一般的に使用する小さなコードモジュールがあります。

man/ サブディレクトリに CVS の man page があります(Unix のオンラインマニュアルシステム用)。ここにある man page は make install を実行したときにシステムの man page におさめられているので

floss$ man cvs

とタイプすると簡潔な紹介と CVS のサブコマンドのリファレンスが読めます。これはクイックリファレンスとしては便利ですが、Cederqvist マニュアル(次の節参照のこと)ほどには最新でもないし完璧でもありません。しかし間違っているわけではなくて不完全なだけですから、何かの足しになればと思います。


Node:The Cederqvist Manual, Next:, Previous:Subdirectories, Up:Anatomy Of A CVS Distribution

The Cederqvist Manual

残りは doc/ サブディレクトリですが、このディレクトリの中の一番重要な住人はあの有名な Cederqvist です。最近では "the Cederqvist" と呼ばれるようです。Per Cederqvist(Signum Support, Linkoping Sweden,www.signum.se)が 1992 年頃最初のバージョンを書き、それ以来たくさんの人々によって更新されてきました。例えば、コントリビュータが新しい機能を CVS に追加したら、 Cederqvist にもドキュメントを残す、というかたちで。

Cederqvist マニュアルは GNU project でも使用されている Texinfo 形式で書かれています。この形式にしておくと、オンライン出力もプリントアウト出力も比較的容易に生成できるのです(それぞれ、Info と PostScript)。 doc/cvs.texinfo がマスタファイルですが、 CVS ディストリビューションには Info と PostScript 形式も入っていますから、自前で Texinfo ツールを走らせる必要はありません。

Cederqvist は導入とチュートリアルとしても使用できますが、リファレンスドキュメントとして使うのが一番便利です。そのため、多くの人はこれをプリントアウトせずにオンラインで閲覧しています(紙に印刷しておきたい人のために、PostScript ファイルは doc/cvs.ps です)。もし今回初めて CVS をインストールしたのなら、マニュアルにアクセスできるようにするためにあとひと手間必要です。

Info ファイル (doc/cvs.info, doc/cvs.info-1, doc/cvs.info-2, など) は make install した時にインストールされました。しかしそれは Info ツリーにコピーされただけなので、Info の目次("Top" ノード)に1行追加する必要があるのです。(これは最初にインストールした時だけ必要なことです、2回目以降なら以前のインストールの時に追加したエントリがありますから)

もし今までに Info ドキュメントをシステムに追加したことがあるならこの手順には慣れていると思います。まず Info ページがどこにインストールされているか探して下さい。デフォルトのインストールを使っていれば(/usr/local/)、 Info ファイルは /usr/local/info/cvs.info* です。次のようにインストールしたのであれば

floss$ ./configure --prefix=/usr

Info ファイルは /usr/info/cvs.* になります。ファイルの場所が分かったら、 Info の目次に行を追加して下さい。そのディレクトリの dir という名前のファイルです(後者の例では /usr/info/dir です)。root 権限がない場合はシステム管理者にそれをしてくれるよう頼んで下さい。以下は CVS ドキュメントが追加される前の dir からの抜粋です:

* Bison: (bison).         The Bison parser generator.
* Cpp: (cpp).             The GNU C preprocessor.
* Flex: (flex).           A fast scanner generator

追加後の同じところは次の通り:

* Bison: (bison).         The Bison parser generator.
* Cpp: (cpp).             The GNU C preprocessor.
* Cvs: (cvs).             Concurrent Versions System
* Flex: (flex).           A fast scanner generator

追加する行の形式はとても重要です。* Cvs: にはアスタリスク、スペース、コロンがあって、そのあとの (cvs). は括弧とピリオドがあります。どれが欠けても Info dif のフォーマットとして間違いで、Cederqvist が読めません。

マニュアルがインストールされ、目次から参照されるようになったら、Info 互換ブラウザで読めるようになります。典型的な Unix システムにインストールされているのは、以下のように起動すれば CVS のページへ直接行けるコマンドライン Info リーダか、

floss$ info cvs

Emacs 中で

M-x info

あるいは

C-h i

とタイプすることによって起動されるものです。

CVS インストール時には、Cederqvist をきちんと読めるよう設定するために時間を取って下さい。何度も調べる必要ができてくるのですから、その分の時間の節約ができます。


Node:Other Sources Of Information, Previous:The Cederqvist Manual, Up:Anatomy Of A CVS Distribution

Other Sources Of Information

ディストリビューション中の Cederqvist, FAQ 他のファイルに加え、インターネット上にも CVS に関するリソースがあります。CVS サーバを管理しようとしている場合、info-cvs メーリングリストに参加したいなと思うかもしれません。購読するには、info-cvs-request@gnu.org にメールを送って下さい(メーリングリスト本体は info-cvs@gnu.org です)。流量は中から多量、1日に約20通程度で、大半が質問のメールです。たいがいは読まずに削除しても構わないですが(質問に回答する時は別です、素敵なことですね)、時々、バグを発見したという報告や、ずっと欲しかった新しい機能を実装するパッチのアナウンスがあるかもしれません。

正式なバグレポートメーリングリストに参加することもできます。ここにはバグレポートがすべて送られます。バグをフィクスしたい人(素晴らしいことですね)か、偏執狂気味で CVS について他の人が見つけた問題を全部知っておかないと気が済まない人以外には必要ないと思います。参加したければ bug-cvs-request@gnu.org までメールを送って下さい。

Usenet ニュースグループもあります、comp.software.config-mgmt というものです。これはバージョン管理と設定管理一般のニュースグループで、 CVS についても少なからず議論されているようです。

最後に CVS 関連のウェブサイトを。少なくとも3つあります。ここ数年間、Cyclic Software の http://www.cyclic.com は CVS の非公式なホームサイトであり、多分近い未来についてもそうでありつづけると思います。Cyclic Software はサーバスペースと、CVS のソースが入ったリポジトリのネットワークアクセスを提供しています。また、Cyclic のウェブページには広範囲にわたるリンクがあります。CVS の実験的パッチあり、CVS とともに使用するサードパーティーツールあり、ドキュメントやメーリングリストアーカイブあり。他にもいろいろあります。分散したリソースの中で、必要なものが見つからない時は、 http://www.cyclic.com から見始めるのがよいでしょう。

ステキなサイトをあと2つ紹介しましょう。Pascal Molli の http://www.loria.fr/~molli/cvs-index.html と、Sean Dreilinger の http://durak.org/cvswebsites/ です。Molli のサイトの一番の目玉はもちろん FAQ ですが、CVS 関連ツールへのリンクや、メーリングリストのアーカイブなどもあります。Dreilinger のサイトはウェブドキュメントを管理する場合の CVS の使い方の情報に詳しく、また、CVS に特化したサーチエンジンがあります。


Node:Starting A Repository, Next:, Previous:Anatomy Of A CVS Distribution, Up:Repository Administration

Starting A Repository

CVS の実行ファイルがシステムにインストールできたら、まずは An Overview of CVS に従って、リモートリポジトリにアクセスするクライアントとして使ってみましょう。しかし、自分のマシンからリビジョンを提供したいのであれば、そこにリポジトリをつくらねばなりません。それを実現するコマンドは以下のとおり、

floss$ cvs -d /usr/local/newrepos init

/usr/local/newrepos はリポジトリになって欲しいディレクトリへのパスです(当然そこへ書込める権限がなければなりません、コマンドを root 権限で実行しても構いません)。init サブコマンドの後ではなく、前に新しいリポジトリの場所を指定するのは直感に反するような気がするかもしれませんが、-d オプションの使い方に関して言えば、他の CVS コマンドと一貫しています。

このコマンドを走らせると、黙って終わります。新しいディレクトリを調べてみましょう:

floss$ ls -ld /usr/local/newrepos
drwxrwxr-x   3 root     root         1024 Jun 19 17:59 /usr/local/newrepos/
floss$ cd /usr/local/newrepos
floss$ ls
CVSROOT
floss$ cd CVSROOT
floss$ ls
checkoutlist     config,v        history     notify     taginfo,v
checkoutlist,v   cvswrappers     loginfo     notify,v   verifymsg
commitinfo       cvswrappers,v   loginfo,v   rcsinfo    verifymsg,v
commitinfo,v     editinfo        modules     rcsinfo,v
config           editinfo,v      modules,v   taginfo

floss$

新しいディレクトリにはサブディレクトリがひとつだけあり(CVSROOT/)、CVS の動作をコントロールするいろいろな管理ファイルがおさまっています。今後、これらのファイルをひとつひとつ見ていく予定です。今のところはリポジトリがちゃんと動くことが目標です。ここで「ちゃんと動く」とは、ユーザがプロジェクトをインポート、チェックアウト、アップデート、コミットできるという意味です。 An Overview of CVS で述べた CVSROOT 環境変数と、リポジトリの中にあるこの CVSROOT サブディレクトリを混同しないように。全然関係ないです。不運にも偶然一致してしまい、同じ名前がついているだけです。前者は、CVS を使うときにいちいち -d <repository-location> とタイプするのがイヤなユーザ向けのもので、後者はリポジトリの管理用サブディレクトリです。

リポジトリが作成できたら、そのパーミッションに注意して下さい。CVS は特別な統一されたパーミッションやファイルの所有権の計画(?)は要求しません。リポジトリへ書き込める権限が必要なだけです。しかし、一部セキュリティー上の理由ですがどちらかというと主に管理者のまっとうな分別として、次の手順を踏むことを強く推奨します。

  1. システムに cvs というグループを追加し、リポジトリにアクセスする必要のあるユーザはそのグループに加えます。例として筆者のマシンの /etc/group ファイルの関連する行をお見せしましょう:
    cvs:*:105:kfogel,sussman,jimb,noel,lefty,fitz,craig,anonymous,jrandom
    
  2. リポジトリのオーナグループとグループパーミッションを変えて、そのグループを反映するようにします:
    floss$ cd /usr/local/newrepos
    floss$ chgrp -R cvs .
    floss$ chmod ug+rwx . CVSROOT
    

これで、グループにいるユーザは誰でも cvs import を実行すれば(このへんのことは An Overview of CVS に説明があります)プロジェクトを始めることができるようになりました。checkout, update, commit も同様に動きます。リポジトリマシンに rsh または sh アクセスができるなら、 :ext: を使ってリモートからアクセスすることもできます。(上の例の chgrp と chmod コマンドだと、誰だかもわからない anonymous ユーザに書込み権限を与えてしまうことにお気づきでしょうか。こうしている理由は、リポジトリを読むだけの anonymous ユーザであろうとも、CVS プロセスがリポジトリ内に一時ロックファイルを作れるようにしておかなければならないので、システムレベルでの書込み権限が必要だからなのです。CVS は read-only 制限を実現するのに Unix ファイルシステムの権限は使っておらず、別の方法を使っているのです。これについては Anonymous Access で述べます。)

コントリビュータがリポジトリマシン上のアカウントを持つ必要がないようなプロジェクトを一般に公開するリポジトリの場合は、パスワード認証サーバを設定するべきです(see The Password-Authenticating Server)。これは匿名の読み出し専用アクセスに必要ですし、マシンのフルアカウントを与えることなしに特定の人々にコミットアクセスを許可する一番簡単な方法でもあります。


Node:The Password-Authenticating Server, Next:, Previous:Starting A Repository, Up:Repository Administration

The Password-Authenticating Server

パスワードサーバのセットアップに必要な手順を実行する前に、このような接続がどのように動いているのか大雑把に見ておきましょう。リモートの CVS クライアントが :pserver: メソッドを使ってリポジトリに接続するとき、クライアントは実際にはサーバマシンの特定のポート番号、明確に言うと 2401 番(49の2乗と言ってもいいです、そういうのがお好きなら)に接続しに行きます。2401番ポートは CVS pserver 用に指定されたデフォルトポートですが、クライアントとサーバの共通了解さえあれば別のポートに設定することも可能です。

CVS サーバはそのポートで接続をずっと待っているわけではなく、実際に接続要求があるまで起動されません。かわりに Unix の inted (InterNET Daemon)プログラムがそのポートを listen しています。接続要求を受け取ったときにどうするかを教えておく必要がありますが、そうすると inetd が CVS サーバを起動して、入って来たクライアントと接続してくれます。

これを実現するには inetd の設定ファイル、/etc/services/etc/inetd.conf を変更します。services ファイルは生のポート番号とサービス名を対応づけ、inetd.conf には inetd が各サービス名に対して何をすればよいかを書いてあります。

まず /etc/services に次の1行を追加して下さい(この行がファイルにないことを確認してからですよ)

cvspserver	2401/tcp

次に /etc/inetd.conf にこれを追加してください:

cvspserver stream tcp nowait root /usr/local/bin/cvs cvs \
   --allow-root=/usr/local/newrepos pserver

(上記は実際のファイルではバックスラッシュなしの1つの長い行にしてください) ただし、tcpwrapper が導入されたシステムにおいては次のようにしてください:

cvspserver stream tcp nowait root /usr/sbin/tcpd /usr/local/bin/cvs \
   --allow-root=/usr/local/newrepos pserver

inetd を再起動して、設定ファイルの変更を反映して下さい。(inetd の再起動の仕方を知らない場合はマシンをリブートしてください、それで動くようになります)

接続を許可するためにはこれだけでいいのですが、ユーザの通常のログインパスワードとは別に、CVS 用のパスワードを設定したいでしょう。そうすればシステム全体のセキュリティについて妥協することなくリポジトリにアクセスさせることができるようになります。

リポジトリ内の CVSROOT/passwd が CVS のパスワードファイルです。このファイルは cvs init を実行したときにデフォルトで生成されたりはしません。 CVS を pserver で提供するとは限らないからです。パスワードファイルが生成されたとしても、CVS ではユーザ名とパスワードを生成できません。自分で作る必要があります。CVSROOT/passwd ファイルのサンプルを示します:

kfogel:rKa5jzULzmhOo
anonymous:XR4EZcEs0szik
melissa:tGX1fS8sun6rY:pubcvs

フォーマットはご覧の通りごく簡単です。各行次のようにしてください:

<USERNAME>:<ENCRYPTED_PASSWORD>:<OPTIONAL_SYSTEM_USERNAME>

余分にコロンとオプショナルシステムユーザ名をつけると、CVS は USERNAME として認証した接続を SYSTEM_USERNAME というシステムアカウントの権限で実行します。言い換えると そのセッションでは SYSTEM_USERNAME としてログインしたときと同様のことがリポジトリ内で実行できる、ということです。

システムユーザ名が指定されていない場合、USERNAME はシステム上に実際に存在するログインアカウントと一致しなければならず、セッションはそのユーザの権限で実行されます。どちらの場合でも、ENCRYPTED_PASSWORD はシステムの実際のログインパスワードと一致しなくても構いません。それは CVS の pserver 接続にのみ用いられる、他と関係ないパスワードです。

パスワードは /etc/passwd にパスワードを保存するときに使われているのと同じアルゴリズムで暗号化されます。ここで「どうやって暗号化したパスワードを持ってくればいいの?」という疑問が出てきます。Unix のシステムパスワードなら、passwd コマンドが /etc/passwd の暗号化を面倒見てくれます。しかし、それに対応するような cvs passwd コマンドはありません(これは何度か提案されていますが、まだ誰も書いていないんですよ。あなたがやってくれますか?)。

不便でしょうもない方法がひとつあります。他にどうしようもなければ、通常のシステムパスワードを passwd コマンドで変えて、暗号化されたものを /etc/passwd から CVSROOT/passwd へカットアンドペーストし、元のパスワードに戻す、というのでなんとかなります(暗号化されたパスワードが /etc/shadows にあって、root 権限がないと読めない場合もあります)。

この方法はしかしかなり厄介です。暗号化されていないパスワードを引数で渡せば暗号化されたものを出力してくれるような、コマンドラインユーティリティを作るほうが簡単でしょう。Perl で書いたそういうツールをお見せしましょう:

#!/usr/bin/perl

srand (time());
my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ? 65 : 97))";
my $salt = sprintf ("%c%c", eval $randletter, eval $randletter);
my $plaintext = shift;
my $crypttext = crypt ($plaintext, $salt);

print "${crypttext}\n";

わたしはこのスクリプトを /usr/local/bin/cryptout.pl に置いています。

floss$ ls -l /usr/local/bin/cryptout.pl

-rwxr-xr-x   1   root   root   265  Jun 14 20:41 /usr/local/bin/cryptout.pl
floss$ cryptout.pl "some text"
sB3A79YDX5L4s

floss$

この例の出力を CVSROOT/passwd のエントリ作成に使ったとします:

jrandom:sB3A79YDX5L4s:craig

で、誰かが次のコマンドを使ってリポジトリに接続するとすると:

remote$ cvs -d :pserver:jrandom@floss.red-bean.com:/usr/local/newrepos login

ここでパスワードに some text とタイプすれば、以降は craig のアクセス権限で CVS コマンドを実行することができます。

CVSROOT/passwd にないユーザ名とパスワードでログインしようとした場合、 CVS は /etc/passwd のユーザ名とパスワードをチェックします。もし存在すれば(そして当然ですがパスワードが一致すれば)、CVS はアクセスを許可します。この振舞いは管理者の便宜のため、つまり通常のシステムユーザの分までいちいち CVSROOT/passwd のエントリを設定しなくてもよいようにするためのものです。しかしこれはセキュリティホールにもなります。なぜなら、そのようなユーザが CVS に接続しようとすると、通常のログインパスワードが暗号化されずにネットワーク上に流れてしまい、パスワード盗み屋に見られてしまうかもしれないからです。このあとでこの「fallback」な振舞いを無効にする方法を述べます。有効にするにしろ無効にするにしろ、ログインアカウントも持っている CVS ユーザに対しては、それぞれを違うパスワードにするように、おそらく強制すべきです。

passwd ファイルでの認証はリポジトリ全体に対して有効なのですが、少し工夫すればプロジェクトごとのアクセス許可を設定するために使用することもできます。一方法を示します:

次のように仮定します。リモートの開発者にプロジェクト foo へのアクセスを許可し、また別のリモートの開発者にプロジェクト bar へのアクセスを許可するが、一方のプロジェクトの開発者には、もう一方のプロジェクトにコミットさせたくない。この場合、プロジェクト別のユーザアカウントとグループをシステム上に作成し、CVSROOT/passwd ファイルで対応させることによって実現できます。

/etc/passwd から関連部分の抜粋:

cvs-foo:*:600:600:Public CVS Account for Project Foo:/usr/local/cvs:/bin/false
cvs-bar:*:601:601:Public CVS Account for Project Bar:/usr/local/cvs:/bin/false

/etc/group からの抜粋:

cvs-foo:*:600:cvs-foo
cvs-bar:*:601:cvs-bar

最後に CVSROOT/passwd からの抜粋です:

kcunderh:rKa5jzULzmhOo:cvs-foo
jmankoff:tGX1fS8sun6rY:cvs-foo
brebard:cAXVPNZN6uFH2:cvs-foo
xwang:qp5lsf7nzRzfs:cvs-foo
dstone:JDNNF6HeX/yLw:cvs-bar
twp:glUHEM8KhcbO6:cvs-bar
ffranklin:cG6/6yXbS9BHI:cvs-bar
yyang:YoEqcCeCUq1vQ:cvs-bar

CVS ユーザ名のうちいくつかはシステムユーザアカウント cvs-foo と対応しており、いくつかは cvs-bar に対応しています。CVS はシステムアカウントのユーザIDの権限で動作しますので、リポジトリのうち関連する部分には適切なユーザとグループの書込み権限を与えておいて下さい。ユーザアカウントをきちんとロックしておけば(ログインパスワードを無効にし、シェルには /bin/false を設定)、このシステムは十分セキュアです(この章で後ほど述べる CVSROOT のパーミッションについて読んでおいて下さいね!)。また、CVS ではレコードやログメッセージの変更をシステムユーザ名ではなく CVS ユーザ名で行うので、ある変更が誰の責任なのかというのもわかります。


Node:Anonymous Access, Next:, Previous:The Password-Authenticating Server, Up:Repository Administration

Anonymous Access

ここまでパスワード認証サーバを使って、リポジトリに対し通常のフルアクセスを許可するやりかたを見てきました(Unix ファイルパーミッションを巧みに利用してアクセス制限をすることは可能ですが)。一方、匿名の読み出し専用アクセスを実現するにはワンステップで済みます: CVSROOT/ の下に新しいファイルを1つか、もしかしたら2つ、追加するだけでいいのです。ファイル名は readerswriters、前者はリポジトリを読めるユーザ名の、後者は読み書きできるユーザ名のリストです。

CVSROOT/readers にユーザ名を書くと、そのユーザはリポジトリ内の全てのプロジェクトに対する読み出しアクセスのみ可能になります。CVSROOT/writers にユーザ名を書くと、そのユーザは書込み権限を得、writers ファイルに書かれていない他の pserver ユーザは読み出しアクセスのみ可能になります(つまり、writers ファイルが存在するということは、そこに書かれていないユーザを読み出しアクセスのみに制限するという意味になります)。両方のファイルに同じユーザ名が書かれている場合、CVS はその矛盾を保守的な方法で解決します: そのユーザは読み出しアクセスのみ可能になります。

ファイルの形式は非常に単純で、各行1ユーザです(最後のユーザの後に改行を入れるのを忘れないで)。readers ファイルのサンプルを示します:

anonymous
splotnik
guest
jbrowse

それらのファイルは CVS ユーザ名に適用されます。システムユーザ名ではありません。CVSROOT/passwd のユーザエイリアス機能を使っている場合(2番目のコロンの後にシステムユーザ名を書いている場合)、readers または writers ファイルに使用するのは一番左側のユーザ名です。

Just to be painfully accurate about it, 許可するのが読み出しアクセスか読み書きアクセスかを決定する時のサーバの動作を正式に説明するとこうなります:

readers ファイルが存在し、その中にそのユーザが書いてあれば、読み出しアクセス。writers ファイルが存在し、その中にそのユーザがなければ、読み出しアクセス(これは readers ファイルが存在していてそのユーザがその中にない場合にも真)。そのユーザが両方に書いてあれば、読み出しアクセス。その他の場合、読み書きアクセス。

従って、典型的な匿名 CVS アクセスのリポジトリは CVSROOT/passwd には次のような行があります

anonymous:XR4EZcEs0szik

/etc/passwd には次のような行

anonymous:!:1729:105:Anonymous CVS User:/usr/local/newrepos:/bin/false

CVSROOT/readers には次のような行

anonymous

当然 /etc/services と /etc/inetd.conf には以前言ったような設定を。これで全部です。

古めの Unix システムでは8文字以上のユーザ名をサポートしていないことに注意して下さい。これに対応するひとつの方法は、CVSROOT/passwd ファイルとシステムファイルでそのユーザを anonymous ではなく anon にしておくことです。anonymous の短縮形は anon だろう、と思う人は多いですから。しかし、 CVSROOT/passwd ファイルにこのような行を書くほうがよいかもしれません:

anonymous:XR4EZcEs0szik:cvsanon

(もちろんシステムファイルでは cvsanon を使って下さい)。こうすれば、多少標準的な anonymous を使ったリポジトリアドレスを公開できます。リポジトリに

cvs -d :pserver:anonymous@cvs.foobar.com:/usr/local/newrepos (etc...)

でアクセスしてくる人は、サーバ上では cvsanon (かなんか)の権限で実行されますが、その人たちはサーバ側でどう設定されているかなんて知る必要はなく、ただ公開されたアドレスだけ見ていればいいのです。


Node:Repository Structure, Next:, Previous:Anonymous Access, Up:Repository Administration

Repository Structure

新しく作られたリポジトリにはプロジェクトはありません。An Overview of CVS の最初のインポートのところからやりなおして、リポジトリに何が起こるかを見ていきましょう。(単純のため、全てのコマンドで CVSROOT 環境変数が /usr/local/newrepos が設定されていると仮定します、インポートやチェックアウトの際 -d でリポジトリを指定しなくてもいいように)

floss$ ls /usr/local/newrepos
CVSROOT/
floss$ pwd
/home/jrandom/src/
floss$ ls
myproj/
floss$ cd myproj
floss$ cvs import -m "initial import into CVS" myproj jrandom start
N myproj/README.txt
N myproj/hello.c
cvs import: Importing /usr/local/newrepos/myproj/a-subdir
N myproj/a-subdir/whatever.c
cvs import: Importing /usr/local/newrepos/myproj/a-subdir/subsubdir
N myproj/a-subdir/subsubdir/fish.c
cvs import: Importing /usr/local/newrepos/myproj/b-subdir
N myproj/b-subdir/random.c

No conflicts created by this import

floss$ ls /usr/local/newrepos
CVSROOT/  myproj/
floss$ cd /usr/local/newrepos/myproj
floss$ ls
README.txt,v  a-subdir/     b-subdir/	  hello.c,v
floss$ cd a-subdir
floss$ ls
subsubdir/    whatever.c,v
floss$ cd ..

floss$

インポート前、リポジトリ内には管理領域しかありません。インポート後、新しいディレクトリ(myproj)が出現しています。新しいディレクトリの中のファイルとサブディレクトリはインポートしたプロジェクトに見えなくはないですが、なんだかファイル名に ,v というサフィックスがついています。これらは RCS 形式のバージョン管理ファイルで(,v は "version" という意味です)、リポジトリの大黒柱になるものです。各 RCS ファイルはプロジェクト内の対応するファイルの、ブランチやタグも含めたリビジョン履歴を保持しています。


Node:RCS Format, Next:, Previous:Repository Structure, Up:Repository Administration

RCS Format

CVS を使うにあたり、RCS 形式について知っておく必要は一切ありません(ソースディストリビューションに素晴らしい記事がありますけれど。doc/RCSFILES をご覧ください)。しかし、この形式の基本的なところを理解していると CVS のトラブルシューティングに非常に役に立ちますので、ファイルの1つ hello.c,v をちょっと覗いてみることにしましょう。ファイル内容を示します:

head     1.1;
branch   1.1.1;
access   ;
symbols  start:1.1.1.1 jrandom:1.1.1;
locks    ; strict;
comment  @ * @;

1.1
date     99.06.20.17.47.26;  author jrandom;  state Exp;
branches 1.1.1.1;
next;

1.1.1.1
date     99.06.20.17.47.26;  author jrandom;  state Exp;
branches ;
next;

desc
@@

1.1
log
@Initial revision
@
text
@#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
}
@

1.1.1.1
log
@initial import into CVS
@
text
@@

うひゃー! もうほとんど無視してもかまわないです; 例えば 1.1 と 1.1.1.1 の関連や、暗黙の 1.1.1 ブランチとかは気にしないで下さい、ユーザ、管理者、どちらの観点からもあまり重要なことではありません。理解すべきは全体のフォーマットです。最初はヘッダフィールドのコレクションです:

head     1.1;
branch   1.1.1;
access   ;
symbols  start:1.1.1.1 jrandom:1.1.1;
locks    ; strict;
comment  @ * @;

そのあとは各リビジョンのメタ情報のグループです(リビジョンの中身はまだ先です)、こんな感じ:

1.1
date     99.06.20.17.47.26;  author jrandom;  state Exp;
branches 1.1.1.1;
next     ;

最後にログメッセージと実際のリビジョンのテキストが来ます:

1.1
log
@Initial revision
@
text
@#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
}
@

1.1.1.1
log
@initial import into CVS
@
text
@@

よくみると、最初のリビジョンの内容が 1.1 という見出しの下にあって、そのログメッセージがなぜか "Initial revision" になっています。インポート時に使ったのは "initial import into CVS" というログメッセージなのに。そっちのログメッセージはもっと下のほう、Revision 1.1.1.1 の下にあります。今この矛盾を気にする必要はありません。これはインポートが特別な場合だから起こることなのです。It happens because imports are a special circumstance: Inorder to make repeated imports into the same project have a usefuleffect, import actually places the initial revision on both the maintrunk and on a special branch (これの理由は Advanced CVS でベンダブランチについて述べるときにもう少し明らかになります). 今のところは 1.11.1.1.1 を同じものとして扱っても構いません。

hello.c の最初の変更をコミットすると、このファイルのことがもう少しわかってきます:

floss$ cvs -Q co myproj
floss$ cd myproj
floss$ emacs hello.c
    (ファイルを変更してみる)

floss$ cvs ci -m "print goodbye too"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in hello.c;
/usr/local/newrepos/myproj/hello.c,v  <--  hello.c
new revision: 1.2; previous revision: 1.1
done

ここでリポジトリ内の hello.c,v を見ると、コミットの結果がわかります:

head  1.2;
access;
symbols
      start:1.1.1.1 jrandom:1.1.1;
locks; strict;
comment   @ * @;

1.2
date   99.06.21.01.49.40;   author jrandom;   state Exp;
branches;
next   1.1;

1.1
date   99.06.20.17.47.26;   author jrandom;   state Exp;
branches
       1.1.1.1;
next   ;

1.1.1.1
date   99.06.20.17.47.26;   author jrandom;   state Exp;
branches;
next   ;

desc
@@

1.2
log
@print goodbye too
@
text
@#include <stdio.h>

void
main ()
{
  printf ("Hello, world!\n");
  printf ("Goodbye, world!\n");
}
@

1.1
log
@Initial revision
@
text
@d7 1
@

1.1.1.1
log
@initial import into CVS
@
text
@@

リビジョン1.2全体の内容がファイルに保存されており、リビジョン1.1の内容は暗号ちっくな形式に置き換わっています:

d7 1

d7 1 は「7行目から始めて、1行削除する」という意味の diff コードです。言い換えると、リビジョン1.1を導出するにはリビジョン1.2から7行目を削除する、ということなのです! 自分で実際にやってみてください。これでリビジョン1.1ができるのがわかると思います。単純に、ファイルに追加した行をなくすだけです。

これは RCS 形式の基本原則を示しています: リビジョン間の相違のみを保存し、そうすることによって各リビジョンそれぞれの全体を保存するのに比べて容量を節約します。一番新しいリビジョンから以前のリビジョンへ戻るには、保存してある diff をより最近のリビジョンに対して patch すればよろしい。つまりこれは、過去に戻ろうとすればするほど、より多くの patch 操作が必要になる、ということです(たとえばリビジョン1.7のファイルがあって、そのファイルのリビジョン1.4へのアクセスを要求された場合、patch で 1.7 から 1.6 を生成し、1.6 から 1.5 を、そして 1.5 から 1.4 を生成します)。幸い、古いリビジョンはあまりアクセスされませんので、実用上 RCS システムはうまく動きます。最近のファイルになるほど取得するコストが低いわけです。

ファイルの冒頭のヘッダ情報が何を意味するか、全てを理解する必要はありません。しかし、ある種の操作は、結果がとても明確にヘッダに示されますので、ヘッダに親しんでおくと便利には違いありません。

トランクに新しいリビジョンをコミットした時、head ラベルが更新されます(先に示した例で、2回目に hello.c をコミットした時、そのラベルがどのように 1.2 になったか注意して見てみてください)。あるファイルをバイナリとして追加した時、あるいはタグをつけた時にも、それらの操作はヘッダに記録されます。例として foo.jpg をバイナリファイルとして追加し、その後二度ほどタグづけしてみましょう:

floss$ cvs add -kb foo.jpg
cvs add: scheduling file 'foo.jpg' for addition
cvs add: use 'cvs commit' to add this file permanently
floss$ cvs -q commit -m "added a random image; ask jrandom@red-bean.com why"
RCS file: /usr/local/newrepos/myproj/foo.jpg,v
done
Checking in foo.jpg;
/usr/local/newrepos/myproj/foo.jpg,v  <--  foo.jpg
initial revision: 1.1
done
floss$ cvs tag some_random_tag foo.jpg
T foo.jpg
floss$ cvs tag ANOTHER-TAG foo.jpg
T foo.jpg
floss$

さて、リポジトリ内の foo.jpg,v のヘッダ部分を見てみましょう:

head   1.1;
access;
symbols
      ANOTHER-TAG:1.1
      some_random_tag:1.1;
locks; strict;
comment   @# @;
expand	@b@;

最後の expand の行の b を見て下さい。これはこのファイルを -kb つきで add したためにこうなっています。通常のテキストファイルでは、チェックアウトとアップデートの時にキーワードや改行コードの変換が行われるのですが、このファイルではそれが行われない、という意味です。タグは symbols セクションに 1タグ1行で記してあります。最初のリビジョンに2回タグをつけたので、タグは両方とも最初のリビジョンについています。(タグ名に英数字、ハイフン、アンダスコアしか使えない理由もこれで説明できます。タグがコロンやピリオドを含んでいたとしたら、RCS ファイルのこの欄のタグとリビジョンの区切りが曖昧になってしまうからですね。)

RCS Format Always Quotes @ Signs

RCS ファイル中の @ シンボルはフィールド(訳注: フィールドとは各リビジョンの領域のことのようです)の区切りに使用されますので、ファイルのテキスト中やログメッセージに出てくる場合にはクオートする必要があります(そうしないと CVS はそれをフィールドの最後だと誤解してしまいます)。クオートするには を2つ続けます。つまり、CVS は @@ が出てくると、フィールドの終わりという意味ではなく、@ 記号であると解釈します。foo.jpg をコミットしたときのログメッセージは

"added a random image; ask jrandom@red-bean.com why"

でした、これは foo.jpg,v 中ではこのようになります:

1.1
log
@added a random image; ask jrandom@@red-bean.com why
@

ログメッセージにアクセスするときにはj random@@red-bean.com の中の @ 記号は自動的にクオートがはずされます:

floss$ cvs log foo.jpg
RCS file: /usr/local/newrepos/myproj/foo.jpg,v
Working file: foo.jpg
head: 1.1
branch:
locks: strict
access list:
symbolic names:
      ANOTHER-TAG: 1.1
      some_random_tag: 1.1
keyword substitution: b
total revisions: 1;	selected revisions: 1
description:
----------------------------
revision 1.1
date: 1999/06/21 02:56:18;  author: jrandom;  state: Exp;
added a random image; ask jrandom@red-bean.com why
============================================================================

floss$

RCS ファイルを手で編集する時くらいしか気にすることはないです(ほとんどないとは思いますが、全然ないわけではありません)。その場合はリビジョン内容とログメッセージで 記号を2つ重ねて書くことを思い出してください。もし忘れたら、RCS ファイルはむちゃくちゃになり、思いもかけないヘンな動作をするでしょう。

Speaking of hand-editing RCS files, don't be fooled by the permissions in the repository:

floss$ ls -l
total 6
-r--r--r--   1 jrandom   users         410 Jun 20 12:47 README.txt,v
drwxrwxr-x   3 jrandom   users        1024 Jun 20 21:56 a-subdir/
drwxrwxr-x   2 jrandom   users        1024 Jun 20 21:56 b-subdir/
-r--r--r--   1 jrandom   users         937 Jun 20 21:56 foo.jpg,v
-r--r--r--   1 jrandom   users         564 Jun 20 21:11 hello.c,v

floss$

(Unix の ls の出力に詳しくない人へ、左のほうの -r--r--r-- は、そのファイルは読めるけど変更できないよ、という意味です) これらのファイルは誰に対してもリードオンリーのように見えますが、ディレクトリパーミッションのほうを考慮に入れなくてはなりません:

floss$ ls -ld .
drwxrwxr-x   4 jrandom   users        1024 Jun 20 22:16 ./
floss$

myproj/ 自身とそのサブディレクトリは、オーナ(jrandom)とグループ(users)の書き込み権限があります。これはつまり、(jrandom 及び users グループのメンバーなら誰でも、の権限で実行される) CVS はそれらのディレクトリでファイルを作ったり削除したりできるということです、既に存在するファイルを直接編集することができないとしても。CVS は RCS ファイルのコピーを取って編集するので、あなたも一時コピーを好きなように変更し、既存の RCS ファイルをその新しいファイルで置き換えたっていいのです。(なんでファイルがリードオンリーなのかというのは聞かないでください、RCS がスタンドアロンで動く時の動作のしかたに関係のある歴史的経緯があるのです)

ついでに言うと、リポジトリのトップレベルディレクトリのグループが cvs であることを考えれば、それらのファイルのグループが users になっているのは望ましいことではないと思います。リポジトリ内でこのコマンドを実行すれば問題を解決できます:

floss$ cd /usr/local/newrepos
floss$ chgrp -R cvs myproj

リポジトリ内に新しく作成されるファイルのグループについては、Unix の通常のファイル作成時のルールが適用されてしまうので、たまにリポジトリ内のファイルやディレクトリを chgrp または chmod してやる必要があると思います。リポジトリのパーミッションをどう構成するかについて、難しい固定した規則はありません、単にどのプロジェクトで誰が作業しているかによります。


Node:What Happens When You Remove A File, Next:, Previous:RCS Format, Up:Repository Administration

What Happens When You Remove A File

あるプロジェクトからファイルを削除する場合、そのファイルは単になくなるだけではありません。 プロジェクトの古いスナップショットを要求されても、 CVS はそのようなファイルにもちゃんとアクセスできなければならないのです。なくなるかわりに、そのファイルは文字通り Attic(訳注: Attic=屋根裏)に移されます:

floss$ pwd
/home/jrandom/src/myproj
floss$ ls /usr/local/newrepos/myproj/
README.txt,v  a-subdir/     b-subdir/     foo.jpg,v   hello.c,v
floss$ rm foo.jpg
floss$ cvs rm foo.jpg
cvs remove: scheduling 'foo.jpg' for removal
cvs remove: use 'cvs commit' to remove this file permanently
floss$ cvs ci -m "Removed foo.jpg" foo.jpg
Removing foo.jpg;
/usr/local/newrepos/myproj/foo.jpg,v  <--  foo.jpg
new revision: delete; previous revision: 1.1
done
floss$ cd /usr/local/newrepos/myproj/
floss$ ls
Attic/      README.txt,v  a-subdir/     b-subdir/   hello.c,v
floss$ cd Attic
floss$ ls
foo.jpg,v
floss$

プロジェクトのリポジトリ内ディレクトリに Attic/ というサブディレクトリがある場合、それは、少なくとも1ファイルはそのディレクトリから削除された、ということを意味します(つまりプロジェクトで Attic という名前のディレクトリは使ってはいけないということです)。CVS は RCS ファイルを Attic/ に移すだけではなく、そのファイルの新しいリビジョンを、 dead という特別なリビジョン状態でコミットします。Attic/foo.jpg,v から関係個所を示します:

1.2
date   99.06.21.03.38.07;   author jrandom;   state dead;
branches;
next	1.1;

あとでファイルを生き返らせた場合CVS は、過去のある時点でこのファイルが死に、今は再び生きている、ということを記録します。

これはつまり、削除したファイルを元に戻したい場合に Attic/ ディレクトリから出してプロジェクトに戻しただけではダメだということを意味します。そうではなくて、作業コピー内で次のようにしてください:

floss$ pwd
/home/jrandom/src/myproj
floss$ cvs -Q update -p -r 1.1 foo.jpg > foo.jpg
floss$ ls
CVS/       README.txt   a-subdir/   b-subdir/   foo.jpg     hello.c
floss$ cvs add -kb foo.jpg
cvs add: re-adding file foo.jpg (in place of dead revision 1.2)
cvs add: use 'cvs commit' to add this file permanently
floss$ cvs ci -m "revived jpg image" foo.jpg
Checking in foo.jpg;
/usr/local/newrepos/myproj/foo.jpg,v  <-- foo.jpg
new revision: 1.3; previous revision: 1.2
done
floss$ cd /usr/local/newrepos/myproj/
floss$ ls
Attic/	      a-subdir/     foo.jpg,v
README.txt,v  b-subdir/     hello.c,v
floss$ ls Attic/
floss$

RCS 形式について知るべきことはもっとたくさんありますが、CVS のリポジトリの管理者としてはこれで十分だと思います。RCS ファイルを直接いじることは滅多にないことです。普通はせいぜいリポジトリ内のファイルのパーミッションをいじったりする程度です、少なくともわたしが経験してきたところでは。そうは言っても、CVS の動作が変になりはじめたら(稀ではありますが可能な範疇を越えているわけではありませんから)、実際に RCS の中身を見てみて、何が起こっているのか調べたくなるだろうと思います。


Node:The CVSROOT/ Administrative Directory, Next:, Previous:What Happens When You Remove A File, Up:Repository Administration

The CVSROOT/ Administrative Directory

newrepos/CVSROOT/ の中のファイルはどのプロジェクトにも属していませんが、そのリポジトリ内での CVS の動作を制御するために使用されます。それらのファイルを編集するときには、普通のプロジェクトと同様、CVSROOT の作業コピーをチェックアウトするのがよいでしょう:

floss$ cvs co CVSROOT
cvs checkout: Updating CVSROOT
U CVSROOT/checkoutlist
U CVSROOT/commitinfo
U CVSROOT/config
U CVSROOT/cvswrappers
U CVSROOT/editinfo
U CVSROOT/loginfo
U CVSROOT/modules
U CVSROOT/notify
U CVSROOT/rcsinfo
U CVSROOT/taginfo
U CVSROOT/verifymsg
floss$

さて、重要な順(だいたい)にファイルを見ていくことにしましょう。各ファイルの最初には説明のコメントが入っていますので見てみてください(コメントの書きかたはどのファイルも同じです。行頭に # があればコメントで、 CVS はファイルを解釈する時にそのような行を無視します)。チェックアウトした作業コピー中の管理ファイルを変更しても、コミットするまでは CVS の動作に何の影響も及ぼさない、ということを忘れないでください。

セキュリティを気にするのであれば、CVSROOT の Unix レベルのパーミッションを適切に設定して、リポジトリの他のところのパーミッションと違うようにしておきたいかもしれません。そうしておけば CVSROOT にコミットできるユーザをきめ細かく制御できます。あとで出てきますが、CVSROOT のファイルを変更可能にしておくということは、どんな CVS ユーザにも(リモートのユーザにも)リポジトリマシン上で任意のコマンドを実行する権利を与えてしまうことを意味するのです。


Node:The config File, Next:, Up:The CVSROOT/ Administrative Directory

The config File

config ファイルでは、グローバルな動作を決定するパラメータを設定します。形式は非常に厳しくて

PARAMETER=VALUE
(etc)

余分なスペースは許されません。例えば、ありそうな config ファイルはこんな感じです:

SystemAuth=yes
TopLevelAdmin=no
PreservePermissions=no

(ここにないエントリは no と設定するのと等価です)

SystemAuth パラメータは、指定されたユーザ名が CVSROOT/passwd ファイルに見つからなかった場合に、システムの passwd ファイルを見に行くかどうかを決定します。システムのセキュリティを守るため、ディストリビューションではno に設定して出荷してあります。

TopLevelAdmin は作業コピーをチェックアウトするときに兄弟 CVS/ ディレクトリを作るかどうかを決定します。兄弟 CVS/ ディレクトリは作業ディレクトリ内ではなく、作業ディレクトリの隣に作成されます。同じリポジトリから多数のいろいろなプロジェクトをチェックアウトしたいと思う場合はこれをオンにすると便利です。そうでなければこれはオフのままにしておいたほうがいいでしょう、思いがけないところに余分な CVS/ ディレクトリができて混乱しますからね。

PreservePermissions はファイルのパーミッションを保存し、同様のメタデータをリビジョン履歴に記録します。これはある種の隠れ機能で、たぶん詳しく説明する価値もありません。もし興味があるなら Cederqvist の Special Files ノードを見てください(ノードというのは、 Texinfo で、Info ドキュメントの中の特定の場所を示す言葉です。Info を読んでいるときにあるノードに行きたければ、g をタイプしたあとにそのノードの名前を打てば、そのドキュメント中のどこからでも飛べます)

LockDir はほとんど使われない機能です。特殊な状況では、パーミッションの問題で、CVS のロックファイルをプロジェクトのサブディレクトリ内に直接作るのではなくもっと別のところに作るようにしたいかもしれません。ロックファイルというのは、同じリポジトリディレクトリ上で同時に複数の操作が行われたとき、CVS がやりそこねるのを防いでいるものです。普通気にする必要はありませんが、ときどきロックファイルを作れないことが原因でアップデートやチェックアウトができなくて困るユーザがでてくるかもしれません(読むだけの操作においても、CVS はロックファイルを作ります。他の CVS が書き込んでいる最中に読んでしまうような状況を避けるためです)。通常このような問題を解決するにはリポジトリのパーミッションを変更するのが普通ですが、それができないようなら LockDir パラメータが便利でしょう。

現在のところパラメータはこれだけですが、CVS の将来のバージョンでは新しいものが追加されるでしょう、Cederqvist やディストリビューション中の config ファイル自体をいつもチェックしておいてくださいね。


Node:The modules File, Next:, Previous:The config File, Up:The CVSROOT/ Administrative Directory

The modules File

modules ファイルではリポジトリ内のプロジェクトの別名や alternate grouping を定義します。module の行は基本的に次の形式です:

MODULE_NAME   DIRECTORY_IN_REPOSITORY

例えば、

mp    myproj
asub  myproj/a-subdir

(右側で指定するパスはリポジトリのトップからの相対パスです。) 開発者がプロジェクトやプロジェクトの一部分をチェックアウトする時の別名を指定しています:

floss$ cvs co mp
cvs checkout: Updating mp
U mp/README.txt
U mp/foo.jpg
U mp/hello.c
cvs checkout: Updating mp/a-subdir
U mp/a-subdir/whatever.c
cvs checkout: Updating mp/a-subdir/subsubdir
U mp/a-subdir/subsubdir/fish.c
cvs checkout: Updating mp/b-subdir
U mp/b-subdir/random.c

あるいは

floss$ cvs -d /usr/local/newrepos/ co asub
cvs checkout: Updating asub
U asub/whatever.c
cvs checkout: Updating asub/subsubdir
U asub/subsubdir/fish.c

両方の場合で、モジュールの名前がどのように作業コピーのディレクトリ名になっているかを見てください。asub の場合、中間に myproj/ ディレクトリができないですが、代りにトップレベルに asub ができました。リポジトリの myproj/a-subdir からできたにもかかわらずです。それらの作業コピー内では、アップデート、コミット、その他の CVS コマンドがすべて正常に動きます。普通と違うのは名前だけです。

ディレクトリ名のあとにファイル名をつけることによって、リポジトリディレクトリ内の指定されたファイルで構成されたモジュールを定義することができます。例えば

readme  myproj  README.txt

no-readme  myproj  hello.c  foo.jpg

とすると、それぞれ、次のようなチェックアウトができるようになります:

floss$ cvs -q co readme
U readme/README.txt
floss$ cvs -q co no-readme
U no-readme/hello.c
U no-readme/foo.jpg
floss$

-a (alias という意味) を使えば複数のリポジトリディレクトリを含むモジュールを定義することができますが、チェックアウトするともとの名前のディレクトリができるので注意してください。たとえば、この行を書くと

twoproj  -a  myproj  yourproj

下のようになります(myproj/ と yourproj/ がリポジトリに存在するとします):

floss$ cvs co twoproj
U myproj/README.txt
U myproj/foo.jpg
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
U yourproj/README
U yourproj/foo.c
U yourproj/some-subdir/file1.c
U yourproj/some-subdir/file2.c
U yourproj/some-subdir/another-subdir/blah.c

twoproj はプロジェクトを両方持ってくるのに便利な名前ではありますが、作業コピーの名前には使われません。(There is no requirement that alias modules refer to multiple directories, by the way; we could have omitted twoproj, in which case myproj would still have been checked out under the name myproj.)

前にアンパサンドをつけることによって、別のモジュールを参照することもできます:

mp    myproj
asub  myproj/a-subdir
twoproj -a myproj yourproj
tp  &twoproj

tp をチェックアウトすると、twoproj のチェックアウトと完全に同一の結果が得られます。

モジュールを処理する仕掛けはまだいくつかありますが、その大半は今述べたものより使う機会が少ないです。それらについて知りたければ、Cederqvist の modules ノードを参照してください。


Node:The commitinfo And loginfo And rcsinfo Files, Next:, Previous:The modules File, Up:The CVSROOT/ Administrative Directory

The commitinfo And loginfo And rcsinfo Files

他の管理ファイルは、大半がコミット処理のさまざまな部分に programmatic な フックをしかけるためのものです(たとえばコミットを許可する前にログメッセージやファイルの状態を検証するようにもできますし、リポジトリの特定ディレクトリへのコミットが発生したら開発者グループに知らせるようにもできます)。

それらのファイルでは共通の文法を用います。各行はこのような形式です:

REGULAR_EXPRESSION    PROGRAM_TO_RUN

この正規表現は、コミットが起こるディレクトリに対してテストされます(リポジトリのトップディレクトリからの相対パス名)。マッチすれば指定したプログラムが実行されます。コミット処理中の各ファイル名がプログラムに渡されます; プログラムはそれらのファイル名を使って何でも好きなことができます、ファイルをオープンして内容を調べたりすることも含めてです。プログラムの終了コードがノンゼロの場合、コミットは実行されません。

(Regular expressions とは文字列のクラスを簡潔に記述するシステムです。正規表現をよく知らないかたは、概略を手短に示しますので、これでとりあえずなんとかなると思います: foo は文字列 foo を含むファイル名にマッチします。foo.*bar は、foo を含み、任意の数の文字が続き、そのあとに文字列 bar が続くファイル名にマッチします。通常の文字列はそれ自体にマッチしますが、.* は特殊だからです。. は任意の文字にマッチし、* は前の文字が任意の数(0含む)だけ続いたものにマッチします。^$ はそれぞれ、文字列の最初と最後にマッチします: ですから、^foo.*bar.*baz$fooで始まり、中ほどのどこかに bar を含んでおり、そして baz で終わる文字列とマッチします。ここではこのくらいにしておきましょう、この概略説明は正規表現の文法全てのうち、かなり省略されたサブセットです)

commitinfo ファイルでは全てのコミット時に実行したい汎用的フックを書きます。commitinfo の行の例をいくつか示します:

^a-subdir*     /usr/local/bin/check-asubdir.sh
ou             /usr/local/bin/validate-project.pl

myproj/a-subdir へコミットすると最初の行にマッチするので、 check-asubdir.sh スクリプトが実行されます。文字列 ou を含む名前のプロジェクト(実際のリポジトリディレクトリ名であってモジュール名である必要はありませ)へコミットすると、 validate-project.pl スクリプトが実行されます。ただし、既に a-subdir の行にマッチしたコミットは除きます。

正規表現の個所に DEFAULT あるいは ALL という語を使うことができます。DEFAULT 行(もし2つ以上ある場合は最初の DEFAULT 行)はどの正規表現にもマッチしなかった場合に実行され、各 ALL 行はマッチする他の行に加えて実行されます。

プログラムに渡されるファイル名は RCS ファイルを指すものではありません、コミットされつつある作業コピーと同じ内容の通常のファイルを指します。CVS はそのファイルをリポジトリ内に一時的に置き、プログラムはリポジトリの中でそのファイルを使うことができます、あまり普通ではありませんね。

loginfo ファイルは commitinfo と同じですが、ファイルの内容ではなくログメッセージに作用します。loginfo ファイルの左側は正規表現で、DEFAULT や ALL 行も書けます。右側のプログラムは標準入力からログメッセージを受け取ります。その入力を使って好きなことができます。

右側にあるプログラムは任意の数のコマンドライン引数を取ることもできます。引数のうちのひとつは特殊な % コードを書いてもよく、それは CVS によって実行時に次のように展開されます:

%s    ------>      コミットされつつあるファイルの名前
%V    ------>      コミット前のリビジョン番号
%v    ------>      コミット後のリビジョン番号 

展開された文字列の最初はリポジトリサブディレクトリ(リポジトリのトップディレクトリからの相対パス)で、そのあとに各ファイルの情報が続きます。たとえば、コミットされたファイルが foo, bar, baz で、それらがすべて myproj/a-subdir の中にあった場合、%s は次のように展開されます。

myproj/a-subdir  foo  bar  baz

そして %V は古いリビジョン番号に展開されます

myproj/a-subdir  1.7  1.134  1.12

and %v their new revision numbers:

myproj/a-subdir  1.8  1.135  1.13

% 記号に続けて中括弧でくくることによって、% 表現を結合することができます。コミット時に、各ファイルごとの対応する情報をカンマで区切ったサブリストに展開されます。例えば、 %{sv} は次のように展開されます:

myproj/a-subdir  foo,1.8  bar,1.135  baz,1.13

%{sVv} だとこのように展開されます:

myproj/a-subdir  foo,1.7,1.8  bar,1.134,1.135  baz,1.12,1.13

(カンマとピリオドを見間違えないよう、注意して見て下さい)

loginfo ファイルのサンプルを示します:

^myproj$   /usr/local/newrepos/CVSROOT/log.pl -m myproj-devel@foobar.com %s
ou         /usr/local/bin/ou-notify.pl  %{sv}
DEFAULT    /usr/local/bin/default-notify.pl  %{sVv}

最初の行では、リポジトリの myproj サブディレクトリへのコミットが発生すると log.pl を起動するよう設定しています。log.pl の引数として、電子メールアドレス(log.pl はそのアドレスへログメッセージを送信すします)、リポジトリ、コミットされたファイルを渡します。

2行目では、リポジトリの文字列 ou を含むサブディレクトリへのコミットが発生すると ou-notify.pl (架空のものです)を起動するよう設定しています。リポジトリのあとに、ファイル名と新しいリビジョン番号をリストで渡します。

3行目では、上記2行にマッチしないコミットが発生した時に default-notify.pl を起動するよう設定しています。渡せる情報は渡しています(リポジトリパス、ファイル名、古いリビジョン番号、新しいリビジョン番号)。


Node:The verifymsg And rcsinfo Files, Next:, Previous:The commitinfo And loginfo And rcsinfo Files, Up:The CVSROOT/ Administrative Directory

The verifymsg And rcsinfo Files

ログメッセージがある一定の標準に従っているかどうか自動的に検証して、従っていなければコミットを中止するようなプログラムが欲しいこともあると思います。これは、verifymsg を使えば実現できます。補助に rcsinfo も使うかもしれません。

verifymsg ファイルは例によって正規表現とプログラムを結び付けるものです。プログラムは標準入力からログメッセージを受け取って、そのログメッセージがある基準に沿っているかどうかチェックして、ゼロまたはノンゼロの終了コードで終了します。ノンゼロで終了した場合はそのコミットは失敗します。

一方、rcsinfo の左側はいつもの正規表現ですが、右側はプログラムではなくてテンプレートファイルを指定します。テンプレートファイルというのはこのようなものです

Condition:
Fix:
Comments:

こんな感じの、有効なログメッセージの形式を満たすために記入する必要のあるフィールドの集合です。開発者みんなが -m オプションを使ってコミットする場合にはあまり有用ではありませんが、多くはそうではないでしょう。 -m で指定するかわりに

floss$ cvs commit

を実行して、CVS が自動的にテキストエディタ(EDITOR 環境変数で指定してあるやつです)を起動するのを待って、そこにログメッセージを記入し、セーブして終了します。そのあと、CVS はコミット処理を続けます。

このシナリオに沿う場合、rcsinfo テンプレートはユーザがタイプする前にエディタ中に挿入されますので、記入すべきフィールドが表示されます。ユーザがコミットした時点で verifymsg 中で指定された適切なプログラムが起動されます。ログメッセージがフォーマットに沿っているかチェックし、結果が終了コードに反映されます(ゼロの場合成功)。

検証プログラムに便利なように、verifymsg のプログラムの最後の引数として、テンプレートへのパスが rcsinfo ファイルから追加されます。なので、お望みならテンプレートをもとに検証処理ができます。

リモートマシンへ作業コピーをチェックアウトした場合には、対応する rcsinfo テンプレートファイルもクライアントに送信されます(作業コピーの CVS/ サブディレクトリに保存されます)。しかしこれは、チェックアウトより後にサーバ上の rcsinfo ファイルが変更された場合、クライアント側からはその変更が再度チェックアウトしない限りわからないということになるので注意して下さい(update しただけではダメなんです)。

また、verifymsg ファイルでは ALL キーワードがサポートされていないことに注意して下さい(DEFAULT はサポートされているのですが)。デフォルトの検証スクリプトを、サブディレクトリ向けのスクリプトでオーバライドしやすくなっているのです。


Node:The taginfo File, Next:, Previous:The verifymsg And rcsinfo Files, Up:The CVSROOT/ Administrative Directory

The taginfo File

loginfo がログメッセージに対して行うようなことを、taginfo はタグに対して行います。taginfo の左側は例によって正規表現で、右側はプログラムです。cvs tag の起動時、各プログラムには自動的に引数が渡されます。順序は次の通り:

arg 1:          タグ名
arg 2:          オペレーション種別 ("add" => tag, "mov" => tag -F, "del" => tag -d)
arg 3:          リポジトリ
arg 4, 5, etc:  ファイルリビジョン [ファイルリビジョン ...]

プログラムがノンゼロを返した場合、tag コマンドは中断されます。

tag コマンドの -F オプションについてまだ説明していませんでしたね、でも上に書いてあることでわかると思います: タグをあるリビジョンから別のリビジョンへ移動します。例えば、Known_Working というタグがあるファイルのリビジョン1.7につけられていて、それをリビジョン1.11につけなおしたい場合には、こうします

cvs tag -r 1.11 -F Known_Working foo.c

こうすると1.7(またはそのファイルでそのタグがつけられていたところ)からはタグが削除され、1.11につけられます。


Node:The cvswrappers File, Next:, Previous:The taginfo File, Up:The CVSROOT/ Administrative Directory

The cvswrappers File

cvswrappers ファイル(くどい名前ですよね)は、どのファイルをバイナリとして扱うかをファイル名によって指定するものです。CVS は、たとえば .jpg ファイルをすべて JPG 画像データと見なしたりはしないので、JPG ファイルを追加するときに自動的に -kb オプションをつけたりはしてくれません。ですが、プロジェクトによっては JPG ファイルはすべてバイナリだと指定できればとても便利です。次のような行を cvswrappers ファイルに書けばそのようなことができます:

*.jpg -k 'b'

b が離れており、クオートされています。RCS の展開モードキーワードは b だけではないからです。o を指定すると $ は展開するが改行の変換は行わないという意味になります。しかし、 b は最も普通のパラメータではあります。

wrappers ファイルから指定できるモードはあといくつかありますが、滅多にない状況のためのものなので、ここに書くほどの価値はありません(意訳:著者はそれらを使ったことがありません)。好奇心旺盛なアナタは Cederqvist の Wrappers ノードを参照してくださいね。


Node:The editinfo File, Next:, Previous:The cvswrappers File, Up:The CVSROOT/ Administrative Directory

The editinfo File

このファイルはもう obsolete です、ディストリビューションにただ含まれているだけです。無視して下さい。


Node:The notify File, Next:, Previous:The editinfo File, Up:The CVSROOT/ Administrative Directory

The notify File

このファイルは CVS の watch 機能とともに用いられるものです。 watch 機能については Advanced CVS を参照のこと。watch (便利ですが必須の機能ではありません)というのが何かわからなければ何を書いても意味ありませんので、このファイルと watch について詳しくは Advanced CVS をお読み下さい。


Node:The checkoutlist File, Previous:The notify File, Up:The CVSROOT/ Administrative Directory

The checkoutlist File

CVSROOT/ の中を見ると、ファイルの作業コピーが RCS リビジョンファイルと並んでいるのが見えると思います:

floss$ ls /usr/local/newrepos/CVSROOT
checkoutlist     config,v       history     notify     taginfo
checkoutlist,v   cvswrappers    loginfo     notify,v   taginfo,v
commitinfo       cvswrappers,v  loginfo,v   passwd     verifymsg
commitinfo,v     editinfo       modules     rcsinfo    verifymsg,v
config           editinfo,v     modules,v   rcsinfo,v

floss$

CVS がその動作を決める際にはその作業バージョンだけを見て、RCS ファイルは見ません。従って、CVSROOT/ の作業コピーをコミットすると(別のマシンにチェックアウトされていようとも)、CVS は自動的にリポジトリ内のファイルを更新します。コミットの最後に次のようなメッセージが表示されるので、何が起きたかわかると思います:

floss$ cvs ci -m "added mp and asub modules" modules
Checking in modules;
/usr/local/newrepos/CVSROOT/modules,v  <--  modules
new revision: 1.2; previous revision: 1.1
done
cvs commit: Rebuilding administrative file database

CVS は標準の管理ファイルに何があったか知っていて必要に応じて CVSROOT/ を再構築します。CVSROOT/ 中にカスタムファイルを置きたければ(プログラムや rcsinfo テンプレートファイル等)、他のファイルと同様に扱うよう、CVS に明示的に伝えなければなりません。

これを伝えるために、checkoutlist ファイルがあります。今まで見てきたファイルとは違うフォーマットになります。

FILENAME     ERROR_MESSAGE_IF_FILE_CANNOT_BE_CHECKED_OUT

例えば、

log.pl           unable to check out / update log.pl in CVSROOT

bugfix.tmpl      unable to check out / update bugfix.tmpl in CVSROOT

CVSROOT 中のいくつかのファイルは、伝統的にリビジョン管理下には置かないことになっています。1つは history で、これはリポジトリ中の全ての操作の a running record を保持しており、cvs history コマンドが使用します(このコマンドは指定されたファイルまたはプロジェクトディレクトリについて、チェックアウト、アップデート、タグ付けの状況の一覧を表示します)。 偶然 history ファイルを削除してしまったら、CVS はログを取るのをやめます。

注意: history ファイルがパーミッション問題の原因になることがありますが、解決するにはファイルをワールドライタブルにするか、ただ削除するかしてください。

リビジョン管理されていないファイルには他に passwd ファイルがあります。それがネットワーク越しにチェックアウトされたとしたら、パスワードの信頼性を損うからです(たとえ暗号化されていようとも)。passwd を checkoutlist に追加するかどうかは、守るべきキュリティの状況を鑑みて決定してください。デフォルトでは checkoutlist に入っていません。

CVSROOT/ ディレクトリについて最後に2つほど: すごいミスをして、壊れた管理ファイルをコミットしたあげくどんなコミットも全く実行できなくなったとしましょう。ありうる話です。もしそうなったら、管理ファイルを直してコミットしなおそうとしても、当然できないわけです。これを解決するには、管理ファイルのリポジトリ内の作業コピーを手で編集して直します。直せるまで、リポジトリ全体がアクセス不可の状態のままになります。

また、セキュリティを確保するため、CVSROOT/ ディレクトリは信頼できるユーザのみが書き込めることを確認してください(信頼できるというのは、その人にパスワードの信頼性を損わないだけの能力があってその努力ができることを信頼できる、という意味です)。*info ファイルは任意のプログラムを実行できる権限を与えてしまうので、CVSROOT/ ディレクトリ中のファイルをコミットまたは編集できる人はすなわち、基本的にはシステムのどんなコマンドでも実行できるということです。このことは心に留めておくべきです。


Node:Commit Emails, Next:, Previous:The CVSROOT/ Administrative Directory, Up:Repository Administration

Commit Emails

loginfo ファイルはコミットメール、すなわちコミットが発生した時にプロジェクトで作業している全員に送信される自動電子メールを設定できます。(commitinfo ではなく loginfo で設定するというのは直観に反するように見えるかもしれませんが、ポイントはメールにログメッセージを含めたいというところです) 配送プログラム(CVS のソースディストリビューションに入っている contrib/log.pl)はシステム上のどこにインストールしてもかまいません。わたしはリポジトリの CVSROOT/ サブディレクトリ内に置くのを週間にしていますが、それはただ好みの問題です。

システム上でうまく動くようにするため、ほんの少しだけ log.pl を編集する必要があるかもしれません、おそらく Perl インタプリタを指す最初の行と、あとは多分この行

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

をお好みのメーラ(Mailという名前かもしれないし違うかもしれない)を起動するように直すくらいでしょう。好きなように設定し終わったら、 loginfo ファイルに下記と同じ行を書き加えてください:

listerizer CVSROOT/log.pl %s -f CVSROOT/commitlog -m listerizer@red-bean.com
RoadMail   CVSROOT/log.pl %s -f CVSROOT/commitlog -m roadmail@red-bean.com
bk/*score  CVSROOT/log.pl %s -f CVSROOT/commitlog -m \
                                        bkscore-devel@red-bean.com

%s はコミットされたファイルの名前に展開されます。 log.pl の -f オプションはファイル名を取り、そのファイルにログメッセージが追加されます(ですから CVSROOT/commitlog は永久に成長するログメッセージのファイルになります)。-m フラグはメールアドレスを取り、 log.pl はそのアドレスへコミットのメッセージを送信します。そこに書くアドレスは通常メーリングリストですが、ひとつの log.pl コマンドラインに -m オプションを必要なだけ並べることもできます。


Node:Finding Out More, Previous:Commit Emails, Up:Repository Administration

Finding Out More

この章で CVS のインストールと管理を完全に紹介しようと思ったのですが、あまりにも使うことがなくて言及する価値がない事柄や、既に Cederqvist マニュアルで十分説明されているようなことを積み残してしまいました。後者に属することとしては、他のリモートアクセス方法、RSH/SSH, kserver (ケルベロス4), GSSAPI(ケルベロス5等を含む) の設定が挙げられます。問題のユーザがリポジトリマシンへ RSH か SSH でログインできることを確認する以外に何も特別なことをする必要がない、ということだけは言っておくべきでしょう。それができて、クライアントにもサーバにも CVS がインストールしてあって、サーバマシンで直接リポジトリを使える正しい権限があれば、:ext: メソッドを経由してリモートからリポジトリにアクセスできます。

CVS の機能のうち特殊ないくつかは、のちほどの章で説明してありますので、その機能が明らかに便利だとわかる文脈のなかで紹介します。一般的な CVS のトラブルシューティングのコツについては Tips And Troubleshooting に書いてあります。 Cederqvist マニュアル全部を読む必要はありませんが、親しんでおくべきです。貴重なリファレンスツールですから。もし何か理由があってあなたのマシンで Info が動かなくて、マニュアルをプリントアウトするのもダメなら、オンラインで http://durak.org/cvswebsites/doc/ または http://www.loria.fr/~molli/cvs/doc/cvs_toc.html で閲覧できます。


Node:Advanced CVS, Next:, Previous:Repository Administration, Up:Top

Advanced CVS

さて、これまでに CVS の使いかたの基礎の考えかたとリポジトリ管理について見てきました。今度は CVS を開発プロセス全体に導入するやりかたを見ていきたいと思います。基本的な CVS の作業サイクル(checkout, update, commit, update, commit, …)は An Overview of CVS で示しました。この章ではそのサイクルを改善し、開発者間のコミュニケーションの補助・プロジェクトの活動と履歴の概要の提供・開発の別ブランチの分離と統一・よくある作業の自動化に対し、CVS をどう役立てていくかについて議論します。テクニックの説明のなかで新しい CVS コマンドを紹介していることもありますが、多くは既に知っているコマンドのより良い使い方を説明しているだけです。


Node:Watches (CVS As Telephone), Next:, Up:Advanced CVS

Watches (CVS As Telephone)

プロジェクトを CVS で使用する場合の主な利点は、CVS がコミュニケーション機器として、また記録係としての機能を果たすことです。この節ではプロジェクトの参加者に、プロジェクト内で何が起こっているかを通知するのに CVS をどう使えばよいかということを集中的に取り上げます。 **************** As is true with other aspects of CVS, these features reward cooperation. **************** 参加者は通知してもらいたいに違いありません; コミュニケーション機能を使わないなら、CVS はそれについてなにもしないこともできます。


Node:How Watches Work, Next:, Up:Watches (CVS As Telephone)

How Watches Work

CVS はデフォルトでは各作業コピーを独立したサンドボックスとして扱います。あなたが変更をコミットするまでは、あなたが作業コピーで何をしているのか、誰にもわかりません。そしてあなたも、他の人が自分の作業コピーで何をしているのかなんてわかりません、通常のコミュニケーション方法をとる以外ないのです。たとえば廊下で「ねえ、いま parse.c いじろうと思うんだけどさ、コンフリクトすんのヤだから編集するときは教えてね!」などと叫んだりするしか。

このような非公式なやりかたは、誰が何を担当しているか全般について皆が知っているようなプロジェクトならうまくいきます。が、このやりかたでは大人数の開発者がコードベースの全てをいじっていて、かつコンフリクトを避けたいような場合にはうまくいかないでしょう。このような場合、メンバーが地理的に分散しているので、お互いの分担責任領域に頻繁に踏み込む必要があっても廊下で叫び合うわけにはいきません。

CVS には監視(watch)と呼ばれる機能があって、ある時刻に誰がどのファイルをいじっているかをお互いに通知し合うことができます。ある開発者が、あるファイルを監視設定すると、他のだれかがそのファイルをいじり始めるとその開発者に CVS から通知が来ます。その通知は普通メールで送信されますが、他の方法に設定することもできます。

監視を使うには、リポジトリの管理エリア内のファイルを1つか2つ変更しなければなりません。また、開発者はチェックアウト/アップデート/コミットのサイクルに余分な手順を加えなければなりません。リポジトリ側の変更はごく簡単なものです: CVSROOT/notify ファイルを編集して、CVS がどのように通知すればいいかを設定します。CVSROOT/users ファイルに電子メールアドレスの行を追加します。

作業コピー側では、開発者は CVS に対し、どのファイルを監視したいかを CVS に指示し、他の誰かがそれらのファイルを編集し始めたら CVSが通知を送れるようにします。また、ファイルを編集し始めた時やし終わった時には CVS にそれを知らせなければなりません。CVS はそれを受けて、そのファイルを監視している他の人に通知します。これらの手順を実現するコマンドを次に示します:

watchコマンドは通常の CVS コマンドのパターンとは違い、もう一段階サブコマンドを必要とします。cvs watch add..., cvs watch remove...,などなど。

以下の例で、リポジトリ側で監視を有効にするやりかたと、開発者側での監視の使いかたを見ていきます。2人のユーザ、jrandom と qsmith を例にとります。同じプロジェクトの作業コピーをそれぞれが持っていて、それらは別のマシン上にあるかもしれません。全ての例において、 $CVSROOT 環境変数が設定してあると仮定しますので、cvs コマンドに -d <REPOS> をつける必要はありません。


Node:Enabling Watches In The Repository, Next:, Previous:How Watches Work, Up:Watches (CVS As Telephone)

Enabling Watches In The Repository

まずは CVSROOT/notify ファイルを編集して、電子メールでの通知を有効にします。開発者の1人がやってもいいですし、もし開発者にリポジトリの管理ファイルを変更する権限がなければ、管理者がやってもいいです。どちらにしろ、まずは管理領域をチェックアウトして、それから notify ファイルを編集します:

floss$ cvs -q co CVSROOT
U CVSROOT/checkoutlist
U CVSROOT/commitinfo
U CVSROOT/config
U CVSROOT/cvswrappers
U CVSROOT/editinfo
U CVSROOT/loginfo
U CVSROOT/modules
U CVSROOT/notify
U CVSROOT/rcsinfo
U CVSROOT/taginfo
U CVSROOT/verifymsg
floss$ cd CVSROOT
floss$ emacs notify
...

notify ファイルは、初回編集時にはこのようになっています:

# The "notify" file controls where notifications from watches set by
# "cvs watch add" or "cvs edit" are sent. The first entry on a line is
# a regular expression which is tested against the directory that the
# change is being made to, relative to the $CVSROOT. If it matches,
# then the remainder of the line is a filter program that should contain
# one occurrence of %s for the user to notify, and information on its
# standard input.
#
# "ALL" or "DEFAULT" can be used in place of the regular expression.
#
# For example:
# ALL mail %s -s "CVS notification"

最後の行の冒頭の#を取ってコメントをはずすだけです。notify ファイルは他の管理ファイルと同様、ディレクトリ名に対する正規表現を書ける柔軟なインタフェースを提供していますが、実際はその柔軟さを活用することはほとんどありません。リポジトリの特定部分にマッチする正規表現を複数行で書くことがあるとすれば、各プロジェクト用に別々の通知機構を使いたいときだけでしょう。しかし、通常の電子メールは完璧に良い通知機構なので、大抵はそれを使います。

電子メール通知を指定するには、標準的な Unix マシンではこの行

ALL mail %s -s "CVS notification"

を書けば動きます。このコマンドは通知をサブジェクトCVS notificationの電子メールで送ります(例によって、特殊表現 ALL は全てのディレクトリにマッチします)。この行のコメントをはずしたら、notify ファイルをコミットしてリポジトリに変更を知らせましょう:

floss$ cvs ci -m "turned on watch notification"
cvs commit: Examining .
Checking in notify;
/usr/local/newrepos/CVSROOT/notify,v  <--  notify
new revision: 1.2; previous revision: 1.1
done
cvs commit: Rebuilding administrative file database
floss$

notify ファイルをこのように編集しさえすればリポジトリの監視ができます。しかしながら、リモートからプロジェクトに参加している開発者がいる場合、CVSROOT/users ファイルも編集する必要があります。users ファイルの目的は、外部のメールアドレスを持っているユーザに対し、電子メール通知をどこへ送信すればよいかを CVS に指示することです。users ファイルの各行の形式は:

CVS_USERNAME:EMAIL_ADDRESS

です。例えば、

qsmith:quentinsmith@farawayplace.com

行の冒頭の CVS ユーザ名というのは、そのユーザ名が CVSROOT/passwd に存在し、かつ pserver アクセスを使っている場合はそのユーザに、そうでなければ CVS を実行している人のサーバ側のシステムユーザ名に対応します。コロンのあとにはそのユーザに通知を送る場合の外部のメールアドレスを書きます。これを書いている時点では、users ファイルは CVS ディストリビューション中に存在しません。管理ファイルなので、普通のやりかたでファイルを作成して cvs add して commit してもらって、それに加え CVSROOT/checkoutlist に書き足してください。こうするとリポジトリ内のチェックアウトコピーが常に保守されるようになります。

これを行うセッションの実地例をお見せしましょう:

floss$ emacs checkoutlist
  ... (add the line for the users file) ...
floss$ emacs users
  ... (add the line for qsmith) ...
floss$ cvs add users
floss$ cvs ci -m "added users to checkoutlist, qsmith to users"
cvs commit: Examining .
Checking in checkoutlist;
/usr/local/newrepos/CVSROOT/checkoutlist,v  <--  checkoutlist
new revision: 1.2; previous revision: 1.1
done
Checking in users;
/usr/local/newrepos/CVSROOT/users,v  <--  users
new revision: 1.2; previous revision: 1.1
done
cvs commit: Rebuilding administrative file database
floss$

CVSROOT/users にメールアドレスを拡張形式で書くこともできますが、空白文字には注意して、全てクオートしてください。例えば、次のは動きますが:

qsmith:"Quentin Q. Smith <quentinsmith@farawayplace.com>"

または

qsmith:'Quentin Q. Smith <quentinsmith@farawayplace.com>'

しかし、これは動きません:

qsmith:"Quentin Q. Smith" <quentinsmith@farawayplace.com>

動くかどうかわからないと思う時は、 notify ファイルに指定するコマンドラインを手で動かしてテストしてください。下記の %s

mail %s -s "CVS notification"

users ファイルのコロンのあとに書いたのと同じものに置き換えてください。コマンドプロンプトでやってみて動いたら、users ファイルでも動きます。

これらが終わった時点で checkoutlist ファイルはこのようになっています:

# The "checkoutlist" file is used to support additional version controlled
# administrative files in $CVSROOT/CVSROOT, such as template files.
#
# The first entry on a line is a filename which will be checked out from
# the corresponding RCS file in the $CVSROOT/CVSROOT directory.
# The remainder of the line is an error message to use if the file cannot
# be checked out.
#
# File format:
#
#       [<whitespace>]<filename><whitespace><error message><end-of-line>
#
# comment lines begin with '#'

users   Unable to check out 'users' file in CVSROOT.

users ファイルはこのようになっていると思います:

qsmith:quentinsmith@farawayplace.com

さて、これでリポジトリの監視用設定はできました。次に開発者が作業コピーでしなければならないことを見ていきましょう。


Node:Using Watches In Development, Next:, Previous:Enabling Watches In The Repository, Up:Watches (CVS As Telephone)

Using Watches In Development

開発者はまず、作業コピーをチェックアウトして、次にプロジェクト中の1ファイルの監視者リストに自分を加えます:

floss$ whoami
jrandom
floss$ cvs -q co myproj
U myproj/README.txt
U myproj/foo.gif
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
floss$ cd myproj
floss$ cvs watch add hello.c
floss$

最後のコマンド cvs watch add hello.c で、誰かが hello.c で作業しはじめたら jrandom に通知するよう CVS に対し指示します(つまり、 jrandom を hello.c の監視リストに加えます)。あるファイルが編集され次第 CVS が通知を送るするためには、編集しようとするユーザがそのファイルに対してまず cvs edit を実行する必要があります。CVS にとって、編集が始まったことを知る方法は他にありません。チェックアウトが済んだら通常 CVS は次のアップデートかコミットまで起動されることはありませんから、そのときにはもうそのファイルは編集されているかもしれません:

paste$ whoami
qsmith
paste$ cvs -q co myproj
U myproj/README.txt
U myproj/foo.gif
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
paste$ cd myproj
paste$ cvs edit hello.c
paste$ emacs hello.c
...

qsmith が cvs edit hello.c を実行したとき、CVS は hello.c の監視リストを見て jrandom がその中にいることを知り、jrandom に対して qsmith がそのファイルを編集しはじめたことをメールを送って知らせます。メールは qsmith から来たようなかたちになります:

From: qsmith
Subject: CVS notification
To: jrandom
Date: Sat, 17 Jul 1999 22:14:43 -0500

myproj hello.c
--
Triggered edit watch on /usr/local/newrepos/myproj
By qsmith

加えて、qsmith (またはどの人でも) が hello.c の新しいリビジョンをコミッ
トするたびに、jrandom はメールを受けとります:

myproj hello.c
--
Triggered commit watch on /usr/local/newrepos/myproj
By qsmith

これらのメールを受けとったら、jrandom は hello.c をアップデートして qsmith が何をしたか見てみたいでしょうし、qsmith にそのファイルをいじった理由をきくためにメールするでしょう。qsmith は cvs edit を実行するのを忘れないように強制はされなかったことに注意してください。おそらく、自分が何をしようとするかを jrandom に知ってもらいたかったから実行したのです(どちらにしろ、cvs edit するのを忘れていたとしても、コミットすれば通知が起こります)。cvs edit を使うのは、あるファイルをいじりはじめる前にそのファイルを監視している人たちにそれを知らせるのが目的です。監視している人たちが、コンフリクトが起こるかもしれないと思った場合には連絡できますから、時間を無駄にせずに済むでしょう。

あるファイルに対し cvs edit を実行した人は全てそのファイルの監視リストに加えて欲しいと思っている(少なくとも一時的に、誰か他の人がそのファイルを編集しはじめた場合とかには)、と CVS は仮定します。qsmith が cvs edit を実行した場合、彼は hello.c の監視者になります。彼と jrandom は第三者が cvs edit を実行した場合(あるいはコミットした場合)、通知を受けとります。

しかし、CVS はそのファイルを編集中の人は編集している間だけ監視者リストに居たいだろうと仮定します。そのようなユーザは編集が済んだら監視者リストからはずされます。そのファイルをずっと監視したい場合は cvs watch add を実行する必要があります。また、デフォルトでコミット時には編集が終わったと仮定します(とりあえず次回までは)。

あるファイルに対し cvs edit を走らせただけで監視者リストに入った人は、一時監視者(temporary watcher)と呼ばれ、変更をコミットし次第監視者リストからはずされます。そのファイルを再度編集したい場合は、 cvs edit を再実行する必要があります。

変更作業が終わるまで何回コミットする必要があるかということは CVS にはわかりませんので、最初のコミットで編集が終わると仮定する以外に良い方法はありません。この仮定は1回限り(one-off)の変更の場合、つまりファイルの 1個所だけ直してコミットするなら、うまくいきます。ちょっと長いあいだ編集して何度かコミットするような場合は、自分を恒久的に監視リストに加えたほうがよいでしょう:

paste$ cvs watch add hello.c
paste$ cvs edit hello.c
paste$ emacs hello.c
...
paste$ cvs commit -m "print hello in Sanskrit"

watch add したので、qsmith はコミット後も hello.c を監視したままになります。(ところで、qsmith は自分が編集したことについては通知されません、他の人にだけ通知が行きます。CVS はかしこいので、誰かがやったことをその人自身宛に通知したりしないのです)


Node:Ending An Editing Session, Next:, Previous:Using Watches In Development, Up:Watches (CVS As Telephone)

Ending An Editing Session

コミットしたくないけど編集が終わったことを明示したい場合は、cvs unedit を実行しましょう:

paste$ cvs unedit hello.c

でも、気をつけてください! こうすると、監視している人全員に編集を終えたことを知らせる以上のことをします。そのファイルに加えたけれどコミットしていない変更を、取り消すかどうか聞いてきます:

paste$ cvs unedit hello.c
hello.c has been modified; revert changes? y
paste$

もし y と答えると、CVS は変更を全て取り消したあと、あなたがもはやそのファイルを編集中ではないということを監視者に知らせます。n と答えると、CVS は変更をそのままに置いておき、あなたは編集者として登録されたままになります(通知はされません、つまりcvs uneditを全く実行しなかったのと同じということになります)。キーの1打の違いで変更が全部取り消されてしまうのはちょっとこわいような気もしますが、原則は理解しやすいと思います: その編集を終える旨世界に宣言するということは、コミットしていない変更を置いておく意味はない、ということです。少なくとも CVS はそのように理解します。おせっかいかもしれませんが、十分気をつけてくださいね!


Node:Controlling What Actions Are Watched, Next:, Previous:Ending An Editing Session, Up:Watches (CVS As Telephone)

Controlling What Actions Are Watched

デフォルトでは、監視している人には3種類のアクションが通知されます: edit, commit, unedit です。ですが、たとえば commit だけを通知して欲しい場合には -a フラグ(a は action という意味)で通知のしかたを調整することができます:

floss$ cvs watch add -a commit hello.c

あるいは、edit と commit だけを監視したいけれど unedit は気にしないという場合には -a フラグを2つ渡してください:

floss$ cvs watch add -a edit -a commit hello.c

-a フラグで監視を追加する場合、いまある監視を削除したりはしません。もし既に3種類のアクションを hello.c について監視している場合には、

floss$ cvs watch add -a commit hello.c

とやっても何の効果もありません、3種類全部を監視したままになります。監視を削除したければ、

floss$ cvs watch remove hello.c

を実行してください。add と同じように、デフォルトでは3つのアクション全ての監視を削除します。 -a 引数を渡すと、指定した監視のみを削除します:

floss$ cvs watch remove -a commit hello.c

これは commit についての通知を受けとるのをやめるが、edit と unedit についての通知は引き続き受けとる、という意味になります(初めに edit と unedit を監視しているとすればそうなるということです)。

-a フラグに渡すアクションで、2つ特殊なものがあります: all と none です。前者は監視できるアクション全てを意味し(これを書いている時点では edit, commit, unedit です)、後者はどれでもないという意味になります。-a がない場合、CVS のデフォルト動作では全てのアクションを監視することになりますし、どれも監視しないというのは監視者リストから自分を削除するというのに等しいので、これら2つの特殊アクションを使うような状況というのは考えにくいです。しかしながら、cvs edit は -a オプションを解釈しますので、この場合にはall または none を指定できるのは便利かもしれません。例えば、だれかがあるファイルをとても短い間だけいじっていて、ほかの人がそのファイルで何をしているかの通知を受け取りたくないとしましょう。その場合、このコマンドを実行すると

paste$ whoami
qsmith
paste$ cvs edit -a none README.txt

README.txt の監視者は qsmith がそのファイルをいじろうとしているという通知を受けとりますが、qsmith は自分の編集中 README.txt の一時監視者には加えられません(通常は加えられます)。どのアクションも監視したくないと言ったからです。

cvs watch コマンドでは、自分自身の監視をいじれるだけだということを覚えておいてください。あるファイルについて自分自身が監視するのを止めることはできますが、誰か他の人の監視状況を変えることはできません。


Node:Finding Out Who Is Watching What, Next:, Previous:Controlling What Actions Are Watched, Up:Watches (CVS As Telephone)

Finding Out Who Is Watching What

cvs edit を実行する前に誰が監視しているか知りたいと思うこともあるでしょう。また、自分を監視リストに加えずに、誰が何を編集しているのか知りたいこともあるでしょう。あるいは、自分の状況を忘れてしまうこともあるでそう。監視を設定/設定解除してファイルをコミットしたあとは、何を監視していて何を編集中なのかを見失いがちです。

CVS は誰が監視していて、誰がファイルを編集中なのかを見せてくれるコマンドを2つ用意しています。cvs watchers と cvs editors です:

floss$ whoami
jrandom
floss$ cvs watch add hello.c
floss$ cvs watchers hello.c
hello.c jrandom  edit unedit  commit
floss$ cvs watch remove -a unedit hello.c
floss$ cvs watchers hello.c
hello.c jrandom  edit commit
floss$ cvs watch add README.txt
floss$ cvs watchers
README.txt      jrandom edit    unedit  commit
hello.c jrandom edit    commit
floss$

最後の cvs watchers コマンドはファイルを指定していないことに注意してください。従って、すべてのファイルの監視者を表示します(つまり、監視者のいるファイルをすべて、ということです)。

他の CVS コマンドと同様、すべての watch コマンドと edit コマンドはこのように動作します。ファイル名を指定した場合、そのファイルについて動作します。ディレクトリ名を指定した場合、そのディレクトリとそのサブディレクトリ内のすべてについて動作します。何も指定しない場合、カレントディレクトリとそれ以下できる限り深いレベルまですべてについて動作します。たとえば(同じセッションを続けます):

floss$ cvs watch add a-subdir/whatever.c
floss$ cvs watchers
README.txt      jrandom edit    unedit  commit
hello.c jrandom edit    commit
a-subdir/whatever.c     jrandom edit    unedit  commit
floss$ cvs watch add
floss$ cvs watchers
README.txt      jrandom edit    unedit  commit
foo.gif jrandom edit    unedit  commit
hello.c jrandom edit    commit  unedit
a-subdir/whatever.c     jrandom edit    unedit  commit
a-subdir/subsubdir/fish.c       jrandom edit    unedit  commit
b-subdir/random.c       jrandom edit    unedit  commit
floss$

コマンドの最後2つはそれぞれ、 jrandom をプロジェクト中のすべてのファイルの監視者にし、プロジェクト中の全ファイルの監視者リストを表示しています。cvs watchers の出力は可変長の情報とタブストップが混合しているので、カラムがいつも完璧に整列しているわけではありません。しかし、各行は一貫した形式になっています:

[FILENAME] [whitespace] WATCHER [whitespace] ACTIONS-BEING-WATCHED...

さて、qsmith がファイルをひとつ編集し始めたとして、何が起こるか見てみましょう:

paste$ cvs edit hello.c
paste$ cvs watchers
README.txt      jrandom edit    unedit  commit
foo.gif jrandom edit    unedit  commit
hello.c jrandom edit    commit  unedit
       qsmith  tedit   tunedit tcommit
a-subdir/whatever.c     jrandom edit    unedit  commit
a-subdir/subsubdir/fish.c       jrandom edit    unedit  commit
b-subdir/random.c       jrandom edit    unedit  commit

hello.c にはもう一人監視者が増えました: qsmith です(ファイル名は繰り返されませんが、行の冒頭は空白文字になっていることに注意してください。このことは watchers の出力を読むプログラムを書くときに重要になるでしょう) hello.c を編集しているので、qsmith はそのファイルを 一時監視 していることになります。ただし彼が hello.c の新しいリビジョンをコミットするとそうではなくなってしまいます。各アクションの前にあるプレフィクス t は、これらが一時監視であることを示します。 qsmith がまた、自分自身を hello.c の正規監視者とした場合

paste$ cvs watch add hello.c
README.txt      jrandom edit    unedit  commit
foo.gif jrandom edit    unedit  commit
hello.c jrandom edit    commit  unedit
       qsmith  tedit   tunedit tcommit edit    unedit  commit
a-subdir/whatever.c     jrandom edit    unedit  commit
a-subdir/subsubdir/fish.c       jrandom edit    unedit  commit
b-subdir/random.c       jrandom edit    unedit  commit

彼は、一時監視者かつ常時監視者であると表示されます。常時監視状態は一時監視をオーバライドして次のようになると思ったかもしれませんが:

        qsmith  edit    unedit  commit

しかし、どんな順序で起こるかわからないので CVS は一時監視を置き換えることはできません。qsmith が編集を終わる前に常時監視をやめたとしたら? あるいは監視したままで編集を終えたとしたら? 前者の場合、tedit/ tunedit/tcommit アクションを残したままedit/unedit/commit アクションは削除されます。後者の場合、逆になります。

とにかく、通常は監視者リスト側のことはあまり気にすることはありません。たいていはプロジェクトのトップレベルで

floss$ cvs watchers

floss$ cvs editors

を実行し、誰が何をしているか見ればよいのです。誰がどのアクションに注意しているか、詳細を知る必要はありません、重要なのは人とファイルです。


Node:Reminding People To Use Watches, Next:, Previous:Finding Out Who Is Watching What, Up:Watches (CVS As Telephone)

Reminding People To Use Watches

監視機構は開発者全員の協力に全面的に依存しているということに、既にお気づきだと思います。誰かが cvs edit をし忘れてファイルを編集し始めた場合、変更がコミットされるまで、誰もそのことを知らないということになってしまいます。cvs edit というのは通常の開発のサイクルに入っておらず、余分に手数のかかるものですから、忘れやすいと言えるでしょう。

CVS は cvs edit を使うよう強制はできませんが、そうするのを忘れないような機構を持っています。それは、watch on コマンドです:

floss$ cvs -q co myproj
U myproj/README.txt
U myproj/foo.gif
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
floss$ cd myproj
floss$ cvs watch on hello.c
floss$

jrandom が cvs watch on hello.c を実行すると、これ以降の myproj のチェックアウトにおいて、作業コピー中の hello.c は読み出し専用で作成されるようになります。qsmith がそれをいじろうとすると、hello.c は読み出し専用であることに気づき、まず cvs edit を実行することを思い出します:

paste$ cvs -q co myproj
U myproj/README.txt
U myproj/foo.gif
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
paste$ cd myproj
paste$ ls -l
total 6
drwxr-xr-x   2 qsmith    users        1024 Jul 19 01:06 CVS/
-rw-r--r--   1 qsmith    users          38 Jul 12 11:28 README.txt
drwxr-xr-x   4 qsmith    users        1024 Jul 19 01:06 a-subdir/
drwxr-xr-x   3 qsmith    users        1024 Jul 19 01:06 b-subdir/
-rw-r--r--   1 qsmith    users         673 Jun 20 22:47 foo.gif
-r--r--r--   1 qsmith    users         188 Jul 18 01:20 hello.c
paste$

こうすると、ファイルは読み書きできるようになります。編集してコミットすると、また読み出し専用になります:

paste$ cvs edit hello.c
paste$ ls -l hello.c
-rw-r--r--   1 qsmith    users         188 Jul 18 01:20 hello.c
paste$ emacs hello.c
  ...
paste$ cvs commit -m "say hello in Aramaic" hello.c
Checking in hello.c;
/usr/local/newrepos/myproj/hello.c,v  <--  hello.c
new revision: 1.12; previous revision: 1.11
done
paste$ ls -l hello.c
-r--r--r--   1 qsmith    users         210 Jul 19 01:12 hello.c
paste$

彼の edit と commit は hello.c の監視者全員に通知されます。jrandom は監視者でなくてもかまわないことに注意してください。cvs watch on hello.c を実行しても、jrandom はそのファイルの監視者リストの一員になったりはしないのです。ただ読み出し専用でチェックアウトされるように指定しただけになります。ファイルを監視したい人は自分を監視者リストに加えるのを忘れないようにする必要があります。CVS はその面倒は見ません。

ファイルひとつについて watch on するのは例外的な状況です。一般にはプロジェクト全体を watch on するのが普通です:

floss$ cvs -q co myproj
U myproj/README.txt
U myproj/foo.gif
U myproj/hello.c
U myproj/a-subdir/whatever.c
U myproj/a-subdir/subsubdir/fish.c
U myproj/b-subdir/random.c
floss$ cd myproj
floss$ cvs watch on
floss$

このアクションはプロジェクト全体についてのポリシーをアナウンスするのに等しいです: 「ファイルをいじるときは cvs edit を使って監視してる人に知らせてね。興味があったり、担当のファイルは自由に監視してください」プロジェクト中の全ファイルは読み出し専用でチェックアウトされますので、何かいじろうとすると cvs edit をしなければならないのを思い出すことでしょう。

おもしろいことに、監視されているファイルをチェックアウトすると読み出し専用になるのに、アップデートではそうはなりません。jrandom が cvs watch on する前に既に qsmith が作業コピーをチェックアウトしていたとすると、qsmith のファイルはアップデートしても読み書きできるままになります。しかし、jrandom が watch on した後でコミットしたファイルは読み出し専用になります。jrandom が watch off しても、

floss$ cvs watch off

qsmith の読み出し専用ファイルは自動的に読み書きできるようになったりはしません。一方、コミット後に読み出し専用には戻らなくなります(watch on のままだと読み出し専用に戻ります)。qsmith がひねくれていて、 cvs edit を全く避けて chmod コマンドで作業コピー中のファイルを書込み可にしたとします

paste$ chmod u+w hello.c

またはこのように全部一挙に

paste$ chmod -R u+w .

したとします。

こういう風なことをされると CVS には手も足も出ません。もともと作業コピーというのはプライベートなサンドボックスなのです。監視機構は the watch features can open them up to public scrutiny a little bit, but only as far as the developer permits. Only when a developer does something that affects the repository (such as commits) is her privacy unconditionally lost.

watch add, watch remove, watch on, watch off の関係はちょっとわかりにくいかもしれません。全体の規則をまとめるとこうなります:

addremove はそのファイルの監視者リストにユーザを追加したり、削除したりするもので、チェックアウト時やチェックアウト後にファイルを読み出し専用にしたりはしません。onoff はファイルのパーミッションについてのものです。ファイルの監視者リストに居るユーザをどうこうしたりはしません。開発者に監視ポリシーを思い出してもらうために作業コピー中のファイルを読み出し専用にするだけです。

ちょっと一貫していないように見えるかもしれません。監視機構を使うというのは、ある意味で CVS の本質に反しています。複数の開発者が自分の作業コピー内で自由に編集し、そのことはコミットするまでお互いから見えない、という理想世界からそれることになります。監視機構は開発者が自分の作業コピーで何をしているかお互いに知らせるために CVS が提供している便利な方法なのです。しかし監視ポリシーを強制する方法はありませんし、編集セッションがどのように構成されるかについて絶対的なコンセプトがあるわけでもありません。まあそれでも監視機構が役に立つような状況もあるのです。


Node:What Watches Look Like In The Repository, Previous:Reminding People To Use Watches, Up:Watches (CVS As Telephone)

What Watches Look Like In The Repository

ブラックボックスと要らぬ神秘を打ち消すため、リポジトリ中で監視機構がどう実装されているかちょっと覗いてみることにしましょう。でも pretty ではないので、ほんとにちょっと覗くだけですよ。

監視するよう設定すると

floss$ pwd
/home/jrandom/myproj
floss$ cvs watch add hello.c
floss$ cvs watchers
hello.c jrandom edit    unedit  commit
floss$

CVS は適切なリポジトリのサブディレクトリ中のある特殊なファイル CVS/fileattr にそれを記録します:

floss$ cd /usr/local/newrepos
floss$ ls
CVSROOT/   myproj/
floss$ cd myproj
floss$ ls
CVS/          a-subdir/     foo.gif,v
README.txt,v  b-subdir/     hello.c,v
floss$ cd CVS
floss$ ls
fileattr
floss$ cat fileattr
Fhello.c        _watchers=jrandom>edit+unedit+commit
floss$

リポジトリに中の CVS サブディレクトリに fileattr が保存されるからといって、リポジトリが作業コピーになったわけではありません。単に、 CVS という名前が既に作業コピー中で図書管理用に予約されているので、リポジトリ中にその名前のサブディレクトリを作るようなプロジェクトは存在しないというのを確信できるから、という理由によります。

fileattr の形式をきちんと説明したりはしません。コマンドごとにそのファイルが変わるのを見るだけで理解できると思います。

floss$ cvs watch add hello.c
floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
Fhello.c        _watchers=jrandom>edit+unedit+commit
floss$ cvs watch add README.txt
floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
Fhello.c        _watchers=jrandom>edit+unedit+commit
FREADME.txt     _watchers=jrandom>edit+unedit+commit
floss$ cvs watch on hello.c
floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
Fhello.c        _watchers=jrandom>edit+unedit+commit;_watched=
FREADME.txt     _watchers=jrandom>edit+unedit+commit
floss$ cvs watch remove hello.c
floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
Fhello.c        _watched=
FREADME.txt     _watchers=jrandom>edit+unedit+commit
floss$ cvs watch off hello.c
floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
FREADME.txt     _watchers=jrandom>edit+unedit+commit
floss$

fileattr 中のレコードを編集してみたりしましょう。qsmith が自分自身を編集者として登録したときに起こることを見てみます:

paste$ cvs edit hello.c

floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
Fhello.c        _watched=;_editors=qsmith>Tue Jul 20 04:53:23 1999 GMT+floss\
+/home/qsmith/myproj;_watchers=qsmith>tedit+tunedit+tcommit
FREADME.txt     _watchers=jrandom>edit+unedit+commit

そのディレクトリのファイル全部に監視者も編集者もいなくなると、CVS は fileattr ファイルも CVS サブディレクトリも削除してしまうことに注意して下さい:

paste$ cvs unedit

floss$ cvs watch off
floss$ cvs watch remove
floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
cat: /usr/local/newrepos/myproj/CVS/fileattr: No such file or directory
floss$

こうやってちょっと見てみるとおわかりの通り、fileattr のフォーマットの解釈の詳細は CVS に任せておいたほうがよいです。

そのフォーマットについて理解するのは、舞台裏でなにが起こっているのか知って満足するというのを置いておくとすると、CVS の監視機構の拡張を書くとか、そのへんをデバッグするとかが主な理由だと思います。普通はリポジトリの中に CVS/ サブディレクトリができたからといってびっくりしない程度で十分です。CVS/ サブディレクトリは CVS にとって、監視者リストのようなメタ情報を安全に保存しておける唯一の場所なのです。


Node:Log Messages And Commit Emails, Next:, Previous:Watches (CVS As Telephone), Up:Advanced CVS

Log Messages And Commit Emails

コミットメールとはコミット時に送信される通知で、ログメッセージやそのコミットに含まれるファイル情報などを示します。通常はプロジェクトの参加者全員に送信されます。そのプロジェクトに興味のある他の人々に送られることもあります。コミットメールの設定方法の詳細は Repository Administration で説明しましたのでここでは繰り返しません。

take into account 考慮に入れる

I have noticed, however, that commit emails can sometimes result in unexpected side effects to projects, effects that you may want to take into account if you set up commit emails for your project.

まず、メッセージは大概無視されることを覚悟して下さい。読まれるかどうかは、少なくとも部分的には、そのプロジェクトにおいてどのくらいの頻度でコミットが行われるかによります。開発者は一日の終わりに1回だけ大きな変更をコミットするのでしょうか、それとも一日を通して小さな変更をたくさんコミットするのでしょうか? プロジェクトが後者に近いようなら、一日中さみだれのようにやってくる小さな通知に対して開発者は壁を作ってしまうでしょうし、各メッセージに対して払う注意も小さくなるでしょう。

これは別に通知が役に立たないという意味ではないです、ただみんながメッセージを全部読んでいるはずだなんて思っちゃいけないというだけのことです。皆に誰が何をしているかを見張ってもらう方法(監視リストに追加する以外で)としては依然便利なものです。誰でも購読できるメーリングリストを送信先にすると、興味のあるユーザ(と未来の開発者)に、日常的にコードに何が起こっているかを知る機会を与える素晴らしい機構になります。

すべてのログメッセージを見ていて、プロジェクト全体の活動について概要を知っている開発者がいるんだ、と考えたいだろうと思います(もちろん良きプロジェクトリーダというものは何らかの手段でこういうことをしているのだろうと思います)。明確な分担区分がある場合、つまり、ある開発者がプロジェクト中のあるサブディレクトリの係りである、ということなんですが、その場合は CVSROOT/loginfo をちょっといじって、分担領域で変更が行われたら、係の部隊が特別に印をつけた変更通知を受け取るようにもできます。これは、開発者が少なくとも自分のサブディレクトリに関係あるメールは読むよう保証するのに役に立つでしょう。

コミットメールが無視されない場合、おもしろい副作用が起こります。リアルタイムのコミュニケーションの手段として使われ始めるのです。そうなった場合のログメッセージの例をお見せしましょう:

Finished feedback form; fixed the fonts and background colors on the
home page.  Whew!  Anyone want to go to Mon Lung for lunch?

別に何も悪いことはないですし、あとでログメッセージを読むと楽しいでしょうね。でも、以下の例のようなログメッセージには注意しなければなりません。ログメッセージは電子メールで配られるだけのものではなく、プロジェクトの履歴として永久に保存されるものなのです。顧客の仕様についてのグチを言ったりするのは、プログラマの間で気晴らしによく行われることです。他のプログラマがメールですぐ見るとわかっている場合、こんなログメッセージをコミットしてしまうのは想像に難くないでしょう:

Truncate four-digit years to two-digits in input.  What the customer
wants, the customer gets, no matter how silly & wrong.  Sigh.
(訳: 入力時の4桁の年を2桁に縮める。お客様は御所望のものを手に入れる、
どんなにバカみたいだろうが間違ってようが気にしない。はー。)

これはおもしろいメールにはなりますが、しかし顧客が後日このログをレビューしたらどうなりますか? (これと同じような問題のために、1つ以上のサイトで、ログメッセージに不快な単語が入らないようガードするスクリプトが起動するように CVSROOT/loginfo が設定されているのに賭けますよ!)

コミットメールの一般的な効果は、短く不明瞭なログメッセージを書きにくくなるということです。良いことだと思います。しかし、聴衆はコミットメールを受け取る人だけではなく、ログを読むかもしれない人全員である、ということに留意する必要があります。


Node:Changing A Log Message After Commit, Next:, Previous:Log Messages And Commit Emails, Up:Advanced CVS

Changing A Log Message After Commit

後悔するようなログメッセージをコミットしてしまった場合には、CVS ではコミットしたあとでそのログを書き直すことができます。これは admin コマンドの -m オプションででき(このコマンドについてはこの章のもう少し後で説明します)、一度に1つのログメッセージ(リビジョン毎、ファイル毎)を変更できます。どのように動くか見てみましょう:

floss$ cvs admin -m 1.7:"Truncate four-digit years to two in input." date.c
RCS file: /usr/local/newrepos/someproj/date.c,v
done
floss$

リビジョン1.7とともにコミットされた元の無礼なログメッセージは、完璧に無垢なメッセージ(退屈にはなりましたが)に置き換えられました。(リビジョン番号と新しいログメッセージの間のコロンを忘れないようにして下さい。)

複数のファイルに良くないメッセージがコミットされてしまった場合、1つずつ cvs admin を実行しなければなりません。各ファイルでリビジョン番号が違うからです。ですから、これは CVS では数少ない、引数にファイル名を1つだけ渡すコマンドなのです:

floss$ cvs admin -m 1.2:"very boring log message" hello.c README.txt foo.gif
cvs admin: while processing more than one file:
cvs [admin aborted]: attempt to specify a numeric revision
floss$

まぎらわしいことに、ファイル名を渡さなかった場合にも同じエラーになります(なぜならそのような場合にはカレントディレクトリ及びそれ以下にあるすべてのファイルを指定したことになるからです):

floss$ cvs admin -m 1.2:"very boring log message"
cvs admin: while processing more than one file:
cvs [admin aborted]: attempt to specify a numeric revision
floss$

(このように不幸にも CVS のエラーメッセージが出てしまった場合には、 CVS の視点に立ってそのメッセージの意味を読み取らねばなりません。)

admin -m を実行すると、そのプロジェクトの履歴を変えてしまうことになるので注意して使って下さい。ログメッセージが変更されたという記録は残りません。ただ、そのリビジョンは最初からその新しいメッセージでコミットされたかのように見えるのです。古いログメッセージの痕跡はどこにも残されません(元のコミットメールをセーブしているのでなければ)。

名前からするとこのコマンドは指定された CVS の管理者にしか使えないように見えますが、実際は、そのプロジェクトの書込み権限さえあれば誰でも cvs admin を実行することができます。それでもやはり注意して使うのが一番良いでしょう。プロジェクトの履歴を変えられるというのは他の有害なものに匹敵するようなことができてしまいます。 admin については CVS Reference に詳しいです。使用を制限する方法も書いてあります。


Node:Getting Rid Of A Working Copy, Next:, Previous:Changing A Log Message After Commit, Up:Advanced CVS

Getting Rid Of A Working Copy

CVS の典型的な使い方では、作業コピーのディレクトリを削除するには、他のディレクトリツリーと同じように削除します:

paste$ rm -rf myproj

しかしこの方法で作業コピーを削除すると、その作業コピーを使い終わったことが他の開発者にわかりません。CVS は明示的に作業コピーを放棄するコマンドを用意しています。リリース(release)はチェックアウトの反対だと思って下さい。その作業コピーでの作業が終わったことをリポジトリに知らせます。チェックアウトと同様、リリースはツリーの親ディレクトリで起動します:

paste$ pwd
/home/qsmith/myproj
paste$ cd ..
paste$ ls
myproj
paste$ cvs release myproj
You have [0] altered files in this repository.
Are you sure you want to release directory 'myproj': y
paste$

リポジトリ中に未コミットの変更があった場合リリースは失敗し、変更のあるファイルの一覧を出力する以外何もしません。そのディレクトリツリーがクリーン(すべて最新)だとするとリリースコマンドはその作業コピーがリリースされたという記録をリポジトリ中に残します。

リリースコマンドに作業コピーのツリーを自動的に削除させることもできます。-d フラグを渡して下さい:

paste$ ls
myproj
paste$ cvs release -d myproj
You have [0] altered files in this repository.
Are you sure you want to release (and delete) directory 'myproj: y
paste$ ls
paste$

CVS バージョン 1.10.6 現在、リリースコマンドは作業コピーを調べてリポジトリの場所を推定することができません(このコマンドが作業コピーの中ではなくその上で実行されるものだからです)。-d <REPOS> グローバルオプションを渡すか、 CVSROOT 環境変数が正しく設定されていることを確認するかして下さい。(このバグは将来のバージョンの CVS では解決されているでしょう)

Cederqvist では、作業コピーを削除する代わりにリリースを使った場合、リリースされたファイルに監視設定をしている人々には unedit をしたかのような通知が行く、と書いてあります。しかしながら筆者が試しにやってみたところ、そうはならないようでした。


Node:History -- A Summary Of Repository Activity, Next:, Previous:Getting Rid Of A Working Copy, Up:Advanced CVS

History - A Summary Of Repository Activity

Repository Administration で、cvs history コマンドについて少し述べました。このコマンドはそのリポジトリで行われた全ての checkout, commit, update, rtag, release の概略を表示します(少なくとも CVSROOT/history ファイルが作成されてログの記録が始まって以降のものは)。この概略の形式と内容は、いろいろなオプションによって変えることができます。

まずはリポジトリ中でログが記録できるようになっていることを確認します。リポジトリ管理者は最初に history ファイルが存在することを確かめなくてはなりません:

floss$ cd /usr/local/newrepos/CVSROOT
floss$ ls -l history
ls: history: No such file or directory
floss$

ない場合には次のように作成して下さい:

floss$ touch history
floss$ ls -l history
-rw-r--r--   1 jrandom   cvs           0 Jul 22 14:57 history
floss$

この history ファイルはリポジトリを使用する人全員が書き込めるようになっていなければなりません。そうしないとそのファイルを変更する CVS コマンドを実行するたびにエラーが起きるようになってしまいます。ワールドライタブルにするのが一番簡単でしょう:

floss$ chmod a+rw history
floss$ ls -l history
-rw-rw-rw-   1 jrandom   cvs           0 Jul 22 14:57 history
floss$

リポジトリが cvs init により作成されたのであれば history ファイルは既に存在しているはずですが、パーミッションの問題は解決する必要があるかもしれません。

以降の例では、しばらくの間 hisotry ログ記録がされていたと仮定します。 history ファイルにデータが蓄積されるだけの時間がたっています。(?)

cvs history の出力は少々ぶっきらぼうです(これは多分、人間が読むためというよりはプログラムが解釈するためにこうなっているのでしょうけれど、ちょっと勉強すれば読めます)。とりあえず走らせてみて出力を見てみましょう:

paste$ pwd
/home/qsmith/myproj
paste$ cvs history -e -a
O 07/25 15:14 +0000 qsmith  myproj =mp=     ~/*
M 07/25 15:16 +0000 qsmith  1.14 hello.c    myproj == ~/mp
U 07/25 15:21 +0000 qsmith  1.14 README.txt myproj == ~/mp
G 07/25 15:21 +0000 qsmith  1.15 hello.c    myproj == ~/mp
A 07/25 15:22 +0000 qsmith  1.1  goodbye.c  myproj == ~/mp
M 07/25 15:23 +0000 qsmith  1.16 hello.c    myproj == ~/mp
M 07/25 15:26 +0000 qsmith  1.17 hello.c    myproj == ~/mp
U 07/25 15:29 +0000 qsmith  1.2  goodbye.c  myproj == ~/mp
G 07/25 15:29 +0000 qsmith  1.18 hello.c    myproj == ~/mp
M 07/25 15:30 +0000 qsmith  1.19 hello.c    myproj == ~/mp
O 07/23 03:45 +0000 jrandom myproj =myproj= ~/src/*
F 07/23 03:48 +0000 jrandom        =myproj= ~/src/*
F 07/23 04:06 +0000 jrandom        =myproj= ~/src/*
M 07/25 15:12 +0000 jrandom 1.13 README.txt myproj == ~/src/myproj
U 07/25 15:17 +0000 jrandom 1.14 hello.c    myproj == ~/src/myproj
M 07/25 15:18 +0000 jrandom 1.14 README.txt myproj == ~/src/myproj
M 07/25 15:18 +0000 jrandom 1.15 hello.c    myproj == ~/src/myproj
U 07/25 15:23 +0000 jrandom 1.1  goodbye.c  myproj == ~/src/myproj
U 07/25 15:23 +0000 jrandom 1.16 hello.c    myproj == ~/src/myproj
U 07/25 15:26 +0000 jrandom 1.1  goodbye.c  myproj == ~/src/myproj
G 07/25 15:26 +0000 jrandom 1.17 hello.c    myproj == ~/src/myproj
M 07/25 15:27 +0000 jrandom 1.18 hello.c    myproj == ~/src/myproj
C 07/25 15:30 +0000 jrandom 1.19 hello.c    myproj == ~/src/myproj
M 07/25 15:31 +0000 jrandom 1.20 hello.c    myproj == ~/src/myproj
M 07/25 16:29 +0000 jrandom 1.3  whatever.c myproj/a-subdir == ~/src/myproj
paste$

There, isn't that clear?

出力を調べる前に、起動時のオプション(-e と -a)に注意して下さい。 history を実行するときには大抵、どのデータをどのように表示するか指定するオプションをつけると思います。この点においてこのコマンドは他の CVS のコマンドとは違います。他のコマンドは普通、オプション無しでも役に立ちます。この例の2つのフラグはそれぞれ、everything (起こったイベントの全種類を表示) と all (全てのユーザについて) という意味です。

history コマンドが他のコマンドと違う点は他にもあって、普通このコマンドは作業コピー内で実行しますが、コマンド出力はその作業コピーのプロジェクトの情報に限定されません。リポジトリ内の全てのプロジェクトの全てのイベント履歴を表示します。作業コピーは履歴データをどのリポジトリから取ってくればよいかを CVS に指定するために使われるだけです。(上の例ではリポジトリの履歴データは myproj プロジェクトのものしかないので、これで全てなのです)

出力の形式は通常このようです:

CODE DATE USER [REVISION] [FILE] PATH_IN_REPOSITORY ACTUAL_WORKING_COPY_NAME

コード文字は CVS のさまざまなオペレーションを指します Table 6.1 を参照してください。

オペレーション(チェックアウトのような)は各ファイルではなくプロジェクト全体についてのもので、リビジョンやファイル名は省略されています。イコール記号の間にリポジトリ内パスが書いてあります。

history コマンドの出力はコンパクトなように、他のプログラムが解釈し易いようにデザインされていますが、CVS still gives you a lot of control over its scope and content. Table 6.2 にあるオプションで、どんなタイプのイベントを報告して欲しいかコントロールします。

Table 6.1  コード文字の意味

Letter	        Meaning
======          =========================================================
O		チェックアウト
T		タグ
F		リリース
W		アップデート(ユーザファイルがない、エントリファイルから削除)
U		アップデート(変更されていないユーザファイルを上書き)
G		アップデート(変更されたユーザファイルにマージ、成功)
C		アップデート(変更されたユーザファイルにマージ、コンフリクト)
M		コミット(ファイル変更)
A		コミット(ファイル追加)
R		コミット(ファイル削除)
E		エクスポート 
Table 6.2  イベントタイプでフィルタリングするオプション

Option	        Meaning
==========      =========================================================
-m MODULE	MODULE に起こった履歴イベントを表示
-c		コミットイベントを表示
-o		チェックアウトイベントを表示
-T		タグイベントを表示
-x CODE(S)	CODE タイプのイベントを全て表示(OTFWUGCMARE から1つ以上)
-e		全タイプのイベント表示。以上。レポートして欲しいイベン
		トのタイプを選択したあと、Table 6.3 に示したオプション
		でさらにフィルタリングすることもできます。

Table 6.3  ユーザでフィルタリングするオプション

Option	        Meaning
==========      =========================================================
-a		全ユーザのアクションを表示
-w		この作業コピー内のアクションのみ表示
-l		ユーザが取ったそのアクションの最後の日付を表示
-u USER 	USER の記録を表示 


Node:Annotations -- A Detailed View Of Project Activity, Next:, Previous:History -- A Summary Of Repository Activity, Up:Advanced CVS

Annotations - A Detailed View Of Project Activity

The annotate Command

history コマンドがプロジェクトの活動を表示するものだとすると、 annotate コマンドはそれにズームレンズをつけるものだと言えます。 annotate を使うとあるファイルの各行を最後に触った人が誰か、どのリビジョンで触ったかまでわかるのです:

floss$ cvs annotate
Annotations for README.txt
***************
1.14         (jrandom  25-Jul-99): blah
1.13         (jrandom  25-Jul-99): test 3 for history
1.12         (qsmith   19-Jul-99): test 2
1.11         (qsmith   19-Jul-99): test
1.10         (jrandom  12-Jul-99): blah
1.1          (jrandom  20-Jun-99): Just a test project.
1.4          (jrandom  21-Jun-99): yeah.
1.5          (jrandom  21-Jun-99): nope.
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.15         (jrandom  25-Jul-99):   /* another test for history */
1.13         (qsmith   19-Jul-99):   /* random change number two */
1.10         (jrandom  12-Jul-99):   /* test */
1.21         (jrandom  25-Jul-99):   printf ("Hellooo, world!\n");
1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
1.11         (qsmith   18-Jul-99):   /* added this comment */
1.16         (qsmith   25-Jul-99):   /* will merge these changes */
1.18         (jrandom  25-Jul-99):   /* will merge these changes too */
1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
1.1          (jrandom  20-Jun-99): }
Annotations for a-subdir/whatever.c
***************
1.3          (jrandom  25-Jul-99): /* A completely non-empty C file. */
Annotations for a-subdir/subsubdir/fish.c
***************
1.2          (jrandom  25-Jul-99): /* An almost completely empty C file. */
Annotations for b-subdir/random.c
***************
1.1          (jrandom  20-Jun-99): /* A completely empty C file. */
floss$

annotate の出力はとても直観的です。左側は、その行が追加、または最後に変更されたリビジョン番号、開発者、日付です。右側は現在のリビジョンのその行自体です。全ての行に注釈がついているので、そのファイルの内容全体を見ようと思えば、注釈情報を無視して右側だけ見ればよいのです。

リビジョン番号かタグを指定すれば、そのリビジョンについての注釈が表示されます。各行についてそのリビジョン以前の最も最近の変更が表示される、ということです。ある1つのファイルの特定のリビジョンを調べ、どの開発者がファイル中のどの部分で活動したかはっきりさせる、というのが annotate の最も普通の使い道です。

例えば上の例の出力で、hello.c の最新のリビジョンは 1.21 で、jrandom がこの行:

printf ("Hellooo, world!\n");

に何かした、というのがわかると思います。

なにをしたか調べるにはは、そのリビジョンと一つ前のリビジョンの diff を取ってみるのが1つの方法です:

floss$ cvs diff -r 1.20 -r 1.21 hello.c
Index: hello.c
===================================================================
RCS file: /usr/local/newrepos/myproj/hello.c,v
retrieving revision 1.20
retrieving revision 1.21
diff -r1.20 -r1.21
9c9
<   printf ("Hello, world!\n");
--
>   printf ("Hellooo, world!\n");
floss$

もう一つの方法は、現在の注釈を一つ前のリビジョンの注釈と比較することです。ファイル全体での全員の活動を見たままで調べられます。

floss$ cvs annotate -r 1.20 hello.c
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.15         (jrandom  25-Jul-99):   /* another test for history */
1.13         (qsmith   19-Jul-99):   /* random change number two */
1.10         (jrandom  12-Jul-99):   /* test */
1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
1.11         (qsmith   18-Jul-99):   /* added this comment */
1.16         (qsmith   25-Jul-99):   /* will merge these changes */
1.18         (jrandom  25-Jul-99):   /* will merge these changes too */
1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
1.1          (jrandom  20-Jun-99): }
floss$

diff ではテキストの変更がより簡潔に示されますが、annotate では the annotation may be preferable because it places them in their historical context by showing how long the previous incarnation of the line had been present (in this case, all the way since revision 1.1). That knowledge can help you decide whether to look at the logs to find out the motivation for the change:

floss$ cvs log -r 1.21 hello.c
RCS file: /usr/local/newrepos/myproj/hello.c,v
Working file: hello.c
head: 1.21
branch:
locks: strict
access list:
symbolic names:
       random-tag: 1.20
       start: 1.1.1.1
       jrandom: 1.1.1
keyword substitution: kv
total revisions: 22;    selected revisions: 1
description:
----------------------------
revision 1.21
date: 1999/07/25 20:17:42;  author: jrandom;  state: Exp;  lines: +1 -1
say hello with renewed enthusiasm
============================================================================
floss$

-r の他に -D DATE オプションを使って注釈をフィルタリングすることができます:

floss$ cvs annotate -D "5 weeks ago" hello.c
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
1.1          (jrandom  20-Jun-99): }
floss$ cvs annotate -D "3 weeks ago" hello.c
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
1.1          (jrandom  20-Jun-99): }
floss$


Node:Annotations And Branches, Next:, Previous:Annotations -- A Detailed View Of Project Activity, Up:Advanced CVS

Annotations And Branches

デフォルトでは annotate は開発のメイントランクの動きを表示します。ブランチの作業コピーで起動した場合でも指定しない限りはトランクのほうを表示します。(トランクに固執するのはバグか仕様か…それはあなたの見方によります。) ブランチタグを -r で渡すとそのブランチの annotate を取ることができます。以下の例は hello.c が Brancho_Gratuito という名前のブランチにある作業コピーからの例です。このブランチ上では少なくとも1つの変更がコミットされました:

floss$ cvs status hello.c
===================================================================
File: hello.c           Status: Up-to-date

  Working revision:    1.10.2.2        Sun Jul 25 21:29:05 1999
  Repository revision: 1.10.2.2        /usr/local/newrepos/myproj/hello.c,v
  Sticky Tag:          Brancho_Gratuito (branch: 1.10.2)
  Sticky Date:         (none)
  Sticky Options:      (none)

floss$ cvs annotate hello.c
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.10         (jrandom  12-Jul-99):   /* test */
1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
1.1          (jrandom  20-Jun-99): }
floss$ cvs annotate -r Brancho_Gratuito hello.c
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.10         (jrandom  12-Jul-99):   /* test */
1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
1.10.2.2     (jrandom  25-Jul-99):   printf ("hmmmmm\n");
1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
1.1          (jrandom  20-Jun-99): }
floss$

ブランチ番号そのものを渡すこともできます:

floss$ cvs annotate -r 1.10.2 hello.c
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.10         (jrandom  12-Jul-99):   /* test */
1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
1.10.2.2     (jrandom  25-Jul-99):   printf ("hmmmmm\n");
1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
1.1          (jrandom  20-Jun-99): }
floss$

ブランチ上のリビジョン番号をフルに渡しても良いです:

floss$ cvs annotate -r 1.10.2.1 hello.c
Annotations for hello.c
***************
1.1          (jrandom  20-Jun-99): #include <stdio.h>
1.1          (jrandom  20-Jun-99):
1.1          (jrandom  20-Jun-99): void
1.1          (jrandom  20-Jun-99): main ()
1.1          (jrandom  20-Jun-99): {
1.10         (jrandom  12-Jul-99):   /* test */
1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
1.1          (jrandom  20-Jun-99): }
floss$

こうする場合には、そのリビジョン番号はそのファイルにだけ有効だということを忘れないようにして下さい。一般的にはできるだけブランチ名を使ったほうが良いと思います。


Node:Using Keyword Expansion, Next:, Previous:Annotations And Branches, Up:Advanced CVS

Using Keyword Expansion

An Overview of CVSkeyword expansion について少し書いたことを思い出せると思います。RCS キーワードはドル記号でくくった特殊な単語で、CVS はテキストファイル中にそれらがないか探し、リビジョン管理情報に展開します。例えばファイルにこういう文字列があった場合
$Author$

そのファイルがあるリビジョンへアップデートされる時に、CVS はこれをそのリビジョンをコミットした人のユーザ名に展開します:

$Author: jrandom $

CVS は展開後の形も認識しますので、展開された後も適切に更新されつづけます。

Although keywords don't actually offer any information that's not available by other means, they give people a convenient way to see revision control facts embedded in the text of the file itself, rather than by invoking some arcane CVS operation.

良く使われるほかのキーワードを紹介します:

$Date$       ==>  最終コミット日時。展開後 ==>
$Date: 1999/07/26 06:39:46 $

$Id$         ==>  ファイル名、リビジョン、日時、著者。展開後 ==>
$Id: hello.c,v 1.11 1999/07/26 06:39:46 jrandom Exp $

$Revision$   ==>  あなたの考えた通りです、展開後 ==>
$Revision: 1.11 $

$Source$     ==> 対応するリポジトリファイルのパス。展開後 ==>
$Source: /usr/local/newrepos/tossproj/hello.c,v $

$Log$        ==>  そのファイルへのログメッセージを蓄積。展開後 ==>
$Log: hello.c,v $
Revision 1.2  1999/07/26 06:47:52  jrandom
...and this is the second log message.

Revision 1.1  1999/07/26 06:39:46  jrandom
This is the first log message...

$Log$ キーワードはこの中では唯一、展開後複数行にわたります。他のものと違い、以前の展開結果が今回の展開結果で置き換えられるのではなく、最新の展開結果に挿入されます。キーワードの直後に、空行1行とともに挿入されます(従って、過去の展開結果は下へ押しやられていきます)。

さらに、行頭と $Log の間にある文字列は展開のプレフィクスになります(ログメッセージをプログラム内コメントにするために利用されます)。例えばファイルにこう書くと

// $Log$

最初のコミットで次のように展開されるでしょう:

// $Log: hello.c,v $
// Revision 1.14  1999/07/26 07:03:20  jrandom
// this is the first log message...
//

2度目のコミットではこうなります:

// $Log: hello.c,v $
// Revision 1.15  1999/07/26 07:04:40  jrandom
// ...and this is the second log message...
//
// Revision 1.14  1999/07/26 07:03:20  jrandom
// this is the first log message...
//

以降も同じように:

// $Log: hello.c,v $
// Revision 1.16  1999/07/26 07:05:34  jrandom
// ...and this is the third!
//
// Revision 1.15  1999/07/26 07:04:40  jrandom
// ...and this is the second log message...
//
// Revision 1.14  1999/07/26 07:03:20  jrandom
// this is the first log message...
//

いつもいつもログ履歴全部をファイルに保存したりしたくはないとお思いでしょう。その場合、長くなったらいつでも古いほうを削除して下さい。 cvs log を実行するよりも便利ですし、みんながログをコンスタントに読んでいなくてはいけないようなプロジェクトにおいても役に立つでしょう。

ファイル中に $Revision$ を持たせ、それをプログラムのバージョン番号として使用するのは、良く使われるテクニックです。このやり方はプロジェクトが本質的に1つのファイルで構成されているか、または頻繁にリリースされていて、リリースごとの更新が保証されているファイルが少なくとも一つある場合にうまくいきます。プログラムのコード中に値として RCS キーワードを使うこともできます:

VERSION = "$Revision: 1.114 $";

CVS はキーワードを他の場合と同じようにただ展開します; プログラミング言語での意味を考慮したりはしませんし、文字列がダブルクオートで保護されていると仮定したりもしません。

キーワードの完全な一覧(あまり知られていないようなのがあといくつかあるんです)は CVS Reference に書いてあります。


Node:Going Out On A Limb (How To Work With Branches And Survive), Next:, Previous:Using Keyword Expansion, Up:Advanced CVS

Going Out On A Limb (How To Work With Branches And Survive)

ブランチは CVS の最も重要な機能であると同時に、最も誤用されやすい機能でもあります。危険または混乱するような変更を、その変更が安定するまで別の開発ラインに隔離しておけるのは便利です。しかしながら、ブランチはきちんと管理しなければ、どの変更をいつマージしたかわからなくなり、プロジェクトを容易に混乱に陥れることになります。


Node:Some Principles For Working With Branches, Next:, Up:Going Out On A Limb (How To Work With Branches And Survive)

Some Principles For Working With Branches

ブランチを使った作業を成功させるためには、開発グループはこれらの原則を守るべきでしょう:

これらの原則を心に留めつつ、典型的なブランチ開発シナリオを見ていくことにしましょう。jrandom がトランク上、qsmith がブランチ上に居ることにします。が、トランク及び/またはブランチ上には複数の開発者が居る可能性があることに注意して下さい。各開発ライン上には通常何人いても構いません。しかし、タグづけとマージは各ラインについて一人だけが実行するのがよいでしょう。


Node:Merging Repeatedly Into The Trunk, Next:, Previous:Some Principles For Working With Branches, Up:Going Out On A Limb (How To Work With Branches And Survive)

Merging Repeatedly Into The Trunk

qsmith はトランクを jrandom と共有していて、それを不安定にさせたくないのでしばらくブランチ上で開発をする必要があるとします。最初のステップはブランチの作成です。qsmith がまず通常の(ブランチでない)タグを作成し、その後ブランチを作っていることに注意してください:

paste$ pwd
/home/qsmith/myproj
paste$ cvs tag Root-of-Exotic_Greetings
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
paste$ cvs tag -b Exotic_Greetings-branch
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
paste$

最初トランクにタグづけした点は、将来、ブランチを作成した時点のトランクにアクセスするときに必要になるでしょう。そういう必要がでてきた場合に、ブランチ自体を参照することなくトランクのスナップショットを参照することができます。ブランチタグでは、ブランチの根のあるトランクではなく、ブランチにアクセスしてしまうので使えません。ブランチが生えているその同じリビジョンに、通常のタグを作るしか方法はありません。(「ブランチの原則4、ブランチポイントにはノン-ブランチタグを作成する」とでも言うべきこのルールを忠実に守っている人も居ますが、多くのサイトではこれが守られていませんし、まあかまわないようにも思えます。趣味の問題ですね) これ以降ではこのようなノン-ブランチタグをブランチポイントタグと呼びます。

ブランチポイントタグは Root-of- で始まり、ハイフンではなくアンダスコアで単語を区切った実際のブランチ名を続ける、という命名規則にも注意してください。実際のブランチを作成する時には、そのタグ名の最後は -branch とします。タグ名を見ればそのタグがブランチタグだとわかるようにするためです。(ブランチポイントタグ Root-of-Exotic_Greetings には -branch とは書いてありません、これはブランチタグではありませんので。) もちろん、この命名規則を使う必要は特にありませんが、何らかの規則を用いるべきだと思います。

ああ、ちょっとうるさく言いすぎましたね。小さなプロジェクトでは、誰が何をしているかをみんながが知っていて、混乱が起こっても容易に回復できるので、このような規則を使わねばならないわけではありません。ブランチポイントタグを使うとかタグに厳しい命名規則を課すかどうかというのは、プロジェクトの複雑度やブランチのやりかたによります。(あとでいつでも戻って古いタグを新しい規則に沿うように直すことができることを忘れないでください。古いタグのバージョンにアクセスして、新しいタグをつけ、古いタグを削除すればよいのです)

さて、qsmith はブランチで作業を開始します:

paste$ cvs update -r Exotic_Greetings-branch
cvs update: Updating .
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
paste$

ファイルをいくつか変更し、それをブランチへコミットします:

paste$ emacs README.txt a-subdir/whatever.c b-subdir/random.c
...
paste$ cvs ci -m "print greeting backwards, etc"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.14.2.1; previous revision: 1.14
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.3.2.1; previous revision: 1.3
done
Checking in b-subdir/random.c;
/usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
new revision: 1.1.1.1.2.1; previous revision: 1.1.1.1
done
paste$
<<<<<<< j-chapter-6.texi =======

この間、jrandom はトランクでの作業を続行しています。qsmith が触った 3つのファイルのうち2つを変更しました。 >>>>>>> 1.4

<<<<<<< j-chapter-6.texi Meanwhile, jrandom is continuing to work on the trunk. She modifies two of the three files that qsmith touched. Just for kicks, we'll have her make changes that conflict with qsmith's work:

======= 一方、 jrandom はトランク上で作業を続行しています。qsmith が触った3つのファイルのうち、2つを変更しました。試しに、qsmith とコンフリクトするような変更を施してみましょう:

>>>>>>> 1.4

floss$ emacs README.txt whatever.c
 ...
floss$ cvs ci -m "some very stable changes indeed"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.15; previous revision: 1.14
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.4; previous revision: 1.3
done
floss$

コンフリクトがあるようには見えません。当たり前ですね、ブランチとトランクをマージしようとしたわけではありませんから。ではここで jrandom がマージを実行します:

floss$ cvs update -j Exotic_Greetings-branch
cvs update: Updating .
RCS file: /usr/local/newrepos/myproj/README.txt,v
retrieving revision 1.14
retrieving revision 1.14.2.1
Merging differences between 1.14 and 1.14.2.1 into README.txt
rcsmerge: warning: conflicts during merge
cvs update: Updating a-subdir
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3
retrieving revision 1.3.2.1
Merging differences between 1.3 and 1.3.2.1 into whatever.c
rcsmerge: warning: conflicts during merge
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.2.1
Merging differences between 1.1.1.1 and 1.1.1.1.2.1 into random.c
floss$ cvs update
cvs update: Updating .
C README.txt
cvs update: Updating a-subdir
C a-subdir/whatever.c
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
M b-subdir/random.c
floss$

ファイルが2つ、コンフリクトを起こしました。たいした問題ではありません、 jrandom はいつものように機転を利かせてコンフリクトを解消し、コミットした後、トランクにマージ成功のタグをつけます:

floss$ emacs README.txt a-subdir/whatever.c
 ...
floss$ cvs ci -m "merged from Exotic_Greetings-branch (conflicts resolved)"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.16; previous revision: 1.15
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.5; previous revision: 1.4
done
Checking in b-subdir/random.c;
/usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
new revision: 1.2; previous revision: 1.1
done
floss$ cvs tag merged-Exotic_Greetings
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
floss$

qsmith は開発を続行するのにマージが終わるのを待つ必要はありません。 qsmith は jrandom がマージした後の一連の変更に対し、タグをつけておけばよいのです。(あとで jrandom はこのタグ名を知る必要があります; 一般に、ブランチというものは開発者が頻繁かつ綿密に連絡を取り合って初めて成り立つものなのです):

paste$ cvs tag Exotic_Greetings-1
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
paste$ emacs a-subdir/whatever.c
 ...
paste$ cvs ci -m "print a randomly capitalized greeting"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.3.2.2; previous revision: 1.3.2.1
done
paste$

もちろん、qsmith は変更が終わり次第タグをつけるべきです:

paste$ cvs -q tag Exotic_Greetings-2
T README.txt
T foo.gif
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
paste$

これをやっている間、qsmith が一連の編集で触ったのとは別のファイルを jrandom が変更したとします:

floss$ emacs README.txt
 ...
floss$ cvs ci -m "Mention new Exotic Greeting features" README.txt
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.17; previous revision: 1.16
done
floss$

ここで qsmith は新たな変更をブランチにコミットし、jrandom は別のファイルのコンフリクトしない変更をトランクにコミットします。jrandom がブランチをもう一度マージしようとしたときに何が起こるか見てみましょう:

floss$ cvs -q update -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/README.txt,v
retrieving revision 1.14
retrieving revision 1.14.2.1
Merging differences between 1.14 and 1.14.2.1 into README.txt
rcsmerge: warning: conflicts during merge
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3
retrieving revision 1.3.2.2
Merging differences between 1.3 and 1.3.2.2 into whatever.c
rcsmerge: warning: conflicts during merge
RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
retrieving revision 1.1
retrieving revision 1.1.1.1.2.1
Merging differences between 1.1 and 1.1.1.1.2.1 into random.c
floss$ cvs -q update
C README.txt
C a-subdir/whatever.c
floss$

コンフリクトが起きてしまいました! これ、予測していましたか?

マージの意味に問題があるようです。先に An Overview of CVS で述べた通り、作業コピー中で

floss$ cvs update -j BRANCH

を実行すると、CVS はブランチの根と先端の相違を作業コピーにマージするのです。今回の場合、それらの変更の大半は、jrandom が最初に行ったマージの時に既にトランクに組み込まれているので、この動作は問題になります。 CVS がその変更を再びマージしようとすると(その変更自身に上書きするようなことになります)、当然コンフリクトだと見なされるわけです。

jrandom が本当にやりたかったことは、ブランチのうち、一番最後に実行したマージ時点と現在のブランチ先端の間の変更を作業コピーにマージする、ということだったのです。これをするには update に -j フラグを2つ渡せばよいのです。An Overview of CVS を思い出して下さい。これをするには、各フラグでどのリビジョンを指定すれば良いかを知っておかなければなりません。幸い、qsmith は最後のマージポイントにタグをつけておきましたので(hurrah for planning ahead!)、これについて問題はありません。まずは jrandom の作業コピーを元のきれいな状態に戻しましょう。そこから再マージするのです:

floss$ rm README.txt a-subdir/whatever.c
floss$ cvs -q update
cvs update: warning: README.txt was lost
U README.txt
cvs update: warning: a-subdir/whatever.c was lost
U a-subdir/whatever.c
floss$

さて、これでマージの準備ができました、今回は qsmith がつけてくれたタグを使うことにしましょう:

floss$ cvs -q update -j Exotic_Greetings-1 -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3.2.1
retrieving revision 1.3.2.2
Merging differences between 1.3.2.1 and 1.3.2.2 into whatever.c
floss$ cvs -q update
M a-subdir/whatever.c
floss$

いいカンジです。qsmith の変更が whatever.c に組み込まれました; jrandom はコミットし、タグをつけます:

floss$ cvs -q ci -m "merged again from Exotic_Greetings (1)"
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.6; previous revision: 1.5
done
floss$ cvs -q tag merged-Exotic_Greetings-1
T README.txt
T foo.gif
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
floss$

qsmith がマージポイントにタグをつけるのを忘れても、望みがなくなってしまったわけではありません。 jrandom が qsmith の最初の変更を大体いつ頃コミットしたか覚えていれば、日付でフィルタリングしてみることができます:

floss$ cvs update -j Exotic_Greetings-branch:3pm -j Exotic_Greetings_branch

日付でフィルタリングするのは、最後の切り札にはなりますが、あまり理想的な方法とは言えません。なぜなら、信頼に足る開発上の名称ではなく、人の記憶に頼った方法だからです。qsmith の初回マージ分の変更が、1回のコミットではなく何度かに分けてコミットされていた場合、jrandom が間違って日付や時刻を指定すると、変更全てではなく一部分だけを取ってきてしまう可能性があります。

qsmith の変更のタグづけできる各ポイントが、1回のコミットでリポジトリに送られなければならない理由はありません。この例でたまたまこうなっているだけなのです。実際には、qsmith はタグとタグの間に何度かコミットするかもしれません。qsmith はブランチ上でひとりで作業したければ、そうすることができます。タグというのはつまり、トランクへマージできると思う点をブランチ上に連続的に記録していくことなのです。jrandom が常に -j フラグを2つ使ってマージし、qsmith のマージタグを正しい順序で注意深く用いてそれぞれを1度だけ使う限り、トランクでダブルマージの問題が起こることはないでしょう。コンフリクトは起こるかもしれませんが、それは人の手で解決しなければならない、不可避なものなのでしょう。同じ領域において、ブランチでもトランクでも変更があるような状況だというわけです。


Node:The Dovetail Approach -- Merging In And Out Of The Trunk, Next:, Previous:Merging Repeatedly Into The Trunk, Up:Going Out On A Limb (How To Work With Branches And Survive)

The Dovetail Approach - Merging In And Out Of The Trunk

ブランチからトランクへ何度もマージするというのは、トランク上に居る人々にとっては良いことです。なぜなら、自分たちの変更も、ブランチからの変更も全てわかるからです。しかしながら、ブランチ上の開発者はトランク上での作業分を組み込めないのです。

これをするためには、ブランチ開発者は時々余分なステップを踏む必要があります。(最新のトランクの変更をマージしたいと思ったり、避けられないコンフリクトをどうにかしたいと思った時はいつでも、という意味です):

paste$ cvs update -j HEAD

特別に予約されたタグ HEAD は、トランクの先端を意味しています。このコマンドは、現在のブランチの根(Exotic_Greetings-branch)から、トランク上の現在の最大のリビジョンまでの変更をすべてマージします。もちろん、 qsmithはこれを実行したあと再度タグをつけなければなりません。トランク上の開発者が qsmith の変更をマージしようとしたときに、誤って自分自身の変更をマージしてしまうことを避けられるようにするためです。

ブランチ開発者も同様に、最後にマージしてから現在状態までのトランクの変更を正確にブランチにマージするために、トランクのマージタグを境界として使用することができます(トランクがマージするのと同じ方法で)。例えば、jrandom がブランチからマージした後 hello.c に変更を施したとします:

floss$ emacs hello.c
 ...
floss$ cvs ci -m "clarify algorithm" hello.c
Checking in hello.c;
/usr/local/newrepos/myproj/hello.c,v  <--  hello.c
new revision: 1.22; previous revision: 1.21
done
floss$

その後、qsmith がこれらの変更をブランチにマージし、コミットし、タグをつけます:

paste$ cvs -q update -j merged-Exotic_Greetings-1 -j HEAD
RCS file: /usr/local/newrepos/myproj/hello.c,v
retrieving revision 1.21
retrieving revision 1.22
Merging differences between 1.21 and 1.22 into hello.c
paste$ cvs -q update
M hello.c
paste$ cvs -q ci -m "merged trunk, from merged-Exotic_Greetings-1 to HEAD"
Checking in hello.c;
/usr/local/newrepos/myproj/hello.c,v  <--  hello.c
new revision: 1.21.2.1; previous revision: 1.21
done
paste$ cvs -q tag merged-merged-Exotic_Greetings-1
T README.txt
T foo.gif
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
paste$

jrandom は hello.c の変更をコミットした後にタグづけせず、qsmith がそれをしたということに注意してください。ここでの原則では、ちょっとした変更のあとにいちいちタグをつける必要はなくて、マージの後、及び自分の開発ラインのマージできる状態をコミットした後に常にタグをつけるべきなのです。こうすれば、他の人(別のブランチにいるのでしょう)に自分自身のマージの基礎となる参照の点がわかります。


Node:The Flying Fish Approach -- A Simpler Way To Do It, Next:, Previous:The Dovetail Approach -- Merging In And Out Of The Trunk, Up:Going Out On A Limb (How To Work With Branches And Survive)

The Flying Fish Approach - A Simpler Way To Do It

albeit slightly limiting ちょっと制限してはいますが

前述のものよりも少々制限はありますが、より簡単な方法があります。ブランチ開発者はトランクがマージしている間開発を停止し、トランク開発者は今までのブランチを置き換えるような全く新しいブランチを作成します。ブランチ開発者はその新しいブランチに移り、開発を続けます。そのサイクルはブランチ開発が必要なくなるまで続きます。こんな感じになります(要約です。今まで通り、jrandom@floss がトランクに、 qsmith@paste がブランチに居ることにします):

floss$ cvs tag -b BRANCH-1
paste$ cvs checkout -r BRANCH-1 myproj

トランク、ブランチともに作業を始めます。そして開発者たちは協議し、ブランチをトランクにマージする時であると決定します:

paste$ cvs ci -m "committing all uncommitted changes"
floss$ cvs update -j BRANCH-1

ブランチでの変更が全てマージされます: ブランチ開発者はトランク開発者がコンフリクトを解消し、コミットしてタグをつけ、新しいブランチを作成するまで作業を停止します:

floss$ cvs ci -m "merged from BRANCH-1"
floss$ cvs tag merged-from-BRANCH-1
floss$ cvs tag -b BRANCH-2

ここでブランチ開発者は自分の作業コピーを新しいブランチで上書きします; マージが起こった時には最新で、新しいブランチは古いブランチの変更が組み込まれたトランクからのものですから、そうしても未コミットの変更を失ったりしないのです。

paste$ cvs update -r BRANCH-2

BRANCH-1 を BRANCH-2 に、BRANCH-2 を BRANCH-3 に置き換えて、限りなくこのサイクルは続いていきます。

筆者はこれを トビウオ テクニックと呼んでいます。ブランチがたえずトランクから生え、短く伸びてはまたトランクへ再接合するからです。このやりかたの利点は、シンプルであること(トランクは常に指定されたブランチの全ての変更をマージする)、それとブランチ開発者はコンフリクトを解消しなくてもいいということです(新しくてきれいなブランチをただ渡されて、その上で作業すればいいのです)。欠点は当然、トランクのマージを実施している間、ブランチ開発者はただ座ってボケっとしていなければならないということです(解決すべきコンフリクトがいくつあるかによって、それなりの時間がかかるでしょう)。あまり重要ではないですが、もうひとつ欠点があります。それは、使われないノンブランチタグの代わりに、使われていない小さなブランチがたくさんできることです。しかし、小さくて使われていないブランチがたくさんあっても気にならなくて、トラブルのないマージを期待しているのなら、トビウオは一番簡単なやり方になるでしょう。心に留めておいてもいいと思います。

どちらのやり方を取るにしろ、分離状態は出来るだけ短い間にするよう努力したほうが良いでしょう。ブランチとトランクをずっとマージしないままにすると、ただのテキストのズレだけでは済まず、意味上のズレに悩まされることになります。テキスト上のコンフリクトを起こす変更は簡単に解消できますが、テキストではなく考え方においてコンフリクトを起こすような変更は、見つけるのも解決するのも大変です。ブランチを隔離するというのは、開発者を自由にしますが、他の人の変更からしばらくの間でも遮蔽されるというのは危険なことです。ブランチを使う場合は普段よりも活発にコミュニケーションをとるようになります。全員がお互いの計画を検討し、同じ軌道に乗っているかどうか確認する必要があります。


Node:Branches And Keyword Expansion -- Natural Enemies, Previous:The Flying Fish Approach -- A Simpler Way To Do It, Up:Going Out On A Limb (How To Work With Branches And Survive)

Branches And Keyword Expansion - Natural Enemies

ファイルに RCS キーワードがあると、ブランチ上とトランク上では違ったように展開されるので、マージするたびににせのコンフリクトが起こることになります。何も変更していなくてもキーワードはオーバラップし、展開結果は一致しません。例えば README.txt に、トランクではこのように書いてあるとします

$Revision: 1.14 $

ブランチ上ではこうです

$Revision: 1.14.2.1 $

マージを実行した時に、以下のようなコンフリクトが起こるでしょう:

floss$ cvs update -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/README.txt,v
retrieving revision 1.14
retrieving revision 1.14.2.1
Merging differences between 1.14 and 1.14.2.1 into README.txt
rcsmerge: warning: conflicts during merge
floss$ cat README.txt
 ...
<<<<<<< README.txt
key $Revision: 1.14 $
=======
key $Revision: 1.14.2.1 $
>>>>>>> 1.14.2.1
 ...
floss$

こうならないようにするために、マージ時に一時的に -kk オプション(何の略なのか知らないんですが、多分 "kill keywords" かな?)を渡して展開を抑制することができます:

floss$ cvs update -kk -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/README.txt,v
retrieving revision 1.14
retrieving revision 1.14.2.1
Merging differences between 1.14 and 1.14.2.1 into README.txt
floss$ cat README.txt
 ...
$Revision$
 ...
floss$

しかし、気をつけることがひとつだけあります: -kk を使うと、そのファイルに設定してあるほかのキーワード展開モードを上書きしてしまいます。特にバイナリファイルにおいては問題になります。バイナリファイルは普通 -kb になっているからです(-kb はキーワード展開と行末コード変換を全て抑制します)。ブランチからバイナリファイルをマージする時には -kk を使わないようにして、コンフリクトは手で直して下さい。


Node:Tracking Third-Party Sources (Vendor Branches), Next:, Previous:Going Out On A Limb (How To Work With Branches And Survive), Up:Advanced CVS

Tracking Third-Party Sources (Vendor Branches)

外部の配布元から取ってきたソフトウェアに、あるサイトでローカルな変更が加えられることがあります。そのローカルな変更を配布元が受け入れない場合(そうできないについて正当な理由はいくらでも考えられます)、そのサイトはそのソフトウェアのアップグレードを受け取るたびにその変更をメンテせねばなりません。

CVS の ベンダブランチ(vendor branches) という機能がこの仕事に役立ちます。ベンダブランチというのは cvs import のわけのわからない最後の2つの引数の説明でした(今まではね)。An Overview of CVS でこじつけたベンダタグとリリースタグのことです。

ここで、それがどのように動くか示します。最初のインポートは他の CVS プロジェクトの最初のインポートと同様です(ちょっと注意してベンダタグとリリースタグを決めないといけませんが):

floss$ pwd
/home/jrandom/theirproj-1.0
floss$ cvs import -m "Import of TheirProj 1.0" theirproj Them THEIRPROJ_1_0
N theirproj/INSTALL
N theirproj/README
N theirproj/src/main.c
N theirproj/src/parse.c
N theirproj/src/digest.c
N theirproj/doc/random.c
N theirproj/doc/manual.txt

No conflicts created by this import

floss$

そしてどこかに作業コピーをチェックアウトして、ローカルな変更を施し、その後コミットして下さい:

floss$ cvs -q co theirproj
U theirproj/INSTALL
U theirproj/README
U theirproj/doc/manual.txt
U theirproj/doc/random.c
U theirproj/src/digest.c
U theirproj/src/main.c
U theirproj/src/parse.c
floss$ cd theirproj
floss$ emacs src/main.c src/digest.c
 ...
floss$ cvs -q update
M src/digest.c
M src/main.c
floss$ cvs -q ci -m "changed digestion algorithm; added comment to main"
Checking in src/digest.c;
/usr/local/newrepos/theirproj/src/digest.c,v  <--  digest.c
new revision: 1.2; previous revision: 1.1
done
Checking in src/main.c;
/usr/local/newrepos/theirproj/src/main.c,v  <--  main.c
new revision: 1.2; previous revision: 1.1
done
floss$

1年後、そのソフトウェアの次のバージョンが Them, Inc. から届きます。ローカルの変更をそのバージョンにも取り入れなければなりません。配布元での変更とローカルの変更は、少しだけ重なっているところがあります。配布元では新しいファイルを一つ追加し、あなたの触ってないファイル2つを変更し、あなたが触ったファイル2つにも変更を加えています。

まずやることは、もう1度インポートすることです。今回は新しいソースからインポートします。初回インポートとほとんど同じです。リポジトリ中、以前と同じプロジェクトの、同じベンダブランチにインポートします。違うのはリリースタグだけです:

floss$ pwd
/home/jrandom/theirproj-2.0
floss$ cvs -q import -m "Import of TheirProj 2.0" theirproj Them THEIRPROJ_2_0
U theirproj/INSTALL
N theirproj/TODO
U theirproj/README
cvs import: Importing /usr/local/newrepos/theirproj/src
C theirproj/src/main.c
U theirproj/src/parse.c
C theirproj/src/digest.c
cvs import: Importing /usr/local/newrepos/theirproj/doc
U theirproj/doc/random.c
U theirproj/doc/manual.txt

2 conflicts created by this import.
Use the following command to help the merge:

       cvs checkout -jThem:yesterday -jThem theirproj

floss$

おやまあ、CVS がこんな風に助けようとしてくれたのは今まで見たことがありませんね。変更をマージするためにどんなコマンドを実行するか教えてくれています。それに、ほとんど正しいですよ! 実際、示されたコマンドは動きますが(yesterday のところを、最初のインポートを含んでいるが2番目のインポートを含んでいない時間間隔に調整したとして)、著者は代わりにリリースタグを使うほうが好きです:

floss$ cvs checkout -j THEIRPROJ_1_0 -j THEIRPROJ_2_0 theirproj
cvs checkout: Updating theirproj
U theirproj/INSTALL
U theirproj/README
U theirproj/TODO
cvs checkout: Updating theirproj/doc
U theirproj/doc/manual.txt
U theirproj/doc/random.c
cvs checkout: Updating theirproj/src
U theirproj/src/digest.c
RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.2
Merging differences between 1.1.1.1 and 1.1.1.2 into digest.c
rcsmerge: warning: conflicts during merge
U theirproj/src/main.c
RCS file: /usr/local/newrepos/theirproj/src/main.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.2
Merging differences between 1.1.1.1 and 1.1.1.2 into main.c
U theirproj/src/parse.c
floss$

インポートでコンフリクトが2つあるというふうに表示されたのに、マージではコンフリクトが1つしかないことに注意して下さい。インポートとその他ではコンフリクトが少し違うものだと CVS が思っているように見えます。インポートでは基本的に、前回のインポートと今回のインポートの間に、あなたとベンダ両方が変更したファイルをコンフリクトとして報告します。しかしマージの時点になると、「コンフリクト」の通常の定義に忠実なのです。つまり、変更が重なっている、という意味になります。重なり合っていない変更は通常の方法でマージされ、そのファイルはただの変更として示されます。

ちょっと diff を取ってみると、ファイルのうち1つだけにコンフリクトマーカがあることがわかります:

floss$ cvs -q update
C src/digest.c
M src/main.c
floss$ cvs diff -c
Index: src/digest.c
===================================================================
RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
retrieving revision 1.2
diff -c -r1.2 digest.c
*** src/digest.c        1999/07/26 08:02:18     1.2
-- src/digest.c        1999/07/26 08:16:15
***************
*** 3,7 ****
-- 3,11 ----
 void
 digest ()
 {
+ <<<<<<< digest.c
   printf ("gurgle, slorp\n");
+ =======
+   printf ("mild gurgle\n");
+ >>>>>>> 1.1.1.2
 }
Index: src/main.c
===================================================================
RCS file: /usr/local/newrepos/theirproj/src/main.c,v
retrieving revision 1.2
diff -c -r1.2 main.c
*** src/main.c  1999/07/26 08:02:18     1.2
-- src/main.c  1999/07/26 08:16:15
***************
*** 7,9 ****
-- 7,11 ----
 {
   printf ("Goodbye, world!\n");
 }
+
+ /* I, the vendor, added this comment for no good reason. */
floss$

こうなると他のマージと同じように、ただコンフリクトを解消すればいいだけの話になります:

floss$ emacs  src/digest.c  src/main.c
 ...
floss$ cvs -q update
M src/digest.c
M src/main.c
floss$ cvs diff src/digest.c
cvs diff src/digest.c
Index: src/digest.c
===================================================================
RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
retrieving revision 1.2
diff -r1.2 digest.c
6c6
<   printf ("gurgle, slorp\n");
--
>   printf ("mild gurgle, slorp\n");
floss$

そして変更をコミットします

floss$ cvs -q ci -m "Resolved conflicts with import of 2.0"
Checking in src/digest.c;
/usr/local/newrepos/theirproj/src/digest.c,v  <--  digest.c
new revision: 1.3; previous revision: 1.2
done
Checking in src/main.c;
/usr/local/newrepos/theirproj/src/main.c,v  <--  main.c
new revision: 1.3; previous revision: 1.2
done
floss$

あとはベンダからの次のリリースを待つだけです。(もちろん、ローカルの変更がちゃんと動くかどうかテストしたいだろうと思いますけれど!)

-------------------------------------------------------------


Node:Exporting For Public Distribution, Next:, Previous:Tracking Third-Party Sources (Vendor Branches), Up:Advanced CVS

Exporting For Public Distribution

CVS は開発者にとっては良い配布機構なのですが、大部分のユーザがソフトウェアを取ってくる時にはダウンロードできるパッケージを取ってくると思います。このパッケージは一般には CVS の作業コピーではなくただのソースツリーで、ユーザのシステムに合わせて簡単にコンフィギュレーション・コンパイルができるようになっているものです。

しかし、CVS はパッケージを作るのに便利な機構を提供しています。 cvs export コマンドです。プロジェクトをエクスポート するというのは、プロジェクトの作業コピーをチェックアウトするのと同じようなものですが、CVS 管理サブディレクトリを抜きにしてプロジェクトツリーをチェックアウトするというところが違います。これはつまり、作業コピーではなくソースツリーだけを取得したということになります。そのソースツリーはどこから来たものなのか、それらのファイルがどの CVS バージョンなのかわかりません。つまり、エクスポートされたコピーはダウンロードしてディストリビューションをほどいたときの状態と同じなのです。そのプロジェクトが作業コピーから直接コンパイルできるようになっているとすると(そしてそうなっているべきなのです!)、エクスポートされたコピーもコンパイルできるはずです。

export コマンドは checkout と同じように動作しますが、タグ名か日付を指定しなければいけないというところが違います。プロジェクトにリリース名でタグをつけて、それに基づいてエクスポートしてみましょう:

floss$ pwd
/home/jrandom/myproj
floss$ cvs -q tag R_1_0
T README.txt
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
floss$ cd ..
floss$ cvs -d /usr/local/newrepos -q export -r R_1_0 -d myproj-1.0 myproj
U myproj-1.0/README.txt
U myproj-1.0/hello.c
U myproj-1.0/a-subdir/whatever.c
U myproj-1.0/a-subdir/subsubdir/fish.c
U myproj-1.0/b-subdir/random.c
floss$ cd myproj-1.0
floss$ ls
README.txt  a-subdir  b-subdir  hello.c

export コマンドは作業コピーの中ではないところで起動されるので、どのリポジトリを使うかを -d グローバルオプションで指定してやらなければならないことに注意して下さい。またこの例では、ディレクトリ名をデフォルトのプロジェクト名ではなく、明示的に指定して(myproj-1.0)エクスポートしています。その名前で作業コピーが既に存在しているからです。この状況は珍しいことではないと思います。

上の例のようにエクスポートコピーが作成されたあとは、プロジェクトが単純なものの場合、次のようにすればリリースが完了します:

floss$ tar cf myproj-1.0.tar myproj-1.0
floss$ gzip --best myproj-1.0.tar
floss$ ls
myproj/   myproj-1.0/   myproj-1.0.tar.gz
floss$ rm -rf myproj-1.0
floss$ mv myproj-1.0.tar.gz /home/ftp/pub/myproj/

もちろん、これらのコマンドを全部手で走らせたりすることはめったにありません。cvs export はリリースやパッケージの処理をするスクリプトから呼ばれることが多いでしょう。公のリリースに先立っていくつかの「テスト」リリースをするような場合は、リリースパッケージを作成する処理は高度に自動化されていることが望ましいと思います。


Node:The Humble Guru, Previous:Exporting For Public Distribution, Up:Advanced CVS

The Humble Guru

この章を全て読んで理解したら(そして実際やってみるとなお良い)、CVS についてとても驚くようなことはもうなにも残っていないと自信を持ってよいでしょう。少なくとも、誰かが新しい機能を CVS に追加するまでは。CVS を大きなプロジェクトで使うために知る必要のあることはすべて述べました。

うぬぼれる前に、4章で言ったことを繰り返しますが info-cvs@gnu.org メーリングリストを購読することをお勧めします。インターネットのメーリングリストの大部分は S/N 比が低下しているにもかかわらず、届く情報のほとんどは待つに値するものです。筆者がこの章を書いている間ずっと購読していたのですが(実はそれより前の章でもそうだったんですが)、CVS の動作の細部の重要なところについて、他の人の投稿から学んだことがどんなにたくさんあるか知ったら、きっと驚くと思います。CVS を本気で使う気があるのなら、また、開発者グループの CVS 管理者の人は特に、他の真面目なユーザの人たちと知識を共有することによって恩恵をたくさん受けることが出来ると思います。


Node:Tips And Troubleshooting, Next:, Previous:Advanced CVS, Up:Top

Tips And Troubleshooting

以前の章でわたしは、CVS は「ブラックボックス」ソフトウェアではないといいました。ブラックボックスは中身を覗かせてくれません。内部にアクセスさせてくれないので、直したり(壊したり)はできません。ブラックボックスというのは普通、何も直す必要がないという前提に立っています。たいがいはちゃんと動いているので、ユーザは内部にアクセスする必要はありません。しかし、動かないとなると全く動きません。直すための選択肢はそう多くないので、どんな問題でも showstopper なのです。

CVS はというと、それはもう完璧に透明な箱のようです。箱がないことを除けば。部品は密閉されたりせず、環境に直接さらされており、環境の一部(予期しないファイルのパーミッションや、中断されたコマンド、競合するプロセス、などなど)はその機構に介入してダメにしてしまうこともあります。しかし、たとえ CVS が完璧に動かないとしても、全く動かないということはまれです。It has the advantage of graceful degradation; どのくらい動作しないかというのは通常、その環境における問題の数と深刻度に比例します。CVS が何をしようとしているか、またどのようにしようとしているかについてよく知っていれば、うまくいかない時に何をすべきかがわかるでしょう。

graceful 礼儀正しい degradation 品格を下げる悪くする退化する

遭遇する可能性のある問題をすべて書くことはできませんが、ここでは比較的よくあるたぐいのことを書いてみました。この章は2つの部分に分かれています: 1つめでは、環境のうち CVS にとって特にデリケートな部分について説明します(主にリポジトリのパーミッションと作業コピー中の管理領域を扱います)。2つめではよくある問題とその解決法をいくつか説明します。これらよくある状況をどう扱うかがわかれば、CVS で予期しない問題が起こってもどういうアプローチを取ればいいか大体わかるのではないかと思います。


Node:The Usual Suspects, Next:, Up:Tips And Troubleshooting

The Usual Suspects

あなたが CVS の管理者("field doctor"と読んで下さい)なら、ユーザの問題の9割は矛盾した作業コピーが原因で、他の9割はリポジトリのパーミッションが間違っていることが原因だというのをご存知でしょう。従って、特定の状況を見ていく前に作業コピーの管理領域についての概要と、リポジトリのパーミッションについての重要な点をいくつか、手短に見ておくことにします。


Node:The Working Copy Administrative Area, Next:, Up:The Usual Suspects

The Working Copy Administrative Area

作業コピーの構造の基本的なところについては、An Overview of CVS で既に見てきました。この章ではもう少し詳細のところを見ます。詳細の大部分は CVS/ 管理サブディレクトリ中のファイルに関連することです。 Entries, Root, Repository ファイルについては見てきたと思いますが、 CVS/ サブディレクトリには状況により他のファイルがあることもあります。それらのファイルをここでは説明します。そういうファイルを見つけた時に驚かないためであり、それらのファイルが原因の問題が起こった時になおすことができるためです。

CVS/Entries.Log

時々、CVS/Entries.Log という名前のファイルが現れることがあります。このファイルは、Entries ファイルを書き換えるに足るような重要な操作をするまで、Entries ファイルへの変更を一時的にキャッシュしておくために使われます。CVS は Entries ファイルの適切な箇所だけを書き換えることができません。変更するには、全部読んでから全部書き戻さなければなりません。これを避けるため、Entries ファイルを書き換える必要が出てくるまでの間、少々の変更を Entries.Log に記録することがあるのです。

Entries.Log のフォーマットは Entries とほぼ同じですが、各行の冒頭に一文字付け加わっています。A は Entries ファイルに追加すべき行を意味し、R は削除すべき行を意味します。

Entries.Log ファイルは、たいていは無視して構いません; 欠いてある情報を人間が理解しなければならないことはめったにありません。しかし、デバッグのため作業コピー中の Entries ファイルを読んでいる場合には Entries.Log ファイルも調べるべきでしょう。

CVS/Entries.Backup

CVS/Entries.Backup ファイルは CVS が実際に新しい Entries ファイルを書く場所です。書いたあと Entries にリネームされます(これは CVS がリポジトリ内に RCS ファイルを書く時に、一時的な RCS ファイルを書いて、完成したら適切な名前に変える、というやりかたと同じです)。完成し次第 Entries にリネームされるので、Entries.Backup ファイルはめったに見ることはないと思います。もし見ることになったとしたら、それはきっと、CVS がオペレーションの最中に中断されたということを意味します。

CVS/Entries.Static

CVS/Entries.Static ファイルが存在している場合、それはそのディレクトリ全体はリポジトリから取得されたものではないということを意味します。(作業コピーが不完全な状態であるということを CVS がわかっている場合は、そのディレクトリの中に余分なファイルを作ったりはしません)

Entries.Static ファイルはチェックアウト中やアップデート中に現われ、オペレーションが完了し次第、すぐに削除されます。Entries.Static が見えたとしたら、それは CVS が中断されたことを意味します。そのファイルがあると、CVS は作業ディレクトリに新しいファイルを作ることができません。(通常、cvs update -d を実行すれば Entries.Static が削除され、問題は解決できます)

Entries.Static がなければ、作業コピーはプロジェクトのファイルをすべて持っている、とは必ずしも言えません。プロジェクトのリポジトリ中に新しいディレクトリが作成されて、誰かが作業コピーを -d フラグなしでアップデートした場合は、作業コピーに新しいディレクトリは作成されません。この場合 CVS はリポジトリ中に新しいディレクトリがあるということに気づかないので、作業コピー中に新しいディレクトリがなくても、アップデートの完了時に Entries.Static ファイルを削除します。

CVS/Tag

CVS/Tag が存在する場合、ある意味、タグ名をディレクトリに関連づける役割を果たします。ある意味、と言ったのはつまり、CVS はディレクトリに関するリビジョン履歴を保存しませんし、厳密に言えばディレクトリにタグをつけることもできないからです。タグというのは通常のファイルのみに、正確に言えば通常のファイルの特定のリビジョンにつけられるものなのです。

しかしながら、あるディレクトリ中の全ファイルが特定のタグをつけられているような状況では、CVS はそのディレクトリ全体にそのタグがつけられているとも考えるのです。たとえば、あるブランチ上の作業コピーをチェックアウトしたとしましょう:

floss$ cvs co -r Bugfix_Branch_1

ここにファイルを1つ追加する場合、その新しいファイルの最初のリビジョンは、そのブランチ上のリビジョンであって欲しいのではないでしょうか。 CVS も同じような理由で、そのディレクトリにブランチでないスティッキータグや日付がセットされているかどうかを知る必要があります。

Tag ファイルには1行しかありません。その行の最初の文字はそのタグがどんな種類かを1文字で示すようなコードです。残りはタグ名です。現状、CVS はその1文字コードを3種類使っています:

(他の1文字コードがあったとしたらそれは、この章が書かれたより後に CVS に新しい種類のタグを追加されたということですね)

Tag ファイルを手で削除したりすべきではありません、かわりに cvs update -A を使用して下さい。

Rarities

他に CVS/ サブディレクトリ中にあるかもしれないファイルを記します:

これらのファイルが何か問題を起こすことは通常ありません、ただ並べてみただけです(詳しい説明は CVS Reference を参照のこと)。

Portability And Future Extension

CVS に新しい機能が追加されたとすると、作業コピーの管理領域に新しい(ここに書いてない)ファイルが現れるかもしれません。新しいファイルが追加されれば、Cederqvist マニュアルの Working Directory Storage ノードにドキュメントされることと思います。また、コードから何か得たいと思ったら、ソースディストリビューション中の src/cvs.h を見てみるとよいでしょう。

最後に、 全ての CVS/* ファイルは、現時点においても将来においても、作業コピーの存在するシステムの改行コードを使用します(たとえば、Unix では LF、Windows では CRLF)。このことは、作業コピーをあるマシンから他のマシンへ移すと CVS はそれを扱えなくなるかもしれない、ということを意味します(でもそういう時には、リビジョン管理下にあるファイル自体の改行コードが新しい環境と不一致を起こすので、別の問題が起きるでしょう)。


Node:Repository Permissions, Previous:The Working Copy Administrative Area, Up:The Usual Suspects

Repository Permissions

CVS はリポジトリのパーミッションについてなにか特定の規則を要求したりはしません。幅広く、さまざまなパーミッションの配置を扱うことが出来ます。しかし、わけの分からない動作を避けたければ、少なくとも以下に述べる基準を満たすようリポジトリをセットアップするべきです:


Node:General Troubleshooting Tips, Next:, Previous:The Usual Suspects, Up:Tips And Troubleshooting

General Troubleshooting Tips

この章は問いと答えをつなげていく形式になっています。インターネットの FAQ (Frequently Asked Questions; よくある質問集) と同じような感じです。すべて、実際に CVS を使用した経験に基づいたものです。個別の例を見ていく前に、もう少し一般的な観点から CVS のトラブルシューティングについて見ておきましょう。

通常、CVS で起きた問題を解決する最初のステップは、それが作業コピーの問題なのか、それともリポジトリの問題なのかを見分けることです。これを見分けるには(別に驚くような方法でもないのですが)、最初に問題の起こった作業コピーとは別の作業コピーで同じ問題が起こるかどうかを見ることです。起こるようならリポジトリの問題で、そうでなければ多分局所的な問題なのです。

作業コピーの問題のほうがよく起こりがちです。それは作業コピーがリポジトリに比べてなにか信頼性が落ちるからとかではなくて、各リポジトリに対して作業コピーはたくさんあるからです。作業コピーのもつれをほどくのがイヤになったら、その作業コピーを消してしまって新しいやつをチェックアウトするほうが時間の無駄にならずに済む場合もあると思います。

もう一度チェックアウトするのにすごく時間がかかるとか、作業コピーになくしたくない未コミットの変更があったり、またはなんでうまくいかないのかをただ知りたい場合でも、問題の原因を探る価値はあります。探り始める時に最初にみるべきところの1つは CVS/ サブディレクトリです。その中のファイルの内容とパーミッションをチェックして下さい。たまにですが、リードオンリーになっていたり、または読むことさえできなくなっていたりします。(これについては、CVS が間違えたというよりもむしろユーザが Unix コマンドをミスタイプしたことが原因ではないかと筆者は疑っています)

リポジトリの問題はたいていの場合、ファイルやディレクトリのパーミッションが間違っていることが原因です。リポジトリのパーミッションが原因だと思ったら、まずトラブルのあった人の実効リポジトリユーザIDを確認して下さい。ローカルユーザ全て及び大部分のリモートユーザに関しては、これは通常のユーザ名か、作業コピーをチェックアウトする時に指定したユーザ名です。 pserver メソッドのユーザエイリアシング( Repository AdministrationAnonymous Access を参照のこと)を使っている場合には、実効ユーザIDは CVSROOT/passwd ファイルの右側に書いてあるものになります。早い時期にこれを見つけておかないと、間違ったところをデバッグすることになってしまい、時間を無駄にしてしまうことがあります。

さてさて、くだらないことをいうのはこれくらいにして…


Node:Some Real Life Problems (With Solutions), Previous:General Troubleshooting Tips, Up:Tips And Troubleshooting

Some Real Life Problems (With Solutions)

これらの状況はすべて、筆者が CVS トラブルシューターとして実際に体験したものばかりです(それと、実は問題ではないのですがよく訊ねられる質問も、答えとともに付け加えておきました)。このリストはとても広範囲に渡るもので、以前の章で見たことでももう一度出てきます。

状況は起こりやすい順に並べました、最も一般的なものが初めに来ています。


Node:CVS says it is waiting for a lock; what does that mean?, Next:, Up:Some Real Life Problems (With Solutions)

CVS says it is waiting for a lock; what does that mean?

CVS が「ロック待ち中」って言うんですが、どういう意味?

このようなメッセージを見たら

cvs update: [22:58:26] waiting for qsmith's lock in /usr/local/newrepos/myproj

これは、その時点でほかの CVS プロセスにロックされているリポジトリ中のサブディレクトリをアクセスしようとしている、という意味です。そのディレクトリ内でプロセスが実行中なので、他の CVS プロセスが使うには一貫性のない状態になっているかもよ、ということです。

しかし、そのメッセージが長い間出ているようなら、何らかの理由で CVS プロセスが後始末しないで失敗に終わってしまったことを示します。CVS が突然、予期しないでお亡くなりになった時、たとえばリポジトリのあるマシンの電源が落ちたとか、そういう時に起こり得ます。

解決法は、問題のリポジトリサブディレクトリから、手でロックファイルを取り除くことです。リポジトリの該当部分に移って、#cvs.lock という名前か、#cvs.wfl#cvs.rfl で始まる名前のファイルを探してください。ファイルのタイムスタンプを今実行中の CVS プロセスの起動時刻と比較してください。ファイルがそれらのプロセスに生成されたのでないようならば、それを削除しても安全です。待機中の CVS プロセスはロックファイルがなくなったと告げて(30秒ほどかかります)、要求されたオペレーションを続行できるようになります。

詳しくは Cederqvist マニュアルの Locks ノードを参照してください。


Node:CVS claims a file is failing Up-To-Date check; what do I do?, Next:, Previous:CVS says it is waiting for a lock; what does that mean?, Up:Some Real Life Problems (With Solutions)

CVS claims a file is failing Up-To-Date check; what do I do?

CVS に、あるファイルが Up-To-Date チェックに失敗しましたと言われました。どうしたらいいの?

パニックにならないで。これは単に、あなたが最後にチェックアウトやアップデートしたよりあとに、リポジトリ中のそのファイルが変更されたことを示しているだけです。そのファイルについて cvs update を実行し、リポジトリから変更分をマージしてください。受け取った変更がローカルの変更とコンフリクトした場合はファイルを編集してコンフリクトを解消してください。そのあと、もう一度コミットしてください。成功するはずです。マージするのに忙しくしている間に他の人が別のリビジョンをコミットする可能性を除けば。


Node:The pserver access method is not working, Next:, Previous:CVS claims a file is failing Up-To-Date check; what do I do?, Up:Some Real Life Problems (With Solutions)

The pserver access method is not working

pserver アクセスメソッドが動きません。

この問題の、最も一般的で、でもちょっとわかりにくい原因は、 inetd の設定ファイル中に --allow-root オプションでリポジトリを書くのを忘れた、というやつです。 Repository Administration で述べた、/etc/inetd.conf のこの例を思い出してください:

cvspserver stream tcp nowait root /usr/local/bin/cvs cvs \
          --allow-root=/usr/local/newrepos pserver

(実際のファイルではこれはひとつの長い行で、バックスラッシュはありません)

--allow-root=/usr/local/newrepos の部分はセキュリティ上の制限で、外部へ提供するつもりのないリポジトリへ pserver アクセスされないようにするためのものです。pserver 経由でアクセスさせるリポジトリは全部、--allow-root に書いておかなければなりません。あなたのシステムのリポジトリを、必要な数だけ --allow-root オプションで書いてください(inetd の引数の制限に達するまで、いくらでもたくさんかけます)。

パスワード認証サーバについて、詳しくは Repository Administration を参照してください。


Node:The pserver access method is STILL not working, Next:, Previous:The pserver access method is not working, Up:Some Real Life Problems (With Solutions)

The pserver access method is STILL not working

pserver アクセスメソッド、まだ動かないんですけど…

はいはい、--allow-root を書かなかったのが原因でなかった場合、可能性は他にもいくつかあります:


Node:My commits seem to happen in pieces instead of atomically, Next:, Previous:The pserver access method is STILL not working, Up:Some Real Life Problems (With Solutions)

My commits seem to happen in pieces instead of atomically

コミットがアトミックにではなく、バラバラにおこなわれるようです

それは CVS がコミットをアトミックにでなく、バラバラにおこなっているからです。:-)

もっと言うと、CVS のオペレーションはディレクトリごとにおこなわれます。複数のディレクトリにわたるコミットを実行したとき(あるいはアップデートやなんか(or an update, or anything else, for that matter))、CVS は順々に各リポジトリディレクトリ中でオペレーションを実行する間、そのディレクトリにロックをかけます。

小〜中サイズのプロジェクトでは、これが問題になることはめったにありません。CVS は各ディレクトリ中での処理を素早くおこなうので、ユーザはその非アトミック性に気づいたりはしないでしょう。しかしあいにく大きなプロジェクトでは次の筋書きのようなきおとが起こり得ます(これが深くてファイルのたくさんあるサブディレクトリを少なくとも2つ(A と B)持つプロジェクトで起こる、と想像してください):

  1. ユーザ qsmith がコミットを始めます。そのコミットには両方のサブディレクトリ中のファイルを含んでいます。CVS はまず B のなかのファイルをコミットします(おそらく qsmith はコマンドライン中でその順にディレクトリを指定したのでしょう)
  2. ユーザ jrandom がアップデートを始めます。アップデートは何らかの理由で作業コピーのディレクトリAから始まりました(CVS はディレクトリやファイルをどの順で処理するかについては何の保証もしません、if left to its own devices)。qsmith はまだ A では作業していませんので、ロックは問題ではないことに注意してください。
  3. そして qsmith が B のコミットを終え、A に移り、A を終えます。
  4. 最後に jrandom が B に移り、アップデートを終えます。

全部終わった時点で、jrandom の作業コピーには qsmith の B の変更は反映されている一方 A の変更が反映されていないことは明らかです。qsmith がそれらの変更をひとつの単位としてコミットしたつもりだったとしても、実際はそのようにはなりませんでした。jrandom の作業コピーは qsmithが予想もしない状態になってしまっています。

もちろん、jrandom がもう一度 cvs update して、さっき取って来れなかった qsmith のコミット分の変更を取って来れば、問題は解決します。しかしそれは、最初に qsmith の変更のうち一部分しか取って来なかったことを知るすべがあれば、の話です。

この quandary に容易な答はありません。ただ、作業コピーが一貫性のない状態になっていることがどうにかして明らかになることを祈るほかありません(そのソフトウェアがビルドできないかもしれませんし、何が起こったのかを認識するまで jrandom と qsmith が混乱した会話をすることになるかもしれません)。

一般に、CVS がアトミックなトランザクションを保証できていないについてはバグだと考えられています。ロックがリポジトリのトップレベルに作られないただひとつの理由というのは、開発者が大勢いる大きなプロジェクトではロックの競合が耐えられないくらい頻繁に起こるであろうということです。従って、CVS は2つの害のうち小さなほうを選びました。競合の頻度を減らすかわりに、とびとびに読んだり書いたりする可能性を許した、ということです。そのうち誰かが CVS を改良し(たとえばリポジトリオペレーションの速度を上げるなどして)、2つの害のどちらけを選んだりしなくてもよくなるかもしれません。それまでは、ノンアトミックな動作に甘んじることになるでしょう。

より詳しいことを知るためには、 Cederqvist マニュアルの Concurrency を参照してください。


Node:CVS keeps changing file permissions; why does it do that?, Next:, Previous:My commits seem to happen in pieces instead of atomically, Up:Some Real Life Problems (With Solutions)

CVS keeps changing file permissions; why does it do that?

CVS がファイルのパーミッションを変更しつづけるんです; なんでそんなことをするんでしょうか?

一般に、 CVS はファイルのパーミッションを保存するについてはあまり良い仕事をしません。プロジェクトをインポートしたあと、それをチェックアウトする場合、新しい作業コピーのファイルのパーミッションが、インポートされたときと同じである保証はありません。作業コピー中のファイルが、普通にファイルを新しく作ったときの標準パーミッションと同じ、ということのほうがありそうです。

しかし、例外が少なくともひとつあります。プロジェクト内に実行可能なシェルスクリプトを保存したい場合には、対応するリポジトリのファイルを実行可能にしておけば、全ての作業コピーでそのファイルを実行可能に保つことができます:

floss$ ls -l /usr/local/newrepos/someproj
total 6
-r--r--r--   1 jrandom  users         630 Aug 17 01:10 README.txt,v
-r-xr-xr-x   1 jrandom  users        1041 Aug 17 01:10 scrub.pl,v*
-r--r--r--   1 jrandom  users         750 Aug 17 01:10 hello.c,v

そのファイルは実行可能ですが、リポジトリ中のファイルがすべてそうであるように、読み出し専用になっているままです(CVS は RCS ファイルの一時的なコピーを作って作業し、そのコピーの中ですべてをおこなってから、準備ができたらそのコピーをオリジナルと置き換える、ということを思い出してください)。

実行可能ファイルをインポートまたは追加した場合には、CVS は実行可能ビットを保存しますので、ファイルが最初から正しいパーミッションならばなにも心配することはありません。ですがたまたま、実行可能にする前にファイルを追加してしまったら、リポジトリ内に移って、RCS ファイルのパーミッションを手で実行可能にしてください。

リポジトリのパーミッションのほうがつねに優先されます。リポジトリ内のそのファイルが実行可能ではなくて、作業コピー中では実行可能だった場合、アップデート後には作業コピーのそのファイルは実行可能ではなくなってしまいます。自分のファイルのパーミッションが黙って変更されてしまうというのは非常にいらだたしいことです。これが起こったら、まずリポジトリをチェックし、対応する RCS ファイルを適切なパーミッションに設定して解決するかどうか見てみてください。

最近、CVS に PreservePermissions という機能が追加されました。この機能はこのような問題を少しは軽減するかもしれません。しかし、この機能を使うと、考えもしなかった他の結果をうむことになるかもしれません(無条件にこの機能を推薦しなかったのはこれが理由です)。CVSROOT/config に PreservePermissions=yes を書く前に、Cederqvist マニュアルの configSpecial Files のノードをよく読んでくださいね。


Node:CVS on Windows complains it cannot find my .cvspass file; why?, Next:, Previous:CVS keeps changing file permissions; why does it do that?, Up:Some Real Life Problems (With Solutions)

CVS on Windows complains it cannot find my .cvspass file; why?

Windows 上の CVS に、.cvspass ファイルが見つからないといって怒られました。どうして?

pserver 接続時に、クライアント側の CVS はホームディレクトリにある .cvspass ファイルを探そうとします。Windows マシンはもともとはホームディレクトリを持っていないので、CVS は %HOME% 環境変数を見にいきます。しかし、HOME を設定するについては非常に慎重でなければなりません。これは動きます:

set HOME=C:

これは動きません:

set HOME=C:\

最後の余分なバックスラッシュが CVS を混乱させてしまい、 C:\\.cvspass がオープンできない、ということになってしまうのです。

ですから、すばやくて不変の解決法は、autoexec.bat に

set HOME=C:

と書いてリブートすることです。こうすれば CVS の pserver は動くでしょう。


Node:My working copy is on several different branches; help?, Next:, Previous:CVS on Windows complains it cannot find my .cvspass file; why?, Up:Some Real Life Problems (With Solutions)

My working copy is on several different branches; help?

わたしの作業コピーはいくつかの別々のブランチの上にいます。どうすればいいですか?

作業ディレクトリの別々のサブディレクトリが、どういうわけか別のブランチにある、という意味でしょうか? おそらく作業コピーのトップレベルではないところで、 -r フラグをつけてアップデートをしてしまったのでしょうね。

たいしたことではありません。トランクに戻りたければ、トップディレクトリでこれを実行してください

cvs update -r HEAD

またはこれです

cvs update -A

あるいは、作業コピー全体をひとつのブランチ上のものにしたければ、こうしてください:

cvs update -r Branch_Name

あるディレクトリをブランチにあげて一時的な作業をする必要があるのなら、作業コピーのなかのサブディレクトリが1つ2つ、他と違うブランチになっていてもべつに構わないと思います。でも、普通は終わったら元に戻しておいたほうがいいでしょう。作業コピー全体が同じ開発ライン上にいるほうが、混乱がうんと少ないですから。


Node:When I do export -d I sometimes miss recent commits, Next:, Previous:My working copy is on several different branches; help?, Up:Some Real Life Problems (With Solutions)

When I do export -d I sometimes miss recent commits

export -d した時に、最近のコミット分がないことがあるんです。

これはリポジトリマシンとローカルマシンの時計がずれているためです。片方あるいは両方の時計を合わせ直すか、-D の引数に日付を指定すれば解決します。未来の日付を指定しても(-D tomorrow など)受け付けられますので、時刻のずれの分を補正できます。


Node:I get an error about val-tags; what should I do?, Next:, Previous:When I do export -d I sometimes miss recent commits, Up:Some Real Life Problems (With Solutions)

I get an error about val-tags; what should I do?

val-tags に関するエラーが起きたようなんですが、どうすればいいですか?

このようなエラーが起きたら:

cvs [export aborted]: cannot write /usr/local/myproj/CVSROOT/val-tags: \
   Operation not permitted

CVSROOT/val-tags ファイルに書く権限のないユーザが CVS を実行しているという意味です。このファイルには有効なタグ名が書いてあって、どのタグが有効かを素早く調べるためのものです。あいにく、リポジトリに関して読み込み専用(例えばプロジェクトをチェックアウトするなど)のオペレーションをした時でも、CVS はこのファイルを変更することがあります。

これは CVS のバグで、これを読んでいる頃には多分解決されていると思います。それまでは val-tags ファイルをワールドライタブルにするか、それができなければそのファイルを削除するか、またはオーナを変更して CVS オペレーションを実行するユーザにするかしてください。(パーミッションを変更するだけでいいと思うかもしれませんが、いくつかの事例ではオーナも変更する必要がありました。)


Node:I am having problems with sticky tags; how do I get rid of them?, Next:, Previous:I get an error about val-tags; what should I do?, Up:Some Real Life Problems (With Solutions)

I am having problems with sticky tags; how do I get rid of them?

スティッキータグで困っています; どうやって取り除けばよいでしょう?

さまざまな CVS オペレーションが、作業コピーに スティッキータグ を設定します。各ファイルの各リビジョンに対応しているタグを意味します。(ブランチの場合は作業コピーに新しく追加されたファイルすべてにスティッキータグが適用されます。) タグか日付を指定してチェックアウトやアップデートを行った場合、作業コピーにスティッキータグが設定されます。例えば:

floss$ cvs update -r Tag_Name

あるいは

floss$ cvs checkout -D '1999-08-16'

日付かノンブランチタグ名が指定された場合には、作業コピーはプロジェクト履歴中のその時点で凍結したスナップショットになります。ですので当然、そこから変更をコミットしたりはできないのです。

スティッキータグを除去するには、-A フラグ付きでアップデートを実行してください

floss$ cvs update -A

こうすればスティッキータグはすべてクリアされ、各ファイルは最新のトランクのリビジョンに更新されます。


Node:Checkouts/updates exit with error saying cannot expand modules, Next:, Previous:I am having problems with sticky tags; how do I get rid of them?, Up:Some Real Life Problems (With Solutions)

Checkouts/updates exit with error saying cannot expand modules

チェックアウト/アップデートがモジュールを展開できないと言って、エラーで終了します。

これは CVS が悪いエラーメッセージを出す一例ですね。そのうち誰かが直すと思いますが、しばらくは困りますね。このエラーメッセージはこのような感じで出ます:

floss$ cvs co -d bwf-misc user-space/bwf/writings/misc
cvs server: cannot find module `user-space/bwf/writings/misc' - ignored
cvs [checkout aborted]: cannot expand modules

CVS は CVSROOT/modules ファイルが何かおかしいといっているように見えます。が、本当はリポジトリ内のパーミッションの問題なのです。チェックアウトしようとしたディレクトリか、その親ディレクトリのうちのどれかが読み出し可能になっていないのです。今回の場合は親ディレクトリでした:

floss$ ls -ld /usr/local/cvs/user-space/bwf

drwx------  19 bwf      users        1024 Aug 17 01:24 bwf/

ひどく間違ったこのエラーメッセージには惑わされないで下さい、これはリポジトリのパーミッションの問題なのです。


Node:I cannot seem to turn off watches, Next:, Previous:Checkouts/updates exit with error saying cannot expand modules, Up:Some Real Life Problems (With Solutions)

I cannot seem to turn off watches

監視をオフにできないみたいなんですけど…。

おそらく、全てのファイルに対して

floss$ cvs watch remove

とやったのでしょうけれど、これを実行するのを忘れたんじゃないですか:

floss$ cvs watch off

監視についての問題を診断する時のヒント: リポジトリに移って、 CVS/fileattr ファイルを直接見れば、なにもかもすっかり明らかになることがあります。詳しくは Repository Administration を参照してください。


Node:My binary files are messed up, Next:, Previous:I cannot seem to turn off watches, Up:Some Real Life Problems (With Solutions)

My binary files are messed up

バイナリファイルがむちゃくちゃになってしまいました。

そのファイルを追加した時に -kb オプションをつけるのを忘れたりしませんでしたか? 忘れている場合、 CVS はそのファイルに対し改行コード変換や RCS キーワード展開を施してしまいます。一番簡単な解決法は通常、そのファイルをバイナリとしてマークして、

floss$ cvs admin -kb foo.gif

そのあと、直してあるそのファイルをコミットします。CVS はそれがバイナリファイルだということをもう知っているので、新しくコミットしたものやそれ以降コミットしたものを CVS が台無しにしてしまうことはないでしょう。


Node:CVS is not doing line-end conversion correctly, Next:, Previous:My binary files are messed up, Up:Some Real Life Problems (With Solutions)

CVS is not doing line-end conversion correctly

改行コードの変換が正しくおこなわれないようなのですが。

CVS クライアントを Unix でないプラットフォームで実行していて、作業コピー中のファイルの改行コード変換がおこなわれていないようならば、きっとそれは知らない間に -kb オプションつきで追加されてしまったんじゃないかと思います。リポジトリの問題は以下のコマンドで解決できます。

floss$ cvs admin -kkv FILE

-kkv というのは、通常のキーワード展開をし、通常の改行コード変換もおこなう、という意味です。(内部的な話をすると、 CVS はキーワード展開と改行コード変換の違いについて少々混同しているところがあります。-k オプションで両方をコントロールできるあたりにそれが現れています。)

あいにく、admin コマンドではリポジトリ内のファイルしか直せません。作業コピーは相変らずそのファイルをバイナリだと思っています。 CVS/Entries のそのファイルの行を手で編集して -kb を削除すればよいのですが、それで他の作業コピーの問題まで解決するわけではありません。


Node:I need to remove a subdirectory in my project; how do I do it?, Next:, Previous:CVS is not doing line-end conversion correctly, Up:Some Real Life Problems (With Solutions)

I need to remove a subdirectory in my project; how do I do it?

プロジェクト内のサブディレクトリを削除しなければいけないのですが、どうやればいいですか?

そうですね、サブディレクトリを完全に削除することはできないのですが、そのディレクトリの中のファイルをすべて削除することはできますよね(まずファイルを削除し、cvs remove して、コミットしてください)。ディレクトリが空になったら、-P フラグをつけてアップデートすると作業コピー中のそのサブディレクトリが自動的に刈り込まれます。


Node:Can I copy .cvspass files or portions of them?, Next:, Previous:I need to remove a subdirectory in my project; how do I do it?, Up:Some Real Life Problems (With Solutions)

Can I copy .cvspass files or portions of them?

.cvspass ファイルや、その一部をコピーしても構いませんか?

はい、構いませんよ。.cvspass ファイルをマシン間でコピーしても構いませんし、各行を個別にコピーしても大丈夫です。サーバの待ち時間が大きいような場合には作業コピーマシンから cvs login を実行するより、この方法を取るほうが早いと思います。

.cvspass を改行コードの異なる2つのマシン間で転送すると動かないということに注意してください(もちろん、手で改行コードを変換してもかまいません、そんなに手間がかかるわけではないでしょうからね)。


Node:I just committed some files with the wrong log message, Next:, Previous:Can I copy .cvspass files or portions of them?, Up:Some Real Life Problems (With Solutions)

I just committed some files with the wrong log message

ファイルをいくつか、間違ったログメッセージでコミットしてしまいました。

これを解決するのに、リポジトリを手で編集する必要はありません。admin コマンドを -m フラグ付きで実行すれば直ります。-m と引数の間には空白を入れないということと、置き換えるログメッセージをクオートで囲むことを忘れないようにして下さい:

floss$ cvs admin -m1.17:'I take back what I said about the customer.' hello.c


Node:I need to move files around without losing revision history, Next:, Previous:I just committed some files with the wrong log message, Up:Some Real Life Problems (With Solutions)

I need to move files around without losing revision history

リビジョン履歴をなくさないでファイルを移動したいんですが。

リポジトリ内で、プロジェクトの中のお望みの新しい場所に RCS ファイルをコピーしてください(移動はしないで下さい)。古いほうの場所にも残しておかなければだめです。次に、作業コピー中でこのようにしてください:

floss$ rm oldfile1 oldfile2 ...
floss$ cvs remove oldfile1 oldfile2 ...
floss$ cvs commit -m "removed from here" oldfile1 oldfile2 ...

これ以降にアップデートをすると、CVS は古いファイルを消し、通常の方法でリポジトリに加えた時と同じように、新しいファイルを作業コピー中に持ってきます(普通よりリビジョン番号が高いところは、新しいファイルを加えた時と違います)。


Node:How can I get a list of all tags in a project?, Next:, Previous:I need to move files around without losing revision history, Up:Some Real Life Problems (With Solutions)

How can I get a list of all tags in a project?

プロジェクト中のタグの一覧が欲しいんですけどどうすればよいですか?

現在の CVS では簡単にこれを実現する方法はありません。ユーザみんながこれがないことで困っているので、この機能を実現するべく作業が進行中だと思います。あなたがこれを読んでいる頃までには cvs tags コマンドかそれに似たようなものが使えるようになっているでしょう。

Until then, there are workarounds. cvs log -h を実行し、結果出力のなかの symbolic names: のあとを読んでください。あるいは、リポジトリマシン上にログインしているならリポジトリ中の RCS ファイルの冒頭を読めばわかると思います。タグが全部(ブランチタグもノンブランチタグも) symbols フィールドにリストしてあります:

floss$ head /usr/local/newrepos/hello.c,v
head	2.0;
access;
symbols
	Release_1_0:1.22
	Exotic_Greetings-2:1.21
	merged-Exotic_Greetings-1:1.21
	Exotic_Greetings-1:1.21
	merged-Exotic_Greetings:1.21
	Exotic_Greetings-branch:1.21.0.2
	Root-of-Exotic_Greetings:1.21
	start:1.1.1.1
	jrandom:1.1.1;
locks; strict;
comment	@ * @;


Node:How can I get a list of all projects in a repository?, Next:, Previous:How can I get a list of all tags in a project?, Up:Some Real Life Problems (With Solutions)

How can I get a list of all projects in a repository?

リポジトリ中のプロジェクト全部の一覧はどうやれば見ることが出来ますか?

タグのリストと同じように、CVS の現在のバージョンでは実装されていません。が、多分すぐ実装されるんじゃないかと思います。多分そのコマンドは cvs list (省略形式だと cvs ls)で、modules ファイルとリポジトリのサブディレクトリの両方を解釈するようなものになるでしょう。

今のところはとりあえず(直接見るか cvs checkout -c を実行するかして) CVSROOT/modules ファイルを調べるのが多分一番いいと思います。ただし、誰もプロジェクトに対してモジュールを明示的に指定していない場合にはそこには何も示されていないでしょう。


Node:Some commands fail remotely but not locally; how should I debug?, Next:, Previous:How can I get a list of all projects in a repository?, Up:Some Real Life Problems (With Solutions)

Some commands fail remotely but not locally; how should I debug?

リモートで失敗するコマンドがあるんですが、ローカルでは失敗しません; どうデバッグするべき?

クライアントとサーバ間の通信に問題が存在することがあります。もしそうならそれは CVS のバグなんですが、さてそれをどうやって追跡すればよいのでしょうか?

CVS ではクライアントとサーバ間のプロトコルを監視する方法があります。ローカル(作業コピーのある)マシン上でコマンドを動かす前に、 CVS_CLIENT_LOG 環境変数をセットしてください。Bourne シェルでは次のように設定します:

floss$ CVS_CLIENT_LOG=clog; export CVS_CLIENT_LOG

CVS はこの変数がセットされると、クライアントとサーバの間の通信を、ファイル2つにすべて記録します。ファイル名はその環境変数に設定された値に基づいたものになります:

floss$ ls
CVS/        README.txt    a-subdir/    b-subdir/    foo.gif     hello.c
floss$ cvs update
? clog.in
? clog.out
cvs server: Updating .
cvs server: Updating a-subdir
cvs server: Updating a-subdir/subsubdir
cvs server: Updating b-subdir
floss$ ls
CVS/              a-subdir/    clog.in     foo.gif
README.txt        b-subdir/    clog.out    hello.c
floss$

clog.in ファイルにはクライアントがサーバに送信したもの全てが、 clog.out ファイルにはサーバがクライアントに送り返してきたもの全てが記録してあります。プロトコルがどんな感じに見えるのか示すために、 clog.out の内容をお見せします:

Valid-requests Root Valid-responses valid-requests Repository           \
Directory Max-dotdot Static-directory Sticky Checkin-prog Update-prog   \
Entry Kopt Checkin-time Modified Is-modified UseUnchanged Unchanged     \
Notify Questionable Case Argument Argumentx Global_option Gzip-stream   \
wrapper-sendme-rcsOptions Set expand-modules ci co update diff log add  \
remove update-patches gzip-file-contents status rdiff tag rtag import   \
admin export history release watch-on watch-off watch-add watch-remove  \
watchers editors init annotate noop
ok
M ? clog.in
M ? clog.out
E cvs server: Updating .
E cvs server: Updating a-subdir
E cvs server: Updating a-subdir/subsubdir
E cvs server: Updating b-subdir
ok

clog.in ファイルのほうはリビジョン番号や他のファイルごとの情報をサーバに送らなければならないので、もう少し複雑です。

クライアント/サーバのプロトコルを書くには紙幅が足りませんが、CVS と一緒に配布されている cvsclient という info ページに完全な解説が載っていますので読んでみてください。また、生のプロトコルを読むといろいろと分かると思います。問題の原因の他の可能性を全て除去するまでクライアントロギングを使う気分になれなかったとしても、クライアントとサーバの間で何が起こっているか探るにはとても価値のあるツールです。


Node:I do not see my problem covered in this chapter, Next:, Previous:Some commands fail remotely but not locally; how should I debug?, Up:Some Real Life Problems (With Solutions)

I do not see my problem covered in this chapter

この章にはわたしの直面している問題は載っていないようですが。

正確かつ完全に問題を記述し、CVS のメーリングリスト info-cvs@gnu.org にメールを送ってください。メンバーはいろいろなタイムゾーンに散らばっているので、いつも筆者が問題を送ってから1, 2時間で答えが返ってきます。 info-cvs-request@gnu.org にメールを送ってメーリングリストに加入してください。質問に答えてあげることもできます。


Node:I think I have discovered a bug in CVS; what do I do?, Next:, Previous:I do not see my problem covered in this chapter, Up:Some Real Life Problems (With Solutions)

I think I have discovered a bug in CVS; what do I do?

CVS のバグを見つけたと思うんですが; 何をすればいいですか?

CVS は完璧から程遠いです。マニュアルを読んで、メーリングリストに質問を投稿しても、それでもバグがあるように思うなら、多分それはバグなのでしょう。

バグを完全に記述したものを bug-cvs@gnu.org に送ってください(このメーリングリストに加入することもできます、 bug-cvs-request@gnu.org にメールを送ってください)。 CVS のバージョン番号と(クライアントのもサーバのも)、バグを再現する方法を書いてくださいね。

もしそのバグを直すパッチを書いたのならそれを同封し、メールのサブジェクトにパッチがあることを書いてください。メインテナはきっととても喜ぶと思います。

(Cederqvist マニュアルの BUGS ノードと、ソースディストリビューションの HACKING ファイルに、これらの手続きについて詳しく書いてあります。)


Node:I have implemented a new feature for CVS; to whom do I send it?, Next:, Previous:I think I have discovered a bug in CVS; what do I do?, Up:Some Real Life Problems (With Solutions)

I have implemented a new feature for CVS; to whom do I send it?

CVS の新しい機能を実装しました; 誰に送ればよいでしょう?

バグを送る時と同じです: bug-cvs@gnu.org にパッチを送ってください。でもまず HACKING ファイルをちゃんと読んでくださいね。


Node:How can I keep up with changes to CVS?, Previous:I have implemented a new feature for CVS; to whom do I send it?, Up:Some Real Life Problems (With Solutions)

How can I keep up with changes to CVS?

CVS の変更はどうやって追いかければよいのでしょうか?

この章で説明したトラブルシューティングのテクニックと既知のバグは、バージョン 1.10.7 時点の CVS では正しいです。でも CVS では物事の動きが速いのです。最近の数章を書いている間に、CVS の非公式な保守責任が Cyclic Software から SourceGear, Inc (http://www.sourcegear.com) に引き継がれました。SourceGear が Cyclic を買収したのです。SourceGear は CVS の保守に関して積極的にやっていくつもりであることをアナウンスし、Cyclic の承認を得ましたので、 which is more or less enough to make it the "lead maintainer" of CVS as of right now. (http://www.cyclic.com のアドレスは引き続き有効ですので、この本で今までに述べた URL は有効です)

現時点、SourceGear はそのへんに散らばっていたいろいろなパッチをまとめてきれいにするのに忙しいようです。それらの大半を CVS に組み込むつもりのようですから。そのなかにはこれまでに挙げてきたバグを直すパッチもありますし、CVS ユーザに新しいトラブルシューティングツールを提供するパッチもあります。

最新のところで何が起こっているかに追いつくための一番よい方法は、CVS のディストリビューション中にある NEWS ファイルを読み、メーリングリストを見て、Cederqvist マニュアルとこの本のオンラインバージョン(http://cvsbook.red-bean.com)の変更を探すことです。


Node:CVS Reference, Next:, Previous:Tips And Troubleshooting, Up:Top

CVS Reference

この章は CVS のコマンド、リポジトリの管理ファイル、キーワード展開、実行時制御ファイル、作業コピー、環境変数、についての完全なリファレンスです。すべてバージョン 1.10.7 時点の CVS に基づいています(正確には 1999/8/20 時点)。


Node:Commands And Options, Next:, Up:CVS Reference

Commands And Options

この節はすべての CVS コマンドのリファレンスです。CVS のコマンドほとんどに共通する書式の約束事がまだよくわからない場合には、個々のコマンドを調べる前に関連する節を読んでおいたほうがいいと思います。


Node:Organization And Conventions, Next:, Up:Commands And Options

Organization And Conventions

コマンドやオプションを調べやすくするため、この節はアルファベット順に並べてあります。以下の約束事があります:


Node:General Patterns In CVS Commands, Next:, Previous:Organization And Conventions, Up:Commands And Options

General Patterns In CVS Commands

CVS コマンドの形式は次のようになります:

cvs [GLOBAL_OPTIONS] COMMAND [OPTIONS] [FILES]

オプションのうち、2つめのまとまりのほうは、コマンドオプション と呼ばれることがあります。が、あまりにもたくさんありますので、場所の節約のため、筆者はただ「オプション」と呼ぶことのほうが多いです。

コマンドの多くは作業コピー中で実行されますので、ファイル引数なしで起動されるでしょう。このようなコマンドではデフォルトで、カレントディレクトリ以下全てのファイルについて実行されます。ですので説明の中で「ファイル」とあればそれは、CVS が動作している対象のファイルのことだと思ってください。CVS をどのように起動するかによって、これらのファイルはコマンドラインで明示的に指定されたりされなかったりします。


Node:Date Formats, Next:, Previous:General Patterns In CVS Commands, Up:Commands And Options

Date Formats

多くのオプションが日時の引数を取ります。CVS は実にさまざまな形式の日時を理解します。多すぎるのでここに一覧できません。自信がない時には、標準の ISO 8601 フォーマットを守ってください:

1999-08-23

これは 1999年8月23日を意味します(実は "23 August 1999" でも全く有効ですが、ダブルクオートで括ることをお忘れなく)。時刻も指定したければ、このようにできます:

"1999-08-23 21:20:30 CDT"

"now", "yesterday", "12 days ago" などの、ごく普通の英語の構文も使えます。一般に、日時のフォーマットは安全に実験できます。CVS があなたの言っている形式を理解できるなら、あなたの言うとおりに解釈するでしょう。理解できないならすぐエラーで終了します。


Node:Global Options, Next:, Previous:Date Formats, Up:Commands And Options

Global Options

CVS のすべてのグローバルオプションを以下に記します。

--allow-root=REPOSITORY

アルファベット順で最初に来るこのオプションは、実際にはコマンドラインで使用されることはありません。-allow-root オプションは pserver コマンドと共に使用して、アクセスを許可するリポジトリを指定します。指定は /usr/local/newrepos のようにリポジトリレベルで行います。 /usr/local/newrepos/myproj のようなプロジェクトサブディレクトリの指定はできません。

このグローバルオプションがコマンドラインで使用されることはありません。通常は /etc/inetd.conf ファイルの中でのみ用いられるものです(Repository Administration 参照)。pserver コマンドが使用されるのもそこだけです。

cvs pserver 経由でアクセスするリポジトリについては、対応する -allow-root オプションをそのホストの /etc/inetd.conf 中に指定しておく必要があります。これはセキュリティのための仕組みで、プライベートなリポジトリに pserver 経由のアクセスをさせないようにするものです。

(The Password-Authenticating Server と、Cederqvist マニュアルの Password Authentication Server ノードを参照してください。)

-a

サーバとの通信すべてを認めます。このオプションは GSSAPI サーバ (gserver) 経由で接続している場合以外には意味はありません。GSSAPI はまだあまり使われていないと思われますので、この本で説明していません(そのうち使われるようになるとは思うのですが)。(詳細は Cederqvist マニュアルの Global OptionsGSSAPI Authenticated を参照のこと。)

-b (Obsolete)

以前はこのオプションで、RCS のバイナリファイルのあるディレクトリを指定していました。現時点で CVS は RCS の機能を内部的に実装していますので、このオプションに意味はありません(互換性のために残してあるだけです)。

-d REPOSITORY

リポジトリを指定します。絶対パスであったり、あるいは接続方法・ユーザ名・ホスト名・パスを含んだ複雑なものである場合もあります。接続方法を指定する場合、構文は次のとおりです:

:METHOD:USER@HOSTNAME:PATH_TO_REPOSITORY

各接続方法について例を示します:

-e EDITOR

コミットメッセージが -m オプションで指定されない場合、コミットメッセージを書くためのエディタとして EDITOR を起動します。普通は -m でのメッセージ指定を行わなかった場合、CVS は $CVSEDITOR$VISUAL$EDITOR 環境変数をこの順でチェックし、その結果に基づいてエディタを起動します。これでうまくいかなかったときには Unix 上のポピュラーなエディタ、vi を起動します。

Invokes EDITOR for your commit message, if the commit message was not specified on the command line with the -m option. Normally, if you don't give a message with -m, CVS invokes the editor based on the $CVSEDITOR, $VISUAL, or $EDITOR environment variables, which it checks in that order. Failing that, it invokes the popular Unix editor vi.

コミット時に -e グローバルオプションと -m オプション両方を渡した場合には、 コマンドラインで指定したコミットメッセージを優先し、-e は無視されます(ですので .cvsrc ファイルで -e を指定しておいも安全です)。

-f

このグローバルオプションを指定すると .cvsrc ファイルを読みません。

--help [COMMAND] or -H [COMMAND]

これら2つのオプションは同義のものです。 COMMAND が指定されない場合、基本的な使用法が標準出力に出力されます。COMMAND が指定された場合はそのコマンドの使用法が出力されます。

--help-options

CVS の全グローバルオプション一覧を短い説明つきで出力します。

--help-synonyms

CVS コマンドの一覧を省略形つきで出力します(update は up だとか、そういうやつです)。

-l

このコマンドをリポジトリ内の CVSROOT/history ファイルに記録しません。コマンドは通常どおり実行されますが、history ファイルに記録が残りません。

-n

作業コピー内、リポジトリ内のファイルを一切変更しません。つまりそのコマンドは「ドライラン」(dry run) で実行するということです。CVS はそのコマンドのほとんどの手順を踏みますが、実際に実行する手前で中止します。

そのコマンドが何をするのか、実際には実行しないで知りたい場合に便利です。自分の作業ディレクトリ中でどのファイルを変更したんだったか知りたいけど、アップデートしたいわけじゃない(リポジトリから変更分を持ってきちゃうから)、というような話はよくあることです。cvs -n update を実行すれば、作業コピー中でファイルを変えることなく、ローカルで何をしたか概要がわかります。

-q

あまり重要でないメッセージの出力を止めて、適当に静かに動作するよう CVS に指示します。何が「重要」かはコマンドによって違います。たとえば、アップデート中の作業コピーの各サブディレクトリへの移動(通常は出力される)については出力されませんが、変更されたファイル、アップデートされたファイルについての情報は出力されます。

-Q

コマンドを実行完了するために絶対必要なもの以外は出力せず、非常に静かに動作するよう CVS に指示します。出力を生成することが唯一の目的であるようなコマンド(diffannotate など)は出力をやめたりしませんが、出力するメッセージとコマンドの効果が関係ないようなコマンド(updatecommit など)は何のメッセージも出力しません。

-r

新しく作る作業ファイルを読み込み専用にします($CVSREAD 環境変数を設定するのと同様の効果)。

このオプションを指定した場合、チェックアウト・アップデートすると作業コピー中のファイルが読み込み専用になります(OSが許せば)。率直に言って筆者はこのオプションを使いたいような理由を思いつきません。

-s VARIABLE=VALUE

変数名 VARIABLE の CVS の内部変数の値を VALUE に設定します。

リポジトリ側の CVSROOT/*info トリガファイルはこのような変数を -s オプションで代入された値に展開することができます。たとえば、 CVSROOT/loginfo に次のような行があったとします:

myproj  /usr/local/bin/foo.pl ${=FISH}

ここで誰かが myproj の作業コピーから、次のようにコミットしたとすると

floss$ cvs -s FISH=carp commit -m "fixed the bait bug"

foo.pl スクリプトは carp を引数として起動されます。ただし書式には注意してください、ドルマーク、イコール、中括弧すべて必要です。一つでも欠けると展開されません(少なくとも思ったようにはなりません)。変数名には英数字とアンダスコアが使用できます。すべてを大文字にする必要はないのですが、そのようにする人が大多数です。

ひとつのコマンド中に -s フラグを何回使ってもかまいません。しかし、トリガスクリプト中で参照されている変数を CVS の起動時に指定しなかった場合、コマンド自体は成功したとしても変数展開は行われず、警告メッセージが出力されます。たとえば loginfo にこのようなエントリがあって、

myproj  /usr/local/bin/foo.pl  ${=FISH}  ${=BIRD}

前述と同様のコマンドが実行された場合には

floss$ cvs -s FISH=carp commit -m "fixed the bait bug"

このコマンドを実行したユーザに対して次のような警告が出力され(最後のほうに出力されます)、

loginfo:31: no such user variable ${=BIRD}

この後 foo.pl スクリプトが引数無しで起動されます。ですが、以下のようにコマンドを実行した場合には

floss$ cvs -s FISH=carp -s BIRD=vulture commit -m "fixed the bait bug"

loginfo 中の ${=FISH}${=BIRD} は両方正しく展開され、警告も出力されません。どちらの場合でもコミットそのものは成功するはずです。

これらの例ではすべて commit を使用しましたが、変数展開は CVSROOT/ のトリガファイルで書ける CVS コマンドならどれでも変数展開できます。ですから -s オプションはグローバルオプションなのです。

(このあとこの章に出てくる Repository Administrative Files の節で、トリガファイル内での変数展開について詳しく述べます)

-T DIR

テンポラリファイルを、普通 CVS が置く場所ではなく DIR に置きます。(とりわけ $TMPDIR 環境変数の値より優先されることに注意してください) DIR は絶対パス指定でなければなりません。

このオプションは、通常のテンポラリディレクトリへの書込み権限をあなたが(and, therefore, CVS doesn't either)持っていない場合に便利です。

-t

CVS コマンドの実行をトレースします。そのコマンドを完遂するまでに通過する各ステップについてのメッセージを出力します。-n オプションと合わせて使うと、まだ良くわかっていないコマンドの動作を実際に動作させる前に確認できて便利です。また、あるコマンドが失敗する時、その原因を調べたい場合にも使えます。

-v or --version

バージョンと著作権情報を表示し、エラーなしで終了します。

-w

$CVSREAD 環境変数

作業ファイルを読み書きできるファイルとして生成します($CVSREAD 環境変数より優先されます)。デフォルトで読み書きできるファイルが生成されますので、このオプションはめったに使用されません。

-r と -w 両方を指定した場合には -w のほうが優先されます。

-x

サーバとの通信内容をすべて暗号化します。このオプションは GSSAPI サーバ(gserver) 経由で接続している場合以外は意味がありません。 GSSAPI 接続はまだめったに使われていませんので、この本では説明していません。(詳しくは Cederqvist マニュアルの Global OptionsGSSAPI Authenticated を参照のこと)

-z GZIPLEVEL

サーバとの通信で使用する圧縮のレベルを指定します。GZIPLEVEL 引数は 1 から 9 の数字です。レベル1は最小限の圧縮(とても速いですがあまり圧縮されません)、レベル9は最大限の圧縮です(CPU時間を食いますがデータは確実に圧縮されます)。レベル9はネットワーク接続が非常に遅い場合にしか役に立たないでしょう。通常は3から5あたりが妥当だと思われます。

-z と引数の間の空白はあってもなくてもかまいません。


Node:add, Next:, Previous:Global Options, Up:Commands And Options

add

Synopsis: add [OPTIONS] FILES

既存のプロジェクトにファイルを追加します。確認のためリポジトリに接続はしますが、そのファイルが実際にリポジトリに現れるのは commit が行われてからです。(removeimport も参照してください。)

オプション:


Node:admin, Next:, Previous:add, Up:Commands And Options

admin

Synopsis: admin [OPTIONS] [FILES]

このコマンドはさまざまな管理作業のためのインタフェースです。とりわけ、キーワード展開のモードを変更したり、コミットしてしまったあとにログメッセージを変更したりするような、リポジトリ内の個別のRCSファイルを対象とする作業を受け持ちます。

引数にファイルを指定しなければ admin は(カレントディレクトリから)再帰的に動作するのですが、ファイル名を明示的に指定するのが普通です。あるプロジェクト内あるいはディレクトリ内のすべてのファイルに対して、1回の admin コマンドで意味のある作業はまず行えないでしょう。ですから、以下の説明の中で「ファイル」とあったら、admin コマンドの引数として指定したファイルのことを指していると考えてください。

リポジトリマシンのシステムに cvsadmin という名前のグループがある場合、そのグループのメンバーのみが admin コマンドを実行できます(cvs admin -k は例外で誰でも実行できます)。ですから、メンバーを持たない cvsadmin グループを作成すれば、すべてのユーザに admin コマンドを実行させないよう設定できます。

オプション:


Node:annotate, Next:, Previous:admin, Up:Commands And Options

annotate

Synopsis: annotate [OPTIONS] [FILES]

各ファイルの各行を誰がいつ変更したか、という情報を表示します。出力結果の1行はそのファイルの1行に対応します。各行の内容を左から右へ順に言うと、その行が変更された最新のリビジョン番号、その変更を行ったユーザと変更日付を括弧でくくったもの、コロン、その行の内容、となります。

例を挙げると、あるファイルが次のようだったとすると

this is a test file
it only has too lines
I mean "two"

このファイルのアノテーションは次のようになります。

1.1          (jrandom  22-Aug-99): this is a test file
1.1          (jrandom  22-Aug-99): it only has too lines
1.2          (jrandom  22-Aug-99): I mean "two"

これを見ると、最初の2行は最初のリビジョンのままで、最後の行はリビジョン1.2で追加または変更された(jrandom による)ということがわかります。

Options:


Node:checkout, Next:, Previous:annotate, Up:Commands And Options

checkout

Synopsis: checkout [OPTIONS] PROJECT(S)

モジュールをリポジトリから作業コピーへチェックアウトする。作業コピーが存在しない場合は生成し、既に存在する場合はアップデートします。(updateもご参照ください。)

Options:


Node:commit, Next:, Previous:checkout, Up:Commands And Options

commit

Synopsis: commit [OPTIONS] [FILES]

作業コピーの変更をリポジトリへコミットします。

Options:


Node:diff, Next:, Previous:commit, Up:Commands And Options

diff

Synopsis: diff [OPTIONS] [FILES]

2つのリビジョン間の相違を表示します(Unix の diff 形式で)。オプションなしで起動した場合は、リポジトリのベースリビジョンと、(おそらくは未コミットの)作業コピーの内容を比較します。ベースリビジョンとは、その作業コピーをリポジトリから取得した時点のリビジョンを指します。誰か他の人が変更をコミットしたにもかかわらずこの作業コピーでアップデートを行っていない場合、リポジトリ中にはベースリビジョンより新しいリビジョンが存在する、ということに注意してください。(rdiffも参照のこと)

Options:

Diff 互換オプション

今まで述べたオプションに加え、cvs diff は GNU バージョンの標準 diff プログラムと共通のオプションを指定できます。このようなオプションの全一覧を、よく使われるものについては説明をつけて下記に示します。(その他については GNU diff のドキュメントをご参照下さい。)

-0 -1 -2 -3 -4 -5 -6 -7 -8 -9
    --binary
    --brief
    --changed-group-format=ARG
    -c
      -C NLINES
      --context[=LINES]
    -e --ed
    -t --expand-tabs
    -f --forward-ed
    --horizon-lines=ARG
    --ifdef=ARG
    -w --ignore-all-space
    -B --ignore-blank-lines
    -i --ignore-case
    -I REGEXP
       --ignore-matching-lines=REGEXP
    -h
    -b --ignore-space-change
    -T --initial-tab
    -L LABEL
      --label=LABEL
    --left-column
    -d --minimal
    -N --new-file
    --new-line-format=ARG
    --old-line-format=ARG
    --paginate
    -n --rcs
    -s --report-identical-files
    -p
    --show-c-function
    -y --side-by-side
    -F REGEXP
    --show-function-line=REGEXP
    -H --speed-large-files
    --suppress-common-lines
    -a --text
    --unchanged-group-format=ARG
    -u
      -U NLINES
      --unified[=LINES]
    -V ARG
    -W COLUMNS
      --width=COLUMNS

下記は cvs diff でよく使用される GNU diff オプションです。


Node:edit, Next:, Previous:diff, Up:Commands And Options

edit

Synopsis: edit [OPTIONS] [FILES]

監視されているファイルの編集を始める合図をします。また、自分をそのファイルの監視リストに一時監視者として加えます(cvs unedit した時点で監視リストから外れます)。(watch, watchers, unedit, editors 参照のこと)

Options:


Node:editors, Next:, Previous:edit, Up:Commands And Options

editors

Synopsis: editors [OPTIONS] [FILES]

監視中のファイルを現在誰が編集しているか表示します。(watch, watchers, edit, unedit を参照のこと。)

Options:


Node:export, Next:, Previous:editors, Up:Commands And Options

export

Synopsis: export [OPTIONS] PROJECT(S)

リポジトリからファイルを取り出して、プロジェクトのファイルツリー(作業コピーではなく、CVS/ 管理サブディレクトリも存在しない)を作ります。主にディストリビューションパッケージを作るために使われます。

Options:


Node:gserver, Next:, Previous:export, Up:Commands And Options

gserver

Synopsis: gserver

これは GSSAPI (Generic Security Services API) サーバです。このコマンドがユーザによって直接起動されることは通常ありません。ユーザが :gserver: アクセスメソッド経由のクライアントから接続しようとした時点でサーバ側で起動されるものです。

cvs -d :gserver:floss.red-bean.com:/usr/local/newrepos checkout myproj

GSSAPI はケルベロスバージョン5用です。ケルベロスバージョン4には :kserver: を使用してください。

GSSAPI ライブラリの設定、使用についてはこの本の範囲の外です。(Cederqvist マニュアルの GSSAPI Authenticated に有用なヒントがあります。)

Options: None.

history [OPTIONS] [FILENAME_SUBSTRING(S)]

リポジトリ内での活動履歴を表示します。具体的には、checkout, commit, rtags, update, release についての記録を表示します。このオプションはデフォルトで checkout のみを表示します(ただし、-x オプションを参照してください)。CVSROOT/history ファイルが存在しない場合このコマンドは動作しません。

history コマンドは他の CVS コマンドとはいくつかの点で異なっています。まず、何らかの意味のあることをしようとするとオプションを指定せねばならないという点(加えて、history のいくつかのオプションは他の CVS コマンドとは違う意味を持ちます)。次に、引数としてファイル名全体を指定するのではなく、ファイル名の一部にマッチする部分文字列を1つ以上指定する点(その部分文字列のうち少なくとも1つにマッチする記録はすべて表示されます)。 3つめに、history の出力は読み方を覚えるまで雑音のようにしか見えない点です。出力の形式については、オプションの説明のあとに特別な節を設けて解説します。(logを参照のこと)

Options:

History 出力

history コマンドの出力は、行の連なったものです。各行が1つの「履歴イベント」を示し、イベントの種類を表わすアルファベットの1文字(コード文字)で始まります。例を示します:

floss$ cvs history -D yesterday -x TMO
M 08/21 20:19 +0000 jrandom 2.2              baar       myproj == <remote>
M 08/22 04:18 +0000 jrandom 1.2              README     myproj == <remote>
O 08/22 05:15 +0000 jrandom myproj =myproj= ~/src/*
M 08/22 05:33 +0000 jrandom 2.18             README.txt myproj == ~/src/myproj
O 08/22 14:25 CDT jrandom myproj =myproj= ~/src/*
O 08/22 14:26 CDT jrandom [99.08.23.19.26.03] myproj =myproj= ~/src/*
O 08/22 14:28 CDT jrandom [Exotic_Greetings-branch] myproj =myproj= ~/src/*

コード文字はさきほど説明した -x と同じです。コード文字のあとに、そのイベントの日時、そのイベントの責任者(ユーザ)、と続きます。

ユーザの次にリビジョン番号かタグか日時のうち、適切なものいずれかが表示されます(上の例に見えるように、日時かタグの場合は角カッコで囲んであります)。コミットの場合は新しいリビジョン番号が表示されます。-D か -r でチェックアウトを行うと、スティッキー日付かタグが角カッコで囲まれて表示されます。通常のチェックアウトでは何も表示されません。

次に対象のファイルの名前、モジュールについてのイベントならばモジュール名が表示されます。前者の場合、その次の2つはモジュール/プロジェクト名、そのユーザのホームディレクトリ内での作業コピーの場所、になります。後者の場合はチェックアウトされた作業コピーのモジュール名(イコールで囲まれます)、そのユーザのホームディレクトリ内での作業コピーの場所、が続きます。(-d オプションを使用した場合には、チェックアウトされた作業コピーの名前はモジュールの名前とは違うものになります。)


Node:import, Next:, Previous:gserver, Up:Commands And Options

import

Synopsis: import [OPTIONS] REPOSITORY VENDOR_TAG RELEASE_TAG(S)

リポジトリに新しいソースをインポートします。新規のプロジェクトを生成するか、または既存のプロジェクトのベンダブランチに新しいベンダリビジョンを生成します。(インポートにおけるベンダブランチの基本的な説明は Advanced CVS をご覧ください、以下の説明がわかりやすくなると思います)

一度にたくさんのファイルやディレクトリを追加するときや、新しいプロジェクトを作成するときには通常 import を使います。ファイルを1つだけ追加したい場合には add を使います。

Options:


Node:init, Next:, Previous:import, Up:Commands And Options

init

Synopsis: init NEW_REPOSITORY

新規のリポジトリ(いろいろなプロジェクトをおさめるためのルートリポジトリのことです)を作成します。大抵はグローバルオプション -d をつけて使うと思います:

floss$ cvs -d /usr/local/yet_another_repository init

CVSROOT 環境変数を設定してあったとしても、それは多分既存のリポジトリを指していると思われますし、もし実際そうだった場合、このコマンドに関しては意味がないですし危険でさえあります。(新規のリポジトリを初期化した後の手順については Repository Administration をご覧ください。)

Options: None.


Node:kserver, Next:, Previous:init, Up:Commands And Options

kserver

Synopsis: kserver

これはケルベロスサーバです。(ケルベロスライブラリがバージョン4以下の場合に使います。バージョン5では GSSAPI を使います、gserver を参照のこと。) このコマンドは通常ユーザが直接起動したりはせず、クライアント側から :kserver: メソッドでユーザが接続してきたときに、サーバ側で起動されます。

cvs -d :kserver:floss.red-bean.com:/usr/local/newrepos checkout myproj

ケルベロスの設定と使用法についてはこの本の範囲外です。(Cederqvist マニュアルの Kerberos Authenticated ノードに役に立つヒントがあると思います。)

Options: None.


Node:log, Next:, Previous:kserver, Up:Commands And Options

log

Synopsis: log [OPTIONS] [FILES]

あるプロジェクトについて、またはプロジェクト中のファイルについてのログメッセージを出力します。log コマンドの出力は、古い RCS プログラム(rlog)をもとにしているので、他の CVS コマンドの出力形式とは少々違います。まずヘッダとして、そのファイル自体についてリビジョンに関係のない情報を出力し、次にログメッセージを出力します(リビジョン順)。各リビジョン部分はリビジョン番号とログメッセージに加え、著者(変更したユーザ)、変更日時、追加・削除された行数からなります。時刻は地方時ではなくすべて UTC (GMT)で表示されます。

log コマンドの出力はファイル単位なので、複数ファイルを含む1回のコミットについて、そのまとまりが1回の変更であるというようなことは直接表示されません。しかし、ログメッセージと日時をきちんと読めば、何が起こったかを再構成できると思います。(複数のログ出力を読みやすい形に整形し直すツールについて、詳しくは Third-Party Toolscvs2cl - Generate GNU-Style ChangeLogs を参照してください。) (historyも参照のこと)

Options:

以下のフィルタリングオプションをすべて読んでも、組み合わせたときにどのような動作をするかはわからないと思います。正確に言うと、log コマンドの動作は次のようになります。-d, -s, -w 各々で選択されたリビジョンの集合の共通部分(intersection)と、-b, -r 各々で選択されたリビジョンの集合を合わせたもの(union)の、共通部分(intersection)が対象になります。


Node:login, Next:, Previous:log, Up:Commands And Options

login

Synopsis: login

CVS サーバに接続し、特定のリポジトリについて認証情報を確認します。このコマンドは作業コピーにもリポジトリにも影響を及ぼしません。あるリポジトリについてパスワードを確認し、以後のためにホームディレクトリの.cvspass ファイルにパスワードを保存するだけです。これ以降、同じユーザ名で同じリポジトリにアクセスするコマンドを使用しても、login を再度要求されることはありません。クライアント側の CVS は .cvspass ファイルからパスワードを取ってくるからです。

このコマンドを使うときは、pserver 接続方法を使用してリポジトリを指定してください。こんな感じです:

floss$ cvs -d :pserver:jrandom@floss.red-bean.com:/usr/local/newrepos

または CVSROOT 環境変数を設定しておいてください。

サーバ側のパスワードを変更したら、再度 login を実行してください。

Options: None.


Node:logout, Next:, Previous:login, Up:Commands And Options

logout

Synopsis: logout

login の反対です。このリポジトリのパスワードを .cvspass から削除します。

Options: None.


Node:pserver, Next:, Previous:logout, Up:Commands And Options

pserver

Synopsis: pserver

これはパスワード認証サーバです。このコマンドは通常ユーザが直接起動したりはせず、クライアント側から :pserver: メソッドでユーザが接続してきたときに、サーバ側で /etc/inetd.conf から起動されます。(login, logout コマンドや、この章の Run Control Files 節の .cvspass ファイルについてを参照のこと。パスワード認証のCVS サーバを設定する際の詳細については Repository Administration を参照のこと。)

Options: None.


Node:rdiff, Next:, Previous:pserver, Up:Commands And Options

rdiff

Synopsis: rdiff [OPTIONS] PROJECTS

diff コマンドと同様ですが、直接リポジトリで動きますので作業コピーが必要ありません。このコマンドでは、あるリリースから別のリリースまでの間の相違を、patch プログラムへの入力に使える形式で取得できます(アップグレードを行いたいユーザ用のパッチファイルを配布することができます)。

patch プログラムの操作はこの本の範囲外です。が、パッチファイルにサブディレクトリ中のファイルの diff も含まれている場合、違いを正しく適用するためには patch に -p オプションをつける必要があるかもしれないことに注意してください。(詳しくは patch のドキュメントを参照してください。) (diff も参照のこと)

Options:


Node:release, Next:, Previous:rdiff, Up:Commands And Options

release

Synopsis: release [OPTIONS] DIRECTORY

チェックアウトを取り消します(作業コピーがもう使用されていないと表示します)。CVS コマンドの多くが作業コピーで実行されるのに対し、このコマンドは作業コピー内ではなく、その直上のディレクトリ(親ディレクトリ)で実行します。作業コピーからリポジトリの場所の情報を得ることができませんので、 CVSROOT 環境変数をセットするか、-d グローバルオプションでリポジトリを指定する必要があります。

リリースは必ずしも使用する必要はありません。CVS はロックをかけませんので作業コピーをただ削除するだけでもよいのです。

しかし、作業コピー中に未コミットの変更があったり、CVSROOT/history ファイルに作業の停止を記録したい場合には release を使うべきです。CVS はまず未コミットの変更がないかどうかをチェックします。もしあればその旨警告し、続けてよいかどうか訊ねます。作業コピーが実際にリリースされたら、リポジトリの CVSROOT/history ファイルにそのことが記録されます。

Options:

作業コピー中に新しいディレクトリを作り、それをリポジトリに追加しなかった場合に -d フラグを指定すると、作業コピーと一緒にそのディレクトリも削除されてしまいます。


Node:remove, Next:, Previous:release, Up:Commands And Options

remove

Synopsis: remove [OPTIONS] [FILES]

プロジェクトからファイルを削除します。通常、このコマンドを起動したときに、そのファイルそのものがディスクから削除されます(ただし -f を参照のこと)。このコマンドはデフォルトで再帰的に動作しますが、削除するファイルの名前を明示的に指定するのが普通です。Note the odd implication of the previous sentence: 普通は自分の作業コピー中にもう存在しないファイルに対し cvs remove を実行します。 Removes a file from a project. Normally, the file itself is removed from disk when you invoke this command (but see -f). Although this command operates recursively by default, it is common to explicitly name the files being removed. Note the odd implication of the previous sentence: Usually, you run cvs remove on files that don't exist anymore in your working copy.

確認のためにリポジトリに接続しますが、この後に commit が実行されるまでそのファイルは実際に削除されたりはしません。また、RCS ファイルはリポジトリから本当に削除されるわけではありません。トランクから削除された場合、 Attic/ サブディレクトリに移動するだけで、ブランチ上ではまだ存在します。ブランチから削除された場合は、場所は変わりませんが状態が dead の新しいリビジョンがブランチ上に追加されます。(add を参照のこと)

Options:


Node:rtag, Next:, Previous:remove, Up:Commands And Options

rtag

Synopsis: rtag [OPTIONS] TAG PROJECT(S)

リポジトリ内のモジュールに直接タグをつけます(作業コピーは不要です)。このコマンドを動作させるためには、CVSROOT 環境変数を指定するか、-d グローバルオプションを指定する必要があると思います。(tag を参照のこと)

Options:


Node:server, Next:, Previous:rtag, Up:Commands And Options

server

Synopsis: server

CVS サーバを起動します。このコマンドをユーザが起動することはありません(クライアント/サーバプロトコルをデバッグするような時以外は)。ですからこれについて触れたことは忘れてください。

Options: None.


Node:status, Next:, Previous:server, Up:Commands And Options

status

Synopsis: status [OPTIONS] [FILES]

作業コピー中のファイルの状態を表示します。

Options:


Node:tag, Next:, Previous:status, Up:Commands And Options

tag

Synopsis: tag [OPTIONS] TAG [FILES]

プロジェクトの特定のリビジョンまたはリビジョンの集合に名前をつけます。プロジェクトの「スナップショットを取る」とも言います。このコマンドは CVS のブランチを作成するときにも使用されます。(-b オプションを参照のこと。rtag も)

Options:


Node:unedit, Next:, Previous:tag, Up:Commands And Options

unedit

Synopsis: unedit [OPTIONS] [FILES]

監視者に対し、ファイルの編集が終わったことを合図します。(watch, watchers, edit, editors を参照のこと)

Options:


Node:update, Next:, Previous:unedit, Up:Commands And Options

update

Synopsis: update [OPTIONS] [FILES]

リポジトリの変更を自分の作業コピーへマージします。副作用として、自分の作業コピー中で変更のあるファイルを表示します(但し、-Q グローバルオプションが指定されている場合はこの表示はありません)。(checkout も参照のこと)

Options:


Node:watch, Next:, Previous:update, Up:Commands And Options

watch

Synopsis: watch on|off|add|remove [OPTIONS] [FILES]

1つ以上のファイルを監視するよう設定します。他の CVS コマンドとは違い、 watch で意味のあることをしようとすると、さらにもうひとつサブコマンドを指定する必要があります。(watchers, edit, editors, unedit, users を参照のこと)

Subcommands:

オプション(すべての watch サブコマンドに使用できるものです)。オプションは3つあって、すべて edit のオプションと同様の意味です:


Node:watchers, Previous:watch, Up:Commands And Options

watchers

Synopsis: watchers [OPTIONS] [FILES]

どのファイルを誰が監視しているかを表示します。

Options - これらは edit の同じオプションと同様の意味です:


Node:Keyword Substitution (RCS Keywords), Next:, Previous:Commands And Options, Up:CVS Reference

Keyword Substitution (RCS Keywords)

CVS はファイル中のテキスト置換を行うことができ、ファイル中のある種の情報を自動的に最新に保てます。置換はすべてドルマークで括った特定のキーワードパターンにより起動されます。All of the substitutions are triggered by a certain keyword pattern, surrounded by dollar signs. たとえばファイル中の

$Revision$

は、こんな風に展開されます:

$Revision: 1.5 $

新しいリビジョンがコミットされても CVS はリビジョン文字列を最新のものに保ち続けます。


Node:Controlling Keyword Expansion, Next:, Up:Keyword Substitution (RCS Keywords)

Controlling Keyword Expansion

CVS はデフォルトでキーワード展開を行い、止めるよう指示しなければ止めません。あるファイルのキーワード展開をずっとやめようと思えば、そのファイルをプロジェクトに追加する際に -k オプション付きにします。追加したあとでやめようと思えば admin コマンドを -k オプション付きで実行します。キーワード制御のモードはいくつかあって、-k オプションで指定できます。通常は o か b モードを使用します。たとえば

floss$ cvs add -ko chapter-9.sgml

このコマンドは chapter-9.sgml というファイルを、キーワード展開なしでプロジェクトに追加します。ファイルのデフォルトキーワード展開モードを o 、つまり展開なしのモードにセットしています。(実際は「o」というのは「古い」(old)の略で、文字列を古い値、つまりその文字列自体と置換する、という意味です。結果として何も変わらないことになります。 I'm sure this logic made sense to somebody at the time.)

各ファイルのデフォルトキーワード展開モードはリポジトリに保存してあります。が、各作業コピーごとにその作業コピーローカルのキーワード展開モードを設定することも可能です。チェックアウトかアップデートの際に -k オプションをつければ設定できます。コマンド1回を実行する間だけモードを有効にしたい場合には diff コマンドの -k オプションを使えます。

指定できるモードを、その前に -k オプションをつけた形で(コマンドラインでタイプするような形で)ここに一覧します。これらのオプションはキーワード展開モードとして、デフォルトでもローカルでも使用できます。


Node:List Of Keywords, Previous:Controlling Keyword Expansion, Up:Keyword Substitution (RCS Keywords)

List Of Keywords

CVS が解釈する、ドルマークで囲まれたキーワードの一覧を記します。キーワード、短い説明、展開後の例の一覧です:


Node:Repository Administrative Files, Next:, Previous:Keyword Substitution (RCS Keywords), Up:CVS Reference

Repository Administrative Files

リポジトリ管理ファイルはリポジトリの CVSROOT サブディレクトリにあります。これらのファイルは CVS の動作をさまざまな面からコントロールします(もちろんそのリポジトリでの動作だけをです)。 Repository Administration の管理ファイルの議論も見てみるとよいでしょう、例があります。


Node:Storage And Editing, Next:, Up:Repository Administrative Files

Storage And Editing

管理ファイルは一般に、リポジトリ内にある他のファイルと同様、リビジョン管理下にあります(例外は後で述べます)。ただし他のファイルと違って、管理ファイルのチェックアウトコピーがリポジトリの CVSROOT サブディレクトリ中の対応する RCS のすぐ隣に保存されています。これらのチェックアウトコピーが実際に CVS の動作を支配しているのです。

通常、管理ファイルを変更する際には CVSROOT モジュールの作業コピーをチェックアウトし、変更してコミットします。CVS は自動的にリポジトリ中のチェックアウトコピーを更新します。(checkoutlist を参照のこと) ただし緊急の場合にはリポジトリ中のチェックアウトコピーを直接編集することも可能ではあります。


Node:Shared Syntax, Next:, Previous:Storage And Editing, Up:Repository Administrative Files

Shared Syntax

すべての管理ファイルにおいて、行頭の # はコメントを意味します。 CVS はその行を無視します。改行直前にバックスラッシュがあると、その改行はなかったことになります。

いくつかのファイル(commitinfo, loginfo, taginfo, rcsinfo)に共通する文法がもう少しあります。これらのファイルの形式は、各行の左端がファイルまたはディレクトリ名にマッチする正規表現で、行の残りはその正規表現にマッチしたファイルを対象に起動されるプログラム(たいてい引数つき)です。このプログラムの起動時の作業ディレクトリはリポジトリのトップディレクトリになります。

これらのファイルで使用できる特殊な正規表現が2つあります。ALL と DEFAULT です。ALL は、そのファイルまたはディレクトリ名が他の正規表現とマッチしたか否かにかかわらず、すべてにマッチします。 DEFAULT は他の正規表現にマッチしなかった場合にマッチします。


Node:Shared Variables, Next:, Previous:Shared Syntax, Up:Repository Administrative Files

Shared Variables

これらのファイルでは実行時に変数を展開することもできます。変数を展開する場合は先頭にドルマークをつけてください(また、念のため中括弧で囲んでください)。CVS がわかっている変数は次の通りです:


Node:User Variables, Next:, Previous:Shared Variables, Up:Repository Administrative Files

User Variables

CVS の任意のコマンドの実行時に、ユーザが独自の変数を設定することもできます。(グローバルオプションの -s を参照のこと) これらの変数は *info ファイル中で、${=VAR} のように等号を先頭につけて参照することができます。


Node:checkoutlist, Next:, Previous:User Variables, Up:Repository Administrative Files

checkoutlist

チェックアウトコピーをリポジトリに保存しておくファイルのリストです。各行にはファイル名と、 CVS が何らかの理由でリポジトリ中にそのファイルをチェックアウトできなかった場合のエラーメッセージを書きます。

FILENAME  ERROR_MESSAGE

CVS は存在している管理ファイルのチェックアウトコピーについてはもうわかっているので、それらを checkoutlist に書く必要はありません。 checkoutlist に書く必要のないファイルは、具体的には次のとおりです: loginfo, rcsinfo, editinfo, verifymsg, commitinfo, taginfo, ignore, checkoutlist, cvswrappers, notify, modules, readers, writers, config


Node:commitinfo, Next:, Previous:checkoutlist, Up:Repository Administrative Files

commitinfo

コミット時に、コミット中のものに基づいて実行するプログラムを指定します。各行には正規表現とコマンドテンプレートを書きます:

REGULAR_EXPRESSION PROGRAM [ARGUMENTS]

PROGRAM にはテンプレート中に書いた引数に続いて追加の引数が渡されます。追加の引数とは、リポジトリのフルパス、コミットしようとするファイル名、です。これらのファイルは PROGRAM によって検査することができます。ファイル内容はコミットしようとしている作業コピー中のファイルと同じです。 PROGRAM がノンゼロで終了した場合、コミットは失敗します。それ以外の場合は成功します。(この章で既に出てきたきた Shared Syntax も参照のこと)


Node:config, Next:, Previous:commitinfo, Up:Repository Administrative Files

config

グローバルな(プロジェクトごとでない)リポジトリパラメータを制御します。各行の書式は次の通りです(LockDir パラメータ以外):

ParameterName=yes|no

LockDir パラメータだけは引数にフルパスを書きます。

以下のパラメータがサポートされています:


Node:cvsignore, Next:, Previous:config, Up:Repository Administrative Files

cvsignore

update, import, release 時に特定のファイルを無視します。デフォルトで既に CVS は数種類のファイルを無視するようになっています。(それらの一覧は既にこの章で述べた import の -I オプションをご覧ください。) cvsignore ファイルに追加のファイル名、ワイルドカードパターンを書けばこのリストに追加できます。各行はファイル名またはパターンです。たとえば:

README.msdos
*.html
blah?.out

こう設定すると CVS はREADME.msdos と、 .html で終わる名前のファイルすべて、blah で始まり .out で終わる名前のファイルすべてを無視します。(技術的には各行に複数のファイル名またはパターンを空白で区切って書いても構わないのですが、1行に1つにしておいたほうが読みやすいです。空白で区切る規則は空白を含むファイル名を指定しようとするとワイルドカードに頼る以外方法がなくなります)

リスト中に ! を書くとそれまでのエントリがすべて取り消されます。(無視リストの処理についてはこの章の Environment Variables 節の $CVSIGNORE をご参照下さい。)


Node:cvswrappers, Next:, Previous:cvsignore, Up:Repository Administrative Files

cvswrappers

ファイル名に基づいてフィルタリングの動作を指定します。各行にはファイルグロブパターン(つまりファイル名またはファイルワイルドカード)、フィルタタイプを示すオプション、そのオプションの引数、を指定します。

Options:

cvswrappers ファイルの例を示します:

*.blob    -m COPY
*.blink   -k o

この cvswrappers ファイルでは、.blob で終わる名前のファイルはマージしないように、.blink で終わる名前のファイルはキーワード展開をしないように設定しています。(この章の Working Copy Files.cvswrappers もご参照下さい)


Node:editinfo, Next:, Previous:cvswrappers, Up:Repository Administrative Files

editinfo

このファイルは廃止されました。


Node:history, Next:, Previous:editinfo, Up:Repository Administrative Files

history

リポジトリ中の活動履歴を累積して保存します。cvs history コマンドが使用します。この機能を使えないようにするには、単にこの履歴ファイルを削除してください。削除しない場合はパーミションの問題を避けるためこのファイルをワールドライタブルにします。

このファイルの内容は CVS の動作を変更したりはしません(当たり前ですが cvs history の出力は別です)。


Node:loginfo, Next:, Previous:history, Up:Repository Administrative Files

loginfo

各コミット時にコミットされるものに基づいてログメッセージを対象に実行するプログラムを指定します。各行には正規表現とコマンドテンプレートを書きます:

REGULAR_EXPRESSION PROGRAM [ARGUMENTS]

PROGRAM には標準入力からログメッセージが渡されます。

引数のところで使用可能な特殊コードがいくつかあります: %s はコミットされつつあるファイルの名前に、%V はコミット前の旧リビジョンに番号、%v はコミット後の新リビジョン番号に展開されます。複数のファイルを含む場合には各要素は空白で区切られます。たとえばあるコミットがファイルを2つ含む場合、%shello.c README.txt に、 %v1.17 1.12 に展開されます。

コードを組み合わせて中括弧で囲むこともできて、その場合には展開単位内はコンマで区切られ、各単位は空白で区切られます。さっきの例だと、 %{sv}hello.c,1.17 README.txt,1.12 に展開されます。

% の展開が済んだら、展開後文字列の前にリポジトリのフルパスがつきます。ですから、展開がすべてすむと次のようになります:

/usr/local/newrepos  hello.c,1.17  README.txt,1.12

PROGRAM がノンゼロで終了した場合コミットは失敗し、それ以外の場合は成功します。(この章の Shared Syntax 節を参照のこと)


Node:modules, Next:, Previous:loginfo, Up:Repository Administrative Files

modules

このファイルでは、リポジトリのディレクトリに名前をマッピングします。各行の書式は次の通りです:

MODULE [OPTIONS] [&OTHERMODULE...] [DIR] [FILES]

DIR はトップレベルプロジェクトディレクトリである必要はなく、サブディレクトリでも構いません。FILES を指定した場合、そのモジュールはそのディレクトリの指定したファイルだけから構成されることになります。

モジュール名の先頭にアンパサンド(&)をつけると、その位置にそのモジュールを展開して指定するのと同じ意味になります。

Options:


Node:notify, Next:, Previous:modules, Up:Repository Administrative Files

notify

監視中のファイルについての通知をどのように行うかをコントロールします。(ここまでに watch, edit コマンドのところを全部読むか、 Advanced CVSWatches (CVS As Telephone) 節を読むことをおすすめします)。各行の通常の書式は:

REGULAR_EXPRESSION PROGRAM [ARGUMENTS]

ARGUMENTS 中に %s と書くと通知されるユーザ名に展開され、通知に関するその他の情報は標準入力を通じて PROGRAM に渡されます(たいていはユーザにメールするための短いメッセージです)。(この章の Shared Syntax を参照のこと)

配布時のままの状態の CVS だと notify ファイルは次の1行だけです:

ALL mail %s -s "CVS notification"

たいていはこれで事足りると思います。


Node:passwd, Next:, Previous:notify, Up:Repository Administrative Files

passwd

pserver アクセスメソッドについての認証情報を設定します。各行の書式は次の通りです:

USER:ENCRYPTED_PASSWORD[:SYSTEM_USER]

SYSTEM_USER が指定されない場合、 USER をシステムユーザ名とみなします。


Node:rcsinfo, Next:, Previous:passwd, Up:Repository Administrative Files

rcsinfo

エディタで対話的に書くログメッセージのための記入フォームを指定します。 rcsinfo の各行は次のように書きます:

REGULAR_EXPRESSION FILE_CONTAINING_TEMPLATE

このテンプレートはチェックアウト時にリモートの作業コピーのほうにコピーされますので、チェックアウト後にテンプレートファイルや rcsinfo ファイルを変更するとリモート作業コピーはそれを認識せず、古いテンプレートを使い続けます。(この章の Shared Syntax も参照のこと。)


Node:taginfo, Next:, Previous:rcsinfo, Up:Repository Administrative Files

taginfo

タグ付け時にプログラムを実行します(通常はタグ名をつけるときにあるパターンとマッチしているかどうかチェックします)。各行の書式は次の通りです:

REGULAR_EXPRESSION PROGRAM

プログラムには一群の引数が渡されます。順に、タグ名、オペレーション(下記参照)、リポジトリ、そのタグ付けに含まれるファイル名/リビジョン番号のペアの繰り返し(空白区切り)、となります。

オペレーションとは add, mov, del のうちのいずれかです(movは tag コマンドに -F オプションをつけたことを意味します)。

PROGRAM がノンゼロで終了すると、tag コマンドは失敗します。(この章の Shared Syntax 節を参照のこと)


Node:users, Next:, Previous:taginfo, Up:Repository Administrative Files

users

ユーザ名とメールアドレスのマッピングを設定します。各行は次のとおりです:

USERNAME:EMAIL_ADDRESS

監視の通知をリポジトリマシンの USERNAME ではなく、EMAIL_ADDRESS に送信します。(単に notify ファイルの %s の展開をコントロールするだけです。) EMAIL_ADDRESS が空白文字を含む場合クオートで囲むことをお忘れなく。

passwd ファイルでユーザの別名を設定している場合、マッチ対象のユーザ名はシステムユーザ名(あれば右側のユーザ名)ではなく CVS ユーザ名の方になります(左側のユーザ名)。


Node:val-tags, Next:, Previous:users, Up:Repository Administrative Files

val-tags

タグ名をより素早く検索するため、有効なタグ名をキャッシュします。このファイルの内容は決して編集してはいけませんが、ユーザがタグをアクセスしたり作成したりする際にトラブルが発生した場合にはパーミションやオーナをいじることがあります。


Node:verifymsg, Previous:val-tags, Up:Repository Administrative Files

verifymsg

ログメッセージのフォーマットを検査するため、rcsinfo と組み合わせて使います。各行の書式は次の通りです:

REGULAR_EXPRESSION PROGRAM [ARGUMENTS]

verifymsg ファイルに書いた最後の引数のあとに、現在のログメッセージのテンプレート(この章の rcsinfo を参照のこと)が追加されて渡されます。 PROGRAM がノンゼロで終了すると、commit は失敗します。


Node:Run Control Files, Next:, Previous:Repository Administrative Files, Up:CVS Reference

Run Control Files

クライアント(作業コピー)側にも CVS の動作に影響するファイルが二三あります。いくつかはリポジトリの管理ファイルと同じようなもので、ほかのはクライアント側だけの動作を制御するものです。

.cvsrc

CVS コマンドを実行する際に自動的につけておきたいオプションを指定します。各行の書式は次の通りです:

COMMAND OPTIONS

COMMAND は CVS コマンドの正式名称(省略しない形式)で、たとえば checkout や update のように指定します(co, up ではだめです)。OPTIONS にはそのコマンドを実行する時にはいつもつけておきたいオプションを指定します。よくある .cvsrc の行を記します:

update -d -P

グローバルオプションを指定するには COMMAND のところに cvs と書いてください。

.cvsignore

追加の無視パターンを指定します。(この章の Repository Administrative Files 節の cvsignore を参照のこと。)

ホームディレクトリに .cvsignore ファイルを置けば、CVS を使う時常に適用されます。作業コピーの各プロジェクトディレクトリにも置けて、その場合はそのディレクトリごとの設定になります(.cvsignore が置いてあるディレクトリにだけ適用され、サブディレクトリには適用されません)。

(無視リストの処理については、この章の Environment Variables 節の $CVSIGNORE を参照のこと。)

.cvspass

pserver メソッド経由でアクセスする各リポジトリのパスワードを保存します。各行の書式は次の通り:

REPOSITORY LIGHTLY_SCRAMBLED_PASSWORD

パスワードは本質的に平文で保存されています。うっかり見てしまう(root ユーザが何の気無しにファイルの中身を見てしまったりするような)のを防ぐために、ほんのちょっとスクランブルがかけてあるだけです。このスクランブルでは、このファイルにアクセスしてパスワードを盗もうと真剣に思っているような人間には歯が立ちません。

.cvspass ファイルはポータブルですので、あるマシンの .cvspass ファイルを別のマシンにコピーすると、新しいマシンではパスワードをすべて持っている状態になり、cvs login する必要がありません。(loginlogout コマンドを参照のこと)

.cvswrappers

cvswrappers ファイルのクライアント側バージョンです。(この章の Repository Administrative Files 節を参照のこと) .cvswrappers ファイルはホームディレクトリにも作業コピーの各ディレクトリ中にも置けます。.cvsignore と同様です。


Node:Working Copy Files, Next:, Previous:Run Control Files, Up:CVS Reference

Working Copy Files

作業コピーの各ディレクトリには CVS/ 管理サブディレクトリには、次のファイルの一部が置いてあります。

各ファイル、ディレクトリが何をするものなのか説明しますと:

CVS/Base/  (directory)

監視をオンにしてある場合、cvs edit はこのディレクトリにそのファイルのオリジナルコピーを置きます。これのおかげでサーバに接続できない時にも cvs unedit が動作するわけです。

CVS/Baserev

Base/ 中の各ファイルのリビジョンの一覧です。各行は次のようです:

FILE/REVISION/EXPANSION

EXPANSION は無視してください、ええと、将来の機能拡張のためなんです。

CVS/Baserev.tmp

一時ファイルです。(後で述べる CVS/Notify.tmp, CVS/Entries.Backup を参照のこと)

CVS/Checkin.prog

modules ファイルで指定した -i オプションのプログラム名を記録します。(この章の Repository Administrative Files 節を参照のこと)

CVS/Entries

このディレクトリ中のファイルのリビジョンを保存しています。各行は次の形式です:

[CODE_LETTER]/FILE/REVISION/DATE/[KEYWORD_MODE]/[STICKY_OPTION]

CODE_LETTER があるとすれば D になっている筈で(それ以外は CVS にただ無視されます。将来の機能拡張のため)、それはディレクトリを意味します。その場合、行の残りの項目はありません。

このファイルは常に存在します。

CVS/Entries.Backup

ただの一時ファイルです。もし Entries ファイルを変更するようなプログラムを書く場合には、新しい内容をまず Entries.backup に書込み、その後アトミックにファイル名を Entries に変更してください。

CVS/Entries.Log

これは基本的に Entries を読んだ後に Entries に適用されるパッチファイルです(このハックはちょっとした変更のたびに Entries を全部書き直すのを避けるために効果的です)。書式は Entries と同じですが、ただし各行の冒頭にコード文字が追加されている点が違います。AEntries に追加する行で、 REntries から削除する行です。その他の文字は将来の機能拡張のため、ただ無視されます。

CVS/Entries.Static

このファイルが存在するということは、リポジトリからはこのディレクトリの一部のみが取得されており、CVS はそのディレクトリの追加のファイルを作成しないということを意味します。この状態は通常 update -d を実行するとクリアされます。

CVS/Notify

まだサーバに送信していない通知を保存しておくファイルです。

CVS/Notify.tmp

Notify の一時ファイルです。Notify ファイルを変更する際には、まず Notify.tmp に書込み、その後ファイル名を Notify に変更する、という手続きをとるのが普通です。

CVS/Repository

そのプロジェクトのリポジトリ中のパスです。絶対パスか、または Root で示されたところからの相対パスです。

このファイルは常に存在しています。

CVS/Root

リポジトリです。つまり、$CVSROOT 環境変数の値か、または -d グローバルオプションで指定したパスです。

このファイルは常に存在します。

CVS/Tag

このディレクトリにスティッキータグまたはスティッキー日付が存在する場合、このファイルの1行目に記録されます。冒頭1文字はタグの種別を表します。 T, N, D で、各々の意味はタグ、ノンブランチタグ、日付です。行の残りはタグまたは日付です。

CVS/Template

rcsinfo ファイルで指定されたログメッセージのテンプレートを保存します。(この章で既に述べたRepository Administrative Filesを参照のこと)これはリモート作業コピーにのみ関係あることです。リポジトリと同じマシン上の作業コピーは rcsinfo ファイルを直接読みます。

CVS/Update.prog

modules ファイルの -u オプションで指定したプログラム名を記録します。(この章の Repository Administrative Files節を参照のこと)


Node:Environment Variables, Previous:Working Copy Files, Up:CVS Reference

Environment Variables

CVS に影響する環境変数の全リストです。


Node:$COMSPEC, Next:, Up:Environment Variables

$COMSPEC

OS/2 でのみ使用します。コマンドインタプリタ名を指定します。デフォルトは CMD.EXE です。


Node:$CVS_CLIENT_LOG, Next:, Previous:$COMSPEC, Up:Environment Variables

$CVS_CLIENT_LOG

クライアント/サーバプロトコルのデバッグに使用します。CVS を使う前に、この変数にファイル名 filename を設定してください。サーバへのトラフィックがすべて filename.in に、サーバからのトラフィックがすべて filename.out に記録されます。


Node:$CVS_CLIENT_PORT, Next:, Previous:$CVS_CLIENT_LOG, Up:Environment Variables

$CVS_CLIENT_PORT

ケルベロス認証のクライアント/サーバアクセスで使用します。


Node:$CVSEDITOR, Next:, Previous:$CVS_CLIENT_PORT, Up:Environment Variables

$CVSEDITOR

コミット時のログメッセージを編集する際に使用するプログラムを指定します。これは $EDITOR$VISUAL より優先されます。


Node:$CVSIGNORE, Next:, Previous:$CVSEDITOR, Up:Environment Variables

$CVSIGNORE

CVS に無視させたいファイル名やワイルドカードパターンを空白で区切ったリストで指定します。(import の -I オプションを参照のこと)

この変数の内容はコマンド実行中に無視リストの最後に追加されます。無視リストは、CVSROOT/cvsignore、ホームディレクトリの.cvsignore ファイル、$CVSIGNORE 環境変数、-I コマンドオプション、そして CVS が動作している各ディレクトリの .cvsignore ファイル、の順で作成されます。! を指定するとその時点までに作成された無視リストがすべて破棄されます。


Node:$CVS_IGNORE_REMOTE_ROOT, Next:, Previous:$CVSIGNORE, Up:Environment Variables

$CVS_IGNORE_REMOTE_ROOT

最近廃止されました。


Node:$CVS_PASSFILE, Next:, Previous:$CVS_IGNORE_REMOTE_ROOT, Up:Environment Variables

$CVS_PASSFILE

CVS に対し別の名前の .cvspass ファイルを使用するよう指示します。(この章の Run Control Files 節の .cvspass を参照のこと)


Node:$CVS_RCMD_PORT, Next:, Previous:$CVS_PASSFILE, Up:Environment Variables

$CVS_RCMD_PORT

サーバ側の rcmd デーモンに接続する際のポーと番号を指定します。(この変数は現在の Unix CVS クライアントでは無視されます。)


Node:$CVSREAD, Next:, Previous:$CVS_RCMD_PORT, Up:Environment Variables

$CVSREAD

作業コピーを checkout, update する際、可能ならファイルを読み出し専用にします(デフォルトでは読み書きモードになります)。(-r グローバルオプションを参照のこと)


Node:$CVSROOT, Next:, Previous:$CVSREAD, Up:Environment Variables

$CVSROOT

リポジトリのパスを指定します。-d グローバルオプションや、作業コピー中の環境でのリポジトリの方が優先されます。リポジトリへのパスの冒頭にはアクセスメソッド、ユーザ名、ホストの指定が来ることもあります。書式は次の通りです:

[[:METHOD:][[USER@]HOST]:]/REPOSITORY_PATH

この章の最初の方にある Global Options 節の -d グローバルオプションのところに指定できるアクセスメソッドの一覧があります。


Node:$CVS_RSH, Next:, Previous:$CVSROOT, Up:Environment Variables

$CVS_RSH

:ext: アクセスメソッドの場合に、サーバへの接続で使用する外部プログラムを指定します。デフォルトは rsh ですが、代わりに ssh を使うのが普通です。


Node:$CVS_SERVER, Next:, Previous:$CVS_RSH, Up:Environment Variables

$CVS_SERVER

サーバ側で起動する CVS プログラムを指定します。デフォルトは当然 cvs です。


Node:$CVS_SERVER_SLEEP, Next:, Previous:$CVS_SERVER, Up:Environment Variables

$CVS_SERVER_SLEEP

サーバの子プロセスの開始を指定した秒数だけ遅らせます。デバッグの時に使用して、デバッガが接続するための時間を作ります。


Node:$CVSUMASK, Next:, Previous:$CVS_SERVER_SLEEP, Up:Environment Variables

$CVSUMASK

リポジトリ中のファイルやディレクトリのパーミションを指定します。(これは普通設定しません。いずれにせよクライアント/サーバでは動作しません)


Node:$CVSWRAPPERS, Next:, Previous:$CVSUMASK, Up:Environment Variables

$CVSWRAPPERS

CVS がラッパーとして使用するファイル名、ワイルドカード、引数のリストを指定します(空白区切り)。(詳しくはこの章の Repository Administrative Files 節の cvswrappers を参照のこと)


Node:$EDITOR, Next:, Previous:$CVSWRAPPERS, Up:Environment Variables

$EDITOR

($CVSEDITOR を参照のこと)


Node:$HOME %HOMEDRIVE% %HOMEPATH%, Next:, Previous:$EDITOR, Up:Environment Variables

$HOME %HOMEDRIVE% %HOMEPATH%

.cvsrc, .cvspass を置く場所を指定します(Unix では $HOME のみが使用されます)。Windows NT では %HOMEDRIVE%%HOMEPATH% が設定されていますが、 Windows 95 では自分で設定する必要があります。

Windows 95 では %HOME% も設定しなければなりません。最後にバックスラッシュを書かないことに注意してください。set HOME=C: などを使ってください。


Node:$PATH, Next:, Previous:$HOME %HOMEDRIVE% %HOMEPATH%, Up:Environment Variables

$PATH

廃止。


Node:$TEMP $TMP $TMPDIR, Next:, Previous:$PATH, Up:Environment Variables

$TEMP $TMP $TMPDIR

一時ファイルをどこに置くかを指定します(サーバは TMPDIR を使用します、 Windows NT は TMP を使用します)。クライアント側でこれを設定してもサーバ側には影響ありません。どちらで指定しても CVS が一時ロックファイルを作る場所には影響しません。(これについて詳しくはこの章の Repository Administrative Files 節の config を参照のこと)


Node:$VISUAL, Previous:$TEMP $TMP $TMPDIR, Up:Environment Variables

$VISUAL

(See $CVSEDITOR.)


Node:Third-Party Tools, Next:, Previous:CVS Reference, Up:Top

Third-Party Tools

多くの人々が CVS を強化するプログラムを書いてきました。これらのプログラムは CVS 開発チームとは別にそれぞれメインテナが居るので、わたしはこれらをサードパーティツールと呼びます。いくつかを除き、これらのプログラムの大半は CVS と一緒には配布されていません。この章では、 CVS と一緒には配布されていないけれども便利なサードパーティツールを説明します。

とてもポピュラーで広く使われている、コマンドライン形式でない、または Unix ベースではない CVS のインタフェースがいくつかありますが(Repository Administration にダウンロードサイトの一覧があります)、この章ではそれら大半について議論しません。ポピュラーなので、メーリングリストやニュースグループでいろいろと情報が見つけやすいと思います。例外は Emacs pcl-cvs インタフェースで、これはとても便利なのですが、インストールにコツがいるのです。


Node:pcl-cvs -- An Emacs Interface To CVS, Next:, Up:Third-Party Tools

pcl-cvs - An Emacs Interface To CVS

Depends on: Emacs, Elib

URLs:

Authors: Per Cederqvist and Stefan Monnier (current maintainer)

pcl-cvs は、2つある Emacs/CVS インタフェースのうちのひとつです。もうひとつは Emacs にビルトインされている、ネイティブVC(Version Control)インタフェースです。pcl-cvs は CVS 専用に書かれていて、CVSでいろいろするのにスムースに動作しますので、わたしは pcl-cvs のほうが好きです。一方、VC はいくつかの違うバックエンドバージョン管理システム(RCS と SCCS、CVS もです)上で動くよう設計されているので、CVS 向けに調整されているわけではありません。例えば、VC はディレクトリベースではなくファイルベースのリビジョン管理インタフェースを提供しています。

pcl-cvs の利点は、たくさんの人が VC を使うより pcl-cvs をダウンロードしてインストールする手間をかけているのを補って余りあります。残念なことに、pcl-cvs には2つ欠点があります: インストールがちょっとややこしいことと(この節の多くはインストールでつまづきそうなハードルを越えるために費されています)、最近のリリースが少し不安定なことです。

後者の問題は一時的なものではありますが、どのバージョンを使えばよいかという疑問がでてきます。Stefan Monnier は最近 pcl-cvs の保守を引き継ぎました; 最新のリリース 2.9.6 (上記のリストの最初のURLからどうぞ)はわたしが試してみたときにはちょっとがたぴししている感じでした。すぐにスムーズに動くようになるとは思うのですが、さしあたっては古いバージョンを使いたいだろうと思います。バージョン1.05を毎日長い間使い続けて、とてもよく動いてくれていたので、ここではそのバージョンを書いておこうと思います。幸い、インストール手順はバージョン間で違いはありません。 pcl-cvs を使うことを決めたら、Monnier のダウンロードサイトをチェックして 2.9.6 より新しいバージョンがでているかどうか見てみることをお勧めします。もしあったら、1.05 に退行してしまう前に試してみてください。

2つのURLがバージョン1.05用であることに気づいたと思います。最初のほうは Per Cederqvist のサイトで、彼は pcl-cvs の古いアーカイブを入手できるようにしてくれています。しかし、彼のアーカイブがいつまであるかはわからないので、1.05 ディストリビューションを ftp.red-bean.com でも入手できるようにしておきました。

以降の手順ではバージョン1.05からの例を使用していますが、それより新しいバージョンでも同じように適用できると思います。


Node:Installing pcl-cvs, Next:, Up:pcl-cvs -- An Emacs Interface To CVS

Installing pcl-cvs

もし Emacs のインストールやサイトメンテナンスについて普段かかわっていなければ、pcl-cvs のインストール手順はちょっと気が遠くなるような感じかもしれません。Emacs がどう動いているか、ちょっとした背景を知っていれば役に立つかもしれませんね。

Emacs の最も高レベルな機能は"Emacs Lisp"と呼ばれる言語で書かれています(Emacs はそれ自体この言語のインタプリタなのです)。人々は Emacs に新しい機能を追加するために、Emacs Lisp コードのファイルを配布します。 pcl-cvs はこの言語で書かれており、Elib と呼ばれる、便利で汎用的な Emacs Lisp 関数に依存しています(一部 Per Cederqvistが書きましたが、pcl-cvs とは別に配布されています)。

Elib は通常の Emacs ディストリビューションには含まれていませんので(少なくとも FSF Emacs には; XEmacs のことはわたしにはわからないです)、 pcl-cvs を使う前にそれをダウンロードしてインストールする必要があります。ftp://ftp.lysator.liu.se/pub/emacs/elib-1.0.tar.gz から入手できます。インストール手順はパッケージの中に同梱されています。

Elib がインストールできたら、pcl-cvs を作ってインストールする準備ができました。これらの手順はバージョン1.05にも2.xシリーズにも適用できます(新しいディストリビューションでは何か変わったことがないか、NEWS と INSTALL ファイルをチェックしてくださいね)。

まず、pcl-cvs をほどきます(バージョン1.05を使っていますが、2.9.6 でも同様です)

floss$ zcat pcl-cvs-1.05.tar.gz | tar xvf -
pcl-cvs-1.05/
pcl-cvs-1.05/README
pcl-cvs-1.05/NEWS
pcl-cvs-1.05/INSTALL
pcl-cvs-1.05/ChangeLog
pcl-cvs-1.05/pcl-cvs.el
pcl-cvs-1.05/pcl-cvs.texinfo
pcl-cvs-1.05/compile-all.el
pcl-cvs-1.05/pcl-cvs-lucid.el
pcl-cvs-1.05/pcl-cvs-startup.el
pcl-cvs-1.05/pcl-cvs.info
pcl-cvs-1.05/Makefile
pcl-cvs-1.05/texinfo.tex

次にソースツリーのトップレベルに移動してください:

floss$ cd pcl-cvs-1.05/

Makefile がありますね。INSTALL ファイル中の手順に従って、Makefile の冒頭のパスをいくつか編集したとして、次を実行します:

floss$ make install

うまく動いたら、やったね。でも、エラーになることもあります(pcl-cvs のコード自体は移植性があるのですが、インストール手順はそうでないこともあります)。エラーになったら、こうしてください:

floss$ make clean
floss$ make

すべてうまくいったら、これらのコマンドはインストールの重要な部分を達成する Emacs Lisp ファイルすべてをバイトコンパイルすることによって。(バイトコンパイルは人間に読める Emacs Lisp コードのファイル(.el ファイル)を、コンパクトで実効性のある形式(.elc ファイル)に変換します。 Emacs は .elc ファイルをロード及び実行する、よりよい性能で、素の .el ファイルよりも。)

バイトコンパイルの段階が成功したとして話を進めます。バイトコンパイルが成功しなくても気にしなくて構いません: .elc ファイルは贅沢品であって、必須のものではありません。.elc だとちょっとパフォーマンスがよくなるだけで、生の .el を使っても pcl-cvs の実行には支障ありません。

make intall が失敗した場合、次のステップでは Emacs Lisp (.el でも .elc でもかまいません)を、自動的にロードされるディレクトリへ移します。 Emacs はローカルにインストールされた Lisp 用のディレクトリをシステム上に持っています。そのディレクトリをみつけるには(そのディレクトリには default.el があるはずです)、次の順で調べて下さい:

  1. /usr/share/emacs/site-lisp/
  2. /usr/local/share/emacs/site-lisp/
  3. /usr/lib/emacs/site-lisp/
  4. /usr/local/lib/emacs/site-lisp/

site-lisp ディレクトリを見つけたら、そこへ Lisp ファイルを全部コピーして下さい(root になる必要があります):

floss# cp -f *.el *.elc /usr/share/emacs/site-lisp/

最後のステップでは、pcl-cvs のエントリポイントについて Emacs に指示し(主なエントリポイントは cvs-update 関数)、オンデマンドで pcl-cvs コードをロードできるようにします。Emacs は起動時にいつも default.el ファイルを読みますので、そこへ pcl-cvs のエントリポイントの一覧を書きましょう。

pcl-cvs には幸い、default.el に書くべき内容が示してあります。単純に、 pcl-cvs-startup.el の内容を default.el にコピーして下さい(または自分の .emacs でよいです、自分のためだけにインストールしたんなら)。そして Emacs を再起動してください。

info ディレクトリに .info ファイルをコピーして、dir ファイルの目次に pcl-cvs を追加してもいいでしょう。


Node:Using pcl-cvs, Next:, Previous:Installing pcl-cvs, Up:pcl-cvs -- An Emacs Interface To CVS

Using pcl-cvs

インストールさえ済めば、pcl-cvs は非常に使いやすいものです。 cvs-update 関数を実行すると、pcl-cvs はバッファをひとつ立ち上げ、作業コピー内で変更または更新されたファイルを示すます。ここからコミットしたり、 diff をとったりできます。

cvs-update が主なエントリポイントなので、何よりも先に、それに便利なキーシーケンスを割り当てることをお勧めします。わたしは .emacs で Ctrl+c v に割り当てるよう設定しています:

(global-set-key "\C-cv" 'cvs-update)

キーを割り当てないなら、M-x cvs-update (Esc-x cvs-update とも言います)とタイプして起動することもできます。

起動されると、cvs-update はカレントバッファのファイルのあるディレクトリにいるかのように cvs update を実行します。つまり、そのディレクトリ内においてコマンドラインで cvs update とタイプしたかのように、ということです。Emacs のなかでどのようなものが見えるかの例を示します:

PCL-CVS release 1.05 from CVS release $Name:  $.
Copyright (C) 1992, 1993 Per Cederqvist
Pcl-cvs comes with absolutely no warranty; for details consult the manual.
This is free software, and you are welcome to redistribute it under certain
conditions; again, consult the TeXinfo manual for details.
 Modified ci README.txt
 Modified ci fish.c
---------- End ----

ローカルに変更されたファイルが2つあります(pcl-cvs のバージョンによってはファイルのあるサブディレクトリが表示されることもあります)。理屈から言うと次のアクションは、ファイルを1つまたは2つ両方ともをコミットすることですが、各行に ci とあるのはそのことを指しています。1つだけコミットする場合、その行にカーソルを移動して、c とタイプしてください。ログメッセージバッファが出てきて、書きたければそこでログメッセージをタイプすることができます(実際にログメッセージが編集できるのは、コマンドラインより pcl-cvs が優れている主な点です)。書けたら Ctrl+c Ctrl+c とタイプしてコミットを完了して下さい。

複数のファイルを共通のログメッセージで一度にコミットしたい場合、まずコミットしたいファイルに m でマークをつけてください。マークしたものはファイルの隣にアスタリスクが表示されます。

PCL-CVS release 1.05 from CVS release $Name:  $.
Copyright (C) 1992, 1993 Per Cederqvist
Pcl-cvs comes with absolutely no warranty; for details consult the manual.
This is free software, and you are welcome to redistribute it under certain
conditions; again, consult the TeXinfo manual for details.
* Modified ci README.txt
* Modified ci fish.c
---------- End ----

どこでもかまいませんので c をタイプしてください、マークしたファイルすべてに(またはマークしたファイルのみに)適用されます。ログメッセージを書き、前と同じように Ctrl+c Ctrl+c でコミットして下さい。

dをタイプするとファイルの(またはマークしたファイルの) cvs diff を実行します。f をタイプするとそのファイルを Emacs に読み込み、編集できるようにします。コマンドは他にもあります; アップデートバッファで Ctrl+h m とタイプすると、他にどんな事ができるかわかると思います。


Node:Error Handling In pcl-cvs, Next:, Previous:Using pcl-cvs, Up:pcl-cvs -- An Emacs Interface To CVS

Error Handling In pcl-cvs

pcl-cvs は歴史的に、 CVS から来たエラーやメッセージを扱うやりかたがちょっと変です(最新のバージョンでは多分直してあります)。CVS から受け取ったメッセージを pcl-cvs が知らないと、ヒステリーになって pcl-cvs の作者にバグレポートを送るメールバッファを立ち上げます。pcl-cvs の知らない CVS メッセージは、コンフリクトの起こるマージ関連のもので、あまり普通には起こりませんが、確実に時々起こるのです。

pcl-cvs が突然メールバッファを起こしてもパニックにならないでください。バッファ内容を注意して読んで下さい、問題となった CVS からの出力がそのバッファの中のどこかに書いてあります。マージのようであれば、メールバッファを削除して、cvs-update を再度実行してください。今度は多分うまくいくでしょう、なぜなら CVS はマージのメッセージを出力しないからです(マージは既に起こりましたからね)。

(Update: この問題は pcl-cvs の最近のバージョンでは解決されているようなので、ここに書いてある注意は多分無視することになるでしょう)


Node:The Future Of pcl-cvs, Previous:Error Handling In pcl-cvs, Up:pcl-cvs -- An Emacs Interface To CVS

The Future Of pcl-cvs

pcl-cvs があんまりメンテされてなくて、リスキーな投資であるかのような印象を与えてしまったかもしれませんが、不安定なのは一時的なことです。 Stefan Monnier は信頼できるメインテナです(この章を書いている間、彼と何度か連絡を取りましたが、いつもすぐ答えてくれました; 彼はバージョン 2.9.6 のバグをいくつか、既に進捗させています)。これが出版される頃には確信を持って 2.9.7 かそれ以降をダウンロードできるでしょう。

実際、今ちょうどこのトピックについて、pcl-cvs の元メンテナ Greg Woods から嬉しいメールが来ました、許可を得て引用します:

From: woods@most.weird.com (Greg A. Woods)
Subject: Re: pcl-cvs maintenance status, stability of recent "release"s?
To: kfogel@red-bean.com
Date: Sun, 29 Aug 1999 18:59:19 -0400 (EDT)

[...]
今 Stefan のリリースをしばらく使ってみて、わたし、自分のブランチを捨
ててしまいました。

2.9.6 バージョンの変なクセを除いて、彼は PCL-CVS において素晴らしい
仕事をたくさんしています。今わたしこれを毎日使っているんですが、かな
り使いやすいです(今の CVS と一緒に使うと、以前 CVS ディストリビュー
ションに入っていたのより、断然使いやすいです! ;-)。

わたしの FTP サイトに、pcl-cvs.README を置きました。そこに、そのへん
のファイルはえらい古いんだよ(少なくともインターネット時間ではね! ;-)、
と指摘して、Stefan の FTP サイトへのポインタを示しておきました。

[...]

この後のメールで Greg が言うには、FSF が Emacs の次のリリース(20.5)に pcl-cvs を含めることを検討しているから、上に書いたインストールアドバイスの大半が obsolete になってしまうよね、ということでした。あーあ。フリーソフトについていくのって大変。時々ね。


Node:cvsutils -- General Utilities For Use With CVS, Next:, Previous:pcl-cvs -- An Emacs Interface To CVS, Up:Third-Party Tools

cvsutils - General Utilities For Use With CVS

Depends on: Perl

URLs:

Authors: Tom Tromey (原作者), Pavel Roskin (現メインテナ)

普通(いつもではないです) cvsutils と呼ばれている一組の小さなプログラムは、CVS の作業コピー内でオフライン操作を行います。

オフライン操作とは、リポジトリにアクセスせずに行われる操作のことです。次にリポジトリにアクセスするまで作業コピーの一貫性を保ったまま操作します。リポジトリまでのネットワーク接続が遅かったり、信頼性に欠けていたりする場合には、オフラインでの動作は非常に便利です。

バージョン 0.1.4 の README ファイルには次のように書いてあります:

CVSU のホームページは
http://www.typhoon.spb.ru/~proski/cvsu/
です。

このアドレスは1999年の年末に変わる予定です。

この節の最初に並べてある red-bean.com FTP サイトに cvsutils のコピーを置いておきました。cvsutils の新しいホームページのアドレスが公開されたら、それも red-bean.com のほうに置きます。

cvsutils のプログラムの一覧を、だいたい便利な順(わたしの考えでですよ)に並べました。便利なほど最初に来ています。偶然ですが、これは安全な順でもあります。

これらのユーティリティーのうちのいくつかは通常の操作において、作業コピー中のローカルな変更やファイルを失うので、安全性が問題になります。ですから、これらのユーティリティを使う前に説明をよく読んで下さい。

このドキュメントはバージョン 0.1.4 現在において正確です。それ以降のバージョンでは、README ファイルを読んで最新の情報をチェックしてください。


Node:cvsu, Next:, Up:cvsutils -- General Utilities For Use With CVS

cvsu

Danger level: None

Contacts repository: No

ディスク中のファイルのタイムスタンプと、CVS/Entries に記録されたタイムスタンプを比較することによって、オフラインの cvs update を実現します。どのファイルがローカルに変更されていて、どのファイルが CVS の管理下にないかを知ることができます。 cvs update とは違って、リポジトリから作業コピーへ変更を持ってきたりはしません。

いろんなオプションがありますが、cvsu はたいがいの場合オプションなしで起動されます:

floss$ cvsu
? ./bar
? ./chapter-10.html
M ./chapter-10.sgml
D ./out
? ./safe.sh
D ./tools

左側のコードは cvs update と同様です、D がディレクトリを意味するところだけ違います。この例は chapter-10.sgml がローカルに変更されています。例には、cvsu が瞬間的に実行されたことは表示されていないですね。わたしの遅いモデム回線では、通常の cvs update だと30秒くらいかかるのです。

次のようにタイプするとオプション一覧が見られます

floss$ cvsu --help


Node:cvsdo, Next:, Previous:cvsu, Up:cvsutils -- General Utilities For Use With CVS

cvsdo

Danger level: Low to none

Contacts repository: No

このコマンドは cvs add と cvs remove の作業コピーへの効果をシミュレートします。リポジトリにはアクセスしません。もちろん、リポジトリに更新を反映させるには、コミットする必要がありますが、少なくとも add と remove コマンドに関してはこの方法でスピードアップできます。使い方は次のとおり:

floss$ cvsdo add FILENAME

または

floss$ cvsdo remove FILENAME

To see a list of further options, run:

floss$ cvsdo --help


Node:cvschroot, Next:, Previous:cvsdo, Up:cvsutils -- General Utilities For Use With CVS

cvschroot

Danger level: Low

Contacts repository: No

このコマンドは作業コピーをいじって新しいリポジトリを指すようにすることにより、リポジトリの移動を処理します。リポジトリが新しい場所に大量にコピーされたときに便利です。リビジョンに変化はなく、作業コピーの全ての CVS/Root (と、もしかすると CVS/Repository) ファイルを変更して新しいリポジトリを指すようにします。cvschroot を使うと、新しい作業コピーをチェックアウトし直すよりもだいぶ速いです。あと、ローカルの変更を失わずに済むのも利点です。

Usage:

floss$ cvschroot NEW_REPOS

For example:

floss$ cvschroot :pserver:newuser@newhost.wherever.com:/home/cvs/myproj


Node:cvsrmadm, Next:, Previous:cvschroot, Up:cvsutils -- General Utilities For Use With CVS

cvsrmadm

Danger level: Low to medium

Contacts repository: No

作業コピー中の全ての CVS/ 管理サブディレクトリを削除します。あとには cvs export で作成したようなディレクトリツリーが残ります。

cvsrmadm を使ってもローカルの変更は失われませんが、その作業コピーは作業コピーとは言えなくなります。

注意して使って下さい。


Node:cvspurge, Next:, Previous:cvsrmadm, Up:cvsutils -- General Utilities For Use With CVS

cvspurge

Danger level: Medium

Contacts repository: No

作業コピー中の CVS 管理下にない全てのファイルを削除します。CVS の管理下にあるファイルのローカルな変更を取り消したりはしません。

注意して使って下さい。


Node:cvsdiscard, Next:, Previous:cvspurge, Up:cvsutils -- General Utilities For Use With CVS

cvsdiscard

Danger level: Medium to high

Contacts repository: Maybe

これは cvspurge の逆のコマンドです。未知のファイルを消してローカルな変更をそのままにするのではなく、cvsdiscard はローカルな変更を全て取り消して(リポジトリから取ってきた新鮮なコピーで置き換わります)、未知のファイルはそのまま置いておきます。

特に注意して使って下さい。


Node:cvsco, Next:, Previous:cvsdiscard, Up:cvsutils -- General Utilities For Use With CVS

cvsco

Danger level: High

Contacts repository: Maybe

cvspurge と cvsdiscard のあわさったコマンドです。ローカルな変更を全て取り消し、未知のファイルを作業コピーから削除します。

偏執狂のような注意をもって使って下さい。


Node:cvsdate, Previous:cvsco, Up:cvsutils -- General Utilities For Use With CVS

cvsdate

このスクリプトは明らかに未完成で、多分完成することはないでしょう。(詳しくは README ファイルを参照してください。)


Node:cvs2cl -- Generate GNU-Style ChangeLogs, Next:, Previous:cvsutils -- General Utilities For Use With CVS, Up:Third-Party Tools

cvs2cl - Generate GNU-Style ChangeLogs

Depends on: Perl

URL: http://www.red-bean.com/~kfogel/cvs2cl.shtml

cvs2cl.pl は cvs log の出力を圧縮し、GNU スタイルのChangeLog 形式に変換します。ChangeLog とは、人間が読みやすいようデザインされた形式で、プロジェクトの変更履歴を時系列順に並べたドキュメントです(以下の例を参照のこと)。

cvs log コマンドの問題は、ファイルごとの出力しかされなくて、別々のファイルに同じログメッセージがあることや、ほぼ同じ時刻であることとかがわからない、つまり、それらのリビジョンが一度にコミットされたということがわからない、という点です。なので、プロジェクトの概要を知るためにログ出力を読むのは絶望的な作業です。実質、同時にひとつのファイルの履歴しか見ることはできません。

cvs2cl.pl が生成した ChangeLog では各ログメッセージはひとつに統一されているので、一群のファイルを一度にコミットしたら、そのコミットは1つのエントリとして示されます。例えば:

floss$ cvs2cl.pl -r
cvs log: Logging .
cvs log: Logging a-subdir
cvs log: Logging a-subdir/subsubdir
cvs log: Logging b-subdir
floss$ cat ChangeLog
...
1999-08-29 05:44  jrandom

   * README (1.6), hello.c (2.1), a-subdir/whatever.c (2.1),
   a-subdir/subsubdir/fish.c (2.1): Committing from pcl-cvs 2.9, just
   for kicks.

1999-08-23 22:48  jrandom

   * README (1.5): [no log message]

1999-08-22 19:34  jrandom

   * README (1.4): trivial change
...
floss$

最初のエントリは4つのファイルが "Committing from pcl-cvs 2.9, just for kicks." というログメッセージとともに一度にコミットされたことを示しています。(-r はそのログメッセージに関連する各ファイルのリビジョン番号を示すためのオプションです)

CVS と同様、cvs2cl.pl はカレントディレクトリを暗黙の引数として取りますが、ファイル名を指定すればそれらについて動作します。以下に最もよく使われるオプションのうちいくつかについて説明します。


Node:cvslock -- Lock Repositories For Atomicity, Next:, Previous:cvs2cl -- Generate GNU-Style ChangeLogs, Up:Third-Party Tools

cvslock - Lock Repositories For Atomicity

Depends on: C compiler for installation; nothing for runtime

URL: ftp://riemann.iam.uni-bonn.de/pub/users/roessler/cvslock/

このプログラムは CVS リポジトリをロックします(読みか書きかのいずれか)。 CVS にもロックを遵守させるため、CVS と同じやりかたでロックを行います。このツールは例えば、リポジトリ全体のコピーを取る必要がある時に、コミットや他の人のロックファイルを受け付けたくないような場合に役立ちます。

cvslock のディストリビューションパッケージは非常によくできているので、通常の GNU のインストール手続きに従ってインストールできます。インストールセッションのログを示します:

floss$ zcat cvslock-0.1.tar.gz | tar xvf -
cvslock-0.1/
cvslock-0.1/Makefile.in
cvslock-0.1/README
cvslock-0.1/COPYING
cvslock-0.1/Makefile.am
cvslock-0.1/acconfig.h
cvslock-0.1/aclocal.m4
cvslock-0.1/config.h.in
cvslock-0.1/configure
cvslock-0.1/configure.in
cvslock-0.1/install-sh
cvslock-0.1/missing
cvslock-0.1/mkinstalldirs
cvslock-0.1/stamp-h.in
cvslock-0.1/cvslock.c
cvslock-0.1/cvslock.1
cvslock-0.1/snprintf.c
cvslock-0.1/cvslssh
cvslock-0.1/VERSION
floss$ cd cvslock-0.1
floss$ ./configure
 ...
floss$ make
gcc -DHAVE_CONFIG_H -I. -I. -I.   -g -O2 -c cvslock.c
gcc -g -O2  -o cvslock  cvslock.o
floss$ make install
 ...
floss$

(make install のところでは root になる必要があることに注意して下さい)

さてこれで cvslock は /usr/local/bin/cvslock としてインストールされました。起動する際には通常の CVS と同様、 -d か $CVSROOT 環境変数でリポジトリを指定できます。そのディレクトリ及びその下のサブディレクトリすべてがロックされます。この例ではサブディレクトリはありませんので、ロックファイルは1つだけ作成されます:

floss$ ls /usr/local/newrepos/myproj/b-subdir/
random.c,v
floss$ cvslock -d /usr/local/newrepos  myproj/b-subdir
floss$ ls /usr/local/newrepos/myproj/b-subdir/
#cvs.rfl.cvslock.floss.27378  random.c,v
floss$ cvslock -u -p 27378 -d /usr/local/newrepos  myproj/b-subdir
floss$ ls /usr/local/newrepos/myproj/b-subdir/
random.c,v
floss$

ロックのクリア時(-u は unlock という意味です)、-p 27378 を指定する必要があったことに注意して下さい。cvslock はロックファイルの名前を作るときに Unix のプロセス ID を使用して、ロックがユニークであることを保証しているからです。たとえロックインスタンスが1つしかなくても、アンロックの際にはどのインスタンスを削除するか指定して下さい。-p フラグでどれが前のインスタンスかを指定し、あとでクリアできるようにします(-p フラグは -u つきでも -u なしでも指定できます)。

ちょっとリポジトリ内でファイルシステムを直接いじる作業をしたいなと思う場合は、-s オプションを指定して cvslock に新しいシェルをさせることができます。現在のシェル内の $SHELL 環境変数で、どのシェルを起動するかが決まります:

floss$ cvslock -s -d /usr/local/newrepos myproj

シェルを抜けるまでロックがかかったままになり、そのロックは抜けたときに自動的に削除されます。 -c オプションで、リポジトリをロックしてコマンドを実行することもできます。-s と同様に、コマンドを開始する前にロックがかかり、コマンドが終了したら削除されます。以下の例では、全てのロックファイル一覧を表示する間だけ、リポジトリがロックされます。

floss$ cvslock -c 'find . -name "*cvslock*" ' -d /usr/local/newrepos myproj
cvslock: '/usr/local/newrepos/myproj' locked successfully.
cvslock: Starting 'find . -name "*cvslock*" -print'...
./a-subdir/subsubdir/#cvs.rfl.cvslock.floss.27452
./a-subdir/#cvs.rfl.cvslock.floss.27452
./b-subdir/#cvs.rfl.cvslock.floss.27452
./#cvs.rfl.cvslock.floss.27452
floss$ find /usr/local/newrepos/myproj -name "*cvslock*" -print
floss$

コマンド(-c オプションの引数)は、指定したリポジトリディレクトリを輪ーキングディレクトリとして実行されます。

デフォルトでは読みのロックを生成します。書きのロックを指定したい場合には -W オプションを使って下さい。(-R で読みのロックを指定できますが、デフォルトですからね。) 終わったら必ず全ロックを削除して、他のユーザの CVS プロセスを必要もないのに待たせたりしないで下さい。

cvslock はリポジトリのあるマシン上で実行しなければならないことに注意して下さい。リモートリポジトリは指定できません。(詳細については man cvslock を実行してください、これは make install でインストールされたマニュアルページです。)


Node:Other Packages, Next:, Previous:cvslock -- Lock Repositories For Atomicity, Up:Third-Party Tools

Other Packages

CVS には他にもたくさんサードパーティのパッケージがあります。そのうちのいくつかへのポインタを下に記します。

CVSUp (Part Of The FreeBSD Project)

CVSUp は CVS リポジトリのミラーに役立つ特別なビルトインサポートのある、有能で汎用性のあるミラリングツールです。FreeBSD オペレーティングシステムではマスタリポジトリから変更を配布するためにこれを使用しているので、ユーザは容易に最新を追いかけることができます。

CVSUp の一般的な情報については、 http://www.polstra.com/projects/freeware/CVSup/ を参照して下さい。

FreeBSD に特化した使い方については、 http://www.freebsd.org/handbook/synching.html#CVSUP を参照して下さい。

CVSWeb: A Web Interface To CVS Repositories

CVSWeb は CVS リポジトリを閲覧するためのウェブインタフェースです。正確には "RCSWeb" というべきかもしれません、実際にはリポジトリ内のリビジョンを直接閲覧したり、ログメッセージを見たり diff を取るのですから。

わたしにとっては特に素晴しいインタフェースのようには思えないのですが、にもかかわらず、これは十分直観的でたくさんのサイトがそれを使っているということを認めなければなりません。

このソフトウェアの原作者は Bill Fenner ですが、現在最も活発に開発が進んでいるバージョンは Henner Zeller のもの http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/ のようです。

Fenner の原作のサイトはこちら http://www.freebsd.org/~fenner/cvsweb/ 、Cyclic Software の CVSWeb 状況概要はこちら http://www.cyclic.com/cyclic-pages/web-cvsweb.html です。

最後に、活動中の CVSWeb を見たいなら、こちら http://sourceware.cygnus.com/cgi-bin/cvsweb.cgi/ が良い例になるでしょう。

The CVS contrib/ Directory

Repository Administration で述べたとおり、たくさんのサードパーティツールが CVS とともに配布されており、それらは contrib/ ディレクトリに入っています。どのツールを CVS と一緒に配布するかを決定する正式は規則は知らないのですが、広く使われているサードパーティツールの多くを集め、見つけやすいように contrib/ に入れる努力は継続中だと思います。そういうツールを探すもっともよい方法は、contrib/ の中を見て、いろいろな CVS のウェブサイトを見て、それからメーリングリストで聞くことです。


Node:Writing Your Own Tools, Previous:Other Packages, Up:Third-Party Tools

Writing Your Own Tools

CVS はときどき、即興で作られた標準のわけのわからないコレクションのように見えるかもしれません。RCS 形式あり、さまざまな出力形式(history, annotate, log, update などなど)あり、リポジトリ管理ファイル形式いくつかあり、作業コピー管理ファイル形式あり、クライアント/サーバプロトコルありロックファイルプロトコルあり…(痺れてきた? まだ続けられるよ、知ってると思うけど)。

幸い、これらの標準はリリース間で一貫したままですから、もしあなたが CVS とともに動くようなツールを書こうとするなら、動く標的に命中させなくちゃいかんようなことにはなりませんから御心配なく。 info-cvs@gnu.org メーリングリストに数人、すべての内部標準に非常に詳しい人々がいます(この本を書くにあたり、2,3人に助けてもらいました)。CVS ディストリビューションについてくるドキュメントもあります(とりわけ doc/cvs.texinfo, doc/cvsclient.texi, doc/RCSFILES)。実装や動作についてのどんな質問に対しても最後の手段として CVS のソースコード自身があります。

これらすべて、あなたは自由に使えるのです。尻込みする理由はありません。自分の CVS ライフを簡単にするようなユーティリティを考えついたら、さあ書きましょう。他の人もそれを欲しがっている可能性が高いですよ。CVS 自身への変更とは違い、スタンドアロンの外部ユーティリティはとても素早く広く配布でき、作者にすぐフィードバックが返ってきますし、より早くユーザ全員にバグフィクスができます。


Node:Index, Next:, Previous:Third-Party Tools, Up:Top

Index

Sorry, the index is still in progress.

Since the online format is searchable anyway, I decided the incompleteness of the index need not delay the release of the chapters. I hope to have the index finished reasonably soon. Volunteer indexers are certainly welcome, too - please email bug-cvsbook@red-bean.com if you're interested.


Node:GNU General Public License, Previous:Index, Up:Top

GNU General Public License

GNU General Public License

Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Preamble

The licenses for most software are designed to take away your freedom to
share and change it. By contrast, the GNU General Public License is intended
to guarantee your freedom to share and change free software--to make sure
the software is free for all its users. This General Public License applies
to most of the Free Software Foundation's software and to any other program
whose authors commit to using it. (Some other Free Software Foundation
software is covered by the GNU Library General Public License instead.) You
can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for this service if you
wish), that you receive source code or can get it if you want it, that you
can change the software or use pieces of it in new free programs; and that
you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to
deny you these rights or to ask you to surrender the rights. These
restrictions translate to certain responsibilities for you if you distribute
copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or
for a fee, you must give the recipients all the rights that you have. You
must make sure that they, too, receive or can get the source code. And you
must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2)
offer you this license which gives you legal permission to copy, distribute
and/or modify the software.

Also, for each author's protection and ours, we want to make certain that
everyone understands that there is no warranty for this free software. If
the software is modified by someone else and passed on, we want its
recipients to know that what they have is not the original, so that any
problems introduced by others will not reflect on the original authors'
reputations.

Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that redistributors of a free program will
individually obtain patent licenses, in effect making the program
proprietary. To prevent this, we have made it clear that any patent must be
licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification
follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License applies to any program or other work which contains a notice
placed by the copyright holder saying it may be distributed under the terms
of this General Public License. The "Program", below, refers to any such
program or work, and a "work based on the Program" means either the Program
or any derivative work under copyright law: that is to say, a work
containing the Program or a portion of it, either verbatim or with
modifications and/or translated into another language. (Hereinafter,
translation is included without limitation in the term "modification".) Each
licensee is addressed as "you".

Activities other than copying, distribution and modification are not covered
by this License; they are outside its scope. The act of running the Program
is not restricted, and the output from the Program is covered only if its
contents constitute a work based on the Program (independent of having been
made by running the Program). Whether that is true depends on what the
Program does.

1. You may copy and distribute verbatim copies of the Program's source code
as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and
disclaimer of warranty; keep intact all the notices that refer to this
License and to the absence of any warranty; and give any other recipients of
the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you
may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it,
thus forming a work based on the Program, and copy and distribute such
modifications or work under the terms of Section 1 above, provided that you
also meet all of these conditions:

   * a) You must cause the modified files to carry prominent notices stating
     that you changed the files and the date of any change.

   * b) You must cause any work that you distribute or publish, that in
     whole or in part contains or is derived from the Program or any part
     thereof, to be licensed as a whole at no charge to all third parties
     under the terms of this License.

   * c) If the modified program normally reads commands interactively when
     run, you must cause it, when started running for such interactive use
     in the most ordinary way, to print or display an announcement including
     an appropriate copyright notice and a notice that there is no warranty
     (or else, saying that you provide a warranty) and that users may
     redistribute the program under these conditions, and telling the user
     how to view a copy of this License. (Exception: if the Program itself
     is interactive but does not normally print such an announcement, your
     work based on the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Program, and can be
reasonably considered independent and separate works in themselves, then
this License, and its terms, do not apply to those sections when you
distribute them as separate works. But when you distribute the same sections
as part of a whole which is a work based on the Program, the distribution of
the whole must be on the terms of this License, whose permissions for other
licensees extend to the entire whole, and thus to each and every part
regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise
the right to control the distribution of derivative or collective works
based on the Program.

In addition, mere aggregation of another work not based on the Program with
the Program (or with a work based on the Program) on a volume of a storage
or distribution medium does not bring the other work under the scope of this
License.

3. You may copy and distribute the Program (or a work based on it, under
Section 2) in object code or executable form under the terms of Sections 1
and 2 above provided that you also do one of the following:

   * a) Accompany it with the complete corresponding machine-readable source
     code, which must be distributed under the terms of Sections 1 and 2
     above on a medium customarily used for software interchange; or,

   * b) Accompany it with a written offer, valid for at least three years,
     to give any third party, for a charge no more than your cost of
     physically performing source distribution, a complete machine-readable
     copy of the corresponding source code, to be distributed under the
     terms of Sections 1 and 2 above on a medium customarily used for
     software interchange; or,

   * c) Accompany it with the information you received as to the offer to
     distribute corresponding source code. (This alternative is allowed only
     for noncommercial distribution and only if you received the program in
     object code or executable form with such an offer, in accord with
     Subsection b above.)

The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all
the source code for all modules it contains, plus any associated interface
definition files, plus the scripts used to control compilation and
installation of the executable. However, as a special exception, the source
code distributed need not include anything that is normally distributed (in
either source or binary form) with the major components (compiler, kernel,
and so on) of the operating system on which the executable runs, unless that
component itself accompanies the executable.

If distribution of executable or object code is made by offering access to
copy from a designated place, then offering equivalent access to copy the
source code from the same place counts as distribution of the source code,
even though third parties are not compelled to copy the source along with
the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as
expressly provided under this License. Any attempt otherwise to copy,
modify, sublicense or distribute the Program is void, and will automatically
terminate your rights under this License. However, parties who have received
copies, or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute the
Program or its derivative works. These actions are prohibited by law if you
do not accept this License. Therefore, by modifying or distributing the
Program (or any work based on the Program), you indicate your acceptance of
this License to do so, and all its terms and conditions for copying,
distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the original
licensor to copy, distribute or modify the Program subject to these terms
and conditions. You may not impose any further restrictions on the
recipients' exercise of the rights granted herein. You are not responsible
for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot distribute so
as to satisfy simultaneously your obligations under this License and any
other pertinent obligations, then as a consequence you may not distribute
the Program at all. For example, if a patent license would not permit
royalty-free redistribution of the Program by all those who receive copies
directly or indirectly through you, then the only way you could satisfy both
it and this License would be to refrain entirely from distribution of the
Program.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply and
the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents
or other property right claims or to contest validity of any such claims;
this section has the sole purpose of protecting the integrity of the free
software distribution system, which is implemented by public license
practices. Many people have made generous contributions to the wide range of
software distributed through that system in reliance on consistent
application of that system; it is up to the author/donor to decide if he or
she is willing to distribute software through any other system and a
licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain
countries either by patents or by copyrighted interfaces, the original
copyright holder who places the Program under this License may add an
explicit geographical distribution limitation excluding those countries, so
that distribution is permitted only in or among countries not thus excluded.
In such case, this License incorporates the limitation as if written in the
body of this License.

9. The Free Software Foundation may publish revised and/or new versions of
the General Public License from time to time. Such new versions will be
similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

10. If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask for
permission. For software which is copyrighted by the Free Software
Foundation, write to the Free Software Foundation; we sometimes make
exceptions for this. Our decision will be guided by the two goals of
preserving the free status of all derivatives of our free software and of
promoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO
THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM
PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO
LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR
THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively convey the
exclusion of warranty; and each file should have at least the "copyright"
line and a pointer to where the full notice is found.

one line to give the program's name and an idea of what it does.
Copyright (C) yyyy  name of author

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when
it starts in an interactive mode:

Gnomovision version 69, Copyright (C) yyyy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
type `show w'.  This is free software, and you are welcome
to redistribute it under certain conditions; type `show c'
for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may be
called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright
interest in the program `Gnomovision'
(which makes passes at compilers) written
by James Hacker.

signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General Public
License instead of this License.