共用レポジトリのレイアウト

Bazaarは共用ブランチ内部のブランチのレイアウトを柔軟に決められるように設計されました。 この柔軟性によってユーザーはBazaarを自分のワークフローに合わせることができますが、”よい”レイアウトとは何かということを疑問に持つようになります。 ここでは代わりになるものをいくつか説明しそれぞれの利点を検討します。

言及すべき重要な点はよいレイアウトは”一般的な”ユーザーが理解できるように ブランチの内容を何らかの形でハイライトします。 SVNにおいて これは “trunk/” ブランチであると考えられ、 大抵のレイアウトではこの命名規約が守られています。 これを “mainline” もしくは “dev” と呼ぶ人もいれば、 CVSから来た人々はしばし “HEAD” と言及します。

“SVN形式” (trunk/, branches/)

SVNからやってきた人々は次のような”標準的な”プロジェクトのレイアウトに慣れています:

repository/       # リポジトリ全体
 +- trunk/        # 開発のメインライン
 +- branches/     # コンテナディレクトリ
 |   +- foo/      # 開発中のfoo機能用ブランチ
 |     ...
 +- tags/         # コンテナディレクトリ
     +- release-X # 特定のリリースバージョンをマークするために専用ブランチ
        ...

Bazaarでは、これは完全に適切なレイアウトです。 SVNからやって来た人が慣れ親しめることが利点で開発の焦点を当てる場所が明確になります。

同じリポジトリで複数のプロジェクトを持つとき、SVNのレイアウトは何を行うのか少し不透明です。

project/trunk

SVN用の望ましい方法はプロジェクトごとにレイアウト用のトップレベルのディレクトリを用意することです:

repository/            # リポジトリ全体
 +- project1/          # コンテナディレクトリ
 |   +- trunk/         # project1の開発のメインライン
 |   +- branches/      # コンテナディレクトリ
 |       +- foo/       # project1のfoo機能の開発用ブランチ
 |         ...
 |
 +- project2/          # project2用のコンテナ
     +- trunk/         # project2用のメインライン
     +- branches/      # project2のブランチ用のコンテナ

これはBazaarでも機能します。 しかしながら、Bazaarでリポジトリを作るのは簡単で( bzr init-repo )、 それらの主要な恩恵を受けられるのは複数のブランチが共通の祖先を共有するときです。

ですのでBazaarに対して望ましい方法は次のとおりです:

project1/          # project1用のリポジトリ
 +- trunk/         # project1の開発のメインライン
 +- branches/      # コンテナディレクトリ
     +- foo/       # project1のfoo機能の開発用ブランチ
       ...

project2/          # project2用のリポジトリ
 +- trunk/         # project2用のメインライン
 +- branches/      # project2のブランチ用のコンテナ

trunk/project

SVNで次のようなレイアウトを利用するプロジェクトもたまにあります:

repository/             # リポジトリ全体
  +- trunk/             # コンテナディレクトリ
  |   +- project1       # project1用のメインライン
  |   +- project2       # project2用のメインライン
  |         ...
  |
  +- branches/          # コンテナ
      +- project1/      # コンテナ (?)
      |   +- foo        # project1の'foo'ブランチ
      +- project2/
          +- bar        # project2の'bar'ブランチ

次のレイアウトはちょっと変形させたものです:

repository/             # リポジトリ全体
  +- trunk/             # コンテナディレクトリ
  |   +- project1       # project1用のメインライン
  |   +- project2       # project2用のメインライン
  |         ...
  |
  +- branches/          # コンテナ
      +- project1-foo/  # project1の'foo'ブランチ
      +- project2-bar/  # project2の'bar'ブランチ

trunk/” 全体をチェックアウトすることで、すべてのプロジェクト用のメインラインを入手できるようにすることが、このレイアウトが採用されている理由だと筆者は考えます。

