彷徨えるフジワラ

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

テンプレート関数 revset() の利用

上記ツイートを契機としたやり取りの過程で、Mercurial 3.0 からテンプレート定義中で revset() が使えるようになったのを、すっかり失念していたことに気付きました……orz

このまま放置すると、また忘れてしまいそうなので、何か実際的な処理で使って、しっかり記憶しておきたいところです。

何か良い題材はないかと考えたところ、先日公開したrevsets に関するエントリで例示している以下の処理が、まさに revset()ユースケースにピッタリなことを思い出しました。

BRANCH=foo

hg log -r "roots(branch($BRANCH))" --template "{rev}\n" |
while read root
do
    hg log -r "heads(descendantsin($root, branch($BRANCH)))" \
           --template "$root {rev}\n"
done |
while read start end
do
    hg diff -r "p1($start)" -r "$end"
done

なお、while ループ内での revsets 問い合わせが非常に複雑なので、簡略化のため以下の様なエイリアス descendantsin の定義を仮定しています。

[revsetalias]
descendantsin($1, $2) = (($1)::) and ($2) and not (((($1)::) and not ($2))::)

但し、エントリ執筆時点の最新版である Mercurial 3.1 では、revsetalias によるエイリアス定義は revset() の中で使用できません…… orz なので、例としてはエイリアス定義を使っていますが、実際の利用ではエイリアス定義を使わないようにしてください

(※ 2014-08-28 追記) 修正が取り込まれましたので、2014-11 にリリースされる 3.2 からは、revset() 中でもエイリアスが使えるようになります。

元の処理では、hg log を二段重ねにしていますが、テンプレート関数 revset を使うことで、これらを単一の hg log に集約することができます。

hg log -r "roots(branch($BRANCH))" \
       --template "{revset('heads(descendantsin(%s, branch($BRANCH)))', rev)
                   % '{rev} {revision}\n'}" |
while read start end
do
    hg diff -r "p1($start)" -r "$end"
done

書き換え後のテンプレート定義における肝は、revset() 関数の利用と、その結果のリビジョン一覧を使ったマップ処理 (詳細はこちらのエントリを参照) になります。

まずはじめに、-r オプションに対する revsets 記述により、テンプレート適用の対象となるリビジョンが抽出されます。

抽出されたテンプレート適用対象リビジョン毎に、テンプレート定義に従って、以下の処理が実施されます:

  1. revset() が revsets 問い合わせ文を構築
  2. revset() が revsets 問い合わせを実施
  3. 問い合わせ結果のリビジョンに対して、マップ処理を実施

テンプレート関数 revset() における問い合わせ文の構築では、第1引数のフォーマット文中の %s 等が、それ以降の引数で置換されます。

revset('heads(descendantsin(%s, branch($BRANCH)))', rev)

上記記述での rev は、通常のテンプレートによる表示の際と同様に、「テンプレート適用対象リビジョン」 (= {rev}) を指します。テンプレート関数に対する引数として、テンプレートキーワードを指定する場合は、波括弧 ("{}") が付かない点に注意してください。

この revsets 問い合わせの実施結果は、元スクリプトでの最初のループにおける revsets 問い合わせに相当することになります。revset() 関数に対する rev 指定が、元スクリプトでの $root に該当します。

hg log -r "roots(branch($BRANCH))" --template "{rev}\n" |
while read root
do
    hg log -r "heads(descendantsin($root, branch($BRANCH)))" 〜〜

テンプレート関数 revset() の結果は、リビジョンのリストになりますので、マップ処理で各要素毎に出力を整形します。

revset(〜〜〜) % '{rev} {revision}\n'

上記マップ処理のフォーマット部分の構成要素は、それぞれ以下の意味を持ちます:

  • {rev} は、テンプレート適用対処リビジョン (= -r による問い合わせ結果の要素)
  • {revision} は、revset() による問い合わせ結果の要素

以上の結果として、(1)「-r オプションでの revsets 記述による問い合わせ」というループと、(2)「テンプレート関数 revset() による問い合わせ」というループの、2つのループによる処理を、1つの hg log 実行で実施できるわけです。

書き換え前後でどちらが分かり易いか?という点に関しては、好みが分かれるところだと思いますが(笑):

上記の条件が成立するようなケースでは、対象リビジョン毎の hg log の繰り返し実施がコスト高になりますので、記述内容が多少複雑でも、単一の hg log 実行に集約できるメリットは大きいと言えるでしょう。

なお、標準のテンプレート機能の範囲では、テンプレート適用対象リビジョンのメタデータは取得できますが、間接的に取得したリビジョンのメタデータは取得できません。例えば、テンプレート適用対処リビジョンに対して:

  • ブランチ名 ⇒ {branch}
  • 第1親リビジョンのリビジョン番号 ⇒ {p1rev}
  • 第1親リビジョンのブランチ名 ⇒ 取得不能

テンプレート適用対象リビジョン以外のリビジョンのメタデータを取得したい場合は、間接的にメタデータを取得するテンプレートフィルタを併用してください。