彷徨えるフジワラ

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

Mercurial Queue の適用事例

コミット前レビュー

レビュアーによるレビューを済ませるまでは、共有リポジトリへの成果反映をさせたくない

『コミット前レビュー』によって、レビュアーから変更内容に対する何らかの指摘があった場合、指摘事項を反映させる必要があるため、何度も変更のやり直しを行う必要がある。

MQ を使用した作業手順

典型的な作業手順は以下の通り。

  1. 修正内容を MQ のパッチに記録する ("hg qnew" + "hg qrefresh")
  2. レビュアーに修正内容パッチを送付
  3. レビュアーはパッチをレビューし、採用の可否を判定
  4. 採用された場合:
    1. パッチを共有リポジトリに取り込む (誰が取り込むかは運用規則次第)
    2. 修正担当は、MQ 管理下のパッチを破棄 ("hg qdelete")
  5. 採用されない場合:
    1. レビュアーの指摘事項を元に、修正内容を改変 ("hg qrefresh")
    2. レビュアーに再度パッチを送付
    3. 以降、パッチが採用されるか、修正が取りやめになるまで上記手順の繰り返し

レビュアーへのパッチ送付には、以下の方法を用いることが可能。

  • メールベースでの送付: "hg email -r 'mq()'" (要 patchbomb 拡張)
  • Bug Tracker/Issue Tracker への upload:
    • "hg export -r" 出力の貼り付け、ないしは
    • .hg/patches 配下のパッチファイルの直接 upload

MQ を併用して変更のやり直しをする場合、以下のコマンドは出力が異なる:

  • "hg diff": 作業領域の内容と、親リビジョン (= 直前の qrefesh 時点の内容) との差分
  • "hg tip -p": 最後に適用されたパッチの内容
  • "hg qdiff": 次に qrefresh した場合にパッチに取り込まれる内容(= "hg tip -p" + "hg diff")

例えば、前者は『レビュアーの指摘事項が適切に反映されている』ことの確認に、後者は『変更全体が妥当な内容である』ことの確認に使用できる。

応用

複数の作業を平行して抱え込む場合は、MQ のキューを複数管理するのが便利。

  1. 修正を開始するに当たって、キューを新規作成 ("hg qqueue --create")
  2. パッチ採用までの流れは、先述の手順通り
  3. パッチ採用/修正取り止めの際には、キューを破棄 ("hg qqueue --purge") することで、関連するパッチをまとめて破棄可能

作業順序と異なる順序で履歴に記録したい

作業手順上は「テストファースト」にしたいが、共有リポジトリに記録される履歴上は、『変更実施 ⇒ テスト作成』の順序にしたい。

履歴上の記録順序が『テスト作成 ⇒ 変更実施』となっている場合、"hg bisect" などで『テストがパスしなくなった原因リビジョン』の特定を行うと、『テスト作成 ⇒ 変更実施』の部分が該当してしまう。

そのようなリビジョン列の都度、"hg bisect" の探索が停止してしまうので、探索精度が著しく低下してしまう。

MQ を使用した作業手順

MQ を使用した典型的な作業手順は以下の通り。

  1. テストを書いて、障害が再現することを確認
  2. テスト追加分の修正を TEST に記録 ("hg qnew" + "hg qrefresh")
  3. 障害修正を実施して、障害が解決されていることを確認
  4. 障害修正を FIX に記録 ("hg qnew" + "hg qrefresh")
  5. 必要であれば、傷害修正は複数のパッチで記録
  6. FIX を外す ("hg qpop")
  7. TEST のテストにより、障害が再現することを再度確認
  8. FIX を再度適用 ("hg qpush")
  9. FIX の修正により、障害が解決されていることを再度確認
  10. TEST/FIX 内容が確定するまで上記手順を繰り返す
  11. 全てのパッチを一旦外す ("hg qpop -a")
  12. 先に FIX パッチ単体を適用 ("hg qpush --move")
  13. 次に TEST パッチを適用 ("hg qpush")
  14. 適用済みパッチを履歴に記録 ("hg qfinish")

パッチとして TEST/FIX を分割することで、障害が再現すること(= TEST の妥当性)や、修正による問題解決(= FIX の妥当性)の確認といった作業の間での行き来が、容易に行えるようになる。

応用

複数の環境でのテストが必要なケースなどでは、MQ 管理下のパッチを構成管理することで、複数環境間で修正内容を伝播させることができる。

固有修正を継続的に保守したい

ある成果物 A に修正 X を加えたいが、修正 X は成果物 A の開発元には取り込まれ無い。
また、成果物 A は随時更新されるため、適宜最新版に修正 X を適用したい。

例えば、OSS プロダクトに独自機能を追加するケースとして:

といったものが考えられる。

あるいは、環境依存/限定用途への対処を行うケースとして:

  • 特殊デバイスのエミュレーション処理の追加
  • テストカバレッジ向上のために、意図的にエラーを発生させる処理の追加
  • 特定環境下でのビルド手順の変更

