CVS/Subversionを使ったバージョン管理(後編:SVNを使ったバージョン管理)

 本特集ではバージョン管理システムの基本的な考え方や用語を解説するとともに、SubversionやCVSを使ったバージョン管理方法について説明していく。前編中編ではバージョン管理の基本事項やCVSについて解説してきたが、後編となる本記事では、Subversionの利用について解説する。

 Subversionは、CVSの後継として開発されたバージョン管理システムだ。CVSでは対応していない、ファイルのリネームや移動、「アトミックなコミット」といった機能が実装されており、リポジトリ管理がより柔軟に行えるため、近年ではバージョン管理にSubversionを利用する例も多い。

Subversionの導入

 SubversionはUNIX/Linuxのほか、WindowsやMac OS Xといったさまざまなプラットフォームで利用できる。Subversionのソースコードやバイナリは、Subversionダウンロードページから入手できる。このページには、AIXやHP-UX、Solarisといった商用UNIXやWindows、Mac OS X、各種LinuxやBSD向けバイナリの配布先へのリンクも掲載されている。また、多くのLinuxディストリビューションやFreeBSD、OpenBSD、Cygwinなどでは、標準のパッケージマネージャからのインストールが可能だ。

 WindowsでSubversionを利用する場合は、Subversionの開発を主導しているCollabNetが提供する公式バイナリインストーラを利用するとよいだろう(ダウンロードページ)。SubversionのCUIクライアントであるsvnコマンドだけを使いたい場合は、「CollabNet Subversion Command Line Client」のみをダウンロードすればよい。

 また、Mac OS X向けには「openCollabNet」プロジェクトにより、バイナリインストーラが提供されている(ダウンロードページ)。そのほか、FinkMacPortsといったパッケージ管理システムからもバイナリが入手可能だ。

 なお、Windows向けのTortoiseSVNなど、GUIでSubversionリポジトリにアクセスできるツールも存在する。これらについては別途解説記事を設けてあるので、そちらを参照してほしい。

Subversionリポジトリの作成

 Subversionでは、リポジトリへのアクセス方法として複数のプロトコルが利用できる。Subversionで利用できるプロトコルは下記の表1のとおりだ。

プロトコル 説明
file ローカルのファイルシステムにアクセスする場合に使用する
http、https サーバーとしてApache httpdを使用する
svn サーバーとしてSubversion付属のsvnserveを使用する
svn+ssh サーバーとしてSubversion付属のsvnserveを使用し、さらにSSHを利用して暗号化された通信経路を使用する
表1 Subversionで利用できるプロトコル

 もし既存のSubversionリポジトリに接続する場合は、リポジトリ側が推奨するプロトコルを使用すればよい。たとえばSourceForge.JPのSubversionリポジトリでは、httpおよびsvn+sshが利用できる(httpによるアクセスはチェックアウトのみ可能)。

 一方、自分でSubversionサーバーを用意する場合は、どのプロトコルを利用するか事前に検討しておく必要がある。使用するプロトコルによってサーバーの設定方法などは異なるので、注意が必要だ。

 サーバーの設定が完了したら、サーバーのファイルシステム内にリポジトリを作成する。リポジトリの作成には、Subversionに付属するsvnadminコマンドを使用する。まず、リポジトリのルートディレクトリとして使用するディレクトリを作成し、続いて「svnadmin create」コマンドを実行する。

$ svnadmin create <リポジトリのルートディレクトリ>

 これにより、リポジトリのルートディレクトリにデータベースや設定ファイル一式が作成され、リポジトリが利用できるようになる。

Subversionサーバーの設定

 Subversionでは、利用するプロトコルにより、利用するサーバーが異なる。最もセットアップが手軽なのが、svn+sshプロトコルを利用する場合だ。この場合、SSH経由で自動的にsvnserverが起動されるため、svnserveプロセスを常時立ち上げておく必要はない。また、認証はSSHによって行われるため、「SSHでアクセス可能なユーザー」=「リポジトリにアクセス可能なユーザー」となり、ユーザー管理が行いやすい。

 一方、WindowsをSubversionサーバーとして利用する場合や、SSHを使用したくない、といった場合はsvnプロトコル、もしくはhttp/httpsを利用することになる。svnプロトコルはセットアップが容易で、設定ファイルを用意してサーバーとなるsvnserveプロセスを動かしておくだけで利用可能になる。ただし、通信内容の暗号化や、通信ログの保存などには対応していない。それに対し、httpやhttpsは別途Apacheおよびmod_dav_svnモジュールのセットアップが必要となる。これらについては本記事内では触れないので、別途Subversion付属のマニュアルなどを参照してほしい。

