▼その他の技

[CVS超入門]

◇版管理
update -p: 編集を放棄して元に戻す
◇作業領域
checkout -d: 違う名前の作業領域を作る
rm backup.tar.gz,v: 不要ファイルを捨てる
作業領域専用ファイルを用意してみる
update -d: リポジトリの新規ディレクトリを取り込む
◇diffをとる
-rBASE -rHEAD: updateの効果を確かめる
-r1: 幹とのdiffをとる
今、commitしたもののdiffをとる(不可)
◇RCS屋
-zLT 無効; $Id$ は日本時間にできない
ident はない
◇ssh
sshでリポジトリにつなぐ
ssh -p でリポジトリにつなぐ
◇やめる
CVSでの管理を無かったことにする
2回目以降のcvs import をなかったことにする

$Id: cvs_rtips.html,v 2.17 2017-10-05 20:47:55+09 kabe Exp $


やっぱりやめた、元に戻そう

作業領域でいじくったファイルが気に入らなくなって、 元に戻したくなった時の一般解は

	% cvs update -p -rBASE screen.c > screen.c

-p てのは「標準出力に吐く」オプションで、 こんな場合に使うのは妙な気がしますが、 他の方法ではうまくいきません。

▽-C でもいい場合もある

誰も commit していないことがわかっている (自分一人とか)のであれば、 「リポジトリ中の最新版で上書き」(ローカル変更と合成しない)の -C オプションで

	% cvs update -C screen.c
上書きして戻すことが可能です。

▽まずい例

なんのオプションもつけずに

	% cvs update screen.c
としても、元には戻りません。 何が起こるかというと、
・現在の状況が BASE==HEAD, つまりまだ誰もcommitしていない
→何も起こりません。というのも、updateは 作業領域で行われた変更は保存するからです。
・現在の状況が BASE < HEAD, つまり誰かが新しい版をcommitした
→HEADと合成されます。 つまり、最新版と作業領域での変更の合成が行われます。 やはり作業領域で行われた変更は保存されます。
保存されるっても、conflictがあれば できあがったファイルは 衝突マーカーで汚染された状態になりますが。

つまり、update [-A] というのは (現在の枝の) 最新版と合成せよ なので、「元に戻す」という用途には使えません。

-C オプションは、「合成をしない」指定であって、 「最新版」の意味はそのままなので、 cvs update [-A] -C は 「[幹の]最新版に上書する(戻す)」という意味になります。

▽枝でのひっつきタグに注意

cvs update -A file では、 副作用でそのファイルのひっつきタグが剥がれる (==幹にひっつく)ので、 枝で作業していた場合は望ましくありません。 他のファイルは枝のままなのに そのファイルだけ幹に属している状態になるので、あとで人間が混乱します。

結局、update -p file > file で上書きするのがもっとも安全なわけで…

▽update -r 使えばいいんでない?

単に cvs update -C -rBASE (「BASE版で上書きする」) とすると、

BASEは本来仮想タグですが、 これに明示的にひっついてしまうと CVS が発狂するので、update -rBASE てのは 原則禁止です。

ではと番号版番で cvs update -C -r1.4 screen.c とすると、 以後そのファイルは点タグ 1.4 にひっついた状態になる (cvs stat で確認すべし)ので、 以後commitができなくなります。 この状態を是正するのに美しい方法はなく、しょうがないので

CVS/Entries を直接いじくってひっつきタグを消す。
具体的には該当行の末尾の ....//T1.4 を ....// に変える。
こうすると、1.4 が最新版だった頃に checkout した状態と 似た感じになんとか戻せます。 (完全に同じではないようだが…実用上は問題なさそう)


ディレクトリ名は変えられんのか

▽動機

複数の枝を同時開発していると、 ディレクトリ的に近いところに複数の作業領域を置きたい ことがあります。 普通に cvs checkout projectY とすると モジュール名の projectY/ でディレクトリが掘られるので、一段余計な階層をかまさないと いけない気がしますが、

	+- projectY-en/
	|      +- projectY/	これ以下作業領域
	+- projectY-ja/
	|      +- projectY/	これ以下作業領域

