マージの技法

マージに関係したコマンドは大きく三つの種類にわけて考えること ができる。一つは実際のマージについての各種の情報を確認するコマンド、 もう一つは実際にマージを実行するためのコマンド、最後にマージに関す る記録を保持するパッチログと呼ばれるデータを管理するためのコマンド、 この三つだ。普通は第1と第3グループのコマンドの一部を実行して、もし マージしたとしたらどのような結果になるかをチェックし、その後実際の マージコマンドを発行する形になる。チェックコマンドはプロジェクトツ リーに対して書き込み処理はしないので気軽に実行できる。一方マージコ マンドもプロジェクトツリーには書き込みが発生するが、アーカイブには 手を触れないので、マージ後にプロジェクトツリーがどのように変形した かを確認することができる。確認の結果プロジェクトツリーが意図した形 に変形していれば、コミットすることになる。これで始めてリビジョンが 追加され、修正点がアーカイブに永続的に保存される。

プロジェクトツリーに実際に書き込むコマンドも二種類に分類でき る。ひとつは過去にマージされたチェンジセットがどれであるかを記録し たパッチログという情報を書き換えるもの、もう一つは実際のソースファ イルを変形させるものである。パッチログは重複したマージを回避するた めに GNU arch が利用する重要な情報だ。パッチログの情報を調整すると、 すでに一度適用したチェンジセットを適用しなかったことにしたり、逆に 適用したことのないチェンジセットを適用したとみなしたりすることがで きる。通常はこのような操作は不要だが、特殊な場面では役にたつ。XXX のコマンドリファレンスにはすべての GNU arch のコマンドに対してデー タの書き込みが発生するかどうか、発生するとすればどの領域に対してか を示した。これを見てこの章のコマンドをいろいろと試してみてほしい。

マージコマンドは用途や場面に応じてみっつある。

tla replay

このコマンドは三つのマージコマンドのうちでもっとも単純なも のだ。replay に対する引数の与え方には三つの方法がある。まず、リ ビジョン名を指定すると、そのリビジョン名に対応するチェンジセット をプロジェクトツリーに対して適用する。これは成功するかも知れない し、衝突を起こして失敗するかも知れないが、いずれにせよ適用する。 リビジョン名に対応するチェンジセットが存在しない場合にはエラーを 返す。これは例えば継続によって作られたのではないベースリビジョン、 base-0 のようなものがそうだ。このコマンドは実際の適用前にパッチ ログを調べてすでにこのツリーに今回のチェンジセットが適用されてい ないかどうかを調べ、重複した適用を避けようとする。

引数にバージョン名を与えた場合は、そのバージョンに含まれる すべてのリビジョン名に対応するチェンジセットを適用する。適用はリ ビジョン番号が小さいものから順におこなわれ、やはりパッチログを チェックしてすでに適用されているものはスキップする。適用に衝突が 起こると、処理はそこでストップする。すでに適用してしまった部分は そのまま生かす。(XXX: ほんと?)

引数は複数指定することができ、この場合引数の順序で上記を実 行する。引数を指定しなかった場合には、デフォルトバージョンが指定 されたのと同じ動作をする。

適用元のチェンジセットが存在するバージョンは、プロジェクト ツリーとの間で共通の祖先を持つ必要はない。これは update と大きく 違うところである。また適用前にプロジェクトツリーにプリスティン以 降に加えられた修正分を退避しないのも update と異る。

他人のバージョンの一つまたは少数のチェンジセットを replay コマンドを使って選択的に適用することを、「チェリーピックする」と 言う。おそらく、イチゴ狩りならぬ、さくらんぼ狩り、みたいなイメー ジの言葉だと思うのだが、実際に聞いたことはない。誰かのシャレた言 葉の説明を本人に求めるほどヤボなことはない。

tla update

これは replay よりも概念的には上位にあるコマンドだ。引数の 与え方は、完全に省略する場合、バージョンを指定する場合、リビジョ ンを指定する場合の三通りがあるのは replay と一緒だが、その意味合 いは大きく異る。

まず、いずれの場合でも、update は実行に当たって、プロジェ クトツリーに対して tla undo を内部的に実行する。つまり直近のリビ ジョンにプリスティンの内容を使って戻る。それから引数の内容に応じ たチェンジセットの適用を実行し、最後に退避しておいたチェンジセッ トを tla redo でもう一度適用するのだ。

引数をなにも与えないと、デフォルトバージョンが指定されたも のと仮定する。バージョンを与えた時の動作は、まずプロジェクトツリー のデフォルトバージョンと、指定したバージョンとの共通の祖先をチェッ クする。共通の祖先とは、この二つのバージョンが分岐した最後のリビ ジョンを言う。そのようなリビジョンが存在しなければ、つまりふたつ のバージョンの起源が異っているなら、update はエラー終了する。こ のとき、共通の祖先より後に引数で指定したバージョンに追加されたチェ ンジセットを古いものから順にプロジェクトツリーに追加していく。引 数に指定したバージョンやプロジェクトツリーのデフォルトバージョン が継続によって作成されたものである場合には、その祖先のバージョン までさかのぼって共通の祖先を調べることに注意すること。どこかで衝 突が起こった場合にはそれまでのすべての適用を含めて処理を完全に取 り消す。プロジェクトツリー内のパッチログを確認して、重複マージを 避けるのは replay コマンドと同様である。

引数にリビジョン名を指定した場合の動作もバージョンを指定し た時と同様だが、そのリビジョンよりもリビジョン番号の大きなチェン ジセットは適用しない。

