彷徨えるフジワラ

年がら年中さまよってます

『Mercurial で pull request』したい Git ユーザが必要になるであろう、Mercurial におけるブランチの概念+アルファ

ツイッターで以下のようなツイートを見かけたので:

Mercurial 推進派としては何か情報発信しておきたいところですが、よくよく考えてみると、そもそも私自身が、送ったものと受けたものを合わせても、両手の指で足りる程度しか pull request を使った事が無いのでした(笑)。

# 後述するように、Mercurial 本体の開発はメーリングリストベースなのです……

しかし、pull request そのものの話は不得手でも、Git と Mercurial での『ブランチ』の違い等なら、とりあえず自分の守備範疇なので、『Mercurial で pull request』したい Git ユーザ向けに、以前書いたMercurial におけるブランチの概念に関する一連のエントリの要約的なものと、現時点で私が思い付く『pull request 時に注意すべき点』をまとめてみました。

pull request 周りに関して(勿論それ以外に関しても)、間違い/補足事項等あれば、お知らせ頂けると助かります > ヘビー pull requester の皆様

『ブランチ』の概念の違い

Git の『ブランチ』は:

  • 『特定のリビジョンを参照』する機能
  • 一種の『ポインタ(pointer)』
  • 『ブランチ』所属のリビジョンは、ポインタの参照先リビジョンとその祖先を、他のリポジトリと比較することで特定
  • 『ポインタ』は移動可能

その一方で、Mercurialの『ブランチ』は:

  • 通常は『名前付きブランチ 』(named branch)のことを意味する
  • 『リビジョンにグループ名を記録』する機能
  • 『同一の名前を持つリビジョンをグループ化』することが可能
  • 『ブランチ』所属のリビジョンは、記録されたグループ名から、リポジトリ単独で特定可能
  • リビジョンに記録された『グループ名』は変更不可

以上のように、概念的に全く違うものを指しています。

Mercurial での『名前付きブランチ』は永続性を持っている (permanent) ことから、長期的にリビジョンを識別するような用途に強みがあります。識別の例としては、以下のようなものがあげられます。

  • 『機能拡張』系は default、『障害修正』系は stable
  • リリース準備期間のリリース向け作業は release-prep
  • まとまった機能単位にブランチを分けて、ある程度収束するまでは個々のブランチで並行作業

Git の場合、master 位置等との比較によって、一時的には『変更が実施されたブランチ』を特定できるかもしれません。

しかし、履歴が進む=masterが移動することで、このような特定は困難になります。rebase 指向で履歴が直列化されてしまう場合や、逆に履歴のツリー構造が複雑なケースでは、特に特定が困難になるでしょう。

その一方で、Mercurial の『名前付きブランチ』であれば、記録された『グループ名』情報を元に、各リビジョン毎の『変更が実施されたブランチ』を容易に特定することができます(事後監査等が必要なケースで便利です)。

例えば、hg log -b ブランチ名hg log -r "branch(ブランチ名)" で『特定のブランチに属するリビジョンのみを表示』することもできます。

なお、Mercurial の『名前付きブランチ』は永続性を持っているため、『hg branches 表示量が増えるのが嫌』という意見もありました。

現在は、ブランチに対して閉鎖操作 (close) を行うことで、hg branches 等での一覧表示から除外することができます。

そのため、障害管理等でのチケット単位のような、細かい粒度で多数の『名前付きブランチ』を作成し、修正〜マージ〜ブランチ閉鎖といった、短命なサイクルで運用した場合でも、hg branches での一覧表示量を抑えることができます。

ちなみに、Mercurial でもブックマーク (bookmark) を使うことで、Git 的な『ブランチ』を実現することができます(ブックマークは破棄も可能です)。

リビジョンの参照性/永続性

Git の場合:

  • 履歴一覧表示の対象は、有効な『ポインタ』の参照先リビジョンと、そこから『親』方向に辿れる祖先リビジョンのみ
  • 履歴の枝分かれの可視化には、新規『ポインタ』による参照の追加が必要
  • 辿れないリビジョンは、一定期間経過後にリポジトリから破棄

Mercurial の場合:

  • 記録されたリビジョンは、全てが履歴一覧表示の対象
  • ポインタ等が無くても、履歴の枝分かれは常に可視状態: 名前なしブランチ (anonymous branch)
  • 明示的に破棄操作をしない限り、リポジトリからのリビジョン破棄は無い

以上のような、参照性/永続性における方針の違いがあります。

Git での pull request における『ブランチ』は、対象リビジョンを特定する手段としての用途以外に、pull request 送信側のリポジトリにおいて、master から枝分かれした一連のリビジョンが、破棄されないように参照を維持する上でも必要なのです。