実は作業領域の名前はモジュール名(projectY)である必要はありません。 ので直接 projectY-en/ とかを作業領域にしてもかまいません。 モジュール名は各作業領域の CVS/ に記録されているので cvs update などはこれでも正常に動きます。

	+- projectY-en/		これ以下作業領域
	+- projectY-ja/		これ以下作業領域

違う名前で作業領域を掘る

いったん ./tmp/ を掘ってそこに checkout し、 mv tmp/projectY ./projectY-en とかしてもいいんですが、

	% cvs checkout -d projectY-en projectY
で直接 ./projectY-en/ に作業領域を展開できます。


ファイルの完全な証拠隠滅

既存のプログラム類を取ってきてとりあえず import でCVS管理下にした後、 *.BAK やら *~ やら backup.tar.gz などに気づいて掃除したくなることが非常によくあります。

CVS的に正しい方法は cvs remove を使って除く方法ですが

	% rm backup.tar.gz
	% cvs remove backup.tar.gz
	% cvs commit backup.tar.gz
これだとリポジトリでは Attic/ 以下に収容され、容量削減などには貢献しません。 ベンダ枝を指定して checkout した際も(正しく)残ったままです。

最初から証拠隠滅するには、リポジトリから消します。 (リポジトリを直接いじれることが条件)

	% rm $CVSROOT/module/backup.tar.gz,v
リポジトリ側ではファイル一覧のようなものは持っていないので、 *,v を消しただけでも不整合は特に起きません。

作業領域のほうでは古いファイルは残ったままですが、 cvs update すれば消えてくれます。

	% cvs update
	cvs update: Updating .
	cvs update: backup.tar.gz is no longer in the repository	消えたときはメッセージあり
	% cvs update
	cvs update
	cvs update: Updating .						消えていれば何も出ない
	% _

今は不要になったけど前の版では使っていた、ようなファイルは ちゃんと cvs remove で Attic/ に収容しましょう。 過去版を取り出したときにファイルが足らなくなります。

作業領域専用のファイルを用意してみる

タグ地図のように、 開発用の作業領域では版管理しておきたいけど、 配布時は不要、というファイルがある場合、 単純に cvs add で追加すると cvs export での配布版にも含まれてしまいます。

ので、幹ではない専用の枝にファイルを追加し、 幹からは消しておく、という手を使います。

▽例:Makefileを、開発専用枝 TAGMAP だけに追加する

	1.1 -----X	(幹から消去)
	  \
	   1.1.2.1 ---* (TAGMAP)
## まずはCVSに登録
	% cvs add Makefile
	cvs add: scheduling file `Makefile' for addition
	cvs add: use 'cvs commit' to add this file permanently
	% cvs commit Makefile
	(エディタ起動 )
	RCS file: $CVSROOT/projectY/Makefile,v
	done
	Checking in Makefile;
	$CVSROOT/projectY/Makefile,v  <--  Makefile
	initial revision: 1.1
	done
## 枝を生やす
	% cvs tag -b TAGMAP Makefile
	T Makefile
## 作業領域のファイルをTAGMAP枝にひっつける
	% cvs update -r TAGMAP Makefile
## 強制的(-f)に枝を伸ばす
	% cvs commit -f Makefile
	(エディタ起動)
	Checking in Makefile;
	$CVSROOT/projectY/Makefile,v  <--  Makefile
	new revision: 1.1.2.1; previous revision: 1.1
	done
## 幹からは消すので、いったん幹に移る
	% cvs update -A Makefile
	U Makefile
## 幹から消す
	% rm Makefile
	% cvs remove Makefile
	cvs remove: scheduling `Makefile' for removal
	cvs remove: use 'cvs commit' to remove this file permanently
	% cvs commit Makefile
	(エディタ起動 )
	Removing Makefile;
	$CVSROOT/projectY/Makefile,v  <--  Makefile
	new revision: delete; previous revision: 1.1
	done
