hgkw/keyword.py
branch0.9.2compat
changeset 343 f5927e5574e6
parent 339 98336da24c5e
child 348 63ebc698d06b
equal deleted inserted replaced
339:98336da24c5e 343:f5927e5574e6
    69 the risk of inadvertedly storing expanded keywords in the change history.
    69 the risk of inadvertedly storing expanded keywords in the change history.
    70 
    70 
    71 To force expansion after enabling it, or a configuration change, run
    71 To force expansion after enabling it, or a configuration change, run
    72 "hg kwexpand".
    72 "hg kwexpand".
    73 
    73 
       
    74 Also, when committing with the record extension or using mq's qrecord, be aware
       
    75 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
       
    76 question to update keyword expansions after all changes have been checked in.
       
    77 
    74 Expansions spanning more than one line and incremental expansions,
    78 Expansions spanning more than one line and incremental expansions,
    75 like CVS' $Log$, are not supported. A keyword template map
    79 like CVS' $Log$, are not supported. A keyword template map
    76 "Log = {desc}" expands to the first line of the changeset description.
    80 "Log = {desc}" expands to the first line of the changeset description.
    77 
    81 
    78 Caveat: "hg import" fails if the patch context contains an active
    82 Caveat: "hg import" fails if the patch context contains an active
    79         keyword. In that case run "hg kwshrink", and then reimport.
    83         keyword. In that case run "hg kwshrink", and then reimport.
    80         Or, better, use bundle/unbundle to share changes.
    84         Or, better, use bundle/unbundle to share changes.
    81 '''
    85 '''
    82 
    86 
    83 from mercurial import commands, cmdutil, context
    87 from mercurial import commands, cmdutil, context, fancyopts
    84 from mercurial import filelog, localrepo, revlog, templater, util
    88 from mercurial import filelog, localrepo, revlog, templater, util
    85 from mercurial.node import *
    89 from mercurial.node import *
    86 from mercurial.i18n import gettext as _
    90 from mercurial.i18n import gettext as _
    87 import getopt, os.path, re, shutil, sys, tempfile, time
    91 import getopt, os.path, re, shutil, sys, tempfile, time
    88 
    92 
    89 # backwards compatibility hacks
    93 # backwards compatibility hacks
       
    94 
       
    95 try:
       
    96     # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
       
    97     # also avoid name conflict with other dispatch package(s)
       
    98     from mercurial.dispatch import _parse
       
    99 except ImportError:
       
   100     try:
       
   101         # commands.parse moves to cmdutil.parse in 0c61124ad877
       
   102         _parse = cmdutil.parse
       
   103     except AttributeError:
       
   104         _parse = commands.parse
    90 
   105 
    91 def _wwrite(repo, f, data, mf):
   106 def _wwrite(repo, f, data, mf):
    92     '''Makes repo.wwrite backwards compatible.'''
   107     '''Makes repo.wwrite backwards compatible.'''
    93     # 656e06eebda7 removed file descriptor argument
   108     # 656e06eebda7 removed file descriptor argument
    94     # 67982d3ee76c added flags argument
   109     # 67982d3ee76c added flags argument
   113     except AttributeError:
   128     except AttributeError:
   114         return f
   129         return f
   115 
   130 
   116 # commands.parse/cmdutil.parse returned nothing for
   131 # commands.parse/cmdutil.parse returned nothing for
   117 # "hg diff --rev" before 88803a69b24a due to bug in fancyopts
   132 # "hg diff --rev" before 88803a69b24a due to bug in fancyopts
   118 def fancyopts(args, options, state):
   133 def _fancyopts(args, options, state):
   119     '''Fixed fancyopts from a9b7e425674f.'''
   134     '''Fixed fancyopts from a9b7e425674f.'''
   120     namelist = []
   135     namelist = []
   121     shortlist = ''
   136     shortlist = ''
   122     argmap = {}
   137     argmap = {}
   123     defmap = {}
   138     defmap = {}
   167             state[name] = True
   182             state[name] = True
   168 
   183 
   169     # return unparsed args
   184     # return unparsed args
   170     return args
   185     return args
   171 
   186 
   172 def findcmd(ui, cmd, table):
   187 fancyopts.fancyopts = _fancyopts
   173     '''findcmd has table argument since 18a9fbb5cd78.'''
       
   174     try:
       
   175         return findcmd.findcmd(ui, cmd, table)
       
   176     except TypeError:
       
   177         return findcmd.findcmd(ui, cmd)
       
   178 # findcmd in commands until 0c61124ad877
       
   179 try:
       
   180     findcmd.findcmd = cmdutil.findcmd
       
   181     findcmd.__doc__ = cmdutil.findcmd.__doc__
       
   182 except AttributeError:
       
   183     findcmd.findcmd = commands.findcmd
       
   184     findcmd.__doc__ = commands.findcmd.__doc__
       
   185 
   188 
   186 
   189 
   187 commands.optionalrepo += ' kwdemo'
   190 commands.optionalrepo += ' kwdemo'
   188 
   191 
   189 def utcdate(date):
   192 def utcdate(date):
   190     '''Returns hgdate in cvs-like UTC format.'''
   193     '''Returns hgdate in cvs-like UTC format.'''
   191     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   194     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
       
   195 
       
   196 def _kwrestrict(cmd):
       
   197     '''Returns True if cmd should trigger restricted expansion.
       
   198     Keywords will only expanded when writing to working dir.
       
   199     Crucial for mq as expanded keywords should not make it into patches.'''
       
   200     return cmd in ('diff1', 
       
   201                    'qimport', 'qnew', 'qpush', 'qrefresh', 'record', 'qrecord')
       
   202 
   192 
   203 
   193 _kwtemplater = None
   204 _kwtemplater = None
   194 
   205 
   195 class kwtemplater(object):
   206 class kwtemplater(object):
   196     '''
   207     '''
   205         'Source': '{root}/{file},v',
   216         'Source': '{root}/{file},v',
   206         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   217         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   207         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   218         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   208     }
   219     }
   209 
   220 
   210     def __init__(self, ui, repo, inc, exc):
   221     def __init__(self, ui, repo, inc, exc, hgcmd):
   211         self.ui = ui
   222         self.ui = ui
   212         self.repo = repo
   223         self.repo = repo
   213         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   224         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
       
   225         self.hgcmd = hgcmd
   214         self.commitnode = None
   226         self.commitnode = None
   215         self.path = ''
   227         self.path = ''
   216 
   228 
   217         kwmaps = self.ui.configitems('keywordmaps')
   229         kwmaps = self.ui.configitems('keywordmaps')
   218         if kwmaps: # override default templates
   230         if kwmaps: # override default templates
   255 
   267 
   256         return subfunc(kwsub, data)
   268         return subfunc(kwsub, data)
   257 
   269 
   258     def expand(self, node, data):
   270     def expand(self, node, data):
   259         '''Returns data with keywords expanded.'''
   271         '''Returns data with keywords expanded.'''
   260         if util.binary(data):
   272         if util.binary(data) or _kwrestrict(self.hgcmd):
   261             return data
   273             return data
   262         return self.substitute(node, data, self.re_kw.sub)
   274         return self.substitute(node, data, self.re_kw.sub)
   263 
   275 
   264     def process(self, node, data, expand):
   276     def process(self, node, data, expand):
   265         '''Returns a tuple: data, count.
   277         '''Returns a tuple: data, count.
   495     Wraps commit to overwrite configured files with updated
   507     Wraps commit to overwrite configured files with updated
   496     keyword substitutions.
   508     keyword substitutions.
   497     This is done for local repos only, and only if there are
   509     This is done for local repos only, and only if there are
   498     files configured at all for keyword substitution.'''
   510     files configured at all for keyword substitution.'''
   499 
   511 
   500     def kwbailout():
   512     if not repo.local():
   501         '''Obtains command via simplified cmdline parsing,
       
   502         returns True if keyword expansion not needed.'''
       
   503         nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
       
   504                         'export', 'grep', 'identify', 'incoming', 'init',
       
   505                         'log', 'outgoing', 'push', 'remove', 'rename',
       
   506                         'rollback', 'tip',
       
   507                         'convert')
       
   508         args = fancyopts(sys.argv[1:], commands.globalopts, {})
       
   509         if args:
       
   510             aliases, i = findcmd(ui, args[0], commands.table)
       
   511             return aliases[0] in nokwcommands
       
   512 
       
   513     if not repo.local() or kwbailout():
       
   514         return
   513         return
       
   514 
       
   515     nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
       
   516                     'export', 'grep', 'identify', 'incoming', 'init',
       
   517                     'log', 'outgoing', 'push', 'remove', 'rename',
       
   518                     'rollback', 'tip',
       
   519                     'convert')
       
   520     hgcmd, func, args, opts, cmdopts = _parse(ui, sys.argv[1:])
       
   521     if hgcmd in nokwcommands:
       
   522         return
       
   523 
       
   524     if hgcmd == 'diff':
       
   525         # only expand if comparing against working dir
       
   526         node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
       
   527         if node2 is not None:
       
   528             return
       
   529         # shrink if rev is not current node
       
   530         if node1 is not None and node1 != repo.changectx().node():
       
   531             hgcmd = 'diff1'
   515 
   532 
   516     inc, exc = [], ['.hgtags']
   533     inc, exc = [], ['.hgtags']
   517     for pat, opt in ui.configitems('keyword'):
   534     for pat, opt in ui.configitems('keyword'):
   518         if opt != 'ignore':
   535         if opt != 'ignore':
   519             inc.append(pat)
   536             inc.append(pat)
   521             exc.append(pat)
   538             exc.append(pat)
   522     if not inc:
   539     if not inc:
   523         return
   540         return
   524 
   541 
   525     global _kwtemplater
   542     global _kwtemplater
   526     _kwtemplater = kwtemplater(ui, repo, inc, exc)
   543     _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
   527 
   544 
   528     class kwrepo(repo.__class__):
   545     class kwrepo(repo.__class__):
   529         def file(self, f, kwmatch=False):
   546         def file(self, f, kwmatch=False):
   530             if f[0] == '/':
   547             if f[0] == '/':
   531                 f = f[1:]
   548                 f = f[1:]
   532             if kwmatch or _kwtemplater.matcher(f):
   549             if kwmatch or _kwtemplater.matcher(f):
   533                 return kwfilelog(self.sopener, f)
   550                 return kwfilelog(self.sopener, f)
   534             return filelog.filelog(self.sopener, f)
   551             return filelog.filelog(self.sopener, f)
       
   552 
       
   553         def wread(self, filename):
       
   554             data = super(kwrepo, self).wread(filename)
       
   555             if _kwrestrict(hgcmd) and _kwtemplater.matcher(filename):
       
   556                 return _kwtemplater.shrink(data)
       
   557             return data
   535 
   558 
   536         def _commit(self, files, text, user, date, match, force, lock, wlock,
   559         def _commit(self, files, text, user, date, match, force, lock, wlock,
   537                     force_editor, p1, p2, extra):
   560                     force_editor, p1, p2, extra):
   538             '''Private commit wrapper for backwards compatibility.'''
   561             '''Private commit wrapper for backwards compatibility.'''
   539             try:
   562             try: