ベンダーブランチ

ソフトウェアを開発する場合が典型的な例ですが、バージョン管理で保守しているデータが しばしば誰かのほかのデータに密接に関係しているか、あるいは依存している ことがあります。一般的にプロジェクトで要求されることは、プロジェクトの 安定性を損なうことなく、外部の資源によって提供されるデータをできる限り 最新に保つことです。この考え方は、あるグループによって作られた情報が もう1つのグループによって作られるものに影響を与える場合、常に成り立ちます。

たとえば、ソフトウェア開発者がサードパーティー製のライブラリを利用する アプリケーションの開発に取り組んでいるとします。SubversionはApache ポータブル 実行時ライブラリと、ちょうどそのような関係を持っています。 (Apache Portable Runtime ライブラリ項参照)。Subversionのソースコードは すべての可搬性の要求を満たすために、APRライブラリに依存しています。 Subversionの開発の初期の段階では、プロジェクトはAPR のAPIの変更を非常に 正確に追いかけていました。常に、ライブラリコードの荒波の、「 最先端」についていきました。いまではAPRもSubversionも開発が落ち着い てきたので、Subversionはよくテストされ、安定したリリース状態にある バージョンのAPRライブラリAPIとのみ同期をとっています。

もしプロジェクトが他の人の情報に依存しているなら、その情報と自分の ものとを同期させるためのいくつかの方法があります。 一番大変な方法ですが、自分のプロジェクトのすべての貢献者に対して 口頭または文書で手続きを伝えることができます。プロジェクトに必要な サードパーティーの情報の特定のバージョンを確実に手に入れることを 伝えます。もしサードパーティーの情報がSubversionリポジトリで管理 されているなら、Subversionの外部定義を使ってその情報の特定のバージョンを 作業コピーディレクトリ中のある場所へ効果的に「結びつける」こと ができるでしょう (外部定義項参照)。

しかし、ときどき自分のバージョン管理システムでサードパーティーのデータ に加えた独自の変更を管理したいこともあります。ソフトウェア開発の 例に戻って説明すると、プログラマは自分自身の目的のために、サードパーティー のライブラリに変更を加える必要があるかも知れません。このような修正は 新しい機能の追加であったりバグフィックスであったりするかも知れませんが、 それはサードパーティーのライブラリの公式なリリースの一部になるまでに 限り管理すべきものです。あるいは、変更は決してライブラリ保守担当には 伝えられず、ソフトウェア開発者の特殊な要求に合うようなライブラリを 作り上げるための独自の修正点としてずっと残り続けるかも知れません。

ここで、面白い状況に直面します。あなたのプロジェクトは、パッチファイルを 適用したりファイルやディレクトリーを完全に別のものに置き換えるような、若干 バラバラな方法でサードパーティーのデータへ独自の修正を加えることができました。 しかし、このようなやり方ではすぐに保守する上で頭痛の種になるので、 あなたの独自の変更をサードパーティーのデータに適用する仕組みが必要となります。 さらにあなたが追跡するサードパーティーのデータのそれぞれの連続したバージョンに それらの変更を再生する仕組みも必要となります。

この問題に対する解決は、ベンダーブランチを 使うことです。ベンダーブランチはサードパーティーあるいはベンダー よって提供された情報を含んでいる、こちらのバージョン管理 システム中のディレクトリツリーです。それぞれの バージョンのベンダーのデータで、自分のプロジェクトに取り込もうと 考えているもののことを、ベンダードロップと いいます。

ベンダーブランチは二つの鍵となる利点があります。まず、 自分のバージョン管理システムに、現時点でサポートされている ベンダードロップを格納することによって、プロジェクトのメンバーは 正しいバージョンのベンダーデータを使っているかどうかの心配をする 必要がなくなります。彼らはいつもの作業コピーの更新の一環として、 簡単に正しいバージョンを受け取ります。 次に、データは自分たちのSubversionリポジトリに あるので、それに対する独自の修正を決まった場所に格納することができます。— 自分たちの独自の修正で置き換えるような自動化された(あるいは最悪の 場合、手でやる)方法を用意する必要がなくなります。

一般的な、ベンダーブランチを管理する方法

ベンダーブランチの管理は一般的にはこんな感じでやります。 最上位ディレクトリを作り(/vendorのようなもの) そこにベンダーのブランチを置きます。それから最上位ディレクトリの サブディレトクリにサードパーティーのコードをインポートします。 それからそのサブディレトクリを、適当な場所にある、自分の主系開発の ブランチにコピーします(たとえば/trunkなど)。 ローカルな変更は常に主系開発ブランチに対して行います。 追いかけているコードの新しいリリースのたびに、それをベンダーブランチに 持っていき、変更点を/trunkにマージします。そして、 ローカルの変更と、ベンダーの変更の間の衝突を解消します。

