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, ISBN 1-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" throu