Mercurial は、同時並行的に開発を進めるようなプロジェクトを管理できる仕組みを持っています。これらの仕組みを理解するために、まずは一般的なソフトウェア開発の仕組みを眺めてみましょう。
多くのソフトウェアプロジェクトでは、重要な新規機能を含む “メジャー” リリースを間欠的に発行します。それと平行して “マイナー ” リリースも発行することがあります。多くの場合、マイナーリリースは元にしたメジャーリリースと同一ですが、バグの修正がなされています。
この章では、「リリース」のようなプロジェクトのマイルストーンの、記録を保持する方法から説明を始めたいと思います。その後で、プロジェクトにおけるフェーズ移行での作業の流れや、その際の作業や成果物を Mercurial によって分離/管理する方法を説明します。
特定のリビジョンを “リリース” と呼ぶことに決定したなら、そのリビジョンの ID を記録するべきです。リビジョンの ID を記録することで、後日何らかの理由(例えばバグの再現や、新規プラットフォームへの移植等)で必要になった際にリリースを再現することがで きます。
“hg tag” コマンドを利用することで、 Mercurial は任意のリビジョンに永続的な名前を付与します。読者の予想通り、この名前のことを “タグ” と呼びます。
リビジョンにとって、タグは “象徴的な名前”(symbolic name) 以外の何者でもありません。タグは純粋に利便性のために存在するもので、リビジョンを参照する際の手軽で永続的な手段となります。 Mercurial は、利用者の用いるタグ名の意味を解釈したりしません。曖昧さが無く解析できることを保証するために必要な少々の制約を除いては、タグ名に何らかの制約をつけたりすることもありませ ん。以下のいずれの文字もタグ名には使用できません。
“hg tags” コマンドを使用することで、リポジトリが保持しているタグを表示させることができます。“hg tags” コマンドの出力において、個々のタグ付けされたリビジョンは、始めにタグ名で、次にリビジョン番号で、最後に一意のリビジョンハッシュ値で識別さ れます。
tip タグが“hg tags” コマンドの出力に列挙されていることに注意してください。tip は、常にリポジトリ中の最新のリビジョン を指す “流動的な” 特殊タグです。
“hg tags” コマンドの出力では、タグはリビジョン番号の逆順(降順)で列挙されています。これは最新のタグは古いタグよりも先 に列挙されることを意味し、それは同時に“hg tags” が出力するタグ一覧の最初にtip が表示されることも意味しま す。
“hg log” コマンドの実行時に、タグと関連付けられたリビジョンを表示する場合、“hg log” コマンドはタグを表示しま す。
Mercurial コマンドに対してリビジョン識別子を指定する必要がある場合、リビジョン識別子を指定する位置では、常にタグ名を 使用することができます。 Mercurial の内部では、タグ名を対応するリビジョン識別子に変換してから使用していま す。
単一のリポジトリが保持できるタグの数にも、単一のリビジョンに付与できるタグの数にも制限はありません。現実的な問題とし て、タグは単にリビジョンの特定を補助するものですから、 “過剰に” (具体的な数はプロジェクトに応じて異なりますが)タグ を付与するのはよろしくありません。多くのタグがあると、リビジョンを特定する利便性が早々に減少してしまいま す。
例えば、あるプロジェクトでは数日毎の頻度でマイルストーンを設定しているとすると、それぞれのマイルストーンにタグを付与す るのは極めて合理的です。しかし、全てのリビジョンで確実に綺麗なビルドができる継続的(continuous)なビルドシステムがある場合は、綺麗なビルド毎にタグを付与すると、大量のノイズを持ち込むことになります。その代わりに、ビルドが失敗するリビジョン(この 事態が稀だと仮定しています!)にタグを付与するか、ビルドの可否を追跡するタグの使用を止めるのが良いでしょ う。
必要の無くなったタグを削除したい場合は“hg tag --remove” コマンドを使用します。
任意の時点でタグの関連付けを変更することもできますので、新規の“hg tag” コマンド実行により、同一のタグが異なるリビジョ ンを識別するようになります。本当にタグを更新したいことを Mercurial に伝えるために、-f オプションを使用しなければなりませ ん。
タグの更新後も、タグが以前に識別していたリビジョンに関する永続的な記録が残りますが、 Mercurial がそれを使用することはあ りません。このように、間違ったリビジョンへのタグの付与には何の不利益もありませんので、タグ付けを間違ったなら、正しいリビ ジョンにタグを付与し直せばよいのです。
Mercurial は、リポジトリ中のリビジョン管理された通常ファイルにタグの情報を格納しています。何らかのタグを付 与すると、.hgtags ファイル中にそのタグを見つけることができるでしょう。“hg tag” コマンドを実行すると、 Mercurial はこのファイルを変更し、自動的に変更をコミットします。このことは、“hg tag” コマンドを実行した際 には、常に対応するチェンジセットを“hg log” コマンドの出力で見ることができる、ということを意味していま す。
.hgtags ファイルを気にする必要は殆どありませんが、時にはマージの際にその存在が意識されることがあります。このファイルの形式は単純で、連続した行から構成されています。各行はチェンジセットのハッシュ値で始まり、空白とタグ名が続きま す。
マージにおける .hgtags ファイルの衝突を解消する際には、.hgtags ファイル修正にひねりが必要です。リポジトリ中のタグを解 析する場合、 Mercurial は決して.hgtags ファイルのワーキングコピーを参照することはありません。その代わりに、 Mercurial は最 も最近コミットされたファイルのリビジョンを調べます。
このような設計の残念な結果として、マージした.hgtags ファイルが、その変更をコミットした後も正しい状態であることを、実 際に検証することができません。マージの際に.hgtags ファイルの衝突を解消する際には、コミット後に“hg tags” コマンドの実行 を忘れずに行ってください。.hgtags ファイルに不正があった場合、“hg tags” コマンドは不正の場所を報告しますので、その箇所を 修正してコミットすれば良いのです。変更内容の正しさを確認するために、変更の後で、再度“hg tags” コマンドを実行してくださ い。
“hg clone” コマンドが特定のチェンジセットを指定して厳密な複製を作成するための-r オプションを持っていることに気付いている かもしれません。新しい複製は、指定したリビジョンよりも後に生じた履歴情報を一切持っていません。このことがタグと相互作用した 場合、、油断していると驚かされる事態になります。
タグの生成が、.hgtags ファイルへの格納の際に、一つのリビジョンとして扱われることを思い出せば、タグが記録されたチェンジ セットが、タグの付与対象となる(古い)チェンジセットを参照するのは当然のことです。タグ foo 時点のリポジトリを複製するため に“hg clone -r foo” を実行した場合、複製されたリポジトリは、複製する際に使用されたタグの作成に関する履歴を持っていませ ん。新しいリポジトリには、プロジェクト履歴の完全なサブセットが含まれますが、唯一、指定に用いたタグの情報は含まれていませ ん。
Mercurial のタグは構成管理されており、プロジェクトの履歴と一体化しているため、誰かが作成したタグは、一緒に作業を行っている 誰もが見ることができます。しかし、リビジョンに名前を付けることは、リビジョン 4237e45506ee が実は v2.0.2 である、というこ とを書き留めておく以上の有用性があります。巧妙なバグを追跡する際に、 “アンがこのリビジョンで症状を見かけた” といった類の備 忘録として、タグを付与したい場合もあるでしょう。
このような場合、ローカルなタグが最適です。-l オプション付きで“hg tag” コマンドを起動することで、ローカルタグを作成 することができます。このコマンド実行の場合、タグは.hg/localtags ファイルに格納されます.hgtags と異な り.hg/localtags は構成管理されません。-l によって作成したタグは、現在作業をしているリポジトリに留まり続けま す2 。
ここで、本章の冒頭で述べた概略に戻り、複数の平行した開発が同時に行われているプロジェクトについて考えて見ましょ う。
新しい “主” リリースや、最新の主リリースに対する新たなマイナーバグ修正、現在は保守状態にあるような古いリリースに対する 予期せぬ “hot fix” のための push があるでしょう。
開発における様々な平行した方向を参照するための一般的な方法は、 “ブランチ” と呼ばれるものです。しかし、 Mercurial が全て の履歴を「ブランチとマージの連続」として扱っていることを、既に何度も見てきました。実際には、表面的には関係しているようで、 その実、たまたま同じ名前であるだけの2つの概念を扱っているのです。
Mercurial において “巨視的な” ブランチを隔離する最も簡単な方法は、隔離用のリポジトリを用意することです。例えば、既にある共 有リポジトリ — これを myproject と呼称します — が “1.0” というマイルストーンに到達している場合、 1.0 リ リースのために使用したリビジョンにタグを付与することで、 1.0 版に対する来るべき保守リリースの準備を行いま す。
タグ付けした時点と同じ内容のmyproject-1.0.1という名の新しい共有リポジトリを複製します。
その後、来る 1.0.1 マイナーリリースに含めるべきバグ修正の作業が必要になったなら、myproject-1.0.1 リポジトリを複製し変 更を行って、その成果を反映します。
その間、次のメジャーリリースへ向けた開発作業は、マイナーリリースに関する作業とは隔離された状態で、myproject リポジト リにおいて活発に続けられます。
保守用ブランチでバグ修正を行ったとすると、多くの場合、プロジェクトのメインブランチに(そしてそれ以外の保守ブランチにおいて も)同じバグが存在する可能性があります。同じバグを何度も直したいと思う開発者は稀ですから、同じ作業を繰り返すことなくバグ修 正を管理するために Mercurial が提供する幾つかの方法を見てみましょう。
最も単純な方法は、作業対象ブランチから複製したローカルリポジトリへ、保守ブランチから変更を pull することで す。
その上で2つのブランチのそれぞれのヘッドをマージし、その成果をメインブランチに反映します。
多くの場合は、リポジトリの分離によってブランチを分離するのが適切な遣り方です。単純ですから理解も簡単ですし、それ故に間違え ることがありません。作業しているブランチと、コンピュータ上の(リポジトリ)ディレクトリの間で、1対1の関係ができていますの で、ブランチ/リポジトリ中のファイルに対して、(Mercurial を意識しない)通常のツールを使用することもできま す。
あなたが(そして共同作業者も) “パワーユーザー” よりも高いレベルにあるのであれば、ブランチ (that you can consider XXXX) を扱う別な方法があります。前の節では、 “微視的” ブランチと “巨視的” ブランチの、利用者レベルでの区別について言及し ました。単一のリポジトリ中で、常に複数の “微視的な” ブランチ(例えば、変更の pull 後にマージしていない状態)を扱っている一方 で、 Mercurial は複数の “巨視的な” ブランチを扱うこともできます。
Mercurial が “巨視的な” ブランチを扱う際の要点は、ブランチに永続的な名前を付けるところにあります。前述のように default という名前のブランチが常に存在しますので、ブランチへの命名を行う前であっても、探せば default ブランチの跡を見つけることが できます。
例えば、“hg commit” コマンドを実行すると、エディタが起動されてコミットメッセージを入力できま す3 が、末 尾の “HG: branch default” を含む行を見てください。これは、default という名前のブランチに対してコミットしている、という ことを表しています。
ブランチに名前をつけるには、まずは“hg branches” を使用します。このコマンドは、リポジトリ中に既に存在する名前付きブラ ンチと、個々のブランチにおける先頭(tip)リビジョンがどれかを列挙します。
実行例では、名前付きブランチを生成する前ですから、唯一存在する default だけが表示されます。
どれが “現在の” ブランチかを知るには、引数無しで“hg branch” コマンドを実行します。このコマンドは、現在のチェンジセッ トの親チェンジセットが、どのブランチ上にあるものかを表示します。
新しいブランチを作成するには、再度“hg branch” コマンドを実行しますが、今回は生成するブランチ名を引数として指定しま す。
ブランチ生成後、“hg branch” コマンドによりどのような副作用を生じたのか、怪しむかもしれません。“hg status” や“hg tip” の出力はどうなっているでしょうか?
作業領域に変更は加えられていませんし、履歴に変化もありません。このことが示唆しているように、“hg branch” コマンドの実 行は何ら永続的な効果を持ちません。このコマンドは、次回のチェンジセットのコミットの際に、何というブランチ名を使用するかを Mercurial に伝えるだけです。
変更をコミットすると、 Mercurial はコミットされたチェンジセットにブランチ名を記録します。一旦 default ブランチから他の ブランチに切り替えてコミットしたなら、“hg log”、“hg tip” やそれに類する出力を持つコマンドの出力に、新たなブランチ名が表 示されていることでしょう。
“hg log” に類するコマンドは、default ブランチ以外に属する全てのチェンジセットに対して、ブランチ名を表示します。そのた め、名前付きブランチを使わない限り、ブランチに関する情報を見ることはありません。
名前付きブランチを作成し、そのブランチ名で変更をコミットしたならば、その変更に連なるその後のコミットは、 同じブランチ名を引き継ぎます。“hg branch” コマンドにより、任意の時点でブランチ名を変更することができま す。
ブランチ名はかなり長い寿命を持つため、実際にはこのようなブランチ名の変更はそれほど頻繁に実行することは無いでしょう(こ のことは規約ではなく、あくまで感想です)。
リポジトリに複数の名前付きブランチがある場合、“hg update” や“hg pull -u” といったコマンド実行の際に、 Mercurial は作業 領域ディレクトリが属するブランチを覚えていて、 “リポジトリ全体” の tip リビジョンではなく、そのブランチの tip リビジョンで作 業領域ディレクトリを更新します。別な名前付きブランチのリビジョンで更新したい場合は、“hg update” コマンドに-C オプションを 指定しなければなりません。
この振る舞いは少々微妙ですから、実例で見てみましょう。始めに、どのブランチ上で作業しているのかと、どんなブランチがリポ ジトリ中に有るのかを確認します。
現在 bar ブランチ上にいますが、古い“hg foo” ブランチも存在します。
foo ブランチおよび bar ブランチの tip リビジョンへの移動は、変更履歴上を直線的に前後することしか必要としないため、“hg update” コマンドに-C オプションを指定すること無しに、それぞれの tip リビジョンへの更新を行うことができま す。
foo ブランチに戻るために“hg update” コマンドを実行すると、foo ブランチ上に留まったままでbar ブランチの tip リビジョン には移動しません。
foo ブランチでの変更のコミットにより、新たなヘッドが生成されます。
foo ブランチから bar ブランチへの更新は、履歴を “横っ飛び” しないとできませんから、 Mercurial は“hg update” コマンドへ の -Cオプションの指定を必要とします。
お気づきの事とは思いますが、 Mercurial におけるマージ処理は対称的ではありません。リビジョン番号 17 のものと 23 のもの、2つ のヘッドをリポジトリが持っているものとしましょう。リビジョン 17 へと“hg update” してからリビジョン 23 と“hg merge” した 場合、 Mercurial はリビジョン 17 をマージの第1親、リビジョン 23 を第2親として記録します。一方で、リビジョン 23 へと“hg update” してからリビジョン 17 と“hg merge” した場合、リビジョン 23 がマージの第1親、リビジョン 17 が第2親として記録され ます。
この振る舞いが、マージを行った際の Mercurial のブランチ名選択に影響します。マージ後にその結果をコミットすると、 Mercurial は第1親のブランチ名を維持しようとします。第1親のブランチ名が foo で、bar ブランチのリビジョンとマージした場 合、マージ後のブランチ名は foo のままとなります。
リポジトリ中に同じブランチ名の複数のヘッドが存在することは、それほど珍しいことではありません。例えば、私とあなたが foo ブランチで作業しているとします。二人がそれぞれ異なる変更をコミットし、私があなたの変更を pull しました。この時点で私のリポ ジトリには、foo ブランチ上に2つのヘッドが存在します。マージの結果、foo ブランチ上の2つのヘッドは期待通り1つになりま す。
しかし、私が bar ブランチで作業していて、foo ブランチの成果をマージした場合、マージの結果は bar ブランチ上に留まりま す。
より具体的な例として、bleeding-edge ブランチで作業していて、最新の成果を stable ブランチから持ち込みたいと思ったとし ます。この場合、stable ブランチの成果を pull してマージした段階で、 Mercurial は “適切な” ブランチ名 (bleeding-edge) を選択 します。
寿命の長い複数のブランチが単一リポジトリで共存している状況だけが、名前付きブランチの利用できる状況だとは考えないでくださ い。リポジトリ1つにブランチ1つの状況であっても、名前付きブランチは有用です。
単純な例としては、ブランチに名前を付与することで、チェンジセットがどのブランチに由来するかの恒久的な記録を得ることがで きます。この記録は、寿命の長いブランチを持つプロジェクトの履歴を辿る際に、多くの情報をもたらすことでしょ う。
リポジトリを共有して作業している場合、pretxnchangegroup フックをそれぞれのリポジトリに対して設定することで、 “不正な” ブランチ名を持つ変更が持ち込まれるのを防ぐことができます。この手法は単純ですが、 “血の滴る刃” とでも言うべき(不安定な)ブ ランチの成果を、誤って “安定した” ブランチへと持ち込むことを防ぐには効果的です。このようなフックは、共有リポジトリの hgrcファイルに以下のように記述します。