[bzr] チェックアウトとブランチの使い分け

bzr が git や hg と大きく異なるのは、メインラインの概念とチェックアウト機能だと思います。
メインラインについては昨日の Advent Calnedar で wonderful_panda さんが解説してくれました。(ブランチのメインラインのイメージについてしゃらくさい話をするよ - wonderful_pandaの日記)
今日はチェックアウト機能について紹介し、通常のブランチとの使い分けを解説します。

チェックアウトとは

チェックアウトとは作業ツリー、あるいは作業ツリーを作成するコマンドのことです。作業ツリーは常にどこかのブランチに紐づいています。
bzr のチェックアウトは、紐づくブランチが別の場所(別のディレクトリや別のサーバー上)にあってもいいところがユニークです。

$ bzr checkout lp:bzr-colo colo-co  # colo-co は lp:bzr-colo に紐づいたチェックアウトになる
$ bzr branch   lp:bzr-colo colo-br  # colo-br は lp:bzr-colo 最新版から分岐したブランチ兼そのブランチの作業ツリー

ちなみに、後者のように独立したブランチでありかつ作業ツリーでもあるディレクトリの事を、 standalone tree と呼びます。

チェックアウトのメリット・デメリット

サーバー上にあるブランチをチェックアウトした場合の、ローカルにブランチを作成する場合に比べた時のメリットとデメリットをまとめます。

メリット: push忘れがなくなる

サーバー上のブランチからローカルにチェックアウトした場合、その作業ツリー上でcommit を実行すると、自動的にサーバー上のブランチにコミットされます。
そのため、 commit してだれかに pull してと連絡したけど、じつは push を忘れていた・・・という事態を避けることができます。

デメリット: オフラインではコミットできなくなる

commit コマンドが自動的にサーバー上のブランチにコミットしようとするので、そのサーバーに接続できない状況ではコミットができなくなります。
(この場合、 bzr unbind というコマンドで、サーバー上のブランチとの紐づけを解除して、ローカルブランチに変換することができます)

デメリット: コミッタが複数人いた場合、 update 時にコンフリクトが発生する可能性がある

サーバー上のブランチをだれかが更新していた場合、コミットしようとするとチェックアウトが最新じゃないから update しろというエラーが発生します。
そこで update した場合、サーバー上の変更とローカルのまだコミットされていない変更がマージされるのですが、その時にコンフリクトが発生するおそれがあります。
この状況でコンフリクトが発生した場合、コンフリクトの解消中に操作ミスをして必要なローカルの変更を削除してしまうと、マージを最初からやり直すことはできません。
これは Subversion でも共通の問題ですね。

メリット: コミッタが複数人いてもメインラインの維持が楽

サーバー上のブランチ (a -> b) からローカルブランチを作って作業を記録し、ローカルのブランチの履歴が (a -> b -> d -> e) となっているとします。

他の人が先にサーバー上のブランチを更新し、 (a -> b -> i -> j ) となりました。ここに自分の修正をマージするときに、

$ bzr merge bzr+ssh://りもーと/ブランチ
$ bzr commit  # 新しいリビジョン X ができる
$ bzr push bzr+ssh://りもーと/ブランチ

としてしまうと、ローカルブランチのメインラインがサーバー上に反映され、サーバー上のメインラインが (a -> b -> d -> e -> X) になってしまいます。
メインラインを意識しない git や mercurial ではこれは通常の操作ですし、bzr でも同じくメインラインを意識しない運用をしても構いません。
が、メインラインを単調増加の1本道にしておきたい場合は、マージ用のチェックアウトがあると便利です。

$ cd ..
$ bzr checkout bzr+ssh://りもーと/ブランチ remote_checkout
$ cd remote_checkout
$ bzr merge ../ローカルブランチ
$ bzr commit

こうすると、サーバー上のメインラインに、自分のローカルブランチの変更をマージするコミットを 1 つ追加した履歴 (a -> b -> i -> j -> X) になります。

ローカルブランチを作ってからサーバー上のブランチに一切更新がなかった場合、リモートのブランチの履歴が (a -> b -> X) (Xは (b -> d -> e) というサブラインのマージ)になることに注意してください。
これにより、ローカルブランチで細かい雑な変更をしていても、メインライン上の履歴では 1 つの綺麗で完結したコミットにすることができます。
一方、ローカルブランチの変更をそのまま持ってきたい場合は、mergeではなくpull を使います。 「枝分かれしてたら merge, してなかったら pull」を自動で選択実行してくれる merge --pull コマンドが便利です。

使い分け

チェックアウトのみ

Subversion から Bazaar に移行したばかりのチームメンバーが、 Bazaar をインストールしてすぐに作業を始められるのがこの運用方法です。
また、コミッタが1人しかいなくて、機能ブランチを作る必要もない場合は、 Bazaar に慣れた開発者にとっても便利です。

チェックアウト+ローカルブランチ

Bazaar に慣れてきたら、サーバー上のブランチを管理するためのチェックアウトと、実際に作業をするためのローカルブランチを使い分けると良いでしょう。

ローカルブランチのみ

サーバー上にブランチを置かないなら別にこれでいいですね。

サーバー上のブランチに push する人が自分一人のケース (例: Launchpad上の lp:~/project/branch など、個人で公開しているブランチ) でも、チェックアウトに比べると push をしないといけないのが面倒ですが、オフラインで作業できるという利点があります。
また、チーム開発しているブランチでも、 hg や git の用にメインラインを意識しないのであればこれでかまいません。

bzr-colo でチェックアウト+ローカルブランチ運用

チェックアウト+ローカルブランチ運用で、サーバー上のブランチを管理するためだけにチェックアウトを置いておくのはディスク容量的にちょっと。。。というケースがあるかもしれません。そんな場合は bzr-colo を使いましょう。
bzr-colo の基本的な使い方は wonderful_panda さんが紹介してくれているので、 (今更だけどbzr-coloの話をするよ(基本編) - wonderful_pandaの日記, 今更だけどbzr-coloの話をするよ(応用編) - wonderful_pandaの日記) ここでは チェックアウト+ローカルブランチ 運用をする方法だけ紹介します。
bzr-colo では、リモートのブランチを colo:origin/main のように origin/ で始まる名前にしてやると、colo-pull で複数のブランチを一気に pull してこれます。 (git で言うと origin よりも remote の方が意味的に近いです)
しかし、普通に使うと、 origin/main はただのブランチなので、上で説明したようなメインラインの入れ替え問題が発生します。そこで、 origin/main をサーバー上の main ブランチに紐づけましょう。

$ bzr switch colo:origin/main       # 一旦、 colo:origin/main に切り替える
$ bzr bind bzr+ssh://りもーと/main  # リモートのブランチに紐づける
$ bzr merge colo:mywork             # ローカル作業ブランチをマージする
$ bzr commit                        # サーバー上のブランチにコミットされる

bzr bind は1度実行しておくだけで、次回からは switch するだけで大丈夫です。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。