UNIX/Linuxの部屋 CVS運用編コマンドの使い方

TOP UNIX/Linuxの部屋 UNIX/Linuxコマンド一覧 用語集 新版 由来/読み方辞書 環境変数マニュアル Cシェル変数 システム設定ファイル システムコール・ライブラリ ネットワークプログラミングの基礎知識 クラウドサービス徹底比較・徹底解説




コマンド CVS運用編 CVS をお仕事なプロジェクトに導入するには このエントリーをはてなブックマークに追加

手作業でリリース管理を行っているプロジェクトに、CVS を導入する際のノウハウについて説明する。なお、当ページ管理人は業務系な人間なので、少々内容に偏りがあると思われる。CVS 自体の説明は、チュートリアル編を参照のこと。

世の中には驚くほど後進的なプロジェクトがたくさん存在するものである。
  • ソースのマスタがない
  • 開発環境と商用環境でソースに差違がある (マスタもないので、どちらが正しいかわからない)
  • 過去のソースが保存されていない (潜在バグが発覚したとき、このバグはいつから存在したのか答えられない)
  • 誰が修正したのかわからない
  • 商用環境などへのリリース作業は修正したファイルを手作業で拾いだし、ファイルをひとつずつ FTP で転送する
運悪くこのようなプロジェクトに関わってしまった場合は、CVS を導入してまともなプロジェクトに変えていこう。

問題提起
大抵のプロジェクトでは、開発環境と商用環境が分かれているだろう。まずは開発環境と商用環境のソースを diff コマンドを使って比較してみよう。開発環境のソースを手作業で商用環境にリリースしているようなプロジェクトでは、まず間違いなくソースに差違が発生しているだろう。差違を見つけたら、「これは問題だ」「CVS で管理することで解決できる」とアピールしよう。

もしソースの差違が全くなかった場合は、ソース管理者が非常に優秀ということである。その場合は稼働工数削減を狙いにする。例えばソース管理者が 1人いて、修正したい場合は以下のような手順を踏んでいるとする。
  • ソース開放依頼書 (Excel シート) に修正対象ソース名・修正理由・修正者・日付を記述する。
  • ソース管理者がソース開放依頼書を確認し、最新ソースを修正者に送付
  • 修正者がソースを修正し、ソース管理者に送付
  • ソース管理者は修正版ソースをマスタに格納
1回あたり何分くらいかかって、1ヵ月で何時間の無駄が発生しているか計算し、「CVS なら質を落とさずに稼働を削減できます」とアピールしよう。

リポジトリ作成
まずはリポジトリにインポートするソースを決めなければならない。開発環境と商用環境から取得したソースで、差違があるものをリストアップし、どちらが正しいかを決定する。また、どれが必要なファイルで、どれが不要なファイルかを選別する。

ローカルルール策定
CVS を使用する上でのプロジェクト内のローカルルールを策定する。最低限、以下のことを明記しておくこと。
作業場所
最も望ましいのはメンバ全員のアカウントを作成し、
/home/member1/work/src/
/home/member2/work/src/
/home/member3/work/src/
/home/member4/work/src/
などと各メンバのホームディレクトリの下にソースをチェックアウトして、開発作業を行う方法である。もし「プログラムを動かすには必ず /home/proj/ の下にファイルを置く必要がある」などの場合は、いちいちコミットしないと動作確認ができないため作業効率が低下してしまう。このようなときは妥協案として
/home/proj/src/
の下を共有の作業場所としてもよい。

担当割の明確化
あるソースを修正する担当者は必ず 1人にしよう。できれば、以下のようにディレクトリ単位で担当者を決めておき、全員に周知しておくこと。
/home/proj/src/program1/ … A さん
/home/proj/src/program2/ … B さん
/home/proj/src/program3/ … C さん
CVS は複数人での作業が可能であるものの、複数人で同じソースを修正すること自体がミスの元である。できるだけひとつの機能を担当するのはひとりだけにしよう。

cvs diff での確認
コミット前に必ず cvs diff で修正点を確認するよう徹底させよう。エディタの操作ミスによる意図せぬ修正や、テスト用コードの戻し忘れがないかを確認できる。また、Windows 上のエディタでソースを作成して ffftp などで UNIX マシンに転送している場合におこりがちな以下のミスにも気づくことができる。
  • エディタのタブ設定の違いにより、全ての行のタブがスペースに変換されてしまった
  • FTP 転送時にアスキーモードとバイナリモードを間違え、改行コード CR+LF のまま UNIX マシンに転送してしまった
  • 「半角カナ→全角カナ」変換の機能を使用してしまい、ソース中の半角カナが全て全角カナに変わってしまった。
