Subversion

オープンソースの開発においてCVSというバージョン管理システムは非常に重 要な要素の一つと考えられています。CVSを使えば、ネットワーク上に分散し た多数の開発者がうまく協調しながらソースを修正していくことが可能です。 オープンソースを開発をサポートしているSourceForgeではプロジェクトごと にCVSリポジトリが用意されていますし、SourceForgeなどを使っていないオー プンソースプロジェクトでも自分でCVSリポジトリを用意しているところが数 多く存在しています。

しかし、CVSを使いこんでいくとCVSによってあたえられる制約がいろいろと苦 痛になってきます。特にファイルの移動やディレクトリの扱いなどがうまく管 理できないことが開発のすすめ方に影響を与えていると言えるでしょう。特に 開発の初期段階では、ソースファイルのレイアウト構造などが固まっていない ために、その段階でCVSの管理下に置くことに躊躇してしまいがちになります。 そのような段階でCVSの管理下にいれても、ファイルの移動にともなって移動 前のログなどを追うことが困難なのであえてCVSの管理下に置こうとしなくな るからです。

そこでCVSの問題点を克服した新しいバージョン管理システムとして開発され ているのがSubversionです。

本稿ではCVSをある程度知っている人を対象に、CVSとの対比をしながら Subversionについて説明していきます。

コラム1 用語

リポジトリ(repository)
バージョン管理下にあるファイル群の履歴やログを記録しているところ
ワーキングコピー(working copy)
リポジトリから編集をおこなう人が各自手元にとりだしてきたコピー。
リビジョン(revision)
バージョン管理する単位で変更ごとに増えていく番号。
チェックアウト(checkout)
リポジトリからワーキングコピーにコピーをとりだす操作
コミット/チェックイン(commit/checkin)
ワーキングコピーでおこなった修正をリポジトリに反映させる操作
ログメッセージ(log message)
コミットする時に、そのコミットはどういう修正を加えたものかを説明するための メッセージ
アップデート(update)
リポジトリに反映されている修正を手元のワーキングコピーにも反映させる操作
コンフリクト(conflict)
競合。ワーキングコピーでおこなった修正とリポジトリに反映されている修正 が同じところを違うように修正している場合におこる
マージ(merge)
他でおこなわれた変更を適用する操作

Subversionの概要

基本的な使い方において、SubversionはCVSとよく似ています。CVSの使用感を そのまま継続していけるように考えて開発されているからです。つまり、 SubversionにおいてもCVSと同じようにリポジトリからワーキングコピーにチェッ クアウトし、ワーキングコピーで修正しそれをリポジトリにコミットしていく という操作が基本です。CVSとの違いが顕著なSubversionの特徴は以下の通りです。

リポジトリにリビジョンがつく
CVSではファイル単位にリビジョン番号がついていましたが、Subversionでは リポジトリにリビジョン番号がつきます。リビジョンに加えられた操作 (ファイルの内容の更新、ファイルの移動・削除、ディレクトリの作成・削除) がコミットされるごとにリビジョン番号が増えていきます。
コミットがアトミック
複数のファイルの変更が本質的に同時におこなわれないと意図したことが 正しく伝えることができません。CVSでは複数のファイルを一つのコマンド ラインでコミットしても、実際にはファイルごとのコミットとなってしまいます。 Subversionでは常にコミットした単位で、リビジョンの更新がおこなわれます。
ファイルの移動
CVSでは管理する単位がファイルだったので、ファイルの移動などはファイルを 削除して新しいファイルを追加するという処理に分割されてしまっていましたが、 Subversionでは移動として扱えるようになっています。
ディレクトリの作成・削除
CVSではディレクトリの扱いがあまりうまくできていませんでした。 特にディレクトリの削除と空ディレクトリにすることの違いが 正しく区別できていません。Subversionではディレクトリもうまく扱う ことができます。
ネットワーク対応
CVSでは、ネットワーク対応は後から追加されたかなりアドホックなものでしたが Subversionでは最初からネットワーク越しに利用することを考えて設計されて います。ファイルを更新する時も、リポジトリとワーキングコピー間は常に その差分だけがやりとりされるようになっています。 またバイナリに対しても効率のよい差分アルゴリズムが採用されています。
言語バインディング
CVSではリポジトリの内容はRCSファイルをまとめたものみたいなものでしたが、 Subversionでは独自のファイルシステムを保持しているデータベースのような 設計になっています。これらにアクセスするためのAPIが内部にありますが それらをPythonなどのLightweight言語から直接アクセスするような 言語バインディングが作られています。

Subversionのインストール

Subversionをインストールするための一番簡単な方法はバイナリパッケージを 利用することでしょう。Debianでは unstableではofficial packageとして提 供されています。

stable(woody)むけには Colin Watson (cjwatson@debian.org) が unofficial apt-lineを提供しているのでこれを利用するのがいいでしょう。

 deb http://people.debian.org/~cjwatson/subversion-woody ./

これらを使えばapt-getで普通にインストールすることができます

 $ apt-get install subversion subversion-tools 

これで主に以下のようなコマンドがインストールされます。

subversion
svn, svnversion, svnadmin, svnadmin-static, svnlook, svndumpfilter, svnserve
subversion-tools
cvs2svn, svn_load_dirs, svnshell

CVSのcvsコマンドに相当するのが、svnコマンドです。svnコマンドもcvsコマンドと 同じようにサブコマンドを指定して利用します。どのようなサブコマンドがあるか は svn help で見ることができます。

 $ svn help

個々のサブコマンドについての詳しい説明は「svn help サブコマンド」のように して見ることができます。

 $ svn help ls

ソースコードから作成

http://subversion.tigris.org/project_source.html に書かれている手順で入手・ビルドします。

  1. http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=260 から最新のソースのtarballをダウンロードしてくる
  2. http://svn.collab.net/repos/svn/trunk/INSTALL に書かれている手順でビルドする。

Subversionは必要とするソフトウェアがたくさんあるのでそれを先に準備して おく必要があります。

	Apache2 の APR (Apache Portable Runtime)とAPR Utility
	Apache 2.0.46 か それより新しいもの (WebDAVを使う場合)
	autoconf 2.50 かそれより新しいもの
	libtool 1.4 かそれより新しいもの
	bisonかyacc
	Neon ライブラリ (WebDAVのためのライブラリ)
	OpenSSL (SSLを使う場合)
	zlib
	Berkeley DB 4.0.14
	Python 2.0 かそれより新しいもの (必要なら)

準備ができたら基本的には以下の手順でビルド・インストールすることになります。

 $ sh ./autogen.sh
 $ ./configure
 $ make
 # make install

リポジトリの準備

リポジトリの作成

cvsではcvs init でリポジトリを作りました。subversionでは svnadmin createでリポジトリを作成します。

コラム2 リポジトリを作る単位

リポジトリはどれくらいの単位でつくるのがいいのでしょうか? 現在のところSubversionではリポジトリ内でのアクセスコントロールが できないことなどから、アクセスコントロールをしなくてよい コミッターのグループ単位に作るのがよいと思われます。 CVSでもSourceForgeなどではプロジェクト単位にリポジトリが 作られているようにSubversionでもプロジェクト単位にリポジトリを 作るのがよいでしょう。

$ ls /path/to/cvsroot
ls: /path/to/cvsroot: そのようなファイルやディレクトリはありません
$ cvs -d /path/to/cvsroot init
$ ls /path/to/cvsroot
CVSROOT/
$
$ ls /path/to/svnrepos
ls: /path/to/svnrepos: そのようなファイルやディレクトリはありません
$ svnadmin create /path/to/svnrepos
$ ls /path/to/svnrepos
README.txt  dav/  db/  format  hooks/  locks/
$

どちらも空のリポジトリが作成されます。CVSの場合はリポジトリで管理され ているファイルのディレクトリ構成がそのままリポジトリディレクトリの中の ディレクトリ構成となっています。CVSROOTはcvsが管理に使うディレクトリで すが、それ以外はリポジトリの管理下におかれているものが基本的にそのまま 配置されるわけです。

Subversionでは、リポジトリで管理されているファイルのディレクトリ構成は リポジトリでのディレクトリ構成に直接は反映していません。すべて db/以下の データベースの中でSubversion独自のファイルシステムとして管理されています。

Subversionではリポジトリの中にアクセスするためのコマンドが用意されています。

$ svn ls file:///path/to/svnrepos
$

このようにSubversionではリポジトリはURL形式で指定します。URL以外だと ワーキングコピーへのパスだと見なされてしまうので気をつけてください。

$ svn ls /path/to/svnrepos
svn: Path is not a working copy directory
svn: '/path/to/svnrepos' is not a working copy
$

後述しますが、Subversionではブランチ/タグの処理をコピー操作でおこないます。 そのため、以下のようなディレクトリをあらかじめ作成しておくような慣習に なっています。(cvs2svnもこのような配置にします)

リポジトリ内の存在しないディレクトリを指定すると以下のようなエラーになっ てしまいます。

$ svn ls file:///path/to/svnrepos/trunk
svn: Filesystem has no item
svn: URL non-existent in that revision.

これらのディレクトリはまず最初に以下のようにして作っておくといいでしょう。 ディレクトリを作るにはmkdirサブコマンドを使います。

$ svn mkdir file:///path/to/svnrepos/trunk

すると以下のようにエディタがたちあがります。



--This line, and those below, will be ignored--

A    file:///path/to/svnrepos/trunk
~
~
"svn-commit.tmp" 4L, 84C                                      1,0-1        全て

"--This line, and those below, will be ignored--"までに ログメッセージを 書きこんでエディタを終了します。するとディレクトリの作成がコミットされます。

Committed revision 1.
$

ログメッセージを指定する時は -m オプションを使うことができます。

$ svn mkdir -m 'setup branches' file:///path/to/svnrepos/branches
Committed revision 2.
$ svn mkdir -m 'setup tags' file:///path/to/svnrepos/tags
Committed revision 3.
$

これでリポジトリは以下のような内容になります

$ svn ls file:///path/to/svnrepos/
branches/
tags/
trunk/
$ svn ls file:///path/to/svnrepos/trunk
$ 

リポジトリへのインポート

以下のようなソースがあり、これをバージョン管理下におこうとしているとし ましょう。

$ ls hello
Makefile  hello.c

CVSの場合は以下のようにインポートしました。

$ ls
hello
$ cd hello
$ cvs -d /path/to/cvsroot import -m 'import hello' hello hello_1 hello_1_0

N hello/Makefile
N hello/hello.c

No conflicts created by this import
$ ls /path/to/cvsroot
CVSROOT/  hello/
$ ls /path/to/cvsroot/hello
Makefile,v  hello.c,v
$

Subversionでは次のようにimportサブコマンドを使ってインポートします。

$ ls 
hello
$ svn import -m 'import hello' file:///path/to/svnrepos/trunk hello hello
Adding         hello/Makefile
Adding         hello/hello.c

Committed revision 4.
$

これは

という意味になります。

CVSではカレントディレクトリ以下をインポートしましたが、Subversionでは このようにインポートしようとするディレクトリを指定します。「.」でカレ ントディレクトリを指定することもできますが、この場合ログメッセージ用に 一時的に作られるファイルもインポートされてしまうのでやらない方がよいで しょう。

インポートしたものは次のようにして見ることができます。

$ svn ls file:///path/to/svnrepos/trunk
hello/
$ svn ls file:///path/to/svnrepos/trunk/hello
Makefile
hello.c
$

コラム3 cvs2svn

既にCVSで管理してきたものをSubversion管理に移行するためのツールとして cvs2svnというものがあります。CVSリポジトリの内容をよみとってdumpファイ ルを生成してそれをsvnadmin loadでSubversionリポジトリに格納していくと いう処理をおこなっています。複雑なCVSリポジトリだと失敗することがある ようですが、簡単なものならばこれでうまくいくでしょう。

$ svnadmin create /path/to/svnrepos
$ cvs2svn -n -s /path/to/svnrepos /path/to/cvsrepos	# 確認
$ cvs2svn -s /path/to/svnrepos /path/to/cvsrepos

基本操作

CVSの基本操作は checkout しておいて アップデート -> 修正 -> コミットの繰り返し でした。Subversionでも基本はほぼ同じとなります。

チェックアウト

CVSではチェックアウトはリポジトリを -d オプションで指定していました。

$ cd /path/to/cvswc
$ cvs -d /path/to/cvsroot checkout hello
cvs checkout: Updating hello
U hello/Makefile
U hello/hello.c
$

Subversionでもチェックアウトはcheckoutサブコマンドを使います。ただし、 リポジトリをURLで指定します。リポジトリのパス名の最後のディレクトリが ワーキングコピーとして作成されます。

$ cd /path/to/svnwc
$ svn checkout file:///path/to/svnrepos/trunk/hello
A  hello/hello.c
A  hello/Makefile
Checked out revision 4.
$

もしリポジトリURLの最後のディレクトリ名と違うディレクトリ名でチェック アウトしたい場合は、そのディレクトリ名を指定します。

$ svn checkout file:///path/to/svnrepos/trunk/hello hello-tmp
A  hello-tmp/hello.c
A  hello-tmp/Makefile
Checked out revision 4.
$

以下基本的に、CVSでの例はディレクトリ(/path/to/cvswc/hello)で Subversionでの例はディレクトリ(/path/to/svnwc/hello)でコマンドを実行し ているものとします。

ワーキングコピーの管理ファイル

さて、ここでチェックアウトされたワーキングコピーを簡単に見てみましょう。 CVSでは、CVSというディレクトリが作成されていました。

$ ls
CVS/   Makefile  hello.c
$ ls CVS
Entries  Repository Root
$

どのリポジトリからチェックアウトしたかはCVSディレクトリの RepositoryファイルやRootファイルの中身を見ればわかりました。

Subversionでは .svnというディレクトリになります。

$ ls
Makefile   hello.c
$ ls -a
./  ../  .svn/  Makefile  hello.c
$ ls .svn
README.txt   entries  prop-base/  text-base/   wcprops/
empty-file   format   props/      tmp/
$

どのリポジトリからチェックアウトしたかはinfoサブコマンドでわかります。

$ svn info
Path: 
Url: file:///path/to/svnrepos/trunk/hello
Repository UUID: 1e0c500f-25c0-0310-91ba-92d4cfa6a45a
Revision: 4
Node Kind: directory
Schedule: normal
Last Changed Author: ukai
Last Changed Rev: 4
Last Changed Date: 2003-06-16 01:08:31 +0900 (Mon, 16 Jun 2003)
$

Subversionでは、リポジトリとワーキングコピー間はどちらもできるだけ差分 でやりとりするようになっています。そのためワーキングコピー側には編集用 に作られるファイルと.svn以下に格納されているリポジトリからチェックアウ ト/アップデートした時のファイルの2種類が格納されることになっています。 そのためワーキングコピーには、CVSと違い、単にリポジトリからとりだした 分の2倍の量が必要となっていまいます。ネットワークで巨大なファイルをや りとりするコストにくらべて、ディスクのコストの方が安いという判断でこの ように設計されているのです。

アップデート

CVSもSubversionもアップデートは同じくupdateサブコマンドを使います。

$ cvs update
cvs update: Updating .
$
$ svn update
At revision 4.
$

修正してコミット

次にファイルを修正してコミットするまでを説明してみましょう。 hello.c を変更してコミットする場合は次のようになります。

差分の確認

まず手元で修正した分の差分を確認するべきでしょう。 差分を見るにはdiffサブコマンドを使います。

$ cvs diff
cvs diff: Diffing .
Index: hello.c
===================================================================
RCS file: /path/to/cvsroot/hello/hello.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 hello.c
--- hello.c     15 Jun 2003 16:06:38 -0000      1.1.1.1
+++ hello.c     15 Jun 2003 16:36:35 -0000
@@ -4,4 +4,5 @@
 main(int argc, char *argv[])
 {
     printf("hello, world\n");
+    exit(0);
 }
$ svn diff
Index: hello.c
===================================================================
--- hello.c     (revision 4)
+++ hello.c     (working copy)
@@ -4,4 +4,5 @@
 main(int argc, char *argv[])
 {
     printf("hello, world\n");
+    exit(0);
 }

ワーキングコピーの状態

どのファイルが更新されているかはCVSではupdateサブコマンドを 使って確認しました。

$ cvs update
cvs update: Updating .
M hello.c
$

Subversionではupdateサブコマンドはリポジトリからワーキングコピーへの更 新のためのサブコマンドであり、ワーキングコピーでなにを修正したかを見るには statusサブコマンドを使います。

$ svn update
At revision 4.
$ svn status
M      hello.c
$

既に説明した通り、Subversionではローカルにアップデートした版も.svn以下 に持っているので、どれが修正されたかを見るstatusサブコマンドを実行するに はリポジトリへのアクセスを必要としません。

コミット

CVSではcommitサブコマンドでコミットしました。

$ cvs commit -m 'add exit' hello.c
Checking in hello.c;
/path/to/cvsroot/hello/hello.c,v  <--  hello.c
new revision: 1.2; previous revision: 1.1
done
$

これで hello.c のリビジョンが 1.1 から 1.2 にあがります。

Subversionでもcommitサブコマンドでコミットします。

$ svn commit -m 'add exit' hello.c
Sending        hello.c
Transmitting file data .
Committed revision 5.
$

これで 修正されたhello.cを含むリポジトリのバージョンが5になります。 statusコマンドの-u オプションや-vオプションを使うとどのリビジョンで 最後に更新されているのかを見ることができます。

$ svn status -u -v
                4        4       ukai   .
                4        4       ukai   Makefile
                5        5       ukai   hello.c
Head revision:     5
$

最初の数字がどのリビジョンでチェックアウト/アップデートしたかを あらわしています。次の数字が最後にコミットされたのがどのリビジョンかを あらわしています。つまり、カレントディレクトリ(.)とMakefileはリポジトリに コミットした時のリビジョン 4、hello.c は最後にコミットしたリビジョン 5で あるという意味になります。

ここでアップデートしみてると次のようになります。Makefileもアップデート されて、アップデートした時のリビジョンは 5になりますが、これが最後にコ ミットされたのはリビジョン4の時であることがわかります。

$ svn update
At revision 5.
$ svn status -u -v
                5        5       ukai   .
                5        4       ukai   Makefile
                5        5       ukai   hello.c
Head revision:      5
$

ログを見る

バージョン管理システムでうれしいことのひとつは過去の変更履歴を見ること ができることでしょう。CVSでもSubversionでもlogサブコマンドを使います。

$ cvs log hello.c

RCS file: /path/to/cvsroot/hello/hello.c,v
Working file: hello.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
 hello_1_0: 1.1.1.1
 hello_1: 1.1.1
keyword substitution: kv
total revisions: 3;     selected revisions: 3
description:
----------------------------
revision 1.2
date: 2003/06/15 16:48:02;  author: ukai;  state: Exp;  lines: +1 -0
add exit
----------------------------
revision 1.1
date: 2003/06/15 16:06:38;  author: ukai;  state: Exp;
branches:  1.1.1;
Initial revision
----------------------------
revision 1.1.1.1
date: 2003/06/15 16:06:38;  author: ukai;  state: Exp;  lines: +0 -0
import hello
=============================================================================
$
$ svn log hello.c
------------------------------------------------------------------------
rev 5:  ukai | 2003-06-16 01:50:22 +0900 (Mon, 16 Jun 2003) | 1 line