リポジトリにファイルをインポートする

 リポジトリに新たにファイルを登録する(インポートする)には、「svn import」コマンドを使用する。

svn import <対象ファイル/ディレクトリ> <プロトコル>://<ユーザー名>@<サーバー>/<リポジトリのルートディレクトリ>/<登録先ディレクトリ>

 たとえば、次のような条件でインポートを行うとしよう。

  • ユーザー名:「john」
  • Subversionサーバー:「svn.sourceforge.jp/svnroot/testproj/」
  • 登録先ディレクトリ:「trunk/foobar」
  • 登録したいファイルやディレクトリが格納されているディレクトリ:「./foobar/」

 この場合、下記のように実行すればよい。

$ svn import foobar svn+ssh://john@svn.sourceforge.jp/svnroot/testproj/trunk/foobar
Subversionリポジトリの推奨構造

 Subversionでリポジトリを管理する場合、リポジトリのルートディレクトリ、もしくはそのサブディレクトリ以下に「trunk/」「tags/」「branches/」という3つのディレクトリを作成するのが慣例となっている(図1図2)。trunk/は開発の本流となるファイル(HEADなどとも呼ばれる)を格納するディレクトリ、tags/は特定のツリーのスナップショットを保存しておくディレクトリだ。また、branches/はメインのソースツリーとは異なる更新を加えたい(ブランチを作成する)場合に使用するディレクトリとなる。通常、リポジトリに新しくディレクトリ(およびファイル)を登録する際は、trunk/ディレクトリ以下にサブディレクトリを作成してファイルを登録することが多い。

図1 図2
図1 リポジトリ構造の例1:ルートディレクトリ以下にtrunk、tags、branchesディレクトリを作成 図2 リポジトリ構造の例2:ルートディレクトリ以下にプロジェクトごとのディレクトリを作成し、それぞれにtrunk、tags、branchesディレクトリを作成

リポジトリからファイルを取り出す

 リポジトリに保存されているファイルをローカルに取り出す(チェックアウトする)には、「svn co」コマンドを使用する。「co」は「checkout」の略で、「svn checkout」と実行しても同じ結果が得られる。svn coコマンドの書式は次のとおりだ。

svn co svn+ssh://<ユーザー名>@<サーバー名>/<取得したいディレクトリのパス>

 たとえば、次の例は「john」というユーザーで「svn.sourceforge.jp」というサーバーにある「/svnroot/testproj/foobar」というディレクトリ以下のファイルを、カレントディレクトリ以下にチェックアウトするものだ。これを実行すると、カレントディレクトリに「foobar」というディレクトリが作成され、「foobar/」以下に取得したファイルが保存される。

$ svn co svn+ssh://john@svn.sourceforge.jp/svnroot/testproj/foobar
Enter passphrase for key '/home/john/.ssh/id_rsa': ←sshのパスフレーズを入力
A    foobar/hello.c
A    foobar/docu1.txt
Checked out revision 2.
SourceForge.JPにおけるSubversionの利用

 SourceForge.JPでは、各プロジェクトごとに自由に利用できるSubversionリポジトリが用意されている。SourceForge.JPのSubversionサーバーは「svn.souceforge.jp」で、「/svnroot/<プロジェクトのUNIX名>」以下が各プロジェクトごとのリポジトリとなる。

 たとえば、「sampleproj」というプロジェクトのリポジトリから、「john」というユーザーでファイルを取得するには次のように実行すればよい。

$ svn co svn+ssh://john@svn.sourceforge.jp/svnroot/sampleproj

 また、SourceForge.JPではhttpによる匿名(認証なし)でのアクセスも許可されている。たとえば、「sampleproj」というプロジェクトのリポジトリから匿名でファイルを取得するには、下記のようにする。

svn co http://<サーバー名>/<サーバー名>/<取得したいディレクトリのパス>

 ただし、SourceForge.JPのSubversionリポジトリでは匿名でのコミットには対応していないため、このようにしてチェックアウトしたファイルは、そのままではコミットできない。もしコミットを行うことを考えているなら、svn+shhプロトコルを利用して、認証付きでチェックアウトを行ってほしい。

加えた変更をリポジトリに保存する

 チェックアウトしたファイルに変更を加えた後、その変更をリポジトリに保存する(コミットする)には、「svn ci」コマンドを使用する。「ci」は「commit」の略で、「svn commit」と入力しても同じ結果が得られる。

svn ci <コミットするディレクトリ>

 このコマンドを実行すると、変更点の概要といったコメントを入力するためのエディタが起動する。なお、ここで起動されるエディタは環境変数SVN_EDITORもしくはVISUAL、EDITORのどれかであらかじめ指定しておく必要がある。コメントの入力後、必要に応じて認証が行われ、認証に成功すればファイルがリポジトリに登録される。なお、コミットするディレクトリは省略が可能で、その場合カレントディレクトリを指定したことになる。

 なお、多くのリポジトリでは、書き込みに認証が必要なことが多い。認証が必要なリポジトリに対して認証なしでコミットを行うと、下記のようなエラーメッセージが表示される。