## 対象ファイルだけ枝に戻す
	% cvs update -r TAGMAP Makefile
	U Makefile

新しい作業領域を作るために cvs checkout をしても、 やはり専用枝のファイルは出てこないので、 個別に cvs update -r TAGMAP Makefile で取り出しておく必要があります。

▽新規の作業領域を作る際は注意!

単純に cvs update -r TAGMAP (ファイル指定なし) とすると、 作業領域にあった幹ファイルは全部掃除されてしまい、 TAGMAP枝にあるファイルしか残りません。 専用枝には、専用ファイル以外は登録されていないため。
この状態はよろしくないのですが、ファイルを消さないというcheckout/updateの オプションはないので、 ファイル名を明示して update -r TAGMAP しましょう。 ファイル名の目処は、cvs -n update -r TAGMAP の出力でつけます。

## 新しい作業領域を作るには
	% cvs checkout projectY
	% cd projectY
	% cvs update -r TAGMAP Makefile
これをやると、作業領域には幹所属のファイルと TAGMAP枝所属の ファイルが混じることになりますが、CVSはこのような状態を 想定していません。多少の不便やおかしな動作は覚悟しよう。

また、専用枝は枝なので、リポジトリ中の更新情報は 前向き、つまり 1.1版からのdiffをずっと重ねたものになります。 幹と違い、更新を大量に重ねていくと取り出しが遅くなってしまいます。

▽作業領域をupdateする際は注意!

TAGMAP枝専用ファイルは他の枝では存在しない扱いなので、

	% cvs update -A		# 幹に移動
など、ファイル指定なしの全体更新updateすると ファイルが消えます。 updateしたあとは、必ずcvs update -rTAGMAP Makefileで 明示的にTAGMAP枝のファイルだけ取り出しておきます。


登録したはずのディレクトリが無い?

他人や自分が新しいサブディレクトリを追加して commit した場合、 他の作業領域で cvs updateしても、デフォルトでは 新規ディレクトリが伝播しません。 エラーも何も出ません。

つまり、新規ディレクトリが追加されても気づかない可能性がある!

新規ディレクトリも引き込むには -d を指定します。

	% cvs update -d

マニュアルでは、デフォルト動作 (新規ディレクトリを引き込まない) は 「欲しくないディレクトリを避けてcheckoutしているのを台無しにしないため」 とありますが、あんまそういう使い方はしないと思います。 cvs update -dを癖にするのがよさそうです。


今 update したら何が変わるんじゃ

複数人での開発などでリポジトリが変化していく環境では、 定期的に cvs update でリポジトリと作業領域を同期 (追っかけ)する必要があります。 いきなり update すると何が上書きされるかわからんので、 まずは cvs -n update で様子をみるわけですが、

cvs -n updateU と表示されるファイルは、 リポジトリ側で(別の人によって)新たな commit が行われたので、 作業領域の更新が必要であることを示します。 今そこに転がっているものは古くなった、てこと。

	% cvs -n update
	cvs update: Updating .
	U hello.c

cvs update [file] すれば更新されますが、 その前に何が変わったのかだけ知りたい。

	% cvs diff -rBASE -rHEAD
【例:MAIN_HELLO枝で作業中、誰かが新しい版を同じ枝にcommitした】
% cvs status
===================================================================
File: hello.c           Status: Needs Merge

   Working revision:    1.2.2.1 Fri Jan 24 19:01:56 2003         === BASE
   Repository revision: 1.2.2.2 /root6.1/xx/cvsroot/p1/hello.c,v === HEAD
   Sticky Tag:          MAIN_HELLO (branch: 1.2.2)               ひっつきタグ
   Sticky Date:         (none)
   Sticky Options:      (none)

   ※自分の作業領域にあるのは 1.2.2.1版が元になっている
   ※リポジトリ中の最新版は 1.2.2.2 になっている