多分、例をあげると、このステップをはっきりさせることができるかも 知れません。あなたの開発チームがサードパーティーの複素数値計算 ライブラリ libcomplex を使った計算プログラムを作っているとします。 まず、ベンダーブランチの初期生成をし、それから最初のベンダードロップ をインポートします。ここではベンダーブランチのディレクトリを libcomplexと呼び、私たちのコードドロップは currentと呼ばれる私たちのベンダーブランチの サブディレクトリの中に置かれます。svn importは 必要なすべての中間的な親ディレクトリを作るので、このような複数の ステップを実際には一つのコマンドで実行することができます。

$ svn import /path/to/libcomplex-1.0 ¥
             http://svn.example.com/repos/vendor/libcomplex/current ¥
             -m 'importing initial 1.0 vendor drop'
…

これで、libcomplexのソースコードを/vendor/libcomplex/current に持ってくることができました。このバージョンにタグ付けし、 (タグ項参照)、主系開発ブランチにコピーしま す。私たちのコピーは既存のcalcプロジェクトディレクトリ 中のlibcomplexという新しいディレクトリを作ります。 これが新たに独自の修正を加えるためのベンダーデータのコピーになります。

$ svn copy http://svn.example.com/repos/vendor/libcomplex/current  ¥
           http://svn.example.com/repos/vendor/libcomplex/1.0      ¥
           -m 'tagging libcomplex-1.0'
…
$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0  ¥
           http://svn.example.com/repos/calc/libcomplex        ¥
           -m 'bringing libcomplex-1.0 into the main branch'
…

プロジェクトの主系のブランチをチェックアウトします。これは最初の ベンダードロップのコピーを含んでいます—そして、libcomplex コードの修正に入ります。もう知っているように、これで修正されたlibcomplex は、私たちの計算プログラムに完全に統合されています。 [38]

何週間かたって、libcomplexの開発者はライブラリの新しいバージョンを リリースしました—バージョン1.1としましょう—それは われわれがほしかったいくつかの機能と関数を含んでいます。この新しい バージョンにアップグレードしたいものですが、既に手元にあるバージョンに 対する修正を失いたくはありません。既に示唆したように、 本質的にわれわれがやらなくてはならないのは、libcomplex1.1 のコピー でlibcomplex1.0 を置き換え、前にやった独自の修正を、新しいライブラリ のバージョンにも再び適用することです。しかし実際には私たちはこの問題に 対して別の方法で対処したいのですが、それはバージョン1.0 と 1.1 の間 にlibcomplexにおきた変更点を私たちの修正されたコピー上に適用するという ものです。

このアップグレードをやるのに、わたしたちはベンダーブランチのコピー をチェックアウトし、currentディレクトリにある ソースコードを、新しい libcomplex 1.1 のソースコードで置き換えます。 文字通り完全に新しいファイルで既存のファイルを上書きしますが、多分 それは既存のファイルやディレクトリの上に libcomplex 1.1 のリリース用 tarball を展開することになるでしょう。ここでの目的は私たちの currentディレクトリ中に libcomplex 1.1 のコードの みを含むようにすることであり、同時にすべてのコードをバージョン管理下に あることを保証するということです。あ、もちろんバージョン管理の履歴に 対しての混乱を最小にとどめるような方法でそうしたいのです。

1.0 のコードを 1.1 のコードに置き換えた後では、 svn status はローカルな修正を加えたファイルの一覧と バージョン化されていないファイル、あるいは失われたファイルも表示 するでしょう。いままで述べたような手順を実行していたのなら、バージョン 化されていないファイルは libcomplex の 1.1 のリリースで新しく導入され たようなものだけのはずです—そのようなファイルをバージョン管理下 に置くのにsvn addを実行します。失われたファイルは 1.0 では存在していたが、1.1 では存在しないようなものに対応しているので svn deleteを実行します。最後に、私たちの current作業コピーが libcomplex 1.1 のコードのみ を含むようになれば、いまの変更をコミットして、つじつまを合わせます。

私たちのcurrentブランチはこれで新しい ベンダードロップを含むようになります。新しいバージョンに(バージョン 1.0のベンダードロップに対して前にしたのと同じ方法で)タグづけをして、 それから前のバージョンのタグと新しい現在のバージョンとの間の差分を 私たちの開発ブランチにマージします。

$ cd working-copies/calc
$ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0      ¥
            http://svn.example.com/repos/vendor/libcomplex/current  ¥
            libcomplex
… # resolve all the conflicts between their changes and our changes
$ svn commit -m 'merging libcomplex-1.1 into the main branch'
…

簡単な場合だと、この新しいバージョンのサードパーティーツールは、 ファイルとディレクトリの観点から見ると、前のバージョンと同じように 見えます。libcomplex のどのソースファイルも削除されたり名称変更された り別の場所に移動されたりはしません—新しいバージョンは単に前の ものからテキストの内容の修正を受けただけに見えます。理想的な状況では 私たちの修正は新しいライブラリのバージョンに対してきれいに適用され、 複雑なことや、衝突は一切起きません。

