2012年11月6日火曜日

Mercurial 拡張を作成してみた

Mercurial の拡張に初挑戦。

思ったより簡単に実装できてよかったです。

今回の題材

今回作ったのは archive コマンドの拡張で指定範囲のリビジョンで変更したファイルを対象とするというもの。 きっと既にあると思いつつも、拡張を作ってみたかったこともあり、着手することにした。

hg archive のオプションに --change REV1:REV2 を追加するというものだ。

参考

主に参考にしたのは、

上の場所を主軸として、今回は wrapcommand を使ったので、そ の場合の構成は Mercurial のソースリポジトリ中、 hgext/largefile を参考にした。

また、同じくMercurial ソース中のhgext/commands.py に標準 のコマンドが定義されているので、ラップした archive コマン ド (実態はPython の関数) の引数を調べるのに使った。

wrapcommand

既存のコマンドを拡張する場合には mercurial.commands.wrapcommand を使うようだ。

どうやら、コールバック uisetup() が呼び出されたタイミング でラッピングするらしい。

wrapcommand の戻り値が cmdtable の要素 (= 三つ組のタプル) に相当するようなので、そのタプルの 2 つ目にオプションの定 義を追加する。

ソースコード

# coding: utf-8

from mercurial import commands, scmutil, extensions
from mercurial.i18n import _

def overridearchive(orig, ui, repo, dest, **opts):
    # get sorted revision numbers
    if opts.has_key('change'):
        older, newer= scmutil.revpair(repo, opts['change'])
        older_ctx = repo[older]
        newer_ctx = repo[newer]
    if newer_ctx.rev() == None: # if unary
        newer_ctx = older_ctx
    if newer_ctx.rev() < older_ctx.rev():
        older_ctx, newer_ctx = newer_ctx, older_ctx

    # get changed files between the revisions
    files = set()
    for revno in range(older_ctx.rev(), newer_ctx.rev() + 1):
        for f in repo.changectx(revno).files():
            files.add(f)
    files = sorted(files)

    # show changed files
    if ui.verbose:
        for f in files:
            ui.write(f + '\n')

    # arrange opts to be passed into the archive command
    del opts['change']
    opts['include'] = list(files) # OVERWRITES

    return orig(ui, repo, dest, **opts)


def uisetup(ui):
    entry = extensions.wrapcommand(commands.table, 'archive', overridearchive)
    addopt = [('', 'change', [], _('REV1:REV2 format'))]
    entry[1].extend(addopt)