その一方で、明示的な操作が無ければリビジョンが破棄されることの無い Mercurial の場合は、pull request における『(名前付き)ブランチ』は必ずしも必要ではありません。対象リビジョンの特定は、リビジョン識別子(ハッシュ値)で十分なわけです。

pull request 先のリポジトリ運用方針の確認

まずは、pull request の送付先プロジェクトにおける、『名前付きブランチ』や『ブックマーク』の運用方針を確認しましょう。

もっとも、閉じた組織内部の運用であれば、メンバー各自に『名前付きブランチ』の作成を許可するケースも多々あるでしょうが、OSSでのリポジトリ運用において、開発コアメンバー以外に『名前付きブランチ』作成を許可するケースが、それほど多いとは思えません。

ですので、多くの場合は、『stabledefault の使い分け』的なものの有無を把握しておけば大丈夫でしょう。

なお、『Mercurial で履歴管理』と『pull request で変更提案受け付け』は、必ずしもイコールではありませんから、まずはそこから先に確認した方が良いかもしれません。

例えば、Mercurial 本体の開発では、変更提案の受け付けはメーリングリスト (ML) ベースです。

これは、プロジェクトリーダである Matt Mackall が以下のような方針を持っているからです。

  • 変更の提案は、開発ML購読者全員の目に触れるべき
    • pull request での提案だと、詳細はホスティングサーバ側で参照
    • 個々の提案の詳細まで辿って見る人の率が下がる
  • 要望に関する議論は、開発ML購読者全員の目に触れるべき
    • pull request 契機の議論だと、議論の詳細はホスティングサーバ側で管理
    • 個々の議論の詳細まで辿って見る人の率が下がる
    • 検索等での S/N 比も、MLベースの方が良い

ちょくちょく Mercurial 本体に変更提案している身としては、この『開発ML購読者全員の目に触れるべき』という方針は、非常に得心するところがあります。

開発MLの未読メールをザーっと眺めるだけでも、差分のソースや、目に付いたコミットメッセージの断片から、着手中の作業の参考になったり、逆にこちらから情報を提供できるもの、あるいは新しい提案のアイディアの元になるようなものを、一定の割合で広い上げることができたりするためです。

まぁ、英語ベースのMLの場合、言葉の上では『ザーっと眺める』と言いつつ、凄い気を張っていたりしますが(笑)。

やり直しが必要な場合

送付した pull request に対して、やり直し要求があった場合、修正内容を見直して=既存の履歴を改変するなどして、再度 pull request を送付することになります。

Mercurial での既存の履歴の改変には、以下のような機能を使用します。

繰り返しになりますが、一度記録された Mercurial の履歴情報は、明示的な操作をしない限り破棄されることはありません。

上記の『履歴改変を行う機能』は、新しい内容のリビジョンを作成した上で、古い内容のリビジョンをローカルリポジトリから破棄することで、見かけ上履歴が改変されたように見せているだけに過ぎません。

改変された履歴情報を、ホスティングサービス上のリポジトリhg push した場合、新規リビジョンが増えはするもの、以前の内容のリビジョンはそのまま残ります(Git の場合、『ブランチ』の参照先が新規リビジョンになることで、参照されなくなった古いリビジョンは、履歴一覧に表示されなくなり、結果として履歴が改変されたように見える筈)。

このままだと、ホスティングサービス上のリポジトリが『複数ヘッド』 (multiple heads) 状態になりますから、以下のいずれかを選択する必要があります。

  • 古いリビジョンをホスティングサービス側で破棄した上で、新規リビジョンを hg push
    • Bitbucket での古いリビジョンの破棄は、リポジトリの管理画面から『チェンジセットの除外(Strip)』で実施
    • 複数ヘッドで混乱することが無い (間違ったリビジョン指定での pull request 等)
    • 以前の変更内容を、ホスティングサービス上では参照できない
  • --force 付きで hg push を実施

ツール/環境への習熟度合以外にも、好みや、自分のうっかり者具合(笑)とも相談してみてください。

更なる詳細に関して

疑問/質問等があれば、日本語MLへの投函や、#mercurialjp 付きツイートなどで、お気軽にお知らせください。障害報告等も歓迎です。

日本語ユーザ向けハブサイトも参照してみてください。

また、手前味噌で恐縮ですが、拙著『入門 TortoiseHg + Mercurial』や、私のサイトで公開している情報が参考になれば幸いです。

入門TortoiseHg+Mercurial

入門TortoiseHg+Mercurial