$ svn ci
svn: Commit failed (details follow):
svn: Can't create directory '/svnroot/testproj/db/transactions/2-1.txn': Permission denied
svn: Your commit message was left in a temporary file:
svn:    '/Users/john/temp/foobar/svn-commit.tmp'

他者がリポジトリに加えた修正をローカルのファイルに反映させる

 他者がリポジトリに加えた修正をローカルのファイルに反映させるには、「svn update」コマンドを使用する。

svn update <コミットするディレクトリ>

 ciコマンドと同様、コミットするディレクトリは省略可能だ。svn updateコマンドを実行すると、ローカルにあるファイルとリポジトリとの差分が確認され、ローカルのファイルが最新のものにアップデートされる。

競合の解消

 もし自分がコミットしたファイルがすでに他人によって変更され、かつ先にコミットされていた場合、コミット時にメッセージが表示され、コミットに失敗する。この場合、まずアップデートを実行して他人による変更点を取り込み、競合を解消させた上で再度コミットを行う必要がある。

 下記は、コミット時に競合が明らかになり、コミットに失敗した例だ。この例では、「echo.pl」というファイルに競合が発生し、そのためにコミットに失敗した、というメッセージが表示されている。この場合、コミット時に入力したメッセージは「svn-commit.tmp」という一時ファイルに保存される。

$ svn ci
Sending        echo.pl
Transmitting file data .svn: Commit failed (details follow):
svn: Out of date: '/echoes/echo.pl' in transaction '2-1'
svn: Your commit message was left in a temporary file:
svn:    '/home/john/Dev/echoes/svn-commit.tmp'

 このように競合が発生したら、まずは「svn update」コマンドを実行し、アップデートを行う。すると、自動的に変更点がチェックされ、可能な限り自動でその変更点がマージされる。ただし、同一の個所に異なる変更が加えられた場合など、自動でのマージに失敗する場合も多数ある。その場合、競合を解決する方法を選択し、場合によっては手動で競合している個所を修正する必要がある。

 たとえば、下記の例は「echo.pl」というファイルに競合が発生している場合の例だ。

$ svn update
Conflict discovered in 'echo.pl'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options:

 ここでキーボードから「df」と入力してEnterキーを押すと変更点の差分が表示される。また、「p」と入力してEnterキーを押すと、競合したファイルがまずリポジトリ内の最新バージョンに更新され、さらに自分が変更を加えた個所をマークアップしてくれる。たとえば、ファイル内で下記のようなマークアップが行われていた場合、「<<<<<<< .mine」から「=======」までの行が自分が変更を加えた内容、「=======」から「>>>>>>> .r<リビジョン番号>」までが最新リビジョンの内容だ。この情報を元にファイルを編集して競合を解消した上で、再度コミットを行えばよい。

my $form_data = {
  'next_url' => '/home.pl',
<<<<<<< .mine
  'email' => '',
  'password' => '',
=======
  'email' => 'hogehoge@foobar.com',
  'password' => 'qwerty',
>>>>>>> .r2
  'stickey' => '1',
};

 なお、update時に競合が発生した場合、競合が発生したファイルごとに「<ファイル名>.mine」および「<ファイル名>.r<変更前リビジョン>」、「<ファイル名>.r<変更後リビジョン>」という3つのファイルが作成される。これらは、それぞれ下記のファイルを内容を含んでいる。

ファイル名 説明
<ファイル名>.mine 自分が変更を加えてコミットを行おうとしたファイル
<ファイル名>.r<変更前リビジョン> 変更を加える前のファイル
<ファイル名>.r<変更後リビジョン> リポジトリ内にある最新リビジョンのファイル
表2 競合発生時に作成されるファイル

 もし自分が加えた変更をすべて反映させたい場合は、「<ファイル名>.mine」を「<ファイル名>」にリネームすればよい。また、リポジトリにある最新版のファイルを優先し、競合する変更をすべて無かったことにするには「<ファイル名>.r<変更後リビジョン>」を「<ファイル名>」にリネームすればよい。

 競合を解消したら、「svn resolved <ファイル名>」コマンドを実行する。これにより、「<ファイル名>.mine」および「<ファイル名>.r<変更前リビジョン>」、「<ファイル名>.r<変更後リビジョン>」が削除され、コミットが実行できる状態になる。

リポジトリにファイルやフォルダを追加する

 リポジトリ内のファイルやフォルダを削除するには、「svn add」コマンドを使用する。