くどいようだが、これを全メンバに徹底すればかなりのコーディングミスは削減できる。cvs diff は面倒な作業ではなく、つまらないミスを事前に発見できる便利な機能であることを宣伝しよう。

コミットのタイミング
未稿 (ソースのコンパイルあるいは動作確認を確認してからコミットすること、など)。

コミットログ
コミットの際に入力するログ (メッセージ) の記述内容。修正理由は必須。できれば修正概要も記述する。もし修正概要を記入しなくても、後からソースの diff を見れば修正点は把握できる。しかし修正理由はソースを見てもわからないので、修正理由の方が優先度が高い。
例1. hoge 値の自動補正機能は今回の開発で削除する予定だったが、これを削除すると fuga 値に影響が及ぶことが発覚したため、削除を取り止める (仕様変更)。
⇒ 修正理由が書いてあるのがよい。
例2: hoge フラグが 1 の場合は fuga フラグを 2 にセットするよう修正。
⇒ 修正概要は書いてあるものの、なぜこの修正を行ったかがわからない。
例3: bugid:4432 対応
⇒ 別途バグ管理をしているなら、一意に特定できる管理番号を記述するだけでもよい。
CVS は誰がコミットしたかが記録されるようになっているが、全メンバが同じアカウントを使用していたり、他サーバにリポジトリを置いて、rsh や ssh を経由してコミットする場合に同じアカウントでログインしている場合は、修正者が全て同じになってしまう。このようなときは、ちょっと面倒だがコミットログに修正者を明記しよう。
例: 〜のため修正 by 68user

マニュアル作成
CVS を利用する上でのマニュアルを作成する。「web 見ろ」「man 読め」と言いたいのをぐっとこらえて、誰でも理解できる親切なマニュアルを作成しよう。
  • ファイルを追加するには
  • ファイルを削除するには
  • 修正点を確認するには
  • コミットするには
  • コミットしたファイルを元に戻すには
  • ディレクトリを作成するには
  • ディレクトリを削除するには
  • 古いコミットログを修正するには
といった HowTo 的な項目をずらっと並べる。プロジェクトメンバの技術レベルが低ければ低いほど、より力を入れて作成しよう。さらにローカルルールの説明も盛りこんでおく。なお、環境変数 CVSROOT が云々というような余計なことは書かないこと。まずは使い方の説明に徹する。「よくわからないから CVS は使わない」というセリフは言わせてはならない。

ビルド構成
大抵は、そのプロジェクトにおけるおおまかなディレクトリ構成が決められているだろう。たとえば
/home/proj/src/ … ソース置き場。この下で make する。
/home/proj/bin/ … 実行ファイル置き場。src/ で make すると bin/ に実行ファイルが置かれる。
/home/proj/lib/ … 設定ファイル置き場。
/home/proj/data/ … プログラムにより生成されるデータファイル置き場 (CSV ファイルなど)。
などである。この場合、CVS 管理を行うのは src/ と lib/ だけでよい。

ソース置き場は時間が経つと必ず汚れていく。テスト目的でソースを一時的に修正して、元に戻さずに忘れてしまったり、誤ってファイルを削除してしまうことがあるからだ。このような汚れたソース置き場で make したところで、正しい実行ファイルが生成されるはずがない。そこでソース置き場は自動的に削除し、最新ソースをチェックアウトすることをお勧めする。この作業は cron などに登録しておいて毎日行うとよい。作業の邪魔にならないよう、深夜や早朝に実施するのがよいだろう。

信頼できないソース置き場で make した場合は、当然ながら bin/ や lib/ も信頼できない。できれば bin/ や lib/ も毎日削除してしまおう。src/ で make したときに、bin/ や lib/ は自動的に mkdir するような Makefile を用意しておく。bin/ や lib/ のゴミ掃除にもなり、一石二鳥だ。

