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 になります。
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-hook
を invoke するスクリプトは下記のようになります。
#!/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_env
と hook_script
は環境にあわせて適宜書き換えてください。
changeset ID の表記についてですが,changeset_id_style
に number
を指定すると,
のようになります。
changeset_id_style
に short
を指定すると,
のようになります。
ほんとうは他の 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でステータスも変更: 気の向くままに・・・)。すばらしい。