svn add <追加するファイル>

 また、ファイルだけでなくフォルダを追加することも可能だ。フォルダを指定すると、自動的にそのフォルダ内のファイルもリポジトリに追加される。なお、svn addコマンドを実行した時点では、ファイルはリポジトリにコピーされない。svn addの実行後、svn ciを実行してコミットを行った時点でファイルはリポジトリにコピーされる。

リポジトリ内のファイルやフォルダを削除する

 リポジトリ内のファイルやフォルダを削除するには、「svn del」コマンドを使用する。「svn delete」や「svn remove」、「svn rm」としても同様の結果が得られる。

svn del <削除するファイル>

 svn delコマンドはリポジトリ内のファイルを削除するように指示を発行するだけで、これだけではローカルのファイルは削除されない。また、実際にリポジトリ内のファイルが削除されるのはsvn addコマンドの場合と同様、コミットの実行時となる。

リポジトリ内のファイルやディレクトリの名前を変更/移動する

 リポジトリ内のファイルやディレクトリの名称を変更するには、「svn move」コマンドを使用する。「svn mv」や「svn rename」、「svn ren」もまったく同様に利用できる。

svn mv <変更前> <変更後>

 また、変更前と変更後を異なるディレクトリにすれば、ファイル/フォルダの移動が行える。この操作は「svn del」の場合と異なり、ローカルのファイルに対してリネームや移動といった変更が即座に行われる。リポジトリへの反映は他のコマンドと同様、コミット実行のタイミングで行われる。

ファイルやディレクトリをコピーする

 ファイルやディレクトリを丸ごと別のディレクトリにコピーするには、「svn cp」コマンドを使用する。「svn copy」でも同様だ。

svn cp <コピー元ファイル/ディレクトリ> <コピー先>

 たとえば、「foo」ディレクトリ以下にあるファイルを、「../branches/foo01」ディレクトリ以下にコピーするには次のようにする。

$ svn cp foo ../branches/foo01

タグを付ける/ブランチを作成する

 CVSなどのバージョン管理システムでは、特定のリビジョンのファイルツリーに名前(タグ)を付けて記録しておく機能があるものがある。Subversionではリポジトリに変更が加えられるごとに新たなリビジョン番号が付加され、いつでも任意のリビジョンのファイルを取り出せる。また、明示的に現在のリポジトリの状態を保存しておきたいという場合は、svn cpコマンドを使用し、前述のtags/ディレクトリ以下にリポジトリの内容をコピーしておけばよい。

 たとえば、リポジトリのtrunk/foobar/ディレクトリ以下のファイルを保存しておきたい場合、以下のように実行すればよい。

$ svn cp svn+ssh://john@svn.sourceforge.jp/svnroot/testproj/trunk/foobar svn+ssh://john@svn.sourceforge.jp/svnroot/testproj/tags/foobar-1.1

 この例では、「foobar-1.1」という名称でtags/ディレクトリ以下にコピーを行っている、名称は任意に指定可能だ。

 また、並行して編集を行うためにリポジトリ内にブランチを作成する場合は、branches/ディレクトリ以下にファイルをコピーし、こちらで作業を行えばよい。

リポジトリやファイルの情報を確認する

 svnは変更差分の表示や変更の差し戻しなど、ここで紹介した機能以外にも非常に多くの機能を備えている。svnが備えている機能を確認するには、「svn help」コマンドを使用すればよい。「svn help」コマンドを実行すると、svnのサブコマンド一覧が表示される。また、サブコマンドのヘルプを確認するには「svn help <サブコマンド>」と、ヘルプを表示したいサブコマンドを引数にしてsvn helpコマンドを実行すればよい。

 そのほか、上記で説明した以外にも、svnはたくさんのサブコマンドを備えている。そのうち、リポジトリやファイルの情報を確認するためによく利用されると思われるものを下記にまとめた。それぞれの詳細は、各自ヘルプで確認していただきたい。

svn status ワーキングコピーに加えた変更状況を確認する
svn diff 自分がワーキングコピーに加えた変更点のdiffを表示する
svn diff -r <比較元リビジョン>:<比較先リビジョン> <ファイル名> 指定したファイルの<比較元リビジョン>と<比較先リビジョン>のdiffを表示する
svn revert <ファイル名> ワーキングコピーの指定したファイルに自分が加えた変更をすべて削除する
svn log リポジトリの変更履歴を表示する
svn cat -r <リビジョン> :<ファイル名> 指定したファイルの指定したリビジョンでの内容を表示する
svn list :<リポジトリ> 指定したリポジトリに含まれるファイル一覧を表示
表3 リポジトリの状態を確認するコマンド