Subversion で svn:externals (外部参照) を設定する

Subversion は、シンボリックリンクをサポートしています。例えば、あるユーティリティモジュールがあり、それを複数のプログラムから使いたい場合、シンボリックリンクを使うと便利です。

シンボリックリンクの作成

試してみましょう。テスト用のリポジトリを作成して Subversion サーバーを起動します。

$ svnadmin create svn-repos
$ vi svn-repos/conf/svnserve.conf 
[general]
anon-access = write  # 匿名書き込みアクセスを許可する
$ svnserve --daemon --foreground --root svn-repos/

サーバーが起動しました。クライアントからチェックアウトします。

$ svn co svn://localhost/ ./myrepo
リビジョン 0 をチェックアウトしました。

$ # 初期ディレクトリとシンボリックリンクを作成
$ cd myrepo
$ mkdir utils tool1 tool2
$ vi utils/myfunc.py
print "do something"
$ (cd tool1; ln -s ../utils/myfunc.py)
$ (cd tool2; ln -s ../utils/myfunc.py)
$ ls -l tool1 tool2
tool1:
lrwxrwxrwx 1 t2y t2y 18  98 12:00 myfunc.py -> ../utils/myfunc.py
tool2:
lrwxrwxrwx 1 t2y t2y 18  98 12:00 myfunc.py -> ../utils/myfunc.py

$ svn add utils tool1 tool2
A         utils
A         utils/myfunc.py
A         tool1
A         tool1/myfunc.py
A         tool2
A         tool2/myfunc.py

$ svn commit -m "added"
追加しています              tool1
追加しています              tool1/myfunc.py
追加しています              tool2
追加しています              tool2/myfunc.py
追加しています              utils
追加しています              utils/myfunc.py
ファイルのデータを送信しています ...
Committed revision 1.

# リポジトリの属性を調べる
$ svn proplist -R .
'tool1/myfunc.py' の属性:
  svn:special
'tool2/myfunc.py' の属性:
  svn:special

シンボリックリンクを作成すると、svn:special という属性が設定されます。

別のクライアントからチェックアウトしてみましょう。

$ svn co svn://172.16.55.131/ ./myrepo2
A    myrepo2/tool2
A    myrepo2/tool2/myfunc.py
A    myrepo2/utils
A    myrepo2/utils/myfunc.py
A    myrepo2/tool1
A    myrepo2/tool1/myfunc.py
リビジョン 1 をチェックアウトしました。

$ cd myrepo2/
$ ls -l tool1 tool2
tool1:
lrwxrwxrwx 1 t2y t2y 18  98 12:09 myfunc.py -> ../utils/myfunc.py
tool2:
lrwxrwxrwx 1 t2y t2y 18  98 12:09 myfunc.py -> ../utils/myfunc.py
$ cat tool1/myfunc.py 
print "do something"

ちゃんとシンボリックリンクが有効になっていますね。

svn:externals (外部参照) 属性の設定

Unix 系 OS であれば、これで大丈夫ですが、Windows ユーザーも使いたいときに課題があります。

But that doesn't in any way limit the usability of working copies on systems such as Windows that do not support symlinks. On such systems, Subversion simply creates a regular text file whose contents are the path to which the original symlink pointed. While that file can't be used as a symlink on a Windows system, it also won't prevent Windows users from performing their other Subversion-related activities.

シンボリックリンクのバージョン管理

Windowsシンボリックリンクをサポートしていないため、リンク先を指すテキストファイルを作成して、ユーザーの Subversion 操作においては邪魔しないとあります。

実際に Windows 環境でチェックアウトしてみましょう。

チェックアウト結果をみると、ファイルをコピーして作成してくれているように見えますが、ファイルの中身は "link ../utils/myfunc.py" です。ドキュメント等であれば良いですが、ユーティリティモジュールなどでは使えないですね。

そんなとき、Windows 環境でもシンボリックリンクのように扱える仕組みが svn:externals 属性です。

Fortunately, Subversion provides support for externals definitions. An externals definition is a mapping of a local directory to the URL―and ideally a particular revision―of a versioned directory. In Subversion, you declare externals definitions in groups using the svn:externals property. You can create or modify this property using svn propset or svn propedit (see the section called “Manipulating Properties”). It can be set on any versioned directory, and its value describes both the external repository location and the client-side directory to which that location should be checked out.

svn:externals 属性のドキュメント

svn:externals のドキュメントによると、リポジトリ内に外部リポジトリの URL を指定したり、そのリポジトリ特定バージョンを取得するように設定できるとあります。もともとは外部のリポジトリを取り組むための機能のようですが、同じリポジトリ内においても利用できます。そして、svn propsetsvn propedit で設定できると記述されていますが、残念ながら実際の設定コマンドのサンプルがここでは記述されていません。そのため、どうコマンドを実行して良いか全く分かりません (> <)

