Documentation

ブランチ、フォーク、マージ、タグ付け

単純かつ完璧な世界においては、開発プロジェクトは図1のように線形になります。


図 1

この円はそれぞれのチェックインを示します。説明のため、それぞれのチェックイン には小さく数字を振ってあります。実際のシステムにおいては、これらのチェックイン 番号は分散システム上で衝突することのない番号として 40桁のSHA1ハッシュが使われます。 しかしながら、連続した番号の方が読みやすいため、このドキュメント中では40桁の SHA1ハッシュの代わりにこちらを使うことにします。

図1の矢印はプロジェクトの進化を示します。最初のチェックインが 1 であり、 チェックイン 2 は 1 を受け継いでいます。換言すれば、チェックイン 2 は チェックイン 1 を元に編集し、それをコミットしたものです。 このような場合、2は1の子供であり、1は2のであると言います。 チェックイン 3 は 2 を受け継いでいるので 3 は 2の子供です。 また、3 は 1 と 2 の子孫であり、1 と 2 は 3 の祖先となります。

これからチェックインのグラフをツリーと呼びます。チェックイン 1 は 祖先を持たないため ルート(根) となり、チェックイン 4 は子孫を持たないため リーフ(葉)となります。(リーフの詳しい定義は後で述べます。)

実際には、このように単純な線形の開発プロジェクトはしばしば邪魔されます。 仮に二人のプログラマが同時に独立した変更をチェックイン 2 に加え、それらを 両方ともチェックインしたとすると、図2のようなチェックイングラフを得ます:


図 2

この図2のグラフにはチェックイン 3 と 4 の二つの葉があります。 チェックイン 2には 3 と 4 の二つの子供があります。この状態を フォーク(fork)と呼ぶことにします。

Fossil はなるべくフォークが起こらないようにします。たとえばチェックイン2 を 変更した二人のプログラマをアリスとボブとし、アリスが変更を先に終わらせて コミットします。するとその結果はチェックイン 3 になります。 その後、ボブが彼の変更点をコミットしようとします。fossil はチェックイン 2 が まだ葉かどうかを確認します。Fossil はチェックイン 3 を見付け、ボブのコミットを "フォークします"といって中断します。これにより、ボブには "fossil update"を 利用してアリスの変更を取得し、それらをマージすることができます。 マージ後にボブはチェックイン 4 をチェックイン 3 の子供として行い、結果として 図1のように線形なグラフが得られます。 これは CVS がどのように働くかと同じです。これは fossil が "autosync" モードに ある場合の動作となります。

しかし、ボブがコミットしようとしている時にオフラインの場合も考えられます。 その場合、彼が既にアリスがコミットしたという事実を知る方法はありません。 もしくは、彼がSQLite の "autosync" モードをオフにしているかもしれません。 また、ボブがアリスの変更を無視して、自身の変更だけを"--force"オプションで 強制的にコミットするかもしれません。 どのような理由にせよ、チェックイン 2は二つのコミットを受け、結果として ツリーは二枚の葉を持つことになります。

さて、このプロジェクトにおいて、多機能でバグフィックス済みだと言う意味では どちらがより「後の」バージョンだと言えるでしょうか? グラフが複数枚の葉を持つ場合、これを決めることはできません。 このため、グラフはなるべく一枚だけの葉を持つようにする方が良いのです。

この状況を解決するため、アリスは fossil のmergeコマンドを使って 彼女のローカルコピーであるチェックイン3にボブの変更を取り込むことができます。 そして、それをチェックイン5としてコミットします。この結果が図3です。


図 3

チェックイン5はチェックイン3から作られているので、チェックイン3の子供です。 しかし、チェックイン5はマージによってチェックイン4の変更も継承しています。 そのため、チェックイン5はチェックイン4のマージチャイルドと呼ばれます。 また、チェックイン5はチェックイン3のダイレクトチャイルドです。 そしてグラフは(チェックイン5の)一枚だけの葉を持つ状態に戻ります。