もし君が誰かの別のバージョン、例えば A/C--B--V--R からの分 岐を A1/C1--B1--V1 という形で作ったあと、このバージョンについて 行くには、自分の変更を A1/C1--B1--V1 に追加していき、ときどき tla update を A/C--B--V という引数で実行すれば、君が分岐した後彼 が加えた修正点のすべてを A1/C1--B1--V1 に取り込むことができる。

tla star-merge

このコマンドはマージ系コマンドの最上に位置するもので、大抵 のマージはこのコマンドを使えばうまくいく。初心者はマージにはいつ もこのコマンドを使うと良い。正確なアルゴリズムは以下のように非常 に複雑だが、利用は簡単だ。原始的なコマンドを組み合わせることで高 度な機能を実現するという GNU arch のコンセプトから行くと、tla を ラップする形で外部コマンドとして実装しても良いほどマクロなコマン ドだ。

このコマンドは、引数として指定した FROM リビジョンとバージョ ンREFERENCE との間の「一番近い共通祖先」にあたるリビジョンを計算 し、それと FROM との差分をプロジェクトツリーに適用する。 REFERENCE を指定しなければプロジェクトツリーのデフォルトバージョ ンを指定したものとみなすが、通常はこれで十分だ[14]。 これだけの話だが、問題は何をもって「一番近い共通祖先」=M.R.C.A とみなすかである。これがすこし複雑になる。

まずはこの計算に必要な登場人物を整理する。

表 5.1. 登場人物

アイテム要素種別意味
FROMリビジョン マージしたいリビジョン。バージョンが指定 された場合は、その中にある最新のリビジョンが指定されたと みなす。
FROM-Vバージョ ンリビジョンFROM が属するバージョンのこ と。
TREEプロジェク トツリー適用先のプロジェクトツリー。
REFERENCEバージョ ンマージの観点を示す。指定しなければ TREE のデフォルトバージョンが指定されたとみなす。
MAYBE_ANCESTOR_1 リビジョンM.R.C.A.の第一候補。 これはもし存在すれば FROM-Vに属する。
MAYBE_ANCESTOR_2 リビジョンM.R.C.A.の第二候補。 これはもし存在すれば REFERENCE に属する。
LAST_MERGE_INTO_FROM リビジョン計算途中で必要になる FROM-Vに 属するリビジョン。

MAYBE_ANCESTOR_1 は FROM リビジョン中にある、FROM-Vに属す るパッチログのうち、TREE に取り込まれた一番最後のものとして定義 される。たとえば、FROM が A/C--B--V--patch-10 だとすれば、このリ ビジョンは A/C--B--V--base-0 ... A/C--B--V--patch-10 のすべての パッチログだけは最低限必ず含むが[15]、このうち TREE に存在するものが A/C--B--V--patch-3 と A/C--B--V--patch-7 であれば、この後者が MAYBE_ANCESTOR_1 になる。どれが選択されるにせよ、FROM-Vに属する リビジョンで FROM リビジョンそのものか、それ以前のリビジョンにな ることに注意。条件にあてはまるパッチログが一つも存在しなければ、 MAYBE_ANCESTOR_1は存在しない。

MAYBE_ANCESTOR_2 は REFERENCE 中のリビジョンのうち、FROM リビジョンに取り込まれたもので一番後のものとして定義される。条件 にあてはまるパッチログがリビジョン FROM に一つも存在しなければ、 MAYBE_ANCESTOR_2 は存在しない。ところでリビジョン FROM は FROM-V に属するパッチログの他にもチェリーピックなどによって他のバージョ ンのパッチログも含むのが普通であることに注意してほしい。この事実 により REFERENCE と FROM が属するバージョンが異っている場合でも FROM リビジョンが FEFERENCE バージョンに属するリビジョンのパッチ ログを含むことはあるのだ。

LAST_MERGE_INTO_FROM は MAYBE_ANCESTOR_2 が存在する場合に 限って定義される。これは MAYBE_ANCESTOR_2 を最後にマージした FROM-Vのあるリビジョンを示す。FROM リビジョンが MAYBE_ANCESTOR_2 をパッチログとして持つので、LAST_MERGE_INTO_FROM は FROM そのも のか FROM以前の FROM-Vに属するリビジョンであることに注意すること。

以上を前提として、M.R.C.A. は以下のようにして求める。まず MAYBE_ANCESTOR_1 と MAYBE_ANCESTOR_2 が共に非存在の場合には M.R.C.A.は定義されず、star-merge は何もせず終了する。どちらか一 方が存在する場合には存在するほうのリビジョンとして定義される。両 方存在する場合には、まずMAYBE_ANCESTOR_1 とLAST_MERGE_INTO_FROM の順序関係が比較される。両方共 FROM-V に属するリビジョンなのでこ の比較は常に意味を持つ。MAYBE_ANCESTOR_1 の方が大きければ、 M.R.C.A. は MAYBE_ANCESTOR_1 と定義される。そうでなければ M.R.C.A. は MAYBE_ANCESTOR_2 とされる。あとは冒頭の差分を求め、 結果を TREE に適用する。これが star-merge の動作のすべてである。



[14] プロジェクトツリーはどれか一つのバージョンに結び付いているわけで はないことに注意しよう。詳しくは XXX 参照。

[15] なぜこう言えるの だろう? プロジェクトツリー中でコミットが成功する条件としてGNU arch が課している制約を思い出してくれればわかると思う。考えてみ てほしい。