しかし、ものごとというものは常に単純であるとは限りません。 実際、ソースファイルはソフトウェアのリリース間であちこち 動くのが普通です。これはわたしたちの修正が新しいバージョンのコード でも正しいということを確認する作業を複雑にしますし、新しいバージョン での修正を手でもう一度やる必要がある状況に、簡単に落ち込んでしまう ことがあります。Subversionが、与えられたソースファイルの(以前の位置を含めての) 履歴について知っていればライブラリの新しいバージョンのマージのステップは とても単純になります。しかし、わたしたちは、Subversionに ソースファイルのレイアウトがベンダードロップ間でどんな風に 変わったかを教えてやる責任があります。

svn_load_dirs.pl

いくつかのファイルの削除、追加、移動があったベンダードロップは サードパーティーデータのアップグレードの手順を複雑にします。 それでSubversionはこの手続きを支援するために svn_load_dirs.plスクリプトを用意しています。 このスクリプトは一般的なベンダーブランチの管理手続きで言ったような インポートのステップを自動化し、間違いを最小にすることができます。 サードパーティーデータの新しいバージョンを主系開発ブランチにマージする ためのマージコマンドを使う責任はまだ残っているものの、 svn_load_dirs.plはより早く簡単にこの処理まで 到達する助けになります。

簡単に言って、svn_load_dirs.plsvn import の拡張で、いくつかの重要な 特徴を持っています:

  • いつでも、このプログラムを実行して、 リポジトリにあるディレクトリを、完全にそれに一致した外部 ディレクトリに持って行き、必要なすべての追加、削除を実行し、 さらにオプションで移動処理も行います。

  • このプログラムは、Subversionが必要とする中間的なコミット間で 必要な複雑な一連の処理を注意深く実行します—たとえば ファイルやディレクトリの名称変更を二回やる前など。

  • それは、オプションで新しいインポートされたディレクトリを タグ付けします。

  • それはオプションで、正規表現にマッチするファイルとディレクトリ に任意の属性を追加します。

svn_load_dirs.pl は三つの必須パラメータを とります。最初の引数は作業対象となるベースになるSubversionディレクトリのURL です。この引数のあとにはURLが続きます—最初の引数に相対的な 形で—ベンダードロップはそこにインポートされます。最後に 三番目の引数はインポートするローカルディレクトリです。前の例を 使うと、典型的なsvn_load_dirs.plの実行は こんな感じになります:

$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex ¥
                   current                                        ¥
                   /path/to/libcomplex-1.1
…

-t オプションにタグ名称を指定して、新しいベンダードロップ をタグ付けするようにsvn_load_dirs.pl に指示することが できます。

$ svn_load_dirs.pl -t libcomplex-1.1                              ¥
                   http://svn.example.com/repos/vendor/libcomplex ¥
                   current                                        ¥
                   /path/to/libcomplex-1.1
…

svn_load_dirs.plを実行するとき、それは 既に存在している「現在の」ベンダードロップの内容を調べて それを指定された新しいベンダードロップの内容と比較します。 簡単な場合、片方のバージョンにあってもう一方にはないようなファイル はなく、スクリプトは新しいインポートを特に問題なく実行します。 しかし、もし、バージョン間でファイルレイアウトに違いがある場合、 svn_load_dirs.pl はこの違いをどうやって解決するか たずねてきます。たとえば、libcomplexのバージョン1.0でmath.c だったファイルはlibcomplex1.1ではarithmetic.c に 名称変更になったことを知っていることをスクリプトに教えてやることが できます。移動によって説明できないような相違点は、通常の 追加と削除として扱われます。

このスクリプトはまたリポジトリに追加される (正規表現にマッチするような)ファイルとディレクトリの属性を設定する ために、別の設定ファイルを受け付けることができます。 この設定ファイルはsvn_load_dirs.pl-p オプションを使って指定されます。 設定ファイルの各行は空白で区切られた二つまたは四つの値です: 追加されるパスに対してマッチさせるPerlスタイルの正規表現、 制御キーワード(break または cont)、そして、オプションで属性名と 属性値がきます。

¥.png$              break   svn:mime-type   image/png
¥.jpe?g$            break   svn:mime-type   image/jpeg
¥.m3u$              cont    svn:mime-type   audio/x-mpegurl
¥.m3u$              break   svn:eol-style   LF
.*                  break   svn:eol-style   native

追加されるパスが正規表現にマッチしたとき、その行の属性 がマッチしたパスに追加されていきます。ただし制御の指定がbreak の場合は属性の追加はその行で打ち止めになります(これはそれ以上の属性変更はこのパスに 行わないことを意味しています)。もし制御指定がcontcontinueの省略形ですが—の場合は マッチング処理は設定ファイルの次の行に続いていきます。

正規表現中のすべての空白、属性名、属性値はシングルまたはダブルクオート でくくる必要があります。空白を囲むために利用しているわけではない クオート文字はバックスラッシュ文字(¥) を前に付ける ことでエスケープできます。バックスラッシュは設定ファイルを解析するとき だけクオートするので、正規表現中で必要なもの以外のほかの文字には 使わないでください。



[38] そして、もちろんあなたのことですから、バグも完全になくなっている、と。