リポジトリのバックアップ
リポジトリはとても大事な資源である。毎日バックアップを取得しておこう。RAID 環境であっても、バックアップは必須である。「誤ってたくさんのファイルをコミットしてしまった」というときなどは、ファイルをひとつずつ元に戻してコミットしなおすよりも、昨日バックアップしたリポジトリに戻したことが早くて正確である。バックアップを行うには、リポジトリを tar と gzip で固めて、
repository-backup-YYYYMMDD.tar.gz
といったファイル名を付けておけばよい。ディスクに余裕があるなら何年分でも保存しておけばよいが、もし直近 50 日分だけ保存しておきたい場合は
% ls -r repository-backup-*.tar.gz | tail +51 | xargs rm
などと 51 個目以降を削除すればよい。

コミットメール
コミットしたときにメーリングリスト宛に以下のようなメールを飛ばすことをお勧めする。
Subject: CVS: proj/src/program1/src committed by 68user.

Update of proj/src/program1/src
In directory hostname:/tmp/cvs-serv28826/proj/src/program1/src

Modified Files:
    program1.c
Log Message:
エラー発生時のログ出力の情報を充実化。

Index: prohello.c
===================================================================
RCS file: /home/cvsroot/proj/src/program1/src/program1.c

retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- program1.c   12 Jul 2004 07:41:24 -0000    1.4
+++ program1.c   28 Jul 2005 07:30:55 -0000    1.5
@@ -80,7 +80,7 @@
    if ( is_error ){
-      log_error("○○処理が異常終了");
+      log_error("○○処理が異常終了 [顧客コード:%d] [物品コード:%d]",
+                customer_code, item_code);
    }
このメーリングリストには開発者メンバ全員が参加する。狙いは以下の通り。
  • どのソースにどういう修正を行ったかを全員に知らせる
  • 修正内容がおかしい場合は、すぐに突っ込む
  • 他メンバの作業内容に興味を持たせる
  • 自分の作業内容がメンバ全員に知られることを意識させる (汚いソース・コピペの抑止)

ただしメンバ全員がメールを熟読することを本気で期待しないこと。他人の仕事に無関心な人は必ずいる。コミットメールをソースレビューの代わりにしようなどと思わず、メールでミスに気づいたらラッキー程度に考えるとよい。ただし誰かひとり、できればあなたは時間がある限りコミットメールをざっと眺めて、たまに適度な突っ込みを入れ、ちゃんと見てるんだぞとアピールしよう。

上記の仕組みを実現するには http://radiofly.to/nishi/cvsbook/ で配布されているスクリプト mailto-committers.pl を使用するとよい (ちなみにこのページで紹介されている本は、CVS 関連の本の中でもっともおすすめである)。

設定方法は以下の通り。
CVSROOT をチェックアウト。
% cd work (適当な作業場所に移動)
% cvs checkout CVSROOT
mailto-committers.pl を用意し、CVSROOT/mailto-committers.pl として追加。
% cd CVSROOT
% cvs add mailto-committers.pl
どこかから jcode.pl を拾ってきて、CVSROOT/jcode.pl として追加。
% cvs add jcode.pl
CVSROOT/checkoutlist に以下の 2行を追加。
mailto-committers.pl
jcode.pl
CVSROOT/loginfo に以下の行を追加 (cvslog@example.co.jp はメーリングリスト)。
DEFAULT $CVSROOT/CVSROOT/mailto-committers.pl %{sVv} $USER cvslog@example.co.jp
上記の修正を全てコミット。
% cvs commit

タグ
商用リリース時には PHASE99_RELEASE などのタグを付加すること。もし開発工程が厳密に分かれているなら、結合テストリリース時には PHASE99_IT_RELEASE タグを付加、というふうにしてもよい。また、商用環境でバグが出たら、修正ソースに PHASE99_BUGFIX007 などとタグを付けるのもアリだろう。

タグを打っておくと「このタグを打った時点から現時点までに修正したソース一覧を表示」といった調査が簡単に行える。しかしタグを打ちすぎるとウザくなる。どういうイベントでタグを打つかは事前に決めておこう。ただしタグを後から削除することもできるし、過去の特定の日時の時点でのソースにタグを打つこともできる。

タグを打つ際は、そのとき修正したソースだけにタグを打つのではなく、モジュール以下の全ファイルにタグを打つことをお勧めする。全ファイルにタグを打っておけば「このタグを打った時点での全ソースを取得」などということが簡単にできるからである。その結果、修正が行われなかったファイルについては、同一リビジョンに複数のタグが打たれることになるが、これは全く問題ない。

なお、各メンバが個別にタグを打つのではなく、ソース管理者が一括してタグを打つとよい。その際は、「いまからタグを打つが、まだコミットしていないソースがあれば教えてほしい」と確認を取ること。