以前に、fossil がautosyncモードであれば、ボブは最初のチェックイン4の時点で フォークに関する警告を受けると知りました。もしこの時点で、ボブがアリスの チェックイン3による変更をチェックアウトしてマージし、それからコミットすれば フォークが起きることはなくなります。そしてグラフは図1のように直線になります。 実際、図1のグラフは図3のサブセットなのです。図3のチェックイン4の丸を 手で隠してみてください。図1のように見えるでしょう。(このとき、葉の チェックイン番号が異なりますが、それは記述上の違いです。二つのチェックインは 同じ内容を含んでいます。)言い換えると、図3は図1のスーパーセットなのです。 図3のチェックイン4は、図1で省略された状態を捉えて追加したと言えます。 このチェックイン(4)はアリスの変更をマージする前のボブのチェックアウトの コピーを示しています。このボブの変更のスナップショットは、アリスの変更から 独立しており、図1には掲載されていません。 一部の人々は図3のアプローチが中間状態も保持しているため好ましいと言います。 一方、他の人々は図1のアプローチの方が開発状態をシンプルな直線で表せ、しかも マージを手動作業ではなく自動で行っているため良いと言うでしょう。 ここではこの議論には立ち入りません。 fossil ではどちらの方法を取ることもできます。

フォークとブランチ

チェックインツリーに2枚以上の葉があることは通常好ましくないと考えられます。 そのため、フォークは図1のように完全に回避されるか、もしくは図3のように 早期に解決されるのが普通です。 しかし、時には複数の葉を利用したい場合もあります。 例えば、片方を開発の最新版として使い、もう片方をテスト済みの最新版として 扱うようなプロジェクトを考えてみてください。 このように要求があって作成された複数の葉のことをフォークではなく ブランチ(branch)と呼ぶことにします。 図4は一つが開発用、もう一つがテスト用にとブランチされたプロジェクトの例です。


図 4

図4で仮定しているシナリオは次のようになります。プロジェクトが開始され、 チェックイン2の時点で最初のリリースのテストに入る準備ができました。 実際のプロジェクトでは、もちろんチェックイン2の時点に至るまでに 数百から数千のチェックインがあることでしょうが、単純化のため今回は その時点をチェックイン2とします。 ここでプロジェクトは別々のチームが使うように二つのブランチに分かれます。 テストチームは青いブランチを使い、バグを探して直します。この作業は チェックイン6または9として表されています。 一方で開発チームは色付けされていない方のブランチを使い、次のリリースに 向けて忙しく開発を進めます。もちろん、開発チームはテストチームによる バグフィックスも取り込みます。 そのため、テストブランチによる変更は定期的に開発ブランチにマージされます。 これは破線によるマージの矢印としてチェックイン6から7あるいは9から10に 表示されています。

図2と図4では、チェックイン2はそれぞれ二つの子供を持っています。 図2の方ではこれをフォークと呼びました。一方、図4ではブランチと言います。 これらの違いは一体何なのでしょう?fossilの内部構造に関して言えば、 これらに違いはありません。異なっているのは、その意図です。 図2について言うと、チェックイン2が複数の子供を持っているのは 同じ幹から並行開発した結果、偶然できたものです。 一方、図4ではチェックイン2が複数の子供を持つのは意図的にしています。 つまり、妥当な分類としては偶然できたのがフォークであり、意図的に 作成されたものがブランチであると言えます。それ以外の点については 共に変わるところはありません。

タグとプロパティ

タグとプロパティは、fossilでは意図を明確にするため、またフォークと ブランチを区別するために用います。図5は図4にタグとプロパティを加えたものです。


図 5

タグ(tag)とは、チェックインに付けられた名前のことです。 また、プロパティ(property)は同様にチェックインに付けられた キーと値のペアを示します。fossil は内部的にはタグを NULL値を値に持つ プロパティで表しています。そのため、タグとプロパティはほぼ同じものであり、 今後「タグ」という単語はタグとプロパティの両方を示すものとします。

タグは、一時タグ、伝播タグ、取り消しタグのどれかになります。 一時タグは、その名の通り設定されたチェックインのみに有効です。 伝播タグは、適用されたチェックインと、その直接の子孫に関して有効になります。 ここで「直接の子孫」とは直接の子供による子孫のことです。 タグの伝播はマージによる子孫には受け継がれません。また、同じタグによる チェックインがあった場合、以前の伝播はすみやかに中止されます。 取り消しタグは、一時タグを上書きする場合か、伝播タグの子孫への継承を 阻止する場合に利用されます。

リポジトリは二つの伝播タグを持った空のチェックインから始まります。 図5において、そのチェックインは1です。branchタグは(その値によって) それがどこのブランチに属するかを示します。 デフォルトのブランチは「幹(trunk)」と呼ばれます。 また、sym-で始まるタグはシンボリック名タグです。これがチェックインに 付加されると、そのチェックインは40文字のSHA1ハッシュ名の代わりに、 シンボリック名によっても特定することができます。 シンボリック名タグが(sym-trunkのように)伝播されると、それは その名前で参照されるブランチの最新のチェックインを示します。 このため、リポジトリ作成時に存在する二つのタグは、チェックインと その子孫において、それらが「trunk」ブランチに属し、シンボリック名 「trunk」で参照されることを示します。

チェックイン4は名前を「test」に変更したbranchタグを持ちます。 このbranchタグはチェックイン6と9へも伝播します。 しかし、タグの伝播はマージを辿らないため、branch=testタグは チェックイン7、8、10には影響しません。 また、チェックイン4における branch タグはbranch=trunkタグの伝播を 阻止するため、これはチェックイン6や9へは到達しません。 このため、チェックイン4、6、9は「test」ブランチに属し、その他のチェックインは 「trunk」ブランチに属することになります。

チェックイン4はシンボリック名「test」を表すsym-testタグも 持っており、これはチェックイン4、6、9に影響します。 チェックイン7、8、10については、マージではタグの伝播が無いためsym-test タグは継承されず、結果として「test」という名前は使えません。 チェックイン1でのsym-trunkタグの4、6、9への伝播を阻止するために チェックイン4ではsym-trunkの取り消しタグが付加されています。 これらの効果によって、結果的にテスト用のブランチには「test」という名称が 利用でき、メインのブランチは「trunk」という名前で参照できるようになっています。

チェックイン4のbgcolor=blueタグはタイムラインにおいて、 自分とその子孫の背景色を青に設定する効果があります。

図5ではまた、チェックイン9において二つの一時タグが付加されています。 (この図では一時タグと伝播タグを区別せずに表記しています。) sym-release-1.0タグはこのチェックイン9が「release-1.0」という名前で 参照されるようにします。またclosedタグはチェックイン9が「閉じた葉」 であることを示します。「閉じた葉」とは、直接の子供を持たない葉を意図します。

用語について

キーワードの定義を以下に示します。

ブランチ(Branch)

branchプロパティに同一の値を持つチェックインの集合

葉(Leaf)

同じブランチ上で、子供を持たないチェックインのこと

閉じた葉(Closed Leaf)

closedタグのついた葉のこと。これらは子孫を持たないことを 意図しており、コマンドラインあるいはWebインターフェース上での葉のリストには 表示されない。

開いた葉(Open Leaf)

閉じた葉でない葉のこと

フォーク(Fork)

同じブランチ上で(マージされていない)複数の子供を持っている部分

ブランチ点(Branch Point)

異なるブランチに属する複数の直接の子供を持つチェックインのこと。 ブランチ点はフォークと似ているが、それぞれの子供は異なるブランチに属している。

図3のチェックイン4は、同じブランチに子供(チェックイン5)を持っているため 葉ではない。図5のチェックイン9は子供(チェックイン10)を持っているが、それは 違うブランチに属しているため葉となる。また、そのclosedタグのため チェックイン9は閉じた葉である。

図3のチェックイン2は同じブランチに属する二つの子供を持つためフォークと 考えられる。一方、図5のチェックイン2も二つの子供を持つが、それぞれが別の ブランチに属しているため、こちらはブランチ点であると言える。