mercurial-backlog拡張を作った中で知ったこと

このエントリはhttp://partake.in/events/902cd6d9-0215-4ea3-b51f-b8ff32e56426:title=の16日目です。ちょっと遅刻しました。。

仕事でBacklogというプロジェクト管理ツールを利用しています。とても便利なツールなのですが、Subversionリポジトリしか連携出来ないという欠点があります。

というわけで、BacklogとMercurialリポジトリを連携するためのmercurial-backlog拡張を作成してみました。

詳しい使い方はリンク先を見て貰うという事で、ここではmercurial拡張を作成する中で知ったことをいくつか紹介します。
(mercurial拡張の作り方についてはhttp://safx-dev.blogspot.com/2011/05/mercurial.html:title=が詳しいです)

ディレクトリ構成

mercurial拡張のディレクトリ構成は次の様になっている事が多いです。

{path-to-extension-root}/
    |-- README
    |-- LISENCE
    |-- {extension-name}/
        |-- __init__.py # エントリポイント
        |-- tests/ # 拡張のテスト

このような構成の場合、hgrcに次の様に記述して拡張を有効にします。

[extensions]
backlog = {path-to-extension-root}/{extension-name}

依存するライブラリについて

setup.pyをしっかり書いてpythonのライブラリやツールのようにインストールさせれば良いのですが、大規模な拡張でないとあまりうまみがありません。
hg-reviewを見てみるとFlaskなどのライブラリをバンドルしています。これを参考にしてライブラリも一緒に配布してしまいましょう。

hg-reviewの例では次の様にbundled以下にライブラリを含んでいます。

hg-review/
    |-- README
    |-- LISENCE
    |-- bundled/
        |-- flask
        |-- jinja2
        |-- werkzeug
        |-- simplejson
        |-- markdown2
    |-- review/
        |-- __init__.py
        |-- ...

hg-reviewでは次のコードをバンドルしたライブラリをpathに含めています。

import sys, os

def unbundle():
    package_path = os.path.split(os.path.realpath(__file__))[0]
    template_path = os.path.join(package_path, 'web_templates')
    media_path = os.path.join(package_path, 'web_media')
    top_path = os.path.split(package_path)[0]
    bundled_path = os.path.join(top_path, 'bundled')
    flask_path = os.path.join(bundled_path, 'flask')
    jinja2_path = os.path.join(bundled_path, 'jinja2')
    werkzeug_path = os.path.join(bundled_path, 'werkzeug')
    simplejson_path = os.path.join(bundled_path, 'simplejson')
    markdown2_path = os.path.join(bundled_path, 'markdown2', 'lib')

    sys.path.insert(0, flask_path)
    sys.path.insert(0, werkzeug_path)
    sys.path.insert(0, jinja2_path)
    sys.path.insert(0, simplejson_path)
    sys.path.insert(0, markdown2_path)

unbundle()

これを改変して__init__.pyなどの先頭で呼び出すと良いでしょう。mercurial-backlogでは次のコードでバンドルしているbackloglibを有効にしています。

def unbundle():
    package_path = os.path.split(os.path.realpath(__file__))[0]
    top_path = os.path.split(package_path)[0]
    bundled_path = os.path.join(top_path, 'bundled')
    backloglib_path = os.path.join(bundled_path, 'backloglib-0.2.1', 'src')

    sys.path.insert(0, backloglib_path)

unbundle()

フックについて

拡張機能としてフックを作成する場合はhookメソッドを__init__.pyにimportしましょう。たとえば__init__.pyが次のようなファイルの場合、

# /path/to/hook-example/example/__init__.py
def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
    """hook example"""
    ui.write(hooktype)
    ui.write(' called\n')

次の様にフックを設定出来ます。

[extensions]
example = /path/to/hook-example/example

[hook]
pre-commit.example = python:example.hook

バンドルされてる○○拡張みたいなことをしたい

○○拡張のコードを読みましょう。mercurial-backlogの場合、引数のリビジョンを取り出すときのコードはtransplant拡張から取ってきたり、

    # revsetsなども解釈してリビジョン番号を取り出す
    for rev in scmutil.revrange(repo, revs):
        bn.node(repo[rev])

フックの定義はnotify拡張から取ってきたり

    ctx = repo[node]
    if hooktype == 'changegroup':
        # pushされた変更を全て取り出す
        start, end = ctx.rev(), len(repo)
        for rev in xrange(start, end):
            bn.node(repo[rev])

notify拡張の様にhgrcから設定を取得したり

        self.spacename = self.ui.config('backlog', 'spacename')
        self.username = self.ui.config('backlog', 'username')
        self.password = self.ui.config('backlog', 'password')
        self.key = self.ui.config('backlog', 'key');

しています。バンドルされている拡張はスニペットとして最適です。

まとめ

mercurial拡張、フックはとても簡単に作成出来ますし、参考になるコードはMercurial自体にたくさん含まれています