自分の作業領域にあるファイルは、1.2.2.1 が元になってはいますが、 修正していれば 1.2.2.1 でも 1.2.2.2 でもない別物であることに注意。 commit するまでRCS版番はつきません。

diffで「作業領域」を明示したい時は-rを省略するしかありません。 たまに不便なこともある。

開発者間のメールとかで単に 「HEAD版が…」 と言っている場合は 大抵 幹の 先端のことを指しています。が、 枝にひっついている作業領域では -rHEAD は 枝の先端であって幹ではありません。注意。


俺様は幹と比較したいんぢゃあ

正しい方法は

という回りくどい方法しかないようです。

CVSで予約されている仮想タグは BASE と HEAD だけで、 幹に割り当てられた仮想タグちうのはありません。しょうがないので

	% cvs diff -r1		#幹 1.x の .x を省略した形

幹の先端→現在の作業領域、の diff になります(逆diff)。 幹の版番が 1.x のままで、整数部が変わってないことが前提です。

ただ、ベンダ枝から変更されていないファイルは 1.1.1.x を参照しなければならない のですが、上の設定は全ファイル 1.x を参照するので、 リポジトリ初回登録時に cvs admin -b で 枝リセット していない限り正しく幹を示しません。

結論:使えね〜

逆向きの「幹の先端を取り込んだらどう変わるのか」 (現状→幹のdiff)をとりたいこともありますが、 -r では「今そこに転がっているもの」を明示できないので不可能です。 (BASEとの比較で良いなら -rBASE -r1 で何とかなる)

実際には cvs update -A としても diff の結果が 丸ごとあたるわけではなく、 「作業領域ローカルの変更は保存される」に従って更新されます。 -rBASE -r1 のほうが近いかも。

要するに幹を指す仮想枝タグが無いのが悪い。

自分で幹のタグ作れるか?

幹の整数部がたまに変わるとか、 もっとかっちょよく幹を参照したいて場合は、 幹に対して枝タグを明示的に打っとけばいい。 んですが cvs tag -b では枝を新規に生やすことしかできないので、 最初から存在する幹には cvs admin (RCS直接いぢり) で打ちます。 かなり変則的な設定なのでおすすめしません。

	% cvs admin -nTRUNKTAG:1 [file]
整数部が変わったらまた全部打ち直します。 "HEAD" と打つと枝ひっつきで発狂するので別の名前にするように。

CVSではRCS版番にこだわる理由が少なく、 RCSでも整数部を変えると別の枝と見なされて -d (日付で指定して取り出し) とかが使いにくくなるので、 強烈な理由がないなら整数部は 1.x のままのほうが便利。

ベンダ枝から変更がないファイルを 1.1.1.x にすべきところが 1.1 (==幹の先端ではなく、最初の版)になってしまう問題も残ります。

結論:使えね〜

今、コミットしたもののdiffをとりたい

その機能、CVSにはないんですよ。

CVSは1回のコミットに対してIDを割り振る、といったことをしていないので、 「前回のコミット」がどれとどれの何、を記録していません。

	% cvs diff -D yesterday
で似たようなことはできるといえばできるんですが

実は $CVSROOT/CVSROOT/history にコミット記録自体はあるのですが、 1回のまとまったコミットに対しての一意のIDが振られてない (時刻かなんかかなぁ)ので、 自動で1回のコミットを取り出すのも難しそうです。

commit前に cvs tag で点タグを打っておけば cvs diff -r 点タグ で 簡単に比較できますが、小さいcommitでそんなのやってる人いないと思います。

Subversion (svn) や git だとコミット毎にID番号が振られるので、 誰のいつのコミットだけのdiffをとる、戻す、のは可能です。 そういうことをしたい回数が多くなってきたらgitに換えどきかもしれません。


$Id$ は日本時間にならんのか

なりません。

CVSは内部的にとにかく GMT に統一して動くようになっています。 RCS では使えていた RCSINIT=-zLT 環境変数も無効です。


ident はないのか

