[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

An Overview of CVS

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=An%20Overview%20of%20CVS"
"j-cvsbook/AnOverviewofCVS"へのコメント(無し)
検索全文Elisp

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

--Brian Fitzpatrick on CVS

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.1 Basic Concepts

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Basic%20Concepts"
"j-cvsbook/A.1BasicConcepts"へのコメント(無し)
検索全文Elisp

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で述 べます)。ここでは作業コピーを変更するときの方法に集中しましょう。

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2 A Day With CVS

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=A%20Day%20With%20CVS"
"j-cvsbook/A.2ADayWithCVS"へのコメント(無し)
検索全文Elisp

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

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

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

A.2.1 Conventions Used In This Tour  
A.2.2 Invoking CVS  
A.2.3 Accessing A Repository  
A.2.4 Starting A New Project  
A.2.5 Checking Out A Working Copy  
A.2.6 Version Versus Revision  
A.2.7 Making A Change  
A.2.8 Finding Out What You (And Others) Did -- update And diff  
A.2.9 CVS And Implied Arguments  
A.2.10 Committing  
A.2.11 Revision Numbers  
A.2.12 Detecting And Resolving Conflicts  
A.2.13 Finding Out Who Did What (Browsing Log Messages)  
A.2.14 Examining And Reverting Changes  
A.2.15 The Slow Method Of Reverting  
A.2.16 The Fast Method Of Reverting  



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.1 Conventions Used In This Tour

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Conventions%20Used%20In%20This%20Tour"
"j-cvsbook/A.2.1ConventionsUsedInThisTour"へのコメント(無し)
検索全文Elisp

ツアーの舞台は 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 に詳しく なりたいと思うに違いありません。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.2 Invoking CVS

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Invoking%20CVS"
"j-cvsbook/A.2.2InvokingCVS"へのコメント(無し)
検索全文Elisp

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 の結果をファイルではなく標準出力に送るという意味です)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.3 Accessing A Repository

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Accessing%20A%20Repository"
"j-cvsbook/A.2.3AccessingARepository"へのコメント(無し)
検索全文Elisp

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 リポジトリ と指定して下さい。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.4 Starting A New Project

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Starting%20A%20New%20Project"
"j-cvsbook/A.2.4StartingANewProject"へのコメント(無し)
検索全文Elisp

既に 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$ 

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.5 Checking Out A Working Copy

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Checking%20Out%20A%20Working%20Copy"
"j-cvsbook/A.2.5CheckingOutAWorkingCopy"へのコメント(無し)
検索全文Elisp

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

 
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 です。最初のリビジョン番号はちょっと特殊なので、そのへんはあとに しましょう、変更してコミットしてみてからリビジョン番号についてみていく予 定です。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.6 Version Versus Revision

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Version%20Versus%20Revision"
"j-cvsbook/A.2.6VersionVersusRevision"へのコメント(無し)
検索全文Elisp

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.7 Making A Change

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Making%20A%20Change"
"j-cvsbook/A.2.7MakingAChange"へのコメント(無し)
検索全文Elisp

このプロジェクトは特にたいしたことはできません。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 は命の恩人です」 という状況になるのです。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

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

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Finding%20Out%20What%20You%20(And%20Others)%20Did%20--%20update%20And%20diff"
"j-cvsbook/A.2.8FindingOutWhatYou(AndOthers)Did--updateAnddiff"へのコメント(無し)
検索全文Elisp

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

 
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 を読むことにかけてはエキスパートです ね。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.9 CVS And Implied Arguments

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=CVS%20And%20Implied%20Arguments"
"j-cvsbook/A.2.9CVSAndImpliedArguments"へのコメント(無し)
検索全文Elisp

今まで述べてきた各 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 の準備ができました。リポジトリに変更をコミットしてみましょう。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.10 Committing

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Committing"
"j-cvsbook/A.2.10Committing"へのコメント(無し)
検索全文Elisp

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 のみぞ知る)。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.11 Revision Numbers

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Revision%20Numbers"
"j-cvsbook/A.2.11RevisionNumbers"へのコメント(無し)
検索全文Elisp

