彷徨えるフジワラ

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

複数ルートなブランチの全ての差分の取得

本ブログエントリで最初に思い付いた暫定版を公開してから、色々指摘を受けて修正した結果、以下の方法なら行けそう、という結論に達しました。

# 対象ブランチ名を指定
BRANCH=foo

hg log -r "roots(branch($BRANCH))" --template "{rev}\n" |
while read root
do
    # 複数ヘッド化を検出する場合はここで実施
    hg log -r "heads($root:: and branch($BRANCH) and not ($root:: and not branch($BRANCH))::)" --template "$root {rev}\n"
done |
while read start end
do
    hg diff -r "p1($start)" -r "$end"
done

外部スクリプト無しで、ぎりぎりワンライナー化できる範囲ではありますね。

(2014-08-22 追記) テンプレート関数 revset() を使った書き換えに関するエントリを公開しました。

上記は POSIX 系環境での B shell 系シェルによる実行を前提にしたものですが、いわたさんが Windows 向けのものを公開しています。

記述内容解説

2つの revsets 記述のうち:

"roots(branch($BRANCH))"

最初の方は以下のような意味を持ちます。これは簡単ですね。

  • $BRANCH に属して、且つ
  • (対象リビジョン= $BRANCH なリビジョンの中に)親を持たない

一般的な運用では、上記条件に該当するリビジョンは1つになるのですが、Mercurial 自体は「複数ルートな名前付きブランチ」を扱うことができます。詳細はこのエントリを参照してください。

この図で言うと、最初の revsets 記述は B1 と B3 を抽出します。

本エントリの発端となった元々の要望が、このような複数ルートな状況を想定してるので、最初の revsets で抽出したルート情報を元に、2つ目の revsets 記述を適用します。

"heads($root:: and branch($BRANCH) and
       not ($root:: and not branch($BRANCH))::)"

root が B3 であれば、$root:: and branch($BRANCH) によって、B3, B4 が抽出されますが、root が B1 の場合、B1, B2 以外に、一旦 default を経由する B3, B4 も含まれてしまいます。

さて、"$root:: and not branch($BRANCH))" は:

  • $root の子孫で、且つ
  • $BRANCH に属さない

ですので、root が B1 なら D3 〜 D6 が抽出されますが、これの末尾に "::" を付けることで、D3, D4 の子孫である B3, B4 も抽出対象に含まれます。

冒頭2つの述語で抽出したものに対して、この結果を and not することで、(B1, B2, B3, B4) and not (D3, D4, D5, D6, B3, B4)(B1, B2) が抽出されます。

最終的には、これに heads() を適用しますので、B2 が抽出されることになります。

以上で、複数ルートな名前付きブランチに対して、それぞれの部分ツリーの root/head 対を抽出することができるようになりました。

再利用向けに、revsetalias での定義も載せておきます。

[revsetalias]
# $1 を起点とする子孫のうち
# $2 の条件を満たす集合の中で、
# $1 から連続しているもののみを抽出
descendantsin($1, $2) = (($1)::) and ($2) and not (((($1)::) and not ($2))::)

この revsetalias 定義を使えば、2つ目の revsets 記述を "heads(descendantsin($root, branch($BRANCH)))" と簡略化できます。

revsetalias 定義で括弧を多様してるのは、任意の revsets 表記に対して、演算子の優先順位の問題を回避するためです。C/C++ でのマクロ引数の扱いみたいなものですね(笑)