ありません。 RCS をインストールしてください。 ident 自体は CVS管理下のファイルでもちゃんと使えます。

ただ現実には CVS管理下でメンテしているようなソースコードは RCS のタグ類はついてないことのほうが多い。 (∴バイナリを ident でみてもあんまし面白くない。) というのも、CVS では枝の管理を ./CVS/ でやってくれるので、 個々のファイルの版番はあまり重要でなくなるため。

実際には版番より枝タグの名前のほうが重要だったりするので、 CVSで埋めるキーワードは $Name$ のほうが使いでがあります。 ただしタグを明示しての checkout/export の時くらいしか 書き替わらないので過信は禁物。

CVS は入ってるが RCS がないなんつー環境はかなり稀だとは思いますが


sshでリポジトリにつなぐ

標準テクニックですが、ssh経由でリポジトリ機につなぐには

	% env CVS_RSH=ssh \
	  cvs -d :ext:user@hostname:/path/to/CVSroot update 

CVS_RSH
リポジトリに :ext: を指定したときに使う リモートシェル。 Red Hat系はデフォルトが rsh だったりするので 明示的に定義する必要あり。 .cshrc とかに入れておいても良い。
・/path/to/CVSroot
リポジトリ機上でのローカルパス。

/path/to/CVSroot を含む -d 全体が作業領域の ./CVS/Root に記録される ので、もし作業領域でパスを隠蔽したい場合は ssh ではなく pserver を使わなければならない。 (設定が必要)


ssh -p でリポジトリにつなぐ

22番ポート以外でsshdを動かしている等、変則設定でsshを使う場合は、 CVS_RSH="ssh -p 10022"とかしてもダメです。ので、

▽ ~/.ssh/config に書く

ssh_config(5) に以下のように書いておけば
	# デフォルト
	Host *

	# "cvsserver" の時だけ
    	Host cvsserver
	Hostname my.host.example.jp
	Port 10022
% cvs -d :ext:user@cvsserver:/path/to/CVSroot で my.host.example.jp:10022 へ接続できます。

▽変則技:ssh -F な専用スクリプト

$HOME/bin/ssh10022 といったスクリプトを作り、中身をこんな感じにして

    	#!/usr/bin/ssh -F
	Hostname my.host.example.jp
	Port 10022
CVS_RSH=$HOME/bin/ssh10022 cvs -d :ext:user@server:/path/to/CVSroot で接続できます。sshの設定ファイルで接続先(Hostname)を書き換えるので、 "server" の部分は何でも良く、隠蔽できる。 (が、/path/to/CVSroot は隠せない)

この手法は作業用より、配布用として余計なものは隠蔽したい cvs export 使用時に有用かもしれません。


CVSでの管理をやめる

CVS での管理を中止したり、初回の cvs import を失敗したので やり直したい、という場合。

当然のことながら、リポジトリを消去すると 今までの履歴は全部消えます。 2回目以降の import を失敗したときは、 下の手順を試してください。

2回目以降のcvs import をなかったことにする

2回目以降にimportした場合、importをやり直すには、 リポジトリを消すわけにはいかないので、 危険ツールとされる cvs admin を使ってベンダ枝の版を削除します。

具体例: cvs -d ~/cvsroot import openssh OPENSSH_P OPENSSH-7-6-P1 でimportしたが、*.old が無視されてしまっているのでやり直したい。

手順:

この操作をしても作業領域は一切いじられないのですが、 上のように作業領域と同じ版を消した場合は、 作業領域にあるファイルはどの版にも属していない中途半端な状態として認識されます。

% cvs status INSTALL
cvs status: INSTALL is no longer in the repository
===================================================================
File: INSTALL           Status: Entry Invalid

   Working revision:    1.1.1.4 Thu Oct  5 10:53:22 2017
   Repository revision: No revision control file
   Sticky Tag:          OPENSSH-7-6-P1 - MISSING from RCS file!
   Sticky Date:         (none)
   Sticky Options:      -ko

かべ@sra-tohoku.co.jp