プロジェクト中の各ファイルはそれぞれリビジョン番号というのを持っています。 ファイルがコミットされるとリビジョン番号の最後のところが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 も走らせません。そのかわりそのファイルを編集して、 同じところを変更してみましょう。こうやると、初めてのコンフリクトにお目 にかかれますよ。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.12 Detecting And Resolving Conflicts

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Detecting%20And%20Resolving%20Conflicts"
"j-cvsbook/A.2.12DetectingAndResolvingConflicts"へのコメント(無し)
検索全文Elisp

コンフリクトを発見するのは簡単です。 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$ 



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.13 Finding Out Who Did What (Browsing Log Messages)

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Finding%20Out%20Who%20Did%20What%20(Browsing%20Log%20Messages)"
"j-cvsbook/A.2.13FindingOutWhoDidWhat(BrowsingLogMessages)"へのコメント(無し)
検索全文Elisp

ここまで、今回のこのプロジェクトはいくつかの変更を経験しました。いままで に起こったことをざっと見ようと思ったとき、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 で紹 介します。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.14 Examining And Reverting Changes

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Examining%20And%20Reverting%20Changes"
"j-cvsbook/A.2.14ExaminingAndRevertingChanges"へのコメント(無し)
検索全文Elisp

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つあります。ゆっくりチマチマ やる方法と、速くてカッコイイ方法です。まずはゆっくりチマチマやる方法を先 に見ましょう。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.15 The Slow Method Of Reverting

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=The%20Slow%20Method%20Of%20Reverting"
"j-cvsbook/A.2.15TheSlowMethodOfReverting"へのコメント(無し)
検索全文Elisp

この方法では 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$ 



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.2.16 The Fast Method Of Reverting

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=The%20Fast%20Method%20Of%20Reverting"
"j-cvsbook/A.2.16TheFastMethodOfReverting"へのコメント(無し)
検索全文Elisp

元に戻すのに速くてカッコイイ方法というのは、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

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Reverting%20Is%20Not%20A%20Substitute%20For%20Communication"
"j-cvsbook/RevertingIsNotASubstituteForCommunication"へのコメント(無し)
検索全文Elisp

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3 Other Useful CVS Commands

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Other%20Useful%20CVS%20Commands"
"j-cvsbook/A.3OtherUsefulCVSCommands"へのコメント(無し)
検索全文Elisp

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

A.3.1 Adding Files  
A.3.2 Adding Directories  
A.3.3 CVS And Binary Files  
A.3.4 Removing Files  
A.3.5 Removing Directories  
A.3.6 Renaming Files And Directories  
A.3.7 Avoiding Option Fatigue  
A.3.8 Getting Snapshots (Dates And Tagging)  
A.3.9 Acceptable Date Formats  
A.3.10 Marking A Moment In Time (Tags)  



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.1 Adding Files

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Adding%20Files"
"j-cvsbook/A.3.1AddingFiles"へのコメント(無し)
検索全文Elisp

ファイル追加には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$ 



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.2 Adding Directories

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Adding%20Directories"
"j-cvsbook/A.3.2AddingDirectories"へのコメント(無し)
検索全文Elisp

ファイルを追加する場合とは違い、ディレクトリを追加するのは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$ 

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.3 CVS And Binary Files

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=CVS%20And%20Binary%20Files"
"j-cvsbook/A.3.3CVSAndBinaryFiles"へのコメント(無し)
検索全文Elisp

今まで黙っていましたが、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 をとる方法も提供するかもしれません。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.4 Removing Files

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Removing%20Files"
"j-cvsbook/A.3.4RemovingFiles"へのコメント(無し)
検索全文Elisp

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

 
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 が存在しないに もかかわらずファイル名を明示的に指定していることに注意してください。もち ろん、コミット時にはファイル名を必ずしも指定する必要はありませんが、こう しておけば作業コピー中の他のファイルの変更をまきこんでコミットしてしまう 心配がなくなります。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.5 Removing Directories

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Removing%20Directories"
"j-cvsbook/A.3.5RemovingDirectories"へのコメント(無し)
検索全文Elisp

前にも言ったとおり、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 を実行してみるとわかると思います。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.6 Renaming Files And Directories

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Renaming%20Files%20And%20Directories"
"j-cvsbook/A.3.6RenamingFilesAndDirectories"へのコメント(無し)
検索全文Elisp