svn:externals の設定は直感的に分かりにくいので備忘録としてまとめておきます。また、Suversion 1.4 でディレクトリ単位、1.6 からファイル単位の外部参照ができるようになったので svn クライアントのバージョンにも注意してください。

$ mkdir tool3
$ svn add tool3
A         tool3

$ cd tool3
$ svn propset svn:externals "../utils/myfunc.py linked_myfunc.py" .
属性 'svn:externals''.' に設定しました

$ svn update
Updating '.':
Fetching external item into 'linked_myfunc.py':
A    linked_myfunc.py
外部項目をリビジョン 5 に更新しました。
リビジョン 5 です。

$ cat linked_myfunc.py 
print "do something"

$ svn commit -m "set svn:externals"
送信しています              .
Committed revision 6.

svn propset svn:externals の引数が覚えられないです (> <)
1つのやり方としては、以下のように相対パスで指定します。

$ svn propset svn:externals "カレントディレクトリからの相対パス  取得後のファイル名" 属性設定したいディレクトリ

svn:externals 属性は、ディレクトリに対して設定されます。

$ svn proplist tool3/linked_myfunc.py 
$ svn proplist tool3
'tool3' の属性:
  svn:externals

別のやり方として、リポジトリのルートパスからの相対パスを指定する方法もあります。今度は同じファイル名 myfunc.py で取得しています。

$ svn propset svn:externals "^/utils/myfunc.py myfunc.py" tool4
属性 'svn:externals''tool4' に設定しました
$ svn update tool4/
Updating 'tool4':
Fetching external item into 'tool4/myfunc.py':
リビジョン 7 の外部項目です。

リビジョン 7 です。
$ svn commit -m "set svn:externals"
送信しています              tool4
Committed revision 8.

Windows 環境でもチェックアウトしてみます。

Unix 系 OS 環境と同様にファイル本体がチェックアウトできました!

外部リポジトリ特定ディレクトリを取得したいときは URL を指定します。こういうときは取得した後のディレクトリ名を変更できると便利です。

$ mkdir svn
$ svn add svn/
A         svn
$ svn propset svn:externals "http://svn.apache.org/repos/asf/subversion/trunk/tools/examples/ tool-example" svn/
属性 'svn:externals''svn' に設定しました

$ svn update
Updating '.':

Fetching external item into 'tool3/linked_myfunc.py':
リビジョン 10 の外部項目です。

Fetching external item into 'tool4/myfunc.py':
A    tool4/myfunc.py
外部項目をリビジョン 10 に更新しました。

Fetching external item into 'svn/tool-example':
A    svn/tool-example/svnserve-sgid.c
A    svn/tool-example/svnput.c
A    svn/tool-example/dumpprops.py
A    svn/tool-example/svnlog2html.rb
A    svn/tool-example/check-modified.py
A    svn/tool-example/svnlook.py
A    svn/tool-example/svnshell.py
A    svn/tool-example/geturl.py
A    svn/tool-example/getlocks_test.c
A    svn/tool-example/info.rb
A    svn/tool-example/svnlook.rb
A    svn/tool-example/get-location-segments.py
A    svn/tool-example/headrev.c
A    svn/tool-example/putfile.py
A    svn/tool-example/revplist.py
A    svn/tool-example/svnshell.rb
A    svn/tool-example/minimal_client.c
A    svn/tool-example/getfile.py
A    svn/tool-example/blame.py
A    svn/tool-example/SvnCLBrowse
A    svn/tool-example/testwrite.c
外部項目をリビジョン 1382235 に更新しました。

リビジョン 10 です。

svn update を実行する度に fetch 処理が実行されます。仕方ないですが、外部リポジトリを参照すると実行に時間がかかるので、更新をチェックしたくないときは "--ignore-externals" オプションを指定します。

$ svn update
Updating '.':
Fetching external item into 'tool3/linked_myfunc.py':
リビジョン 11 の外部項目です。
Fetching external item into 'tool4/myfunc.py':
リビジョン 11 の外部項目です。
Fetching external item into 'svn/tool-example':
リビジョン 1382235 の外部項目です。

リビジョン 11 です。

$ svn update --ignore-externals
Updating '.':
リビジョン 11 です。

あまり頻繁に使う機能ではありませんが、使いたいときに実際の設定コマンドのサンプルを見つけられなくて苦労しました。

ちなみに Subversion もいまや Apache プロジェクトのトップレベルプロジェクトなんですね。ホームページも刷新されていて驚きました。

2012/9/10 追記

同じディレクトリに複数ファイルの svn:externals 属性を設定するには、svn propedit を使わないと設定できないようです。

$ svn propedit svn:externals tool5 --editor-cmd=vim  # vim エディタで複数ファイルを設定
^/utils/myfunc.py myfunc.py
^/utils/myfunc2.py myfunc2.py

属性 'svn:externals' の新しい値を 'tool5' に設定しました