Mercurial 勉強中 (8) - hook を Trac と絡めて

TracMercurial を使ってインタフェースとして Trac を利用している場合*1,hook を設定するとさらに便利になります。

trac の post-commit-hook ヘルパスクリプトとは

たいていの SCM には commit 時などについでに何か(メールを送信したりスペルチェックをしたり)を実行させること(hook)ができます。trac-post-commit-hook というのは,Trac に付属している hook 用スクリプトで,commit メッセージをもとに ticket を閉じたり参照したりできるようにするものです。

たとえば(下記は Subversion ではなく Mercurial の例ですが),

内容を変更した(fixes #1)
HG: user: dayflower
HG: branch default
HG: changed index.html

のように commit メッセージを記述して push(Subversion の場合 commit)すると,



ticket のステータスが closed になります。

実際,ticket 側のログにも残ります。


trac-post-commit-hook を使わないなら Trac を使う意味がないというくらい素晴らしい機能です。Ticket Driven Development をやってみたくなりますね。


Subversion の場合,レポジトリフォルダ下の hooks/ フォルダに post-commit という名前の実行可能なものを放り込んでおくと commit 時に(trac-post-commit-hook に限らず)さまざまな hook を実行することができます。具体的な例は http://yamashita.dyndns.org/blog/247/ を参照してください。

Mercurial で hook

Subversion ではレポジトリフォルダ下の hooks/ フォルダでしたが,Mercurial の場合はレポジトリ下 .hg/hgrc に hook の所在を記述します。

たとえば push 時に hook を実行したいのであれば,マスタレポジトリの hgrc に下記のような設定を書き加えます。

[hooks]
incoming.trac = /var/www/repos/example/.hg/trac-post-commit.sh

incoming.trac となっていますが,ここは incoming だけでも大丈夫です。複数の hook をかけたい場合このようにサブ名称を付与しますので*2,例として & conflict を避けるため .trac を付け加えました。

push なのに incoming?という気がしますが,マスタレポジトリ側の立場からみると,外部からの push は incoming に相当するからです。

trac-post-commit-hookinvoke するスクリプトは下記のようになります。

#!/bin/sh

TRAC_ENV=/var/www/trac/example
REV=$HG_NODE

/usr/bin/python /usr/share/trac/contrib/trac-post-commit-hook -p "$TRAC_ENV" -r $REV

$HG_NODE という環境変数に push された revision が入っているんでそれを渡しているだけです。

ticket の changeset 参照を短くしたい

このままでも Subversion のときのようにうまく動くのですが,



ticket で参照している changeset の名前が長いですよね。

なので,invoker をシェルスクリプトではなく Python スクリプトとして書き起こしてみました。

#!/usr/bin/env python

trac_env    = '/var/www/trac/example'
hook_script = '/usr/share/trac/contrib/trac-post-commit-hook'

# style:: 'short', 'long', or 'number'
changeset_id_style = 'number'

import os

def invoke_trac_hook(trac_env, rev):
    def _make_smart_rev(trac_env, rev):
        if changeset_id_style == 'long':
            return rev
        else:
            import trac.env
            env = trac.env.open_environment(trac_env)
            # instance of mercurial.hg.repository
            repo = env.get_repository().repo
            ctx = repo.changectx(rev)
            if changeset_id_style == 'short':
                import mercurial.node
                return mercurial.node.short(ctx.node())
            else:   # 'number'
                return str(ctx.rev())

    smart_rev = _make_smart_rev(trac_env, rev)

    f = open(hook_script, 'r')
    try:
        import imp
        ext = os.path.splitext(hook_script)[1]
        m = imp.load_module('trac_hook', f, f.name, (ext, f.mode, imp.PY_SOURCE))
        m.CommitHook(project=trac_env, rev=smart_rev)
    finally:
        f.close()

invoke_trac_hook(trac_env, os.environ['HG_NODE'])

trac_envhook_script は環境にあわせて適宜書き換えてください。

changeset ID の表記についてですが,changeset_id_stylenumber を指定すると,



のようになります。

changeset_id_styleshort を指定すると,



のようになります。

ほんとうは他の Timeline 等での表記([][3:9058d29b14e4][])と揃えたいところなのですが,単純にこの形式を revision として指定しても Wiki が対応していないのでリンクを貼ってくれません。TracMercurial か trac-post-commit-hook を改造する((CommitHook クラスの __init__ ですべての処理を行ってしまっているので,クラスを継承して処理を修正する,ということができないんです))ことになります。

おまけ

trac-post-commit-hook の改造の話がでたのでついでですが。

付属の trac-post-commit-hook は Ticket の close と refer しかできませんが,結構わかりやすいコードになので,ワークフローに沿って Ticket のステータスを変更するように改造することもできます(⇒ trac-post-commit-hookでステータスも変更: 気の向くままに・・・)。すばらしい。