あるファイルの名前を変えるということは、新しい名前でファイルを作って、古 いのを消すというのと等価です。 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 の操作で行うのが一番いいの です。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.7 Avoiding Option Fatigue

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Avoiding%20Option%20Fatigue"
"j-cvsbook/A.3.7AvoidingOptionFatigue"へのコメント(無し)
検索全文Elisp

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

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

 
diff -c
update -P
cvs -q

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.8 Getting Snapshots (Dates And Tagging)

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Getting%20Snapshots%20(Dates%20And%20Tagging)"
"j-cvsbook/A.3.8GettingSnapshots(DatesAndTagging)"へのコメント(無し)
検索全文Elisp

バグレポートが舞い込んできて壊れた状態になったプログラムの話に戻りましょ う。開発者は突然、最後のリリースの時点のプロジェクト全体にアクセスしなく てはなりません。ファイルはあれもこれも変更してあって、おまけにリビジョン 番号がファイルによってバラバラであろうとも、です。ログメッセージを見て、 ファイルごとにリリースのときのリビジョン番号はどれだったか探して、それぞ れ -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$ 



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.9 Acceptable Date Formats

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Acceptable%20Date%20Formats"
"j-cvsbook/A.3.9AcceptableDateFormats"へのコメント(無し)
検索全文Elisp

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 シェルがそれ をひとつの引数として扱うようにするためにつけています。空白を含んでいなく ても害はないのでいつも使うようにするのがよいでしょう。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.3.10 Marking A Moment In Time (Tags)

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Marking%20A%20Moment%20In%20Time%20(Tags)"
"j-cvsbook/A.3.10MarkingAMomentInTime(Tags)"へのコメント(無し)
検索全文Elisp

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

そのイベントが起こった日付を思い出そうとしたり、ログメッセージを読んでそ の日付を推測したりするのは、さぞかし退屈な作業でしょう。おそらく、そのよ うなイベントは重要なのでしょうから、リビジョン履歴のなかにそのようにきち んと記録されています。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 は歴史を変えさせてくれない、と言いました。さて、今から歴史を変える方 法を教えます。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.4 Branches

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Branches"
"j-cvsbook/A.4Branches"へのコメント(無し)
検索全文Elisp

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

A.4.1 Branching Basics  
A.4.2 Merging Changes From Branch To Trunk  
A.4.3 Multiple Merges  
A.4.4 Creating A Tag Or Branch Without A Working Copy  



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.4.1 Branching Basics

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Branching%20Basics"
"j-cvsbook/A.4.1BranchingBasics"へのコメント(無し)
検索全文Elisp

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

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

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

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

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

**************************************************************** 彼女の変更はトランクにあるコードに何の影響も与えません

同じバグフィクスがトランクのほうにも必要かどうか見つけだす

Her change won't have affected the code on the trunk, nor would she want it to without first finding out (whether the trunk needs the same bug fix or not). ****************************************************************

もし必要なら、ブランチの変更をトランクにマージすることもできます。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など))。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.4.2 Merging Changes From Branch To Trunk

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Merging%20Changes%20From%20Branch%20To%20Trunk"
"j-cvsbook/A.4.2MergingChangesFromBranchToTrunk"へのコメント(無し)
検索全文Elisp

さて、ブランチ上にバグフィクスがコミットされました。ここで作業コピーをト ランクのリビジョンの一番大きいものに切り替えて、そっちでもそのバグフィク スをする必要があるかどうか見てみます。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$ 

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

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.4.3 Multiple Merges

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Multiple%20Merges"
"j-cvsbook/A.4.3MultipleMerges"へのコメント(無し)
検索全文Elisp

トランクにマージした後にも、ブランチ上での開発が続くことがあります。たと えば前のリリース版に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 という名前のタグをふたつつけたりはできません。)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

A.4.4 Creating A Tag Or Branch Without A Working Copy

URL="http://www.bookshelf.jp/cgi-bin/goto.cgi?file=j-cvsbook&node=Creating%20A%20Tag%20Or%20Branch%20Without%20A%20Working%20Copy"
"j-cvsbook/A.4.4CreatingATagOrBranchWithoutAWorkingCopy"へのコメント(無し)
検索全文Elisp

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

こんな方法があります: 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 を使う人には不可欠なものですから。


[ << ] [ >> ]           [表紙] [目次] [索引] [検索] [上端 / 下端] [?]