hgkw/keyword.py
author Christian Ebert <blacktrash@gmx.net>
Thu, 21 Dec 2006 19:52:25 +0100
branchsolo-extension
changeset 48 59fedb6b41da
parent 47 0617e7d497f6
child 49 5acfe7dc3086
permissions -rw-r--r--
add header and start documentation

# keyword.py - keyword expansion for mercurial
#
# Copyright 2006 Christian Ebert <blacktrash@gmx.net>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

'''keyword expansion hack against the grain of a DSCM

This extension lets you expand RCS/CVS-like keywords in a Mercurial
repository.

There are many good reasons why this is not needed in a distributed
SCM, still it may be useful in very small projects based on single
files (like LaTeX packages), that are mostly addressed to an audience
not running version control.

The extension consists actually in 2 parts:

    1. extension code (reposetup) that is triggered on checkout and
       logging of changes.
    2. a pretxncommit hook (hgrc (5)) that expands keywords immediately
       at commit time in the working directory.

Simple setup in hgrc:

    # enable extension
    hgext.keyword =
    
    # filename patterns for expansion are configured in this section
    [keyword]
    *.sty = expand
    ...

    # set up pretxncommit hook
    [hooks]
    pretxncommit =
    pretxncommit.keyword = python:hgext.keyword.pretxnkw
'''

from mercurial.i18n import _
from mercurial import commands, cmdutil, context, filelog, revlog, util
import os.path, re, sys

# supported keywords for use in regexes
hgkeywords = 'Id|Header|Author|Date|Revision|RCSFile|Source'

def kwexpand(matchobj, repo, Revision, f, date, Author):
    '''Called by kwfilelog.read and pretxnkw.
    Sets supported keywords as local variables and evaluates them to
    their expansion if matchobj is equal to string representation.'''

    RCSFile = os.path.basename(f)+',v'
    Source = os.path.join(repo.root, f)+',v'
    Date = util.datestr(date=date)
    revdateauth = '%s %s %s' % (Revision,       # %Y-%m-%d %H:%M:%S
            util.datestr(date=date, format=util.defaultdateformats[0]),
            util.shortuser(Author))
    Header = '%s %s' % (Source, revdateauth)
    Id = '%s %s' % (RCSFile, revdateauth)

    return '$%s: %s $' % (matchobj.group(1), eval(matchobj.group(1)))


def reposetup(ui, repo):
    if not repo.local():
        return

    class kwrepo(repo.__class__):
        def file(self, f):
            if f[0] == '/':
                f = f[1:]
            return filelog.filelog(self.sopener, f, self, self.revlogversion)

    class kwfilelog(filelog.filelog):
        def __init__(self, opener, path, repo,
                     defversion=revlog.REVLOG_DEFAULT_VERSION):
            super(kwfilelog, self).__init__(opener, path, defversion)
            self._repo = repo
            self._path = path

        def read(self, node):
            data = super(kwfilelog, self).read(node)
            if util.binary(data):
                return data

            c = context.filectx(self._repo, self._path, fileid=node,
                                 filelog=self)
            f = c.path()
            if f.startswith('.hg'):
                return data

            for pat, opt in self._repo.ui.configitems('keyword'):
                if opt == 'expand':
                    mf = util.matcher(self._repo.root,
                            '', [pat], [], [])[1]
                    if mf(f):

                        def kwexpander(matchobj):
                            return kwexpand(matchobj,
                                    self._repo, c.changectx(), f,
                                    c.date(), c.user())

                        re_kw = re.compile(r'\$(%s)\$' % hgkeywords)
                        return re_kw.sub(kwexpander, data)
            return data

        def add(self, text, meta, tr, link, p1=None, p2=None):
            if (not util.binary(text) and
                   self._repo.ui.config('keyword', 'remove', True)):
                re_kw = re.compile(r'\$(%s): [^$]+? \$' % hgkeywords)
                text = re_kw.sub(r'$\1$', text)
            return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)

    filelog.filelog = kwfilelog
    repo.__class__ = kwrepo


def pretxnkw(ui, repo, hooktype, **args):
    '''pretxncommit hook that collects candidates for keyword expansion
    on commit and expands keywords in working dir.'''

    if hooktype != 'pretxncommit': # bail out with error
        return True

    # reparse args, opts again as pretxncommit hook is silent about them
    cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:]
    # exclude tag and import
    if repr(cmd).split()[1] in ('tag', 'import_'):
        return False

    files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts)
    # validity checks should have been done already
    modified, added = repo.status(files=files, match=match)[:2]
    candidates = [f for f in modified + added if not f.startswith('.hg')]

    if not candidates:
        return False

    # only check files that are configured in keyword section
    files = []
    # python2.4: files = set()
    for pat, opt in repo.ui.configitems('keyword'):
        if opt == 'expand':
            mf = util.matcher(repo.root, '', [pat], [], [])[1]
            for candidate in candidates:
                if mf(candidate) and candidate not in files:
                    files.append(candidate)
                # python2.4:
                # if mf(candidate): files.add(candidate)

    if not files:
        return False

    user, date = repo.changelog.read(repo.changelog.tip())[1:3]
    # expand both expanded and unexpanded keywords
    re_kw = re.compile(r'\$(%s)(: [^$]+? )?\$' % hgkeywords)

    for f in files:
        data = repo.wfile(f).read()
        if not util.binary(data):

            def kwexpander(matchobj):
                return kwexpand(matchobj,
                        repo, args['node'][:12], f, date, user)

            data, kwct = re_kw.subn(kwexpander, data)
            if kwct:
                ui.note(_('expanding keywords in %s\n' % f))
                # backup file?
                repo.wfile(f, 'w').write(data)