本当は怖いエクステンション - Mercurial Advent Calendar 2012

2012 年 12 月 6 日 はてなブックマークへ追加 はてなブックマーク - 本当は怖いエクステンション - Mercurial Advent Calendar 2012 Bookmark this on Delicious

Mercurial Advent Calendar 2012 の 6 日目です。

@h0k0r0bi さんの hg now を作ったよ というバトンを受け、予定を変えて自作エクステンションの紹介をしようと思います。 リポジトリ変換や hgweb の話はまた後日しますね。

are you textful?

textful エクステンション は、 リポジトリのファイルにフィルタをかけて、見やすくするための拡張機能です。

例えば、

して、 hg diffkdiff3 で差分を見られるようになります。

どうやってるの?

fctx.data() メソッド を差し替えています。

fctx.data() は、リポジトリや作業コピーのファイルへアクセスするためのインターフェースです。 なので、このメソッドをハックすればファイルの内容を化かせるんですね。

fctx オブジェクトは、 localrepo -> ctx -> fctx という経路でできるので、 まずは localrepo を差し替えます。

def wraprepo(repo):
    class textfulrepo(repo.__class__):
        def __getitem__(self, changeid):
            ctx = super(textfulrepo, self).__getitem__(changeid)  # 本物の ctx
            if changeid is None:
                return wrapworkingctx(ctx)  # 作業コピー
            return wrapchangectx(ctx)       # リポジトリのリビジョン
        # 略
    repo.__class__ = textfulrepo

次は ctx です。 changectxworkingctx で若干引数が異なるので、 それぞれ関数を定義します。

def wrapworkingctx(ctx):
    class textfulworkingctx(ctx.__class__):
        def filectx(self, path, filelog=None):
            fctx = super(textfulworkingctx, self).filectx(path, filelog)
            return wrapfilectx(fctx)
    ctx.__class__ = textfulworkingctx

def wrapchangectx(ctx):  # 略

最後に fctx です。 filectxworkingfilectx がありますが、 data() メソッドの形が同じなので区別する必要はありません。

def wrapfilectx(fctx):
    class textfulfilectx(fctx.__class__):
        def data(self):
            d = super(textfulfilectx, self).data()
            return 'hoge ' + d
    fctx.__class__ = textfulfilectx

はい、できました。

そんなんで大丈夫?

ところで、 Mercurial から見えるファイルの中身を化かしたりして、 リポジトリが壊れたりしないでしょうか? — もちろんそんな事はあります。

textful エクステンションでは、 fctx.data() を差し替えたままリポジトリへ ライトアクセスが起きないようにしています。

class textfulrepo(repo.__class__):
    # 略

    def lock(self, wait=True):
        if wait:
            raise error.Abort(_('disallowed to modify textfulrepo'))
        else:
            raise error.LockUnavailable(errno.EACCES,
                                        'Operation denied', '', '')

    def wlock(self, wait=True):
        self.lock(wait)

リポジトリの書き変え前には、必ずロックをかける決まりになっているので、 ロックを要求されたらアボートさせています。 投機的なロック wait=False には LockUnavalable で答えていますが、 これは、要求がたいていキャッシュ更新前のロックだからです。 呼び出し元はキャッシュ更新をあきらめて、残りの作業を続けることでしょう。

エクステンションのダークサイドが少し見えたような気がしますね。

明日は @y_sumida さんです。よろしくお願いします。

コメント

blog comments powered by Disqus