hgkw/keyword.py
branchstable
changeset 389 d18a329bf222
parent 386 c27209bf8385
child 390 ee07603fab12
equal deleted inserted replaced
375:ed160b097bad 389:d18a329bf222
    82 
    82 
    83 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
    83 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
    84 from mercurial import patch, localrepo, templater, templatefilters, util
    84 from mercurial import patch, localrepo, templater, templatefilters, util
    85 from mercurial.node import *
    85 from mercurial.node import *
    86 from mercurial.i18n import _
    86 from mercurial.i18n import _
    87 import re, shutil, sys, tempfile, time
    87 import re, shutil, tempfile, time
    88 
    88 
    89 commands.optionalrepo += ' kwdemo'
    89 commands.optionalrepo += ' kwdemo'
    90 
    90 
    91 # hg commands that do not act on keywords
    91 # hg commands that do not act on keywords
    92 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
    92 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
    93                 ' log outgoing push remove rename rollback tip convert')
    93                 ' log outgoing push remove rename rollback tip convert email')
    94 
    94 
    95 # hg commands that trigger expansion only when writing to working dir,
    95 # hg commands that trigger expansion only when writing to working dir,
    96 # not when reading filelog, and unexpand when reading from working dir
    96 # not when reading filelog, and unexpand when reading from working dir
    97 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
    97 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
    98 
    98 
    99 def utcdate(date):
    99 def utcdate(date):
   100     '''Returns hgdate in cvs-like UTC format.'''
   100     '''Returns hgdate in cvs-like UTC format.'''
   101     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   101     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   102 
   102 
   103 
   103 
   104 _kwtemplater = None
   104 _kwtemplater, _cmd, _cmdoptions = None, None, None
       
   105  
       
   106 # store originals of monkeypatches
       
   107 _patchfile_init = patch.patchfile.__init__
       
   108 _dispatch_parse = dispatch._parse
       
   109 
       
   110 def _kwpatchfile_init(self, ui, fname, missing=False):
       
   111     '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
       
   112     rejects or conflicts due to expanded keywords in working dir.'''
       
   113     _patchfile_init(self, ui, fname, missing=missing)
       
   114     if _kwtemplater.matcher(self.fname):
       
   115         # shrink keywords read from working dir
       
   116         kwshrunk = _kwtemplater.shrink(''.join(self.lines))
       
   117         self.lines = kwshrunk.splitlines(True)
       
   118 
       
   119 def _kwdispatch_parse(ui, args):
       
   120     '''Monkeypatch dispatch._parse to obtain
       
   121     current command and command options (global _cmd, _cmdoptions).'''
       
   122     global _cmd, _cmdoptions
       
   123     _cmd, func, args, options, _cmdoptions = _dispatch_parse(ui, args)
       
   124     return _cmd, func, args, options, _cmdoptions
       
   125 
       
   126 dispatch._parse = _kwdispatch_parse
       
   127 
   105 
   128 
   106 class kwtemplater(object):
   129 class kwtemplater(object):
   107     '''
   130     '''
   108     Sets up keyword templates, corresponding keyword regex, and
   131     Sets up keyword templates, corresponding keyword regex, and
   109     provides keyword substitution functions.
   132     provides keyword substitution functions.
   116         'Source': '{root}/{file},v',
   139         'Source': '{root}/{file},v',
   117         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   140         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   118         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   141         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   119     }
   142     }
   120 
   143 
   121     def __init__(self, ui, repo, inc, exc, restricted):
   144     def __init__(self, ui, repo, inc, exc, restrict):
   122         self.ui = ui
   145         self.ui = ui
   123         self.repo = repo
   146         self.repo = repo
   124         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   147         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   125         self.restricted = restricted
   148         self.restrict = restrict
   126         self.commitnode = None
   149         self.commitnode = None
   127         self.path = ''
   150         self.path = ''
   128 
   151 
   129         kwmaps = self.ui.configitems('keywordmaps')
   152         kwmaps = self.ui.configitems('keywordmaps')
   130         if kwmaps: # override default templates
   153         if kwmaps: # override default templates
   159 
   182 
   160         return subfunc(kwsub, data)
   183         return subfunc(kwsub, data)
   161 
   184 
   162     def expand(self, node, data):
   185     def expand(self, node, data):
   163         '''Returns data with keywords expanded.'''
   186         '''Returns data with keywords expanded.'''
   164         if self.restricted or util.binary(data):
   187         if self.restrict or util.binary(data):
   165             return data
   188             return data
   166         return self.substitute(node, data, self.re_kw.sub)
   189         return self.substitute(node, data, self.re_kw.sub)
   167 
   190 
   168     def process(self, node, data, expand):
   191     def process(self, node, data, expand):
   169         '''Returns a tuple: data, count.
   192         '''Returns a tuple: data, count.
   210         text = _kwtemplater.shrink(text)
   233         text = _kwtemplater.shrink(text)
   211         if self.renamed(node):
   234         if self.renamed(node):
   212             t2 = super(kwfilelog, self).read(node)
   235             t2 = super(kwfilelog, self).read(node)
   213             return t2 != text
   236             return t2 != text
   214         return revlog.revlog.cmp(self, node, text)
   237         return revlog.revlog.cmp(self, node, text)
   215 
       
   216 
       
   217 # store original patch.patchfile.__init__
       
   218 _patchfile_init = patch.patchfile.__init__
       
   219 
       
   220 def _kwpatchfile_init(self, ui, fname, missing=False):
       
   221     '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
       
   222     rejects or conflicts due to expanded keywords in working dir.'''
       
   223     _patchfile_init(self, ui, fname, missing=missing)
       
   224 
       
   225     if _kwtemplater.matcher(self.fname):
       
   226         # shrink keywords read from working dir
       
   227         kwshrunk = _kwtemplater.shrink(''.join(self.lines))
       
   228         self.lines = kwshrunk.splitlines(True)
       
   229 
       
   230 
   238 
   231 def _iskwfile(f, link):
   239 def _iskwfile(f, link):
   232     return not link(f) and _kwtemplater.matcher(f)
   240     return not link(f) and _kwtemplater.matcher(f)
   233 
   241 
   234 def _status(ui, repo, *pats, **opts):
   242 def _status(ui, repo, *pats, **opts):
   410     Wraps commit to overwrite configured files with updated
   418     Wraps commit to overwrite configured files with updated
   411     keyword substitutions.
   419     keyword substitutions.
   412     This is done for local repos only, and only if there are
   420     This is done for local repos only, and only if there are
   413     files configured at all for keyword substitution.'''
   421     files configured at all for keyword substitution.'''
   414 
   422 
   415     if not repo.local():
   423     global _kwtemplater
   416         return
   424     hgcmd, hgcmdopts = _cmd, _cmdoptions
   417 
   425 
   418     hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
   426     try:
   419     if hgcmd in nokwcommands.split():
   427         if (not repo.local() or hgcmd in nokwcommands.split() 
   420         return
   428             or '.hg' in repo.root.split('/')
   421 
   429             or repo._url.startswith('bundle:')):
   422     if hgcmd == 'diff':
       
   423         # only expand if comparing against working dir
       
   424         node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
       
   425         if node2 is not None:
       
   426             return
   430             return
   427         # shrink if rev is not current node
   431     except AttributeError:
   428         if node1 is not None and node1 != repo.changectx().node():
   432         pass
   429             hgcmd = 'diff1'
   433 
   430 
   434     inc, exc = [], ['.hg*']
   431     inc, exc = [], ['.hgtags']
       
   432     for pat, opt in ui.configitems('keyword'):
   435     for pat, opt in ui.configitems('keyword'):
   433         if opt != 'ignore':
   436         if opt != 'ignore':
   434             inc.append(pat)
   437             inc.append(pat)
   435         else:
   438         else:
   436             exc.append(pat)
   439             exc.append(pat)
   437     if not inc:
   440     if not inc:
   438         return
   441         return
   439 
   442 
   440     global _kwtemplater
   443     if hgcmd == 'diff':
   441     _restricted = hgcmd in restricted.split()
   444         # only expand if comparing against working dir
   442     _kwtemplater = kwtemplater(ui, repo, inc, exc, _restricted)
   445         node1, node2 = cmdutil.revpair(repo, hgcmdopts.get('rev'))
       
   446         if node2 is not None:
       
   447             return
       
   448         # shrink if rev is not current node
       
   449         if node1 is not None and node1 != repo.changectx().node():
       
   450             hgcmd = 'diff1'
       
   451 
       
   452     restrict = hgcmd in restricted.split()
       
   453     _kwtemplater = kwtemplater(ui, repo, inc, exc, restrict)
   443 
   454 
   444     class kwrepo(repo.__class__):
   455     class kwrepo(repo.__class__):
   445         def file(self, f, kwmatch=False):
   456         def file(self, f, kwmatch=False):
   446             if f[0] == '/':
   457             if f[0] == '/':
   447                 f = f[1:]
   458                 f = f[1:]
   449                 return kwfilelog(self.sopener, f)
   460                 return kwfilelog(self.sopener, f)
   450             return filelog.filelog(self.sopener, f)
   461             return filelog.filelog(self.sopener, f)
   451 
   462 
   452         def wread(self, filename):
   463         def wread(self, filename):
   453             data = super(kwrepo, self).wread(filename)
   464             data = super(kwrepo, self).wread(filename)
   454             if _restricted and _kwtemplater.matcher(filename):
   465             if restrict and _kwtemplater.matcher(filename):
   455                 return _kwtemplater.shrink(data)
   466                 return _kwtemplater.shrink(data)
   456             return data
   467             return data
   457 
   468 
   458         def commit(self, files=None, text='', user=None, date=None,
   469         def commit(self, files=None, text='', user=None, date=None,
   459                    match=util.always, force=False, force_editor=False,
   470                    match=util.always, force=False, force_editor=False,