add exit
------------------------------------------------------------------------
rev 4:  ukai | 2003-06-16 01:08:31 +0900 (Mon, 16 Jun 2003) | 1 line

import hello
------------------------------------------------------------------------
$

競合

複数で同じファイルを編集した場合、CVSもSubversionもできるかぎりマージ しようとします。それぞれの変更箇所が重なっていない場合、それらの変更が すべて反映されます。しかし変更箇所が重なってその変更の仕方が違う場合に 競合(conflict)となります。

CVSでは競合はこのようになります。

$ cvs update
cvs update: Updating .
RCS file: /path/to/cvsroot/hello/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
$ cat hello.c
#include 

int
main(int argc, char *argv[])
{
<<<<<<< hello.c
    printf("hello, world!!\n");
=======
    printf("hello, world!\n");
>>>>>>> 1.3
    exit(0);
}
$

このように cvs updateをした時に「C」で表示され、 ファイルの中にコンフリ クトマークが挿入されます。 「<<<<<<<」から 「=======」までがワーキングコピーで編集していた内容、 「=======」から 「>>>>>>>」までがリポジトリで 更新されていた内容です。

したがって「=======」より上の部分を有効にすれば手元の変更を 有効にしてコミットすることになり、「=======」より下の部分を有効に すればリポジトリの内容を有効にして最新版に反映したことになります。 どちらかを選ぶ必要もなく、その部分を新たに書きなおしてもかまいません。 ここでは次のように変更してコミットしてみましょう。

$ cat hello.c
#include 

int
main(int argc, char *argv[])
{
   printf("Hello, world!\n");
   exit(0);
}
$ cvs update
cvs update: Updating .
M hello.c
$ cvs commit -m 'capitalize' hello.c
Checking in hello.c;
/path/to/cvsroot/hello/hello.c,v  <--  hello.c
new revision: 1.4; previous revision: 1.3
done

Subversionの場合は次のようになります。

$ svn update
C  hello.c
Updated to revision 6.
$

CVSと同じようにSubversionでも「C」で競合(Conflict)を あらわしています。この時、競合の元になったファイルも作成されています。

$ ls
Makefile  hello.c  hello.c.mine  hello.c.r5  hello.c.r6
$ svn status -u -v
	 6        6       ukai   .
	 6        4       ukai   Makefile
C        6        6       ukai   hello.c
?                                hello.c.mine
?                                hello.c.r5
?                                hello.c.r6
Head revision:      6
$ cat hello.c
#include 

int
main(int argc, char *argv[])
{
<<<<<<< .mine
    printf("hello, world!!\n");
=======
    printf("hello, world!\n");
>>>>>>> .r6
    exit(0);
}
$

C」がついているのが競合しているファイルです。 「?」がついているのはリポジトリにはないファイルです。

この例では、リビジョン5が編集前にアップデートした時のリビジョンで、 リビジョン6がアップデートで更新しようとしたリビジョン、つまり リポジトリの最新のリビジョンです。

hello.c
マージしてコンフリクトマークがはいっているファイル。CVSの場合と同じ
hello.c.mine
svn updateする前のワーキングコピーのファイル
hello.c.r5
hello.c.mineを変更する元になったリビジョンの内容
hello.c.r6
リポジトリにある内容

図1 コンフリクト時の関係

                        誰かがコミット
	---- hello.c.r5 ----> hello.c.r6   リポジトリでの変更
チェックアウト|                       |
アップデート  |                       |
	      |                       |
	      v                       v
	    hello.c.mine  ---------> hello.c
	 ワーキングコピーでの変更     アップデートした結果(コンフリクト)

コンフリクトマークは CVSの時と同じく 「<<<<<<<」から 「=======」までが手元で加えていた変更、 「=======」から 「>>>>>>>」までがリポジトリで加えら れていた変更です。

コンフリクトを解消するために、ここで必要な変更をhello.cに加えます。自 分の変更した修正にする場合はhello.c.mine を hello.c にコピーすればいい でしょう。リポジトリの最新版にあわせたい場合は、この場合 hello.c.r6 を hello.c にコピーします。

ここでは、CVSの例と同じような変更をhello.cにほどこすことにしましょう。

$ cat hello.c
#include 

int
main(int argc, char *argv[])
{
    printf("Hello, world!\n");
    exit(0);
}
$ svn status -u -v hello.c
C               6        6       ukai   hello.c
Head revision:      6
$

CVSの時と違って競合を解消するにはresolveサブコマンドを使います。 resolveサブコマンドを使うことで競合状態が解消され、コンフリクト時に 作成された .mime, .r<リビジョン>ファイルが削除されます。

$ svn resolve hello.c
Resolved conflicted state of hello.c
$ svn status -u -v hello.c
M               6        6       ukai   hello.c
Head revision:      6
$ ls
Makefile  hello.c
$

このように C(競合)から M(手元で修正されている) にかわります。この修正をコミットします。

$ svn commit -m 'capitalize' hello.c
Sending        hello.c
Transmitting file data .
Committed revision 7.
$

元に戻す

リポジトリに加えられた変更を元に戻したい時があります。もちろん他の開発 者がおこなった変更を何もいわずに勝手に元に戻すのはよいこととは言えませ ん。しかし、自分が間違った修正をコミットしてしまってそれを戻したい時も ありますし、議論した結果やはり戻そうという話になることもあります。その ような時にどうするのがいいのかを説明します。

以前のリビジョンに戻す

CVSでは幾つか方法がありますが、簡単な方法の一つはupdateの-pオプションを 使うことです。戻したいリビジョンを -r オプションで指定します。 次のようにすれば hello.cをリビジョン1.3の内容に戻せます。

$ cvs -Q update -p -r 1.3 hello.c > hello.c
$ cvs update
cvs update: Updating .
M hello.c
$ cvs commit -m 'reverted to 1.3' hello.c
Checking in hello.c;
/path/to/cvsroot/hello/hello.c,v  <--  hello.c
new revision: 1.5; previous revision: 1.4
done
$

もしくはcvs updateでマージする方法があります。リビジョン1.5をリビジョン1.4に 戻す場合は次のようにすることになります。

$ cvs update -j 1.5 -j 1.4 hello.c
RCS file: /path/to/cvsroot/hello/hello.c,v
retrieving revision 1.5
retrieving revision 1.4
Merging differences between 1.5 and 1.4 into hello.c
$ cvs commit -m 'reverted to 1.4' hello.c
Checking in hello.c;
/path/to/cvsroot/hello/hello.c,v  <--  hello.c
new revision: 1.6; previous revision: 1.5
done
$

Subversionでは update の -pオプションのかわりにcatサブコマンドが使えます。

$ svn cat -r6 hello.c

updateサブコマンドをそのまま使うとこのようになります。

$ svn update -r6 hello.c
U  hello.c
Updated to revision 6.
$ svn status -u -v hello.c
       *        6        6       ukai   hello.c
Head revision:      7
$

ここで「*」はリポジトリにワーキングコピーにとりだしたものより 新しいリビジョンがあることをあらわしています。これでは修正にはならないので コミットはできません。

そこで、CVSの時と同じようにマージします。Subversionでは updateではなくmergeというサブコマンドを使います。

$ svn update hello.c
U  hello.c
Updated to revision 7
$ svn merge -r 7:6 hello.c
U  hello.c
$ svn status -u -v
	 7        7       ukai   .
	 7        4       ukai   Makefile
M        7        7       ukai   hello.c
Head revision:      7
$

これで hello.cにリビジョン7からリビジョン6に加えられた変更がワーキング コピーのhello.cにほどこされます。これはリビジョン6からリビジョン7に加 えられた変更を逆にあてる、つまりリビジョン7からリビジョン6に戻すという ことです。これをコミットすればリビジョン6の時のhello.cの内容に戻すこと ができます。

$ svn commit -m 'reverted to 6' hello.c
Sending        hello.c
Transmitting file data .
Committed revision 8.
$

ワーキングコピーでの修正を無しにする

場合によってはワーキングコピーで作業していた内容をコミットせずに リポジトリからとりだした時の状態に戻したいことがあります。

CVSではそのファイルをワーキングコピーから削除してアップデートしたり していました。

$ cvs update
cvs update: Updating .
M hello.c
$ rm hello.c
$ cvs update
cvs update: Updating .
cvs update: warning: hello.c was lost
U hello.c
$

Subversionでは revertサブコマンドを使えば、最後にアップデートもしくは コミットした時の内容にワーキングコピーを戻すことができます。

$ svn status -uv
	 7        7       ukai   .
	 7        4       ukai   Makefile
M        8        8       ukai   hello.c
Head revision:      8
$ svn revert hello.c
Reverted hello.c
$ svn status -uv
	 7        7       ukai   .
	 7        4       ukai   Makefile
	 8        8       ukai   hello.c
Head revision:      8
$

ファイルを追加する

ファイルを追加する時は add サブコマンドを使います。

