hgkw/keyword.py
branch0.9.2compat
changeset 379 290d023e8306
parent 348 63ebc698d06b
child 380 0ed26effe190
equal deleted inserted replaced
348:63ebc698d06b 379:290d023e8306
    77 
    77 
    78 Expansions spanning more than one line and incremental expansions,
    78 Expansions spanning more than one line and incremental expansions,
    79 like CVS' $Log$, are not supported. A keyword template map
    79 like CVS' $Log$, are not supported. A keyword template map
    80 "Log = {desc}" expands to the first line of the changeset description.
    80 "Log = {desc}" expands to the first line of the changeset description.
    81 
    81 
    82 Caveat: "hg import" fails if the patch context contains an active
    82 Caveat: With Mercurial versions prior to 4574925db5c0 "hg import" might
    83         keyword. In that case run "hg kwshrink", and then reimport.
    83         cause rejects if the patch context contains an active keyword.
       
    84         In that case run "hg kwshrink", and then reimport.
    84         Or, better, use bundle/unbundle to share changes.
    85         Or, better, use bundle/unbundle to share changes.
    85 '''
    86 '''
    86 
    87 
    87 from mercurial import commands, cmdutil, context, fancyopts
    88 from mercurial import commands, cmdutil, context, fancyopts
    88 from mercurial import filelog, localrepo, revlog, templater, util
    89 from mercurial import filelog, localrepo, revlog, templater, util
    89 from mercurial.node import *
    90 from mercurial.node import *
    90 from mercurial.i18n import gettext as _
    91 from mercurial.i18n import gettext as _
    91 import getopt, os.path, re, shutil, sys, tempfile, time
    92 import getopt, os.path, re, shutil, sys, tempfile, time
    92 
    93 
       
    94 commands.optionalrepo += ' kwdemo'
       
    95 
       
    96 # hg commands that do not act on keywords
       
    97 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
       
    98                 ' log outgoing push remove rename rollback tip convert')
       
    99 
       
   100 # hg commands that trigger expansion only when writing to working dir,
       
   101 # not when reading filelog, and unexpand when reading from working dir
       
   102 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
       
   103 
       
   104 def utcdate(date):
       
   105     '''Returns hgdate in cvs-like UTC format.'''
       
   106     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
       
   107 
       
   108 
       
   109 _kwtemplater = None
       
   110 
    93 # backwards compatibility hacks
   111 # backwards compatibility hacks
       
   112 
       
   113 try:
       
   114     # avoid spurious rejects if patchfile is available
       
   115     from mercurial.patch import patchfile
       
   116     _patchfile_init = patchfile.__init__
       
   117 
       
   118     def _kwpatchfile_init(self, ui, fname, missing=False):
       
   119         '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
       
   120         rejects or conflicts due to expanded keywords in working dir.'''
       
   121         _patchfile_init(self, ui, fname, missing=missing)
       
   122         if _kwtemplater.matcher(self.fname):
       
   123             # shrink keywords read from working dir
       
   124             kwshrunk = _kwtemplater.shrink(''.join(self.lines))
       
   125             self.lines = kwshrunk.splitlines(True)
       
   126 except ImportError:
       
   127     pass
       
   128 
       
   129 try:
       
   130     # templatefilters module introduced in 9f1e6ab76069
       
   131     from mercurial import templatefilters
       
   132     template_filters = templatefilters.filters
       
   133     template_firstline = templatefilters.firstline
       
   134 except ImportError:
       
   135     template_filters = templater.common_filters
       
   136     template_firstline = templater.firstline
    94 
   137 
    95 try:
   138 try:
    96     # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
   139     # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
    97     # also avoid name conflict with other dispatch package(s)
   140     # also avoid name conflict with other dispatch package(s)
    98     from mercurial.dispatch import _parse
   141     from mercurial.dispatch import _parse
   185     return args
   228     return args
   186 
   229 
   187 fancyopts.fancyopts = _fancyopts
   230 fancyopts.fancyopts = _fancyopts
   188 
   231 
   189 
   232 
   190 commands.optionalrepo += ' kwdemo'
       
   191 
       
   192 # hg commands that trigger expansion only when writing to working dir,
       
   193 # not when reading filelog, and unexpand when reading from working dir
       
   194 restricted = ('diff1', 'record',
       
   195               'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
       
   196 
       
   197 def utcdate(date):
       
   198     '''Returns hgdate in cvs-like UTC format.'''
       
   199     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
       
   200 
       
   201 
       
   202 _kwtemplater = None
       
   203 
       
   204 class kwtemplater(object):
   233 class kwtemplater(object):
   205     '''
   234     '''
   206     Sets up keyword templates, corresponding keyword regex, and
   235     Sets up keyword templates, corresponding keyword regex, and
   207     provides keyword substitution functions.
   236     provides keyword substitution functions.
   208     '''
   237     '''
   214         'Source': '{root}/{file},v',
   243         'Source': '{root}/{file},v',
   215         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   244         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   216         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   245         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   217     }
   246     }
   218 
   247 
   219     def __init__(self, ui, repo, inc, exc, hgcmd):
   248     def __init__(self, ui, repo, inc, exc, restricted):
   220         self.ui = ui
   249         self.ui = ui
   221         self.repo = repo
   250         self.repo = repo
   222         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   251         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   223         self.hgcmd = hgcmd
   252         self.restricted = restricted
   224         self.commitnode = None
   253         self.commitnode = None
   225         self.path = ''
   254         self.path = ''
   226 
   255 
   227         kwmaps = self.ui.configitems('keywordmaps')
   256         kwmaps = self.ui.configitems('keywordmaps')
   228         if kwmaps: # override default templates
   257         if kwmaps: # override default templates
   231             self.templates = dict(kwmaps)
   260             self.templates = dict(kwmaps)
   232         escaped = map(re.escape, self.templates.keys())
   261         escaped = map(re.escape, self.templates.keys())
   233         kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
   262         kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
   234         self.re_kw = re.compile(kwpat)
   263         self.re_kw = re.compile(kwpat)
   235 
   264 
   236         templater.common_filters['utcdate'] = utcdate
   265         template_filters['utcdate'] = utcdate
   237         self.ct = self._changeset_templater()
   266         self.ct = self._changeset_templater()
   238 
   267 
   239     def _changeset_templater(self):
   268     def _changeset_templater(self):
   240         '''Backwards compatible cmdutil.changeset_templater.'''
   269         '''Backwards compatible cmdutil.changeset_templater.'''
   241         # before 1e0b94cfba0e there was an extra "brinfo" argument
   270         # before 1e0b94cfba0e there was an extra "brinfo" argument
   259             '''Substitutes keyword using corresponding template.'''
   288             '''Substitutes keyword using corresponding template.'''
   260             kw = mobj.group(1)
   289             kw = mobj.group(1)
   261             self.ct.use_template(self.templates[kw])
   290             self.ct.use_template(self.templates[kw])
   262             self.ui.pushbuffer()
   291             self.ui.pushbuffer()
   263             self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
   292             self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
   264             return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
   293             return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer()))
   265 
   294 
   266         return subfunc(kwsub, data)
   295         return subfunc(kwsub, data)
   267 
   296 
   268     def expand(self, node, data):
   297     def expand(self, node, data):
   269         '''Returns data with keywords expanded.'''
   298         '''Returns data with keywords expanded.'''
   270         if util.binary(data) or self.hgcmd in restricted:
   299         if self.restricted or util.binary(data):
   271             return data
   300             return data
   272         return self.substitute(node, data, self.re_kw.sub)
   301         return self.substitute(node, data, self.re_kw.sub)
   273 
   302 
   274     def process(self, node, data, expand):
   303     def process(self, node, data, expand):
   275         '''Returns a tuple: data, count.
   304         '''Returns a tuple: data, count.
   508     files configured at all for keyword substitution.'''
   537     files configured at all for keyword substitution.'''
   509 
   538 
   510     if not repo.local():
   539     if not repo.local():
   511         return
   540         return
   512 
   541 
   513     nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
       
   514                     'export', 'grep', 'identify', 'incoming', 'init',
       
   515                     'log', 'outgoing', 'push', 'remove', 'rename',
       
   516                     'rollback', 'tip',
       
   517                     'convert')
       
   518     hgcmd, func, args, opts, cmdopts = _parse(ui, sys.argv[1:])
   542     hgcmd, func, args, opts, cmdopts = _parse(ui, sys.argv[1:])
   519     if hgcmd in nokwcommands:
   543     if hgcmd in nokwcommands.split():
   520         return
   544         return
   521 
   545 
   522     if hgcmd == 'diff':
   546     if hgcmd == 'diff':
   523         # only expand if comparing against working dir
   547         # only expand if comparing against working dir
   524         node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
   548         node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
   536             exc.append(pat)
   560             exc.append(pat)
   537     if not inc:
   561     if not inc:
   538         return
   562         return
   539 
   563 
   540     global _kwtemplater
   564     global _kwtemplater
   541     _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
   565     _restricted = hgcmd in restricted.split()
       
   566     _kwtemplater = kwtemplater(ui, repo, inc, exc, _restricted)
   542 
   567 
   543     class kwrepo(repo.__class__):
   568     class kwrepo(repo.__class__):
   544         def file(self, f, kwmatch=False):
   569         def file(self, f, kwmatch=False):
   545             if f[0] == '/':
   570             if f[0] == '/':
   546                 f = f[1:]
   571                 f = f[1:]
   548                 return kwfilelog(self.sopener, f)
   573                 return kwfilelog(self.sopener, f)
   549             return filelog.filelog(self.sopener, f)
   574             return filelog.filelog(self.sopener, f)
   550 
   575 
   551         def wread(self, filename):
   576         def wread(self, filename):
   552             data = super(kwrepo, self).wread(filename)
   577             data = super(kwrepo, self).wread(filename)
   553             if hgcmd in restricted and _kwtemplater.matcher(filename):
   578             if _restricted and _kwtemplater.matcher(filename):
   554                 return _kwtemplater.shrink(data)
   579                 return _kwtemplater.shrink(data)
   555             return data
   580             return data
   556 
   581 
   557         def _commit(self, files, text, user, date, match, force, lock, wlock,
   582         def _commit(self, files, text, user, date, match, force, lock, wlock,
   558                     force_editor, p1, p2, extra):
   583                     force_editor, p1, p2, extra, empty_ok):
   559             '''Private commit wrapper for backwards compatibility.'''
   584             '''Private commit wrapper for backwards compatibility.'''
   560             try:
   585             try:
   561                 return super(kwrepo, self).commit(files=files, text=text,
   586                 return super(kwrepo, self).commit(files=files, text=text,
   562                                                   user=user, date=date,
   587                                                   user=user, date=date,
   563                                                   match=match, force=force,
   588                                                   match=match, force=force,
   564                                                   lock=lock, wlock=wlock,
   589                                                   lock=lock, wlock=wlock,
   565                                                   force_editor=force_editor,
   590                                                   force_editor=force_editor,
   566                                                   p1=p1, p2=p2, extra=extra)
   591                                                   p1=p1, p2=p2, extra=extra)
   567             except TypeError:
   592             except TypeError:
   568                 return super(kwrepo, self).commit(files=files, text=text,
   593                 try:
   569                                                   user=user, date=date,
   594                     return super(kwrepo, self).commit(files=files, text=text,
   570                                                   match=match, force=force,
   595                                                       user=user, date=date,
   571                                                   force_editor=force_editor,
   596                                                       match=match, force=force,
   572                                                   p1=p1, p2=p2, extra=extra)
   597                                                       force_editor=force_editor,
       
   598                                                       p1=p1, p2=p2,
       
   599                                                       extra=extra,
       
   600                                                       empty_ok=empty_ok)
       
   601                 except TypeError:
       
   602                     return super(kwrepo, self).commit(files=files, text=text,
       
   603                                                       user=user, date=date,
       
   604                                                       match=match, force=force,
       
   605                                                       force_editor=force_editor,
       
   606                                                       p1=p1, p2=p2, extra=extra)
   573 
   607 
   574         def commit(self, files=None, text='', user=None, date=None,
   608         def commit(self, files=None, text='', user=None, date=None,
   575                    match=util.always, force=False, lock=None, wlock=None,
   609                    match=util.always, force=False, lock=None, wlock=None,
   576                    force_editor=False, p1=None, p2=None, extra={}):
   610                    force_editor=False, p1=None, p2=None, extra={},
       
   611                    empty_ok=False):
   577             # (w)lock arguments removed in 126f527b3ba3
   612             # (w)lock arguments removed in 126f527b3ba3
   578             # so they are None or what was passed to commit
   613             # so they are None or what was passed to commit
   579             # use private _(w)lock for deletion
   614             # use private _(w)lock for deletion
   580             _lock = lock
   615             _lock = lock
   581             _wlock = wlock
   616             _wlock = wlock
   603                         _p2 = ''
   638                         _p2 = ''
   604                     else:
   639                     else:
   605                         _p2 = hex(_p2)
   640                         _p2 = hex(_p2)
   606 
   641 
   607                 node = self._commit(files, text, user, date, match, force,
   642                 node = self._commit(files, text, user, date, match, force,
   608                                     _lock, _wlock, force_editor, p1, p2, extra)
   643                                     _lock, _wlock, force_editor, p1, p2, extra,
       
   644                                     empty_ok)
   609 
   645 
   610                 # restore commit hooks
   646                 # restore commit hooks
   611                 for name, cmd in commithooks.iteritems():
   647                 for name, cmd in commithooks.iteritems():
   612                     ui.setconfig('hooks', name, cmd)
   648                     ui.setconfig('hooks', name, cmd)
   613                 if node is not None:
   649                 if node is not None:
   616                 return node
   652                 return node
   617             finally:
   653             finally:
   618                 del _wlock, _lock
   654                 del _wlock, _lock
   619 
   655 
   620     repo.__class__ = kwrepo
   656     repo.__class__ = kwrepo
       
   657     try:
       
   658         patchfile.__init__ = _kwpatchfile_init
       
   659     except NameError:
       
   660         pass
   621 
   661 
   622 
   662 
   623 cmdtable = {
   663 cmdtable = {
   624     'kwdemo':
   664     'kwdemo':
   625         (demo,
   665         (demo,