このレイアウトはBazaarでも使えますが、一般的にお勧めできません。

  1. 一回の bzr branch/checkout/get は一つのブランチを作ります。 単独のコマンドですべてのメインラインを入手する利点が得られません。 [1]
  2. repository/trunk/foofoo プロジェクトの trunktrunk ブランチの単なる foo ディレクトリなのか明らかではありません。 この混乱の一部はSVNによるものです。 SVNはプロジェクトのブランチ用に使う1つのプロジェクトのファイルに対して同じ”名前空間”を使うからです。 Bazaarにおいて、プロジェクトを構成するファイルの明確な定義、もしくはブランチの位置の対立軸があります (ブランチごとに唯一の .bzr/ ディレクトリか、チェックアウトの中にたくさんの .svn/ ディレクトリかという対立軸です)
[1]注: NestedTreeSupport は”メタプロジェクト”を作成する方法を提供します。 メタプロジェクトはリポジトリのレイアウトにかかわらず複数のプロジェクトを集約します。 1つのプロジェクトを bzr checkout すれば、必要なサブプロジェクトがすべて手に入ります。

入れ子形式 (project/branch/sub-branch/)

SVNではできない、Bazaarによる別のスタイルは、ブランチ同士を入れ子にすることです。 Bazaarは作業ツリーなしのリポジトリ作成(--no-trees)をサポート(と推奨)しているのでこのスタイルが可能になります。 作業ファイルはブランチの設置場所に混ぜられていないので、 好きな名前空間にブランチを設置できます。

1つの可能性は次のとおりです:

project/             # リポジトリ全体、*と* プロジェクトのメインラインのブランチ
 + joe/              # 開発者Joeの開発のプライマリブランチ
 |  +- feature1/     # 開発者Joeのfeature1開発ブランチ
 |  |   +- broken/   # feature1を開発するためのステージングブランチ
 |  +- feature2/     # Joeのfeature2開発ブランチ
 |    ...
 + barry/            # Barryの開発ブランチ
 |  ...
 + releases/
    +- 1.0/
        +- 1.1.1/

このレイアウトのアイディアはブランチ用の階層的なレイアウトを作ることです。 変更はたいていより上位の名前空間のブランチへと流れていきます。 また、このレイアウトではユーザーに独自の作業をするための場所も提供します。 このレイアウトの素晴らしい点の1つは、グローバルな branches 名前空間を散らかさずにミニブランチを置けるので、ブランチ作成が”手軽”になることです。

このレイアウトのもう一つの利点は、ブランチの名前の中で詳細な内容を指定する際に繰り返しが減ることです。

例です:

bzr branch http://host/repository/project/branches/joe-feature-foo-bugfix-10/

上と下を比較します:

bzr branch http://host/project/joe/foo/bugfix-10

また、 repository/project/branches/ ディレクトリの中のリストがあれが何かわかるかもしれません:

barry-feature-bar/
barry-bugfix-10/
barry-bugfix-12/
joe-bugfix-10/
joe-bugfix-13/
joe-frizban/

Versus こういったブランチが開発者のディレクトリに分散している。 ブランチの数が少なければ、 branches/ は一見するだけですべてのブランチが見えるという素晴らしい利点があります。 ブランチの数が多ければ、 branches/ はすべてのブランチが見えてしまうというはっきりした欠点があります。 (調べるブランチが100あるとき、興味のあるブランチを見つけるのが難しくなります)。

入れ子ブランチはたくさんのブランチよりもスケーラブルのようです。 しかしながら、それぞれの個別のブランチは見つけにくいです。 (たとえば”Joeはfoo機能ブランチでバグ修正10に取り組んでいるのか、それともbarの機能ブランチを取り組んでいるのか?”)

他の小さな利点は次のようなものです:

 bzr branch http://host/project/release/1/1/1
もしくは
 bzr branch http://host/project/release/1/1/2

1.1.1と1.1.2のリリースを示します。 これはリリースする数と一度に見られる能力よりも分割する方がゲインが多いかによります。

ステータスによる種類分け (dev/, merged/, experimental/)

ブランチをbreak upする他の方法はこれらを現在のステータス順でソートすることです。 そうするとレイアウトは次のようになります:

project/               # レイアウト全体
 +- trunk/             # 開発に焦点を当てたブランチ
 +- dev/               # 進行中の作業用コンテナディレクトリ
 |   +- joe-feature1   # Joeの現在のfeature-1ブランチ
 |   +- barry-bugfix10 # bugfix 10に対するBarryの作業内容
 |    ...
 +- merged/            # これらのブランチがマージされたことを示すコンテナ
 |   +- bugfix-12      # すでにマージされたバグ修正
 +- abandonded/        # 'dead-end'と見なされているブランチ

これはたくさんの利点と欠点があります。 あまり多くない数のアクティブに開発されているブランチを見ることができるか、今までに作られた全てのブランチが見えるかという違いがあります。 古いブランチは削除しない限り失われませんが、別ディレクトリへと整理されるのでたいていの場合お目当てのブランチを見つけやすくなります。 (反対に、古いブランチは見つけにくくなります)。

このレイアウトで最大の欠点、ブランチが移動することです。 誰かが project/dev/new-feature ブランチをフォローしているとき、 そのブランチが trunk/ にマージされると project/merged/new-feature に移動するので bzr pull が突然機能しなくなります。 この回避策はいくつかあります。 1つは利用者を導くために古いブランチから新しいブランチにリクエストするHTTPリダイレクトを使うことです。 bzr >= 0.15 ではユーザーに http://old/path http://new/path にリダイレクトされることを教えてくれます。 しかしながら、HTTP以外の方法(SFTP、ローカルファイルシステム、など)を通してブランチにアクセスしている場合は役に立ちません。

一時的なリダイレクト用にシンボリックリンクを利用することも可能です (シンボリックリンクがリポジトリ内にある限りトラブルはほんのわずかしかありません)。 しかし、シンボリックリンクを結局削除したくなったり、散乱の削減の恩恵を得られません。 シンボリックリンクの代わりの別の可能性は BranchReference を使うことです。 bzr コマンドを通してこれらを作るのは現時点では難しいですが、便利だと思う人がいれば変わるかもしれません。 これは実際には Launchpadbzr checkout https://launchpad.net/bzr をできるようにしている方法です。 BranchReference は機能的にはシンボリックリンクですが、他のURLの参照ができます。 相対パスによる参照ができるように拡張されれば、HTTP、SFTP、ローカルパスを通しても動作するでしょう。

日付/リリース/その他で種類分け (2006-06/, 2006-07/, 0.8/, 0.9)

スケーラビリティを可能にする別の方法は”現在の”ブランチのブラウジングを許可することです。 基本的に、活発に開発されるブランチは新しく作られ古いブランチはマージもしくは廃棄されることを前提とします。

基本的に日付レイアウトは次のようになります:

project/                # projectリポジトリ全体
 +- trunk/              # 一般的なメインライン
 +- 2006-06/            # この月に作成されたブランチ用のディレクトリのコンテナ
 |   +- feature1/       # "project"の"feature1"用ブランチ
 |   +- feature2/       # "project"の"feature2"用ブランチ
 +- 2005-05/            # 異なる月に作成されるブランチ用のコンテナディレクトリ
     +- feature3/
     ...

これは “私の新しいブランチをどこに設置すればいいの?” という質問に素早く答えてくれます。 機能が長期間開発されるのであれば、ブランチを最新の日付にコピーして、そこで作業を続けることも道理にかなっています。 最新の日付と、そこからのさかのぼっていくことで活発なブランチを見つけることができます。 (小さな欠点は 大抵のディレクトリリストは古い順にソートされているので、多くの場合新しいブランチにたどり着くために余計なスクロールが必要になることです)。 古いブランチを新しい位置にコピーしたくない場合、ブランチを探すのが面倒になるのも欠点です。

別の候補は、リリースをターゲットにしたものです:

project/          # リポジトリ概要
 +- trunk/        # メインラインの開発ブランチ
 +- releases/     # リリースブランチ用のコンテナ
 |   +- 0.8/      # リリース0.8のブランチ
 |   +- 0.9/      # リリース0.9のブランチ
 +- 0.8/          # リリース0.8をターゲットとするブランチ用のコンテナ
 |   +- feature1/ # 0.8にマージする予定の"feature1"用のブランチ
 |   +- feature2/ # "リリース0.8をターゲットとしたfeature2"用のブランチ
 +- 0.9/
     +- feature3/ # リリース0.9をターゲットとした"feature3"用のブランチ

その派生として、ブランチが 0.9 ディレクトリに入っていることが 0.9に 向けた ブランチであることではなく 0.9 から 派生したブランチであることを意味するようにすることや、 0.8/release が0.8ブランチの公式リリースであることを意味するようにすることが考えられます。

一般的なアイディアはリリースをターゲットにすることで、何のブランチがマージされるのを待っているのか調べることができます。 このレイアウトはブランチの状態(開発中なのか、終了してレビューを待っているのか)に関する情報を提供しません。 これは履歴を隠す効果もあり、日付ベースの種類分けと同じような利点と欠点を持っています。

シンプルな開発者名 (project/joe/foo, project/barry/bar)

別の利用できるレイアウトは、開発者ごとにディレクトリを割り当てて、その下にブランチのためのサブディレクトリを作ることです。次のようになります:

project/      # リポジトリ全体
 +- trunk/    # メインラインのブランチ
 +- joe/      # Joeのブランチ用のコンテナ
 |   +- foo/  # Joeの"project"の"foo"ブランチ
 +- barry/
     +- bar/  # Barryの"project"の"bar"ブランチ

このアイデアでは、branchは入れ子になっておらず、branchは開発者によってのみグループ化されます。

Launchpad で使われている派生系はこのようになっています:

repository/
 +- joe/             # Joeのブランチ
 |   +- project1/    # Joeのブランチである"project1"用のコンテナ
 |   |   +- foo/     # Joeの"project1"の"foo"ブランチ
 |   +- project2/    # Joeの"project2"ブランチ用のコンテナ
 |       +- bar/     # Joeの"project2"の"bar"ブランチ
 |        ...
 |
 +- barry/
 |   +- project1/    # Barryの"project1"のブランチ用のコンテナ
 |       +- bug-10/  # Barryの"project1"の"bug-10"ブランチ
 |   ...
 +- group/
     +- project1/
         +- trunk/   # "project1"に焦点をあてたメイン開発

このレイアウトではそれぞれの開発者が取り組んでいるものを簡単に見ることができます。 焦点のブランチは”group” ディレクトリに保存されます。 これによって”グループ”が取り組んでいるブランチを見分けられます。

これによって異なる人々の作業内容をそれぞれ分離できますが、”プロジェクトX用のすべてのブランチ”を見つけるのが難しくなります。 Launchpad はデータベースバックエンドを伴う素晴らしいウェブインターフェイスを提供していて、”view”をこのレイアウトのトップに追加することでこの欠点を補っています。 これはそれぞれの個人が “~/public_html” ディレクトリを持ち、そこで独自のウェブページを公開する個人用ホームページのモデルに近いです。 一般的に、集中型のプロジェクト用に共用リポジトリを作成するとき、個人単位で分割してからプロジェクト単位に分割することを望まないでしょう。 通常はプロジェクト単位で分割してから個人単位で分割するとよいでしょう。

要約

最後に、誰にとってもうまくいく唯一の命名規則はありません。 開発者の人数、新しいブランチが作成される頻度、ブランチのライフサイクルなどによって異なります。 自身に問いかける質問は次のとおりです:

  1. 寿命の長い少数のブランチを作るか、もしくはたくさんの”ミニ”機能ブランチを作るか (加えて: ミニ機能ブランチをたくさん 作りたい が現在のVCSでは苦痛なのでできないのではないか?)
  2. 1人で開発しているのか、大きなチームか?
  3. チームであれば、一般的に全員が同時に同じブランチに取り組むことを計画しているか? もしくは人々が追跡することを想定した”安定”ブランチを持つか。