$ cvs update
cvs update: Updating .
? README
$ cvs add README
cvs add: scheduling file `README' for addition
cvs add: use 'cvs commit' to add this file permanently
$ cvs commit -m 'add README' README
RCS file: /path/to/cvsroot/hello/README,v
done
Checking in README;
/path/to/cvsroot/hello/README,v  <--  README
initial revision: 1.1
done
$

Subversionでも同じくaddサブコマンドを使います。

$ svn status -uv
	 7        7       ukai   .
	 7        4       ukai   Makefile
?                                README
	 8        8       ukai   hello.c
Head revision:      8
$

コンフリクトの時に説明したように、リポジトリにないファイルは 「?」のマークがつきます。addサブコマンドでそのファイルを 追加するようになります。

$ svn add README
A         README
$ svn status -uv
                7        7       ukai   .
                7        4       ukai   Makefile
A               0       ?          ?    README
                8        8       ukai   hello.c
Head revision:      8
$

このように追加する予定のものに「A」のマークがつきます。 実際にこれをリポジトリに追加するにはコミットする必要があります。

$ svn commit -m 'add README' README
Adding         README
Transmitting file data .
Committed revision 9.
$ svn status -uv
	 7        7       ukai   .
	 7        4       ukai   Makefile
	 9        9       ukai   README
	 8        8       ukai   hello.c
Head revision:      9
$

ディレクトリを追加する

ディレクトリを追加する場合、CVSではmkdirしてaddサブコマンドで追加して いました。CVSではファイルの時と違ってコミットする必要はありませんでした。

$ mkdir doc
$ cvs add doc
Directory /path/to/cvsroot/hello/doc added to the repository
$

Subversionの場合はmkdirサブコマンドを使います。

$ svn mkdir doc
A         doc
$ svn status -uv
                7        7       ukai   .
                7        4       ukai   Makefile
                9        9       ukai   README
A               0       ?          ?    doc
                8        8       ukai   hello.c
Head revision:      9
$

CVSとは違ってこのままではリポジトリには反映されていません。ファイルの 時と同様コミットする必要があります。

$ svn commit -m 'mkdir doc' doc
Adding         doc

Committed revision 10.
$

mkdirサブコマンドで作るかわりに、mkdirしたディレクトリをaddサブコマンドで 追加してコミットする方法もあります。

$ mkdir subdir
$ svn status -uv
               7        7       ukai   .
               7        4       ukai   Makefile
               9        9       ukai   README
              10       10       ukai   doc
               8        8       ukai   hello.c
?                                      subdir
Head revision:     10
$ svn add subdir
A         subdir
$ svn commit -m 'newdir subdir' subdir
Adding         subdir

Committed revision 11.
$

ファイルを削除する

ファイルを削除するにはdeleteサブコマンドを使います。

$ cvs delete -f README
cvs remove: scheduling `README' for removal
cvs remove: use 'cvs commit' to remove this file permanently
$ cvs commit -m 'delete README' README
Removing README;
/path/to/cvsroot/hello/README,v  <--  README
new revision: delete; previous revision: 1.1
done
$

Subversionでもdeleteサブコマンドで削除することができます。

$ svn delete README
D         README
$ ls
Makefile  doc/  hello.c  subdir
$

このように svn delete した時点でワーキングコピーからは既に削除されてい ます。しかしこの時点ではワーキングコピーで削除するというマークがついて いるだけでリポジトリにはまだ反映していません。statusサブコマンドでみる と次のように「D」がついています。

$ svn status -uv
	 7        7       ukai   .
	 7        4       ukai   Makefile
D        9        9       ukai   README
	10       10       ukai   doc
	 8        8       ukai   hello.c
	11       11       ukai   subdir
Head revision:     11
$

これをリポジトリに反映させるにはやはりコミットする必要があります。

$ svn commit -m 'delete README' README
Deleting       README

Committed revision 12.
$

ディレクトリを削除する

CVSではディレクトリを削除することはできません。できることは空ディレクトリを ワーキングコピーでは作成しない/削除するようにすることだけでした。

$ cvs update -dP
cvs update: Updating .
cvs update: Updating doc
cvs update: Updating subdir
$ ls
CVS/  Makefile  hello.c
$

このように空ディレクトリは削除されてしまいます。この状態でもリポジトリ にはディレクトリは残ったままです。

$ cvs update -d doc
cvs update: Updating doc
CVS/  Makefile  doc/  hello.c
$

基本的にCVSではディレクトリはバージョン管理の対象とはなっていません。 RCSとおなじで扱う単位がファイルだからです。

したがってディレクトリが消されてなくなった場合と空ディレクトリがある場 合とを区別できません。CVSで管理する時は、意味のある空ディレクトリを残 すために、なんらかのドットファイルをそのディレクトリに置いておく(つま り空ディレクトリにしないために意味のないファイルを置く)といったことが おこなわれていました。

Subversionではディレクトリもファイルと同じように管理されているので deleteサブコマンドで削除できます。

$ svn delete subdir
D         subdir
$ svn status -uv
	 7        7       ukai   .
	 7        4       ukai   Makefile
	10       10       ukai   doc
	 8        8       ukai   hello.c
D       11       11       ukai   subdir
Head revision:     12
$ ls
Makefile  doc/  hello.c  subdir/
$

ディレクトリはsvn deleteしただけではワーキングコピーにも残っています。

$ svn commit -m 'rmdir ' subdir
Deleting       subdir
Committed revision 13.
$ svn status -uv
	 7        7       ukai   .
	 7        4       ukai   Makefile
	10       10       ukai   doc
	 8        8       ukai   hello.c
Head revision:     13
$ ls
Makefile  doc/  hello.c
$

このようにコミットするとそのディレクトリが消えます。 ディレクトリを消すためにはあらかじめそのディレクトリが空になっている 必要があります。ディレクトリを消す場合、その中に格納されている ファイルをまず最初に消しておかなければなりません。

CVSの時と違って、Subversionでは空ディレクトリは空ディレクトリが 存在しているものとして扱うことができます。

ファイルの名前を変更する

CVSではファイルの名前の変更、つまりファイルの移動は、古いファイル名で のファイルの削除と新しいファイル名でのファイルの追加という2つの操作で おこなっていました。

$ cvs update -d doc
$ cd doc
$ vi README
$ cvs add README
cvs add: scheduling file `README' for addition
cvs add: use 'cvs commit' to add this file permanently
$ cvs commit -m 'add README' README
RCS file: /path/to/cvsroot/hello/doc/README,v
done
Checking in README;
/path/to/cvsroot/hello/doc/README,v  <--  README
initial revision: 1.1
done
$ mv README README.txt
$ cvs delete README
cvs remove: scheduling `README' for removal
cvs remove: use 'cvs commit' to remove this file permanently
$ cvs add README.txt
cvs add: scheduling file `README.txt' for addition
cvs add: use 'cvs commit' to add this file permanently
$ cvs commit -m 'rename README to README.txt' README README.txt
Removing README;
/path/to/cvsroot/hello/doc/README,v  <--  README
new revision: delete; previous revision: 1.1
done
RCS file: /path/to/cvsroot/hello/doc/README.txt,v
done
Checking in README.txt;
/path/to/cvsroot/hello/doc/README.txt,v  <--  README.txt
initial revision: 1.1
done
$

このように移動することはできなくありませんが、CVS的には新しいファイル (この例ではREADME.txt)が古いファイル(この例ではREADME) から移動してき たものであるかどうかはコミットログをみないと判断できません。README.txt のログを見る場合も移動前のログはREADMEのログとしてしか記録されていませ ん。

Subversionではファイルの移動も移動として管理することができます。 そのためのmoveというサブコマンドが用意されています。

$ cd doc
$ vi README
$ svn add README
A         README
$ svn commit -m 'add README' README
Adding         README
Transmitting file data .
Committed revision 14.
$ svn move README README.txt
A         README.txt
D         README
$ svn status -uv
               13       10       ukai   .
D              14       14       ukai   README
A  +            -       ?          ?    README.txt
Head revision:     14
$ ls
README.txt
$

svn statusで「+」とあるのはヒストリをともなった追加であることを意味しています。

$ svn commit -m 'rename README to README.txt'
Deleting       doc/README
Adding         doc/README.txt

Committed revision 15.
$

CVSの時と同じくREADMEを削除してREADME.txtを追加しただけのように見えますが そうではありません。

CVSではコミットがファイル単位なので、「古いファイルの削除と新しいファ イルの追加は時間が近くてコミットログが同じなのでおそらく同時にコミット したのだろう」というくらいしかわかりません。その2つのコミットの間に別 のコミットにわりこまれたり、中断して後のコミットが失敗してしまうことも あり得ます。

Subversionではコミットはアトミックなので古いファイルの削除と新しいファイルの 追加は必ず同時の操作として処理されます。追加と削除という二つの処理ではなく 追加と削除二つまとめて一つの処理となります。リビジョン番号も1しか増えません。

CVSとの違いはログにもあらわれてきます。CVSの場合は新しいファイルのログを みても、追加された後しか見ることはできませんでした。

$ cvs log README.txt

RCS file: /path/to/cvsroot/hello/doc/README.txt,v
Working file: README.txt
head: 1.1
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 1;     selected revisions: 1
description:
----------------------------
revision 1.1
date: 2003/06/15 19:34:30;  author: ukai;  state: Exp;
rename README to README.txt
=============================================================================
$

Subversionではファイル名を変更する前のログも見ることができます。

$ svn log README.txt
------------------------------------------------------------------------
rev 15:  ukai | 2003-06-16 04:41:51 +0900 (Mon, 16 Jun 2003) | 1 line

rename README to README.txt
------------------------------------------------------------------------
rev 14:  ukai | 2003-06-16 04:38:00 +0900 (Mon, 16 Jun 2003) | 1 line

add README
------------------------------------------------------------------------
$

「add README」というのは READMEというファイル名だった時のログです。 このような場合は -v オプションを使うとより詳しい情報を見ることができます。

$ svn log -v README.txt
------------------------------------------------------------------------
rev 15:  ukai | 2003-06-16 04:41:51 +0900 (Mon, 16 Jun 2003) | 1 line
Changed paths:
   D /trunk/hello/doc/README
   A /trunk/hello/doc/README.txt (from /trunk/hello/doc/README:14)

rename README to README.txt
------------------------------------------------------------------------
rev 14:  ukai | 2003-06-16 04:38:00 +0900 (Mon, 16 Jun 2003) | 1 line
Changed paths:
   A /trunk/hello/doc/README

add README
------------------------------------------------------------------------
$

このように README.txtはREADMEから移動してきて追加されたものであるという 情報が記録されているわけです。

ディレクトリの名前を変更する

ディレクトリ名を移動する時も同じような処理になります。 CVSの場合は新しいディレクトリを追加して、そこに古いディレクトリに あった中身をコピーし、古いディレクトリから削除してコミットするという 操作になります。

$ ls
CVS/  Makefile  doc/  hello.c
$ mkdir doc-en
$ cvs add doc-en
Directory /path/to/cvsroot/hello/doc-en added to the repository
$ mv doc/README.txt doc-en
$ cd doc
$ cvs delete README.txt
cvs remove: scheduling `README.txt' for removal
cvs remove: use 'cvs commit' to remove this file permanently
$ cd ../doc-en
$ cvs add README.txt
cvs add: scheduling file `README.txt' for addition
cvs add: use 'cvs commit' to add this file permanently
$ cd ..
$ cvs commit -m 'rename doc to doc-en'
cvs commit: Examining .
cvs commit: Examining doc
cvs commit: Examining doc-en
Removing doc/README.txt;
/path/to/cvsroot/hello/doc/README.txt,v  <--  README.txt
new revision: delete; previous revision: 1.1
done
RCS file: /path/to/cvsroot/hello/doc-en/README.txt,v
done
Checking in doc-en/README.txt;
/path/to/cvsroot/hello/doc-en/README.txt,v  <--  README.txt
initial revision: 1.1
done
$ cvs update -P
cvs update: Updating .
cvs update: Updating doc
cvs update: Updating doc-en
$ ls
CVS/  Makefile  doc-en/  hello.c
$

Subversionではディレクトリもmoveサブコマンドで移動してしまうことができ ます。

$ ls
Makefile  doc/  hello.c
$ svn move doc doc-en
$ svn move doc doc-en
A         doc-en
D         doc/README.txt
D         doc
$

これで doc から doc-enへの移動ができます。これだけ見るとREADME.txtは削 除されるだけのように見えますが、statusサブコマンドでみるとちゃんと移動 されるようになっていることがわかります。

$ svn status -uv
	13       13       ukai   .
	13        4       ukai   Makefile
D       13       10       ukai   doc
D       15       15       ukai   doc/README.txt
A  +     -       10       ukai   doc-en
   +     -       15       ukai   doc-en/README.txt
	13        8       ukai   hello.c
Head revision:     15
$ ls
Makefile  doc/  doc-en/  hello.c
$ ls doc
$ ls doc-en
README.txt
$

ただし、この状態でコミットしようとするとエラーになってしまいます。

$ svn commit -m 'rename doc to doc-en' doc doc-en
Deleting       doc
svn: Transaction is out of date
svn: Commit failed (details follow):
svn: out of date: `/trunk/hello/doc' in txn `1j'
$

これは docのリビジョンが15であるべきなのに13のままだからです。このよう な場合は、まず svn updateしてからコミットしないといけません。

$ svn update
At revision 15.
$ svn commit -m 'rename doc to doc-en'
Deleting       doc
Adding         doc-en
Adding         doc-en/README.txt

Committed revision 16.
$ svn status -uv
	15       15       ukai   .
	15        4       ukai   Makefile
	16       16       ukai   doc-en
	16       16       ukai   doc-en/README.txt
	15        8       ukai   hello.c
Head revision:     16
$

ファイルの移動と同じようにディレクトリの移動でも以前のログを 見ることができます。

$ svn log -v doc-en/README.txt
------------------------------------------------------------------------
rev 16:  ukai | 2003-06-16 05:02:41 +0900 (Mon, 16 Jun 2003) | 1 line
Changed paths:
   D /trunk/hello/doc
   A /trunk/hello/doc-en (from /trunk/hello/doc:13)
   A /trunk/hello/doc-en/README.txt (from /trunk/hello/doc/README.txt:15)

rename doc to doc-en
------------------------------------------------------------------------
rev 15:  ukai | 2003-06-16 04:41:51 +0900 (Mon, 16 Jun 2003) | 1 line
Changed paths:
   D /trunk/hello/doc/README
   A /trunk/hello/doc/README.txt (from /trunk/hello/doc/README:14)

rename README to README.txt
------------------------------------------------------------------------
rev 14:  ukai | 2003-06-16 04:38:00 +0900 (Mon, 16 Jun 2003) | 1 line
Changed paths:
   A /trunk/hello/doc/README

add README
------------------------------------------------------------------------

CVSの時は、最初にインポートするときに優れたレイアウトをしっかりと決め、 後から何度もディレクトリを移動しなくてもよいようにすることが一番よいと されてきました。しかし、バージョン管理するまでにつくってきたものがあり、 それを管理するようにする場合はともかく、一から作ろうとしている時に優れ たレイアウトを最初から作るというのはなかなか難しいものです。そのためあ る程度おおまかな形ができるまではなかなかCVSのリポジトリにいれるのは躊 躇するものでした。また、レイアウトが定まっていない状態でCVSのリポジト リにいれても移動にともなうデメリットを考えると最初の段階ではバージョン 管理するメリットがあまり感じられなかったと言えるでしょう。

しかし、Subversionではそんなことを気にせず気楽にファイルやディレクトリの 移動ができます。移動しても移動前からの差分を見たりログを見たりすることも 簡単にできます。Subversionなら、まだレイアウトなどが固まっていなくても とりあえずバージョン管理化においてもデメリットはありません。むしろ、 初期段階の試行錯誤の状態もコミットしていけば全て記録に残せるというメリット があります。


スナップショットを入手する

CVSではリビジョン番号はファイル単位についていました。そのためにある時 点のスナップショットをとることはあまり簡単ではありません。スナップショッ トをとりたいような場合は普通、日付タグを使うか、あらかじめタグをうって おく必要がありました。滅多にコミットがないのならば日付タグで大丈夫です がそうでないのならばやはりタグをうっておかないとある時点のスナップショッ トをとるのは難しいものでした。例えば、docからdoc-enに移動する前の状態 をとりたい場合はその処理がおこなわれた時間をさがしだしてとりだす必要が ありました。

Subversionではリビジョン番号はファイルごとではなくリポジトリについてい ます。従って、リポジトリに対する操作ごとにタグがうってあるものとみなす ことができます。過去のリポジトリのスナップショットごとにリビジョン番号 がふられていると思えばいいでしょう。したがってリビジョン番号さえわかれ ば過去のある時点のスナップショットをとりだすことができます。例えば、 docからdoc-enに移動する前の状態をとりたい場合は次のようにします。

$ svn log doc-en
------------------------------------------------------------------------
rev 16:  ukai | 2003-06-16 05:02:41 +0900 (Mon, 16 Jun 2003) | 1 line

rename doc to doc-en
------------------------------------------------------------------------
rev 10:  ukai | 2003-06-16 03:39:57 +0900 (Mon, 16 Jun 2003) | 1 line

mkdir doc
------------------------------------------------------------------------
$

これで rev 16で docからdoc-enに移動したことがわかります。したがって rev 15に戻せばdocからdoc-enに移動する前の状態になるわけです。

$ svn update -r15
A  doc
A  doc/README.txt
D  doc-en
Updated to revision 15.
$ ls
Makefile  doc/  hello.c
$ svn status -uv
    *       15       15       ukai   .
 	    15        4       ukai   Makefile
	    15       15       ukai   doc
	    15       15       ukai   doc/README.txt
    *                                doc-en
    *                                doc-en/README.txt
    	    15        8       ukai   hello.c
Head revision:     16
$

これで docからdoc-enに移動する前の状態にすることができました。 最新に戻すには単に svn update とするだけです。

$ svn update
A  doc-en
A  doc-en/README.txt
D  doc
Updated to revision 16.
$ ls
Makefile  doc-en/  hello.c
$ 

もちろんCVSと同じく日付をリビジョンとして使うこともできます。その場合 日付をあらわす文字列を { と } でかこみます。リビジョンとして使える 文字列はこのようなものがあります。

{DATE}
日付の場合は { と } で囲む
HEAD
リポジトリの最新リビジョン
BASE
ワーキングコピーをチェックアウト/アップデートした時のリビジョン
COMMITTED
最後にコミットした時のリビジョン
PREV
最後にコミットする直前のリビジョン

タグ

CVSではスナップショットとして扱うためにタグをうっておくことはほぼ必須 だったと言えるでしょう。タグをうつのは特別な処理とみなされtagというサ ブコマンドを使っておこなっていました。タグをうつと、それぞれのファイル に対してそのタグがうたれた時はそのファイルのリビジョンがなんだったかを 記録しました。

$ cvs tag hello_1_1
cvs tag: Tagging .
T Makefile
T hello.c
cvs tag: Tagging doc-en
T doc-en/README.txt
$

これで、以下のようにそれぞれのファイルのリビジョンに対して hello_1_1 というタグがつけられます。ディレクトリには そのような管理情報を記録しておくところがないのでタグは つきません。

			hello_1_1タグに対応するリビジョン番号
 Makefile:		1.1.1.1
 hello.c:		1.6
 doc-en/README.txt	1.1