リリース作業
開発環境とネットワーク的に接続されていない環境、たとえば商用環境へのリリース作業について説明する。

まず、できれば商用環境にも CVS をインストールしておこう。そして以下のような手順を推奨する。
  • 開発環境で cvs diff して、コミット忘れがないことを確認
  • 開発環境で cvs tag か cvs rtag でタグを付ける (PHASE99_RELEASE など)
  • リポジトリをまるごと tar で固めて商用環境へ持っていく。修正したファイルだけを選んで持っていかないこと。手で選別すると必ず漏れが発生するため。
  • 商用環境のリポジトリを削除 (または mv でリネーム)
  • 商用環境にて tar を展開して、リポジトリを配置
  • 商用環境にて cvs diff を行い、商用環境のファイルを直接いじった人がいないことを確認 (念のため)。
  • 商用環境にてソース置き場・バイナリ置き場などを全てリネーム (例: mv src src.old)
  • 商用環境にてソースをチェックアウト
  • 商用環境にてソースを make
もしリリース後に商用環境でバグが発見された場合の手順は以下のとおり。
  • できれば開発環境で原因究明。
  • 原因がわかったら開発環境でソースを修正してコミット。
  • 開発環境から商用環境にリポジトリを持っていって再リリース
もし時間的・物理的制限により、商用環境にて原因究明せざるをえない場合は以下のようにする。
  • 商用環境で原因究明。必要ならソースを修正してもよい。
  • 正常に動いたら cvs diff の結果を開発環境に持っていく (cvs diff でパッチを作成してもよい)。
  • 開発環境で修正版をコミット。
  • 開発環境から商用環境にリポジトリを持っていって再リリース
とにかく商用環境でコミットしないこと。もし商用環境と開発環境でそれぞれのリポジトリにコミットするようなことがあると、どちらがマスタかわからなくなってしまう。リポジトリの流れを「開発環境→商用環境」の一方向に限定するのがポイントである。

開発サイクルとブランチ
長期間の開発では、数ヶ月ごとにフェーズ 1、フェーズ 2、フェーズ 3 …などと開発・テスト・リリースのサイクルを繰り返すのが一般的である。基本的にはフェーズ n とフェーズ n+1 は重ならないはずであるが、フェーズ n のリリース前にフェーズ n+1 の開発を開始しなければならない状況もあるだろう。また、フェーズ n+1 の開発中にフェーズ n のバグが発覚したので修正ソースをコミットしたいときもある。

いずれの場合でも、一般的にはブランチを導入すべきケースである。しかし当ページ管理人の私見では、CVS のブランチは非常にわかりづらい。テキストベースでは分岐状況を直観的に把握するのは難しく、現在修正中のファイルが trunk なのかブランチなのかも混乱しがちである。慣れている人が操作するのであれば全く問題ないが、プロジェクトメンバのスキルが低い場合はブランチを導入するのはためらわれる。

そこで提案するのが、フェーズごとにモジュールをコピーして管理する方法である。Phase1 の開発時は
$CVSROOT/Phase1/
にてソースを管理する。Phase1 の商用リリースが完了し、Phase2 の開発が始まったら、
% cd $CVSROOT
% cp -rp Phase1 Phase2
とモジュールをコピーし、
% cvs checkout Phase2
というふうにソースを取得して開発する。もし Phase1 の商用リリース後にバグが発見された場合は、Phase1 に対して修正を行うとともに、Phase2 に対しても同じ修正を行う。フェーズを重ねるごとに
$CVSROOT/Phase1/
$CVSROOT/Phase2/
$CVSROOT/Phase3/
$CVSROOT/Phase4/
とモジュールが増えていくので、4世代前の古いリポジトリは削除してよい、などと決めておけばよいだろう (Phase1 の履歴が削除されるわけではないことに注意。リポジトリをPhase1 の履歴は Phase2〜4 全てに含まれている)。

上記のモジュールをコピーする方法を取るにせよ、あるいはブランチを導入するにせよ、いずれの場合でも並行開発期間をできる限り短くしよう。Phase1 の商用リリース直後に Phase2 のモジュールをコピーまたはブランチを作成するのではなく、Phase2 の設計が完了し、ソースを作成し、コミットが必要になったときに初めて Phase2 のモジュールをコピーまたはブランチを作成する。