といったものが考えられる。

通常、このような修正は、対象成果物に取り込まれるのが一般的だが、以下の様な理由から、対象成果物の構成管理ラインには取り込まれないケースも考えられる。

  • ライセンス問題/守秘義務が絡むために公開できない
  • あまりに特殊なケースのため、開発元による取り込みが絶望的
  • 契約内容に含まれていない成果であるため、発注元が受け取ってくれない

解決策の一つとして:

  1. 対象成果物のリポジトリを複製 ("hg clone" ないし convert/hgsubversion 等による別形式リポジトリからの取り込み)
  2. 独自変更用の名前付きブランチを作成
  3. 独自変更ブランチ上で独自変更を実施
  4. 適用対象リビジョンと独自変更ブランチを、独自変更ブランチ側でマージ
  5. 独自変更ブランチ側のマージ実施リビジョンから成果物をビルド〜利用
  6. 適用対象の開発が進んだ場合:
    1. 対象成果物リポジトリから取り込み
    2. 最新成果と独自ブランチを再度マージ

という運用が考えられるが、この方法では、独自変更を適用したリビジョンを得るためには、都度マージを実施しなければならない。例えば以下の様なニーズがある場合、これをマージで実現するのは現実的とは言えない。

MQ を使用した作業手順
  1. 対象成果物のリポジトリを複製 ("hg clone" ないし convert/hgsubversion 等による別形式リポジトリからの取り込み)
  2. 変更適用対象のリビジョンで作業領域を更新
  3. 変更内容を MQ で管理 ("hg qnew"/"hg qrefresh")
  4. パッチ適用状態で成果物をビルド〜利用
  5. 適用対象の開発が進んだ場合:
    1. 全てのパッチ適用を解除 ("hg qpop -a")
    2. 対象成果物リポジトリから取り込み
    3. 新たな変更適用対象のリビジョンで作業領域を更新
    4. パッチを適用 ("hg qpush -a")
応用

"hg bisect" で特定条件を満たすリビジョンを探索する際に:

素の成果物だと条件判定が難しいが、何らかの変更を加えることで、条件判定が容易になる

といったケースでは、"hg bisect" 実行と MQ を以下のように組み合わせる。

  1. あらかじめ改変内容に相当するパッチを MQ 配下に作成しておく
  2. "hg bisect" で条件判定対象リビジョンに移動
  3. "hg qpush" で独自改変を適用
  4. 対象リビジョンの条件合致性を判定
  5. "hg qpop" で独自改変を解除
  6. "hg bisect" で判定結果を指定 〜 次の判定対象リビジョンに移動
  7. (3) 以降を繰り返す

MQ 管理領域の履歴管理

MQ 管理下にあるパッチを、複数のリポジトリで使用したい

MQ 管理下で作成したパッチの共有が必要な例としては:

  • 作成されたパッチをチーム内で共有したい
  • 作成されたパッチを複数の環境で適用したいが、パッチ内容の転送を scp/FTP 等で実施するのは面倒

あるいは、共有そのものは不要でも:

  • 継続的に MQ パッチを更新するので、バックアップ代わりに変更履歴を記録しておきたい
MQ を使用した作業手順
  1. MQ 管理領域自体の構成管理を開始 ("hg init --mq")
  2. パッチを作成
    • MQ 管理領域の状態は "hg status --mq" で参照可能
    • パッチの追加 ("hg qnew") でパッチファイル状態が add に
    • パッチの削除 ("hg qdelete") でパッチファイル状態は remove に
    • パッチの更新 ("hg qrefresh") でパッチファイルの状態が modified に
  3. パッチ内容が確定した時点で MQ 管理領域の内容を履歴に記録 ("hg commit --mq")
  4. 連携先リポジトリでも MQ 管理領域の構成管理を開始
  5. 連携先リポジトリの MQ 管理領域に、管理領域の履歴情報を反映 ("hg push --mq")
    • デフォルトの反映先は .hg/patches/.hg/hgrc での default-push/default 設定先
    • .hg/patches/.hg/hgrc で列挙した連携先をコマンド行で指定可能
    • 『連携先 URL』には、パッチ管理領域である『連携先リポジトリ + "/.hg/patches"』を指定する
  6. 連携先リポジトリで MQ 管理領域の内容を最新版で更新 ("hg update --mq")
  7. 連携先リポジトリでパッチ適用 ("hg qpush")
応用

管理対象となっている一連のパッチの中に、状況/環境によって、適用の要否が変化するパッチが存在する場合がある (例: 環境依存の解消/環境エミュレーションに相当するようなパッチ)。

このようなケースで、各パッチの適用の有無を実行時に制御するには、以下の機能を使用する。

  • 各パッチ毎に "hg qguard" で「ガード」を設定 (設定は共有)
  • "hg qselect" で「ガード」選択状況を設定 (各環境毎に設定)