hgkw/keyword.py
branchself_initializing_hook
changeset 81 fd5d3a974ea7
parent 80 cee5fef33cf8
child 82 9bf0f7db5928
equal deleted inserted replaced
68:b285ab731fff 81:fd5d3a974ea7
     8 There are many good reasons why this is not needed in a distributed
     8 There are many good reasons why this is not needed in a distributed
     9 SCM, still it may be useful in very small projects based on single
     9 SCM, still it may be useful in very small projects based on single
    10 files (like LaTeX packages), that are mostly addressed to an audience
    10 files (like LaTeX packages), that are mostly addressed to an audience
    11 not running a version control system.
    11 not running a version control system.
    12 
    12 
    13 Supported keywords are (changeset 000000000000):
    13 Supported $keywords$ and their $keyword: substition $ are:
    14     $Revision: 000000000000 $
    14     Revision: changeset id
    15     $Author: Your Name <address@example.com> $
    15     Author:   full username
    16     $Date: %a %b %d %H:%M:%S %Y %z $
    16     Date:     %a %b %d %H:%M:%S %Y %z $
    17     $RCSFile: basename,v $
    17     RCSFile:  basename,v
    18     $Source: /path/to/basename,v $
    18     Source:   /path/to/basename,v
    19     $Id: basename,v 000000000000 %Y-%m-%d %H:%M:%S %z shortname $
    19     Id:       basename,v csetid %Y-%m-%d %H:%M:%S %z shortname
    20     $Header: /path/to/basename,v 000000000000 %Y-%m-%d %H:%M:%S %z shortname $
    20     Header:   /path/to/basename,v csetid %Y-%m-%d %H:%M:%S %z shortname
    21 
    21 
    22 The extension, according to its hackish nature, is a hybrid and consists
    22 Install:
    23 actually in 2 parts:
    23     Copy keyword.py in hgext folder.
    24 
       
    25     1. pure extension code (reposetup) that is triggered on checkout and
       
    26        logging of changes.
       
    27     2. a pretxncommit hook (hgrc (5)) that expands keywords immediately
       
    28        at commit time in the working directory.
       
    29 
    24 
    30 Simple setup in hgrc:
    25 Simple setup in hgrc:
    31 
    26 
    32     # enable extension
    27     # enable extension
    33     hgext.keyword =
    28     hgext.keyword =
    34     
    29     
    35     # filename patterns for expansion are configured in this section
    30     # filename patterns for expansion are configured in this section
    36     [keyword]
    31     [keyword]
    37     *.sty = expand
    32     **.py = expand
    38     ...
    33     ...
    39 
       
    40     # set up pretxncommit hook
       
    41     [hooks]
       
    42     pretxncommit =
       
    43     pretxncommit.keyword = python:hgext.keyword.pretxnkw
       
    44 '''
    34 '''
    45 
    35 
    46 from mercurial.i18n import _
       
    47 from mercurial import context, util
    36 from mercurial import context, util
    48 import os.path, re
    37 import os.path, re
    49 
    38 
    50 
    39 
    51 re_kw = re.compile(
    40 re_kw = re.compile(
    54 
    43 
    55 def kwexpand(matchobj, repo, path, changeid=None, fileid=None, filelog=None):
    44 def kwexpand(matchobj, repo, path, changeid=None, fileid=None, filelog=None):
    56     '''Called by kwfilelog.read and pretxnkw.
    45     '''Called by kwfilelog.read and pretxnkw.
    57     Sets supported keywords as local variables and evaluates them to
    46     Sets supported keywords as local variables and evaluates them to
    58     their expansion if matchobj is equal to string representation.'''
    47     their expansion if matchobj is equal to string representation.'''
    59 
       
    60     c = context.filectx(repo, path,
    48     c = context.filectx(repo, path,
    61             changeid=changeid, fileid=fileid, filelog=filelog)
    49             changeid=changeid, fileid=fileid, filelog=filelog)
    62     date = c.date()
    50     date = c.date()
    63 
       
    64     Revision = c.changectx()
    51     Revision = c.changectx()
    65     Author = c.user()
    52     Author = c.user()
    66     RCSFile = os.path.basename(path)+',v'
    53     RCSFile = os.path.basename(path)+',v'
    67     Source = repo.wjoin(path)+',v'
    54     Source = repo.wjoin(path)+',v'
    68     Date = util.datestr(date=date)
    55     Date = util.datestr(date=date)
    69     revdateauth = '%s %s %s' % (Revision,
    56     revdateauth = '%s %s %s' % (Revision,
    70             util.datestr(date=date, format=util.defaultdateformats[0]),
    57             util.datestr(date=date, format=util.defaultdateformats[0]),
    71             util.shortuser(Author))
    58             util.shortuser(Author))
    72     Header = '%s %s' % (Source, revdateauth)
    59     Header = '%s %s' % (Source, revdateauth)
    73     Id = '%s %s' % (RCSFile, revdateauth)
    60     Id = '%s %s' % (RCSFile, revdateauth)
    74 
       
    75     return '$%s: %s $' % (matchobj.group(1), eval(matchobj.group(1)))
    61     return '$%s: %s $' % (matchobj.group(1), eval(matchobj.group(1)))
    76 
    62 
    77 def kwfmatchers(ui, repo):
    63 def kwfmatches(ui, repo, files):
    78     '''Returns filename matchers from ui keyword section.'''
    64     '''Selects candidates for keyword substitution
    79     return [util.matcher(repo.root, '', [pat], [], [])[1]
    65     configured in keyword section in hgrc.'''
       
    66     files = [f for f in files if not f.startswith('.hg')]
       
    67     if not files:
       
    68         return []
       
    69     candidates = []
       
    70     fmatchers = [util.matcher(repo.root, '', [pat], [], [])[1]
    80             for pat, opt in ui.configitems('keyword')
    71             for pat, opt in ui.configitems('keyword')
    81             if opt == 'expand']
    72             if opt == 'expand']
       
    73     for f in files:
       
    74         for mf in fmatchers:
       
    75             if mf(f):
       
    76                 candidates.append(f)
       
    77                 break
       
    78     return candidates
    82 
    79 
    83 
    80 
    84 def reposetup(ui, repo):
    81 def reposetup(ui, repo):
    85     from mercurial import filelog, revlog
    82     from mercurial import filelog, revlog
    86 
    83 
    92             if f[0] == '/':
    89             if f[0] == '/':
    93                 f = f[1:]
    90                 f = f[1:]
    94             return filelog.filelog(self.sopener, f, self, self.revlogversion)
    91             return filelog.filelog(self.sopener, f, self, self.revlogversion)
    95 
    92 
    96     class kwfilelog(filelog.filelog):
    93     class kwfilelog(filelog.filelog):
       
    94         '''
       
    95         Superclass over filelog to customize it's read, add, cmp methods.
       
    96         Keywords are "stored" unexpanded, and expanded on reading.
       
    97         '''
    97         def __init__(self, opener, path, repo,
    98         def __init__(self, opener, path, repo,
    98                      defversion=revlog.REVLOG_DEFAULT_VERSION):
    99                      defversion=revlog.REVLOG_DEFAULT_VERSION):
    99             super(kwfilelog, self).__init__(opener, path, defversion)
   100             super(kwfilelog, self).__init__(opener, path, defversion)
   100             self._repo = repo
   101             self._repo = repo
   101             self._path = path
   102             self._path = path
   102 
   103 
       
   104         def iskwcandidate(self, data):
       
   105             '''Decides whether to act on keywords.'''
       
   106             return (kwfmatches(ui, self._repo, [self._path])
       
   107                     and not util.binary(data))
       
   108 
   103         def read(self, node):
   109         def read(self, node):
       
   110             '''Substitutes keywords when reading filelog.'''
   104             data = super(kwfilelog, self).read(node)
   111             data = super(kwfilelog, self).read(node)
   105             if not self._path.startswith('.hg') and not util.binary(data):
   112             if self.iskwcandidate(data):
   106                 for mf in kwfmatchers(ui, self._repo):
   113                 return re_kw.sub(lambda m:
   107                     if mf(self._path):
   114                         kwexpand(m, self._repo, self._path,
   108                         ui.debug(_('expanding keywords in %s\n' % self._path))
   115                             fileid=node, filelog=self), data)
   109                         return re_kw.sub(lambda m:
       
   110                                 kwexpand(m, self._repo, self._path,
       
   111                                     fileid=node, filelog=self),
       
   112                                 data)
       
   113             return data
   116             return data
   114 
   117 
   115         def size(self, rev):
   118         def add(self, text, meta, tr, link, p1=None, p2=None):
   116             '''Overrides filelog's size() to use kwfilelog.read().'''
   119             '''Removes keyword substitutions when adding to filelog.'''
   117             node = revlog.node(self, rev)
   120             if self.iskwcandidate(text):
   118             if super(kwfilelog, self).renamed(node):
   121                 text = re_kw.sub(r'$\1$', text)
   119                 return len(self.read(node))
   122             return super(kwfilelog, self).add(text,
   120             return revlog.size(self, rev)
   123                     meta, tr, link, p1=None, p2=None)
   121 
   124 
   122         def cmp(self, node, text):
   125         def cmp(self, node, text):
   123             '''Overrides filelog's cmp() to use kwfilelog.read().'''
   126             '''Removes keyword substitutions for comparison.'''
   124             if super(kwfilelog, self).renamed(node):
   127             if self.iskwcandidate(text):
   125                 t2 = self.read(node)
   128                 text = re_kw.sub(r'$\1$', text)
   126                 return t2 != text
   129             return super(kwfilelog, self).cmp(node, text)
   127 
   130 
   128     filelog.filelog = kwfilelog
   131     filelog.filelog = kwfilelog
   129     repo.__class__ = kwrepo
   132     repo.__class__ = kwrepo
       
   133     ui.setconfig('hooks',
       
   134             'pretxncommit.keyword', 'python:hgext.keyword.pretxnkw')
   130 
   135 
   131 
   136 
   132 def pretxnkw(ui, repo, hooktype, **args):
   137 def pretxnkw(ui, repo, hooktype, **args):
   133     '''pretxncommit hook that collects candidates for keyword expansion
   138     '''pretxncommit hook that collects candidates for keyword expansion
   134     on commit and expands keywords in working dir.'''
   139     on commit and expands keywords in working dir.'''
       
   140     from mercurial.i18n import gettext as _
   135     from mercurial import cmdutil, commands
   141     from mercurial import cmdutil, commands
   136     import sys
   142     import sys
   137 
   143 
   138     if hooktype != 'pretxncommit':
   144     if hooktype != 'pretxncommit':
   139         return True
   145         return True
   142     if repr(cmd).split()[1] in ('tag', 'import_'):
   148     if repr(cmd).split()[1] in ('tag', 'import_'):
   143         return False
   149         return False
   144 
   150 
   145     files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts)
   151     files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts)
   146     modified, added = repo.status(files=files, match=match)[:2]
   152     modified, added = repo.status(files=files, match=match)[:2]
   147     candidates = [f for f in modified + added if not f.startswith('.hg')]
       
   148     if not candidates:
       
   149         return False
       
   150 
   153 
   151     fmatchers = kwfmatchers(ui, repo)
   154     for f in kwfmatches(ui, repo, modified+added):
   152     for f in candidates:
   155         data = repo.wfile(f).read()
   153         for mf in fmatchers:
   156         if not util.binary(data):
   154             if mf(f):
   157             data, kwct = re_kw.subn(lambda m:
   155                 data = repo.wfile(f).read()
   158                     kwexpand(m, repo, f, changeid=args['node']),
   156                 if not util.binary(data):
   159                     data)
   157                     data, kwct = re_kw.subn(lambda m:
   160             if kwct:
   158                             kwexpand(m, repo, f, changeid=args['node']),
   161                 ui.debug(_('overwriting %s expanding keywords\n' % f))
   159                             data)
   162                 repo.wfile(f, 'w').write(data)
   160                     if kwct:
       
   161                         ui.debug(_('overwriting %s expanding keywords\n' % f))
       
   162                         repo.wfile(f, 'w').write(data)
       
   163                 break