Subversionではリビジョン番号を使えばスナップショットを得るためのタグと して使えるのでCVSほどタグをがんばってうつ必要はありません。しかしなに か意味のある文字列をあたえた方がわかりやすいものです。例えばリリースす るなどといった時のリビジョンは他のリビジョンとは区別しておいた方がわか りやすいといえるでしょう。そのような場合にはタグをうっておくべきです。 Subversionでは、そのような場合にコピーを使います。これはCVSでバージョ ン管理する前にやっていたバージョン管理(?)の仕方に近いと言えるかもしれ ません。つまり現状をとりあえずコピーしておいて後で参照できるようにして おくということです。慣習的にタグをうつ時はtags/以下にコピーすることに よっておこないます。

$ svn info .
Path: 
Url: file:///path/to/svnrepos/trunk/hello
Repository UUID: 1e0c500f-25c0-0310-91ba-92d4cfa6a45a
Revision: 16
Node Kind: directory
Schedule: normal
Last Changed Author: ukai
Last Changed Rev: 16
Last Changed Date: 2003-06-16 05:02:41 +0900 (Mon, 16 Jun 2003)
$

このようにこのワーキングコピーのリポジトリパスは file:///path/to/svnrepos/trunk/helloでした。これを file:///path/to/svnrepos/tags/hello-1.1 にコピーすることが タグをうつことに相当します。

$ svn copy -m 'tag hello-1.1' file:///path/to/svnrepos/trunk/hello file:///path/to/svnrepos/tags/hello-1.1 

Committed revision 17.
$

どのようなタグがうたれているかは tags を ls するとわかります。

$ svn ls file:///path/to/svnrepos/tags
hello-1.1/
$

コピーといってもファイルの内容がコピーされているわけではありません。む しろUNIXのファイルシステムのハードリンクに近いといえるでしょう。あるリ ビジョンの時点のファイルの内容がどのようなファイル名であらわされるかと いった情報だけがコピーされると考えられます。したがってコピーという操作 は非常にはやくおこなわれます。

ブランチ

CVSでは並行して編集をすすめていくためにブランチを使うことができました。 ブランチをつくるにもtagサブコマンドを使いましたが、ブランチをあらわすた めに-bオプションを使います。

$ cvs tag -b hello_1_1-branch
cvs tag: Tagging .
T Makefile
T hello.c
cvs tag: Tagging doc-en
T doc-en/README.txt
$

これで hello_1_1-branch というブランチができました。このブランチで 作業する場合は、このブランチ名でチェックアウトします。

$ cd ..
$ cvs -d /path/to/cvsroot checkout -r hello_1_1-branch -d hello-1.1 -P hello
cvs checkout: Updating hello-1.1
U hello-1.1/Makefile
U hello-1.1/hello.c
cvs checkout: Updating hello-1.1/doc
cvs checkout: Updating hello-1.1/doc-en
U hello-1.1/doc-en/README.txt
cvs checkout: Updating hello-1.1/subdir
$ cd hello-1.1

Subversionではブランチもタグと同じようにコピーします。慣習的にブランチ に対してはbranches/を使います。

$ svn copy -m 'branch hello-1.1' file:///path/to/svnrepos/trunk/hello file:///path/to/svnrepos/branches/hello-1.1       

Committed revision 18.
$ svn ls file:///path/to/svnrepos/branches
hello-1.1/
$

これで hello-1.1というブランチができています。とりだす時はtrunk からとりだす時と同じようにします。

$ cd ..
$ svn checkout file:///path/to/svnrepos/branches/hello-1.1
A  hello-1.1/hello.c
A  hello-1.1/doc-en
A  hello-1.1/doc-en/README.txt
A  hello-1.1/Makefile
Checked out revision 18.
$

マージ

ブランチで加えられた修正は、普通トランクの方にも反映しておく必要があります。 その場合、単にトランクでアップデートしても反映されません。

CVSではupdateの-jオプションでマージしました。

$ cvs update -j hello_1_1-branch
cvs update: Updating .
RCS file: /path/to/cvsroot/hello/hello.c,v
retrieving revision 1.6
retrieving revision 1.6.2.1
Merging differences between 1.6 and 1.6.2.1 into hello.c
cvs update: Updating doc-en
$

確認したのちにコミットします。

$ cvs commit -m 'merge from hello_1_1-branch'
cvs commit: Examining .
cvs commit: Examining doc-en
Checking in hello.c;
/path/to/cvsroot/hello/hello.c,v  <--  hello.c
new revision: 1.7; previous revision: 1.6
done
$

Subversionではmergeサブコマンドを使います。その前にブランチを作った時 のリビジョンを調べます。

$ svn log file:///path/to/svnrepos/branches/
------------------------------------------------------------------------
rev 19:  ukai | 2003-06-16 06:12:11 +0900 (Mon, 16 Jun 2003) | 1 line

use options
------------------------------------------------------------------------
rev 18:  ukai | 2003-06-16 06:00:59 +0900 (Mon, 16 Jun 2003) | 1 line

branch hello-1.1
------------------------------------------------------------------------
rev 2:  ukai | 2003-06-16 00:39:47 +0900 (Mon, 16 Jun 2003) | 1 line

branches
------------------------------------------------------------------------
$

rev 18でhello-1.1ブランチを作っていたこれがこのログでわかります。rev 18から 最新までに加えられて変更をマージする場合は次のようにします。

$ svn merge -r 18:HEAD file:///path/to/svnrepos/branches/hello-1.1
U  hello.c
$ svn status -uv
               19       16       ukai   .
               19        4       ukai   Makefile
               19       16       ukai   doc-en
               19       16       ukai   doc-en/README.txt
M              19        8       ukai   hello.c
Head revision:     19
$

これで hello-1.1で加えられていた変更がワーキングコピーにも加えられまし た。ここで問題がないかどうかdiffをみたり動かしてみたりした後に、コミッ トします。

$ svn commit -m 'merged all hello-1.1 branch changes into the trunk'
Sending        hello.c
Transmitting file data .
Committed revision 20.
$

Subversionではブランチ間を移動するためのswitchというサブコマンドが用意 されています。これを使うと今のワーキングコピーを指定したブランチに変更 してupdateをかけたのと同じことになります。trunkとブランチの間をいった りきたりするのに便利な機能です。

$ svn switch file:///path/to/svnrepos/branches/hello-1.1
U  hello.c
Updated to revision 20.
$ svn info
Path: 
Url: file:///path/to/svnrepos/branches/hello-1.1
Repository UUID: 1e0c500f-25c0-0310-91ba-92d4cfa6a45a
Revision: 20
Node Kind: directory
Schedule: normal
Last Changed Author: ukai
Last Changed Rev: 19
Last Changed Date: 2003-06-16 06:12:11 +0900 (Mon, 16 Jun 2003)
$ svn status -uv
               20       19       ukai   .
               20        4       ukai   Makefile
               20       16       ukai   doc-en
               20       16       ukai   doc-en/README.txt
               20       19       ukai   hello.c
Head revision:     20
$

このように Url: がブランチの方に変更されています。 戻る時も同じように switch します。

$ svn switch file:///path/to/svnrepos/trunk/hello
U  hello.c
Updated to revision 20.
$ svn info
Path: 
Url: file:///path/to/svnrepos/trunk/hello
Repository UUID: 1e0c500f-25c0-0310-91ba-92d4cfa6a45a
Revision: 20
Node Kind: directory
Schedule: normal
Last Changed Author: ukai
Last Changed Rev: 20
Last Changed Date: 2003-06-16 06:21:59 +0900 (Mon, 16 Jun 2003)

$ svn status -uv
	20       20       ukai   .
	20        4       ukai   Makefile
	20       16       ukai   doc-en
	20       16       ukai   doc-en/README.txt
	20       20       ukai   hello.c
Head revision:     20
$

switchサブコマンドはリポジトリURLを変更してアップデートするようなものです。


キーワード置換

CVSの場合、テキストファイルではキーワード置換がおこなわれていました。 例えば $Id$ という文字列があればそれは

 $Id: hello.c,v 1.9 2003/06/15 21:31:35 ukai Exp $

のような文字列に変換されていたわけです。

Subversionの場合は、キーワード置換が必要な場合はプロパティを設定 しておく必要があります。プロパティに何が設定されているかどうかを 見るには proplistサブコマンドを使います。

コラム4 プロパティ

svn:keywords以外のプロパティとしては以下のものがあります。

svn:ignore
CVSの.cvsignoreに相当します
svn:executable
実行可能属性をあらわします。
svn:eol-style
改行の種類です。 native, LF, CR, CRLF のどれかを指定します。
svn:mime-type
テキストかバイナリかの判断に使われます。 text/ ではじまる MIME typeならテキストの扱いです。 これはApacheでみせる時のContent-Typeにも使われます。
svn:externals
外部モジュールの指定です

詳しくは svn help propset で見ることができます。

$ svn proplist -v hello.c
$

デフォルトではこのようにプロパティはついていません。

キーワード置換をするためには svn:keywords というプロパティに キーワード置換をするものが何かを指定する必要があります。 Subversionでは次のものをキーワード置換できます。

URL, HeadURL
HEADバージョンのURL
Author, LastChangedBy
そのファイルを最後に修正したユーザ名
Date, LastChangedDate
そのファイルが最後に修正された日時
Rev, LastChangedRevision
そのファイルが最後に修正されたリビジョン
Id
以上4つをまとめたもの

たとえば $Id$を置換する場合はpropsetサブコマンドを使って svn:keywordsプロパティの値にIdを設定します。

$ svn propset svn:keywords Id hello.c
property `svn:keywords' set on 'hello.c'
$ svn proplist -v hello.c
Properties on 'hello.c':
  svn:keywords
$ svn status
 M     hello.c
$

この場合2文字目に「M」がつきます。これはプロパティの変更があったことを 意味しています。

このようにして、ファイルに「$Id$」という 文字列をいれてコミットするとその文字列がCVSの時と似たような感じに置換されます。

$ cat hello.c
#include 
static char svnid[] = "$Id$";

int
main(int argc, char *argv[])
{
    char *s = "world";
    if (argc >= 2)
     s = argv[1];
    printf("Hello, %s!\n", s);
    exit(0);
}
$ svn status hello.c
MM     hello.c
$ svn commit -m 'add id' hello.c
Sending        hello.c
Transmitting file data .
Committed revision 21.
$ 

これでプロパティの追加がおこなわれ、ファイルの中のキーワードが置換 されています。

$ cat hello.c
#include 
static char svnid[] = "$Id: hello.c 20 2003-06-15 21:41:46Z ukai $";
 
int
main(int argc, char *argv[])
{
    char *s = "world";
    if (argc >= 2)
            s = argv[1];
    printf("Hello, %s!\n", s);
    exit(0);
}
$

ネットワークの設定

CVSはネットワーク越しに使えるようになった頃から、インターネットにおけ るオープンソースの開発にはなくてはならないものと見なされるようになって きました。ネットワーク経由で使えるようにするためにはどうすればいいのか を説明します。

CVS

CVSをネットワーク越しに使うには主に次の2つが使われています。

CVS pserver

pserverの場合、リポジトリを公開する側ではinetd経由で cvs pserverを 実行するように設定しておきます。Debianでは、pserverを使うように した場合、次のような感じの設定がおこなわれます。

cvspserver      stream  tcp     nowait.40       root    /usr/sbin/tcpd  /usr/sbin/cvs-pserver

/usr/sbin/cvs-pserverの中で必要なオプションをつけて cvs pserverを 実行しています。

そしてパスワードをCVSROOT/passwdに登録しておきます。

pserverを利用する側はリポジトリの場所として「:pserver:ユーザ名@ホスト 名:パス名」を使います。基本的に最初にloginサブコマンドを使って認証をお こないます。認証が成功すれば~/.cvspassにパスワードが暗号化されて保存さ れ以下そのパスワードを使って通信します。

$ cvs -d :pserver:ユーザ名@ホスト名:パス名 login
(Logging in to ユーザ名@ホスト名)
CVS password: ---- パスワードを入力
$ 

後はローカルの時と同じようにcvsコマンドを利用することができます。

CVS ext(ssh)

extでsshを使う場合、リポジトリを公開する側では特になにもすることはありません。 sshでそのユーザがログインできるようにして、リポジトリのパーミッションが そのユーザが読み書きできるようにしておくだけです。

extでsshを利用する側はリポジトリの場所として 「:ext:ユーザ名@ホスト名:パス名」を使います。extはデフォルトでは rshを使うのでsshを使う場合には環境変数 CVS_RSH に sshを設定しておく 必要があります

$ CVS_RSH=ssh; export CVS_RSH
$

pserverと違って、認証はsshでおこなわれるのでCVSでなにかをする必要はあ りません。extを使う時は CVS_RSHで指定されたコマンドを使ってリポジトリ のあるマシンにログインして、cvs serverコマンドを起動してそれと手元の cvsプロセスと通信して処理をおこなっています。

sshの場合、ssh-agentにパスフレーズを記憶させておけば毎回パスフレーズを 入力する必要がなくなるので便利です。

Subversion

Subversionの場合、ネットワーク対応にするにはいくつかの方法があります。

svnserveをinetd経由で使う

Subversionでは、svnserveというコマンドを使ってネットワーク経由で リモートのリポジトリにアクセスする仕組が用意されています。

リポジトリを公開する側では、svnserveコマンドをinetd経由で起動するよう にすることができます。Subversionではport 3690を使います。もしまだ /etc/services に登録されていないければ以下のような内容を /etc/services に登録します。

svn 3690/tcp

そしてinetd.confに以下のような内容を登録し、inetdを再起動します。

svn stream  tcp nowait ユーザ /usr/sbin/tcpd /usr/bin/svnserve -r /parent/of/svnrepos -R

このようにしておくとsvnserveはユーザ権限でリポジトリにアクセスしにいき ます。そのためそのユーザがリポジトリを読み書きできるようにリポジトリの パーミッションを調整しておく必要があります。

/parent/of/svnrepos は /path/to/svnrepos の上のディレクトリです。もし /path/to/svnreposが /var/local/subversion/repos などの場合 /parent/of/svnrepos には /var/local/subversion を指定します。このよう に -r オプションを使うことで、ここが svnserveで処理する時の/ ディレク トリとなります。つまりURLのパス名に reposを使うことで /var/local/subversion/reposというリポジトリにアクセスするようになりま す。

-R オプションを付けることで読みとり専用のsvnserveとして動くようになり ます。svnserveをこのようにして使う場合はアクセス制御する方法がtcpdしか なくなるので読みとり専用にしたほうがいいでしょう。

このようなリポジトリにリモートからアクセスする時は「svn://ホスト名/パ ス名」のようなURLを使います。この時「svnserveの-rオプションで指定した パス」/「URLのパス名」がホスト名で示されるホスト上でのリポジトリのパス として使われます。

svnserveをデーモンで動かす。

inetdで動かすかわりにデーモンとして動かすこともできます。この場合は オプションは基本的にinetdの時と同じで -d オプションを付けて動かすことに なります。

$ svnserve -r /parent/of/svnrepos -R -d
$

このリポジトリにアクセスする側はinetdの時と全く同じです。

svnserveをssh経由で使う。

CVSの時と同じくsshを使ってリモートのリポジトリにアクセスすることもでき ます。Subversion 0.23以前は、~/.subversion/servers にsshをトンネルエー ジェントとして使うような設定を書いていましたが、Subversion 0.23から 「svn+ssh://[ユーザ名@]ホスト名/パス名」というURLを使うようになりまし た。この場合リモートのホストにsshにログインして、snvserve -t を実行し それと通信して処理をおこないます。

なお、~/.ssh/authorized_keys で、svn+ssh によるアクセスを許す 鍵の前に

command="svnserve -t"

を付けておけば、それ以外のコマンドは実行されなくなります。 このようにすることでSubversionのためだけのアカウントを作ることができます。

この時もリポジトリのパーミッションに注意する必要があります。 複数のユーザで使う時は、みな同じグループに属するようにしておいて umask 002 でsvnserveを実行するようにしておけば問題ありません。

Apache2を使う

Subversionは Apache2を使うことで WebDAV/DeltaV プロトコルを使って 通信することができます。この方法を使う場合は次のようなメリットがあります。

ただしWebDAVを使う場合、svnプロトコルを使うよりも遅いようです。

Apache2を使う場合、mod_dav_svn.soをいうモジュールを使うことになります。 Debianの場合、これは libapache2-dav-svn というパッケージになっているので これをapt-getでインストールします。

このモジュールを有効にするにはa2enmodを使います。

# a2enmod dav_svn
Module dav_svn installed successfully; run apache2ctl graceful to enable.
#

このコマンドは基本的に /etc/apache2/mods-available にあるものに /etc/apache2/mods-enable/ からシンボリックリンクをはって apache2を再起動しているだけです。/etc/apache2/mods-enable/ 以下に あるのが今有効になっているモジュールの設定というわけです。

dav_svnの設定ファイルは dav_svn.confというファイルです。

一番簡単な設定だと次のような感じになります。

<Location /svn/repos>
  DAV svn
  SVNPath /path/to/svnrepos
  SVNAutoversioning on
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /etc/subversion/passwd
  <LimitExcept GET PROPFILE OPTIONS REPORT>
   Require valid-user
  </Limit>
</Location>

Location でどのようなパスで外に見せるかを設定します。この場合 「http://ホスト名/svn/repos/」というURLでアクセスされることになります。

DAV svn とすることで、ここでWebDAVが有効になります。

SVNPath で Subversionのリポジトリのローカルのパスを指定します。もしリ ポジトリが同じディレクトリの中にたくさんある場合はSVNParentPath を使っ た方がいいかもしれません。

SVNAuthoversioning on とすることで、普通のWebDAVクライアントでも 読み書きができるようになります。

このままでは誰でも読み書きができてしまうので普通は認証を書けておきます。 どの認証でもいいのですが一番簡単なAuthUserFileを使ったのがこの例です。 AuthUserFileで指定したファイルは htpasswd2 コマンドを使ってパスワード の登録をします。

# rm -f /etc/subversion/passwd
# htpasswd2 -c /etc/subversion/passwd ユーザ名1
New password: 
Re-type new password: 
Adding password for user ユーザ名1
# htpasswd2 /etc/subversion/passwd ユーザ名2
New password: 
Re-type new password: 
Adding password for user ユーザ名2
#

以下の設定は、読みとりはanonymousでできるけれでも書きこみには 認証を必要とするという設定になります。

<LimitExcept GET PROPFILE OPTIONS REPORT>
 Require valid-user
</Limit>

もし、読みとりにも認証を必要とするのならば<LimitExcept>なしで Require valid-user などを直接書くことになります。

このようにした場合、Apache2からリポジトリへは Apache2を動かしているユーザ (www-data)がアクセスしにいくことに なります。そのためリポジトリはそのユーザ(www-data)が読み書きできる ようにしておく必要があります。WebDAV経由でしかそのリポジトリに アクセスしないようにしておいてリポジトリをwww-dataの所有にして しまうのが一番簡単な方法です。

このように設定されたWebDAVを使ったSubversionリポジトリは 「http://ホスト名/svn/repos」のようなURLでアクセスできます。

認証がかけてある場合、リポジトリにアクセスする時にユーザ名と パスワードを入力する必要があります。これらの認証情報は--no-auth-cache オプションなどを使わない限り、~/.subversion/auth/ 以下に格納 されています。今のところ特に暗号がかけられているわけでもなく 平文で保存されているのでパーミッションには十分注意する必要があります。

hooks

CVSではCVSROOT/loginfoなどを使うことで、コミットされた時にメールを送っ たり、公開する場所でcvs updateをして即座に反映させたりといったことを実 現することができました。

Subversionではhooksを使うことで同じようなことが実現できます。これは リ ポジトリの hooksというディレクトリ以下にあります。svnadmin createでリ ポジトリ作ると以下のようなファイルがhooksディレクトリに作られています。

$ ls hooks
post-commit.tmpl          pre-commit.tmpl          start-commit.tmpl
post-revprop-change.tmpl  pre-revprop-change.tmpl
$

これらはいずれもテンプレートであり実際には使われません。実際にフックと して使うためには .tmplという拡張子をのぞいたファイルを実行可能にしてこ こに置いておく必要があります。

例えばコミットされたらメールを送るようにするには★リスト1のようなスク リプトを post-commit としておきます。

リスト1 メールをなげるpost-commit

#!/usr/bin/ruby

REPOS=ARGV[0]
REV=ARGV[1].to_i

svnauthor=%x{svnlook #{REPOS} rev #{REV} author}.chomp!
svndate=%x{svnlook #{REPOS} rev #{REV} date}.chomp!
svnchanged=%x{svnlook #{REPOS} rev #{REV} changed}.chomp!
svnlog=%x{svnlook #{REPOS} rev #{REV} log}.chomp!
svndiff=%x{svnlook #{REPOS} rev #{REV} diff}.chomp!

toaddr = ['foo@example.com', 'bar@example.net']

require 'net/smtp'
Net::SMTP.start( 'localhost', 25 ) {|smtp|
    smtp.send_mail <<EndOfMail, 'svn@example.jp', *toaddr
From: Subversion Admin <svn@example.jp>
To: Subversion committers:;
Subject: Subversion commit #{REPOS} #{REV}

Subversion committed to #{REPOS} #{REV}
#{svnauthor}
#{svndate}

changed:
#{svnchanged}

log:
#{svnlog}
#{svndiff}

EndOfMail
}

post-commitスクリプトにはリポジトリのパスと、リビジョン番号がわたされ て起動されます。この例にあるようにsvnlookを使うとそのコミットで誰がい つどのようなコミットをしたのかといった情報をとることができます。

post-commit を★リスト2のようにしておくと公開する場所でsvn updateをし て即座に反映させることができます。これを使う場合、 /path/to/checkouted/dir にあらかじめチェックアウトしておきます。このよ うにすることでコミットされる度にsvn updateがおこなわれます。このスクリ プトのように svnlook dirs-changed を見るようにしておけば不必要なアップ デートをさけることができます。

★リスト2 即座にupdateするpost-commit

#!/bin/sh -e

REPOS="$1"
cd /path/to/checkouted/dir
umask 002
svn update `svnlook dirs-changed $REPOS | sed -e 's,trunk/,,'` &

リポジトリのバックアップ

CVSの場合リポジトリはすべてテキストであり、いざという時にエディタなど で修正できるという安心感がありました。バックアップは基本的にリポジトリ をそのままコピーするだけで済んでいました。

Subversionの場合、リポジトリはデータベースなので、このデータベースの中 身が壊れるとどうしようもなくなってしまいます。バージョン管理システムは 過去の履歴もすべて記憶しているために、この情報が失われていまった時の損 害はかなり大きなものと言えるでしょう。従ってリポジトリのバックアップは 非常に重要です。

リポジトリをそのままコピーしてしまってもいいのですが、hot-backup.py と いうスクリプトがあるのでこれを使うといいでしょう。Debianでは subversion-toolsパッケージで/usr/lib/subversion/hot-backup.py として用 意されています。

これを

# /usr/lib/subversion/hot-backup.py /path/to/svnrepos /var/backups/subversion/

のように使うと /var/backups/subversion/ に repos-n-m のような かんじでリポジトリのコピーが作成されていきます。

その他の方法としては svnadmin の dumpコマンドがあります。svnadminの dumpサブコマンドを使うとバイナリのデータベースからテキスト形式に変換さ れて出力されます。このフォーマットはソースのnotes/fs_dumprestore.txt に記述されています。

バックアップを取る時は次のようにしていきます。

% svnadmin dump /path/to/svnrepos rev1 rev2 > dumpfile1
% svnadmin dump /path/to/svnrepos rev2+1 rev3 --incremental > dumpfile2
% svnadmin dump /path/to/svnrepos rev3+1 rev4 --incremental > dumpfile3
  :

これを元に戻すにはsvnadminのloadサブコマンドを使います。

% svnadmin create /path/to/svnnewrepos 
% svnadmin load /path/to/svnnewrepos < dumpfile1
% svnadmin load /path/to/svnnewrepos < dumpfile2
% svnadmin load /path/to/svnnewrepos < dumpfile3
 :

既知の問題点

さて、Subversionはまだ開発が続いており、いろいろと問題点も残っています。 例えば次のようなものが問題と言えるでしょう。

アクセスコントロールが貧弱

アクセスコントロールが貧弱です。基本的にリポジトリ単位でしかアクセスを 制御できません。つまりリポジトリにアクセスする権限さえあればそのリポジ トリの中身に対する操作はなんでもすることができます。CVSではファイルご とのパーミッションである程度制限をかけることができましたが、Subversion にはそのような機能はまだありません。今やるとすれば hooksを使うことくら いです。ただし、アクセスコントロールに関してはどのレイヤでアクセス制御 をおこなえばいいのかといった仕様から現在検討がおこなわれています。

日本語ファイル名の扱い

日本語ファイル名の扱いがまだきちんとできていません。

annotateがない

CVSにはannotateという機能があって、どの行を誰が最後に変更したかを見る ことができるのですが、Subversionにはまだありません。これは1.0移行に svn blameとして実装される予定になっています。

シンボリックリンクやハードリンクの管理

CVSでもできませんでしたがSubversionでもシンボリックリンクやハードリン クの管理ができません。これはワーキングコピー側のファイルシステムにもよ るので難しいところです。今のところ1.0移行に実装される予定になっていま す。

仕様が枯れていない

まだ1.0にもならない開発の初期段階ということもあり、仕様がかわることが よくあります。最近だと、認証情報の保存場所が .svn/から ~/.subversion/authにかわったりしましたし、sshトンネルをつかったsvnプロ トコルも~ /.subversion/servers でのsvn-tunnel-agent=ssh という設定から svn+ssh: というURLを使うようにするといった変更がありました。


よくあるトラブル

Subversionに慣れていないとはまりがちなトラブルをまとめて おきます。

パーミッション問題

読み書きできないとリポジトリのデータベースをオープンすることができませ ん。したがって、複数人で使う時は group writable で、全員 umask 002 で アクセスするようにしておくのが確実です。もしくはリポジトリのアクセスは WebDAVのみに限定して、apache2を実行しているユーザ(www-data)の所有にす るというのも一つの方法です。

Subversionをアップグレードした後

Subversionをアップグレードした時、特にBerkeley DBのバージョンがかわっ た時などにリポジトリのアクセスができなくなってしまう場合があります。そ のような時は svnadmin recover /path/to/svnrepos を実行してデータベース の修復をする必要があります。

コミット途中にとめた/ネットワークがきれた

コミットしている途中にインタラプトをかけてとめたり、ネットワークの接続 がきれてしまってコミットが完全に終了しなかった場合にロックが残ってしま うことがあります。Subversionではコミットをアトミックにするために同じワー キングコピーでコミットをかける時にコミットする部分にロックをかけていま す。コミットの処理が中断されたりするとそのロックが残ったままになってし まい、これが残ったままだと新たなコミットがまったくできなくなってしまい ます。このような場合、svn cleanup でロックを消す必要があります。


まとめ

SubversionはよりよいCVSをめざした新しいバージョン管理システムです。特 にファイルの移動が自由にできること、コミットがアトミックであることなど が非常に特徴的です。CVSの時は、ある程度全体像がみえてきて、レイアウト がきまったくらいにようやくインポートしようという気になりますが、 Subversionの場合、ファイルの移動がディレクトリの扱いが気楽なので初期段 階からとりあえずリポジトリにいれて管理する気になります。

Subversionというソフトウェアもすばらしいものですが、Subversionプロジェ クト自体に見習うべき点が非常に多いと思います。Subversionは非常に美しい 設計方針で開発されており、コーディングスタイルも決められているために、 その慣習さえ理解してしまえばかなり読みやすいソースコードだと思います。 このあたりはソースコードの HACKINGというファイルにいろいろと書かれてい るので興味のある人は一読することをおすすめします。

しかしながらまだまだ開発途上であることには注意しておく必要があるでしょ う。これからも仕様がかわることも予想されます。まだまだCVSほど枯れては いるとは言えませんが、Subversion自体がSubversionで管理されるようになっ てかなりの時間もたっていますし、いろいろなプロジェクトでSubversionを使 うようになってきたのでもう実用に使うことはできると言えるでしょう。

より詳しい説明は Subversion: The Definitive Guide を読んでください。こ れは O'Reillyで発行される予定のSubversion本です。ソースコードにも含ま れていますし、 http://svnbook.red-bean.com/ で読むこともできます。 Debianでは/usr/share/doc/subversion/bookにインストールされています。日 本語訳が http://subversion.bluegate.org/doc/book.html にあります。


Fumitoshi UKAI