hgkw/keyword.py
branch0.9.2compat
changeset 411 b1a7f5eddeba
parent 402 5e3707030bce
child 412 79a44b1c202b
equal deleted inserted replaced
402:5e3707030bce 411:b1a7f5eddeba
    83         cause rejects if the patch context contains an active keyword.
    83         cause rejects if the patch context contains an active keyword.
    84         In that case run "hg kwshrink", and then reimport.
    84         In that case run "hg kwshrink", and then reimport.
    85         Or, better, use bundle/unbundle to share changes.
    85         Or, better, use bundle/unbundle to share changes.
    86 '''
    86 '''
    87 
    87 
    88 from mercurial import commands, cmdutil, context, fancyopts
    88 from mercurial import commands, cmdutil, context, fancyopts, filelog
    89 from mercurial import filelog, localrepo, revlog, templater, util
    89 from mercurial import localrepo, patch, revlog, templater, util
    90 from mercurial.node import *
    90 from mercurial.node import *
    91 from mercurial.i18n import gettext as _
    91 from mercurial.i18n import gettext as _
    92 import getopt, os, re, shutil, tempfile, time
    92 import getopt, os, re, shutil, tempfile, time
    93 
    93 
    94 commands.optionalrepo += ' kwdemo'
    94 commands.optionalrepo += ' kwdemo'
    98                 ' log outgoing push remove rename rollback tip'
    98                 ' log outgoing push remove rename rollback tip'
    99                 ' convert email glog')
    99                 ' convert email glog')
   100 
   100 
   101 # hg commands that trigger expansion only when writing to working dir,
   101 # hg commands that trigger expansion only when writing to working dir,
   102 # not when reading filelog, and unexpand when reading from working dir
   102 # not when reading filelog, and unexpand when reading from working dir
   103 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
   103 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
   104 
   104 
   105 def utcdate(date):
   105 def utcdate(date):
   106     '''Returns hgdate in cvs-like UTC format.'''
   106     '''Returns hgdate in cvs-like UTC format.'''
   107     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   107     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   108 
   108 
   109 
   109 
   110 _kwtemplater = _cmd = _cmdoptions = None
   110 # make keyword tools accessible
   111 
   111 kwx = { 'templater': None, 'hgcmd': None }
   112 # backwards compatibility hacks
   112 
       
   113 # monkeypatches and backwards compatibility hacks
   113 
   114 
   114 try:
   115 try:
   115     # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
   116     # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
   116     from mercurial import dispatch
   117     from mercurial import dispatch
   117     _dispatch_parse = dispatch._parse
   118     _dispatch_parse = dispatch._parse
   121         _dispatch_parse = cmdutil.parse
   122         _dispatch_parse = cmdutil.parse
   122     except AttributeError:
   123     except AttributeError:
   123         _dispatch_parse = commands.parse
   124         _dispatch_parse = commands.parse
   124 
   125 
   125 def _kwdispatch_parse(ui, args):
   126 def _kwdispatch_parse(ui, args):
   126     '''Monkeypatch dispatch._parse to obtain
   127     '''Monkeypatch dispatch._parse to obtain running hg command.'''
   127     current command and command options (global _cmd, _cmdoptions).'''
   128     cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args)
   128     global _cmd, _cmdoptions
   129     kwx['hgcmd'] = cmd
   129     _cmd, func, args, options, _cmdoptions = _dispatch_parse(ui, args)
   130     return cmd, func, args, options, cmdoptions
   130     return _cmd, func, args, options, _cmdoptions
       
   131 
   131 
   132 try:
   132 try:
   133     setattr(dispatch, '_parse', _kwdispatch_parse)
   133     setattr(dispatch, '_parse', _kwdispatch_parse)
   134 except (NameError, ImportError):
   134 except (NameError, ImportError):
   135     # 0.9.4 needs ImportError
   135     # 0.9.4 needs ImportError
   138     else:
   138     else:
   139         commands.parse = _kwdispatch_parse
   139         commands.parse = _kwdispatch_parse
   140 
   140 
   141 try:
   141 try:
   142     # avoid spurious rejects if patchfile is available
   142     # avoid spurious rejects if patchfile is available
   143     from mercurial.patch import patchfile
   143     _patchfile_init = patch.patchfile.__init__
   144     _patchfile_init = patchfile.__init__
       
   145 
   144 
   146     def _kwpatchfile_init(self, ui, fname, missing=False):
   145     def _kwpatchfile_init(self, ui, fname, missing=False):
   147         '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
   146         '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
   148         rejects or conflicts due to expanded keywords in working dir.'''
   147         rejects or conflicts due to expanded keywords in working dir.'''
   149         try:
   148         try:
   150             _patchfile_init(self, ui, fname, missing=missing)
   149             _patchfile_init(self, ui, fname, missing=missing)
   151         except TypeError:
   150         except TypeError:
   152             # "missing" arg added in e90e72c6b4c7
   151             # "missing" arg added in e90e72c6b4c7
   153             _patchfile_init(self, ui, fname)
   152             _patchfile_init(self, ui, fname)
   154         if _kwtemplater.matcher(self.fname):
   153         self.lines = kwx['templater'].shrinklines(self.fname, self.lines)
   155             # shrink keywords read from working dir
   154 except AttributeError:
   156             kwshrunk = _kwtemplater.shrink(''.join(self.lines))
       
   157             self.lines = kwshrunk.splitlines(True)
       
   158 except ImportError:
       
   159     pass
   155     pass
       
   156 
       
   157 _patch_diff = patch.diff
       
   158 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
       
   159              fp=None, changes=None, opts=None):
       
   160     # only expand if comparing against working dir
       
   161     if node2 is not None:
       
   162         kwx['templater'].matcher = util.never
       
   163     elif node1 is not None and node1 != repo.changectx().node():
       
   164         kwx['templater'].restrict = True
       
   165     _patch_diff(repo, node1=node1, node2=node2, files=files, match=match,
       
   166                 fp=fp, changes=changes, opts=opts)
   160 
   167 
   161 try:
   168 try:
   162     from mercurial.hgweb import webcommands
   169     from mercurial.hgweb import webcommands
   163 
   170 
   164     def _kwweb_changeset(web, req, tmpl):
   171     def _kwweb_changeset(web, req, tmpl):
   165         '''Wraps webcommands.changeset turning off keyword expansion.'''
   172         '''Wraps webcommands.changeset turning off keyword expansion.'''
   166         try:
   173         if kwx['templater']:
   167             _kwtemplater.matcher = util.never
   174             kwx['templater'].matcher = util.neve
   168         except AttributeError:
       
   169             pass
       
   170         return web.changeset(tmpl, web.changectx(req))
   175         return web.changeset(tmpl, web.changectx(req))
   171 
   176 
   172     def _kwweb_filediff(web, req, tmpl):
   177     def _kwweb_filediff(web, req, tmpl):
   173         '''Wraps webcommands.filediff turning off keyword expansion.'''
   178         '''Wraps webcommands.filediff turning off keyword expansion.'''
   174         try:
   179         if kwx['templater']:
   175             _kwtemplater.matcher = util.never
   180             kwx['templater'].matcher = util.neve
   176         except AttributeError:
       
   177             pass
       
   178         return web.filediff(tmpl, web.filectx(req))
   181         return web.filediff(tmpl, web.filectx(req))
   179 
   182 
   180     webcommands.changeset = webcommands.rev = _kwweb_changeset
   183     webcommands.changeset = webcommands.rev = _kwweb_changeset
   181     webcommands.filediff = webcommands.diff = _kwweb_filediff
   184     webcommands.filediff = webcommands.diff = _kwweb_filediff
   182 
   185 
   183 except ImportError:
   186 except ImportError:
   184     from mercurial.hgweb.hgweb_mod import hgweb
   187     from mercurial.hgweb.hgweb_mod import hgweb
   185 
   188 
   186     def _kwweb_do_changeset(self, req):
   189     def _kwweb_do_changeset(self, req):
   187         try:
   190         if kwx['templater']:
   188             _kwtemplater.matcher = util.never
   191             kwx['templater'].matcher = util.never
   189         except AttributeError:
       
   190             pass
       
   191         req.write(self.changeset(self.changectx(req)))
   192         req.write(self.changeset(self.changectx(req)))
   192 
   193 
   193     def _kwweb_do_filediff(self, req):
   194     def _kwweb_do_filediff(self, req):
   194         try:
   195         if kwx['templater']:
   195             _kwtemplater.matcher = util.never
   196             kwx['templater'].matcher = util.never
   196         except AttributeError:
       
   197             pass
       
   198         req.write(self.filediff(self.filectx(req)))
   197         req.write(self.filediff(self.filectx(req)))
   199 
   198 
   200     hgweb.do_changeset = hgweb.do_rev = _kwweb_do_changeset
   199     hgweb.do_changeset = hgweb.do_rev = _kwweb_do_changeset
   201     hgweb.do_filediff = hgweb.do_diff = _kwweb_do_filediff
   200     hgweb.do_filediff = hgweb.do_diff = _kwweb_do_filediff
   202 
   201 
   306         'Source': '{root}/{file},v',
   305         'Source': '{root}/{file},v',
   307         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   306         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   308         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   307         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   309     }
   308     }
   310 
   309 
   311     def __init__(self, ui, repo, inc, exc, hgcmd):
   310     def __init__(self, ui, repo, inc, exc):
   312         self.ui = ui
   311         self.ui = ui
   313         self.repo = repo
   312         self.repo = repo
   314         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   313         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   315         self.restrict = hgcmd in restricted.split()
   314         self.restrict = kwx['hgcmd'] in restricted.split()
   316         self.commitnode = None
       
   317         self.path = ''
       
   318 
   315 
   319         kwmaps = self.ui.configitems('keywordmaps')
   316         kwmaps = self.ui.configitems('keywordmaps')
   320         if kwmaps: # override default templates
   317         if kwmaps: # override default templates
   321             kwmaps = [(k, templater.parsestring(v, quoted=False))
   318             kwmaps = [(k, templater.parsestring(v, quoted=False))
   322                       for (k, v) in kwmaps]
   319                       for (k, v) in kwmaps]
   336                                                False, '', False)
   333                                                False, '', False)
   337         except TypeError:
   334         except TypeError:
   338             return cmdutil.changeset_templater(self.ui, self.repo,
   335             return cmdutil.changeset_templater(self.ui, self.repo,
   339                                                False, None, '', False)
   336                                                False, None, '', False)
   340 
   337 
   341     def substitute(self, node, data, subfunc):
   338     def getnode(self, path, fnode):
   342         '''Obtains file's changenode if commit node not given,
   339         '''Derives changenode from file context.'''
   343         and calls given substitution function.'''
   340         c = context.filectx(self.repo, path, fileid=fnode)
   344         if self.commitnode:
   341         return c.node()
   345             fnode = self.commitnode
   342 
   346         else:
   343     def substitute(self, data, path, node, subfunc):
   347             c = context.filectx(self.repo, self.path, fileid=node)
   344         '''Replaces keywords in data with expanded template.'''
   348             fnode = c.node()
       
   349 
       
   350         def kwsub(mobj):
   345         def kwsub(mobj):
   351             '''Substitutes keyword using corresponding template.'''
       
   352             kw = mobj.group(1)
   346             kw = mobj.group(1)
   353             self.ct.use_template(self.templates[kw])
   347             self.ct.use_template(self.templates[kw])
   354             self.ui.pushbuffer()
   348             self.ui.pushbuffer()
   355             self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
   349             self.ct.show(changenode=node, root=self.repo.root, file=path)
   356             return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer()))
   350             return '$%s: %s $' % (kw, template_firstline(self.ui.popbuffer()))
   357 
       
   358         return subfunc(kwsub, data)
   351         return subfunc(kwsub, data)
   359 
   352 
   360     def expand(self, node, data):
   353     def expand(self, path, node, data):
   361         '''Returns data with keywords expanded.'''
   354         '''Returns data with keywords expanded.'''
   362         if self.restrict or util.binary(data):
   355         if not self.restrict and self.matcher(path) and not util.binary(data):
   363             return data
   356             changenode = self.getnode(path, node)
   364         return self.substitute(node, data, self.re_kw.sub)
   357             return self.substitute(data, path, changenode, self.re_kw.sub)
   365 
   358         return data
   366     def process(self, node, data, expand):
   359 
   367         '''Returns a tuple: data, count.
   360     def iskwfile(self, path, islink):
   368         Count is number of keywords/keyword substitutions,
   361         '''Returns true if path matches [keyword] pattern
   369         telling caller whether to act on file containing data.'''
   362         and is not a symbolic link.
   370         if util.binary(data):
   363         Caveat: localrepository._link fails on Windows.'''
   371             return data, None
   364         return self.matcher(path) and not islink(path)
   372         if expand:
   365 
   373             return self.substitute(node, data, self.re_kw.subn)
   366     def overwrite(self, node=None, expand=True, files=None):
   374         return data, self.re_kw.search(data)
   367         '''Overwrites selected files expanding/shrinking keywords.'''
   375 
   368         ctx = self.repo.changectx(node)
   376     def shrink(self, text):
   369         mf = ctx.manifest()
       
   370         if node is not None:     # commit
       
   371             files = [f for f in ctx.files() if f in mf]
       
   372             notify = self.ui.debug
       
   373         else:                    # kwexpand/kwshrink
       
   374             notify = self.ui.note
       
   375         candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
       
   376         if candidates:
       
   377             self.restrict = True # do not expand when reading
       
   378             candidates.sort()
       
   379             action = expand and 'expanding' or 'shrinking'
       
   380             overwritten = []
       
   381             for f in candidates:
       
   382                 fp = self.repo.file(f)
       
   383                 data = fp.read(mf[f])
       
   384                 if util.binary(data):
       
   385                     continue
       
   386                 if expand:
       
   387                     changenode = node or self.getnode(f, mf[f])
       
   388                     data, found = self.substitute(data, f, changenode,
       
   389                                                   self.re_kw.subn)
       
   390                 else:
       
   391                     found = self.re_kw.search(data)
       
   392                 if found:
       
   393                     notify(_('overwriting %s %s keywords\n') % (f, action))
       
   394                     self.repo.wwrite(f, data, mf.flags(f))
       
   395                     overwritten.append(f)
       
   396             _normal(self.repo, overwritten)
       
   397             self.restrict = False
       
   398 
       
   399     def shrinktext(self, text):
       
   400         '''Unconditionally removes all keyword substitutions from text.'''
       
   401         return self.re_kw.sub(r'$\1$', text)
       
   402 
       
   403     def shrink(self, fname, text):
   377         '''Returns text with all keyword substitutions removed.'''
   404         '''Returns text with all keyword substitutions removed.'''
   378         if util.binary(text):
   405         if self.matcher(fname) and not util.binary(text):
   379             return text
   406             return self.shrinktext(text)
   380         return self.re_kw.sub(r'$\1$', text)
   407         return text
       
   408 
       
   409     def shrinklines(self, fname, lines):
       
   410         '''Returns lines with keyword substitutions removed.'''
       
   411         if self.matcher(fname):
       
   412             text = ''.join(lines)
       
   413             if not util.binary(text):
       
   414                 return self.shrinktext(text).splitlines(True)
       
   415         return lines
       
   416 
       
   417     def wread(self, fname, data):
       
   418         '''If in restricted mode returns data read from wdir with
       
   419         keyword substitutions removed.'''
       
   420         return self.restrict and self.shrink(fname, data) or data
   381 
   421 
   382 class kwfilelog(filelog.filelog):
   422 class kwfilelog(filelog.filelog):
   383     '''
   423     '''
   384     Subclass of filelog to hook into its read, add, cmp methods.
   424     Subclass of filelog to hook into its read, add, cmp methods.
   385     Keywords are "stored" unexpanded, and processed on reading.
   425     Keywords are "stored" unexpanded, and processed on reading.
   386     '''
   426     '''
   387     def __init__(self, opener, path):
   427     def __init__(self, opener, path):
   388         super(kwfilelog, self).__init__(opener, path)
   428         super(kwfilelog, self).__init__(opener, path)
   389         _kwtemplater.path = path
   429         self.kwt = kwx['templater']
   390 
   430         self.path = path
   391     def kwctread(self, node, expand):
       
   392         '''Reads expanding and counting keywords, called from _overwrite.'''
       
   393         data = super(kwfilelog, self).read(node)
       
   394         return _kwtemplater.process(node, data, expand)
       
   395 
   431 
   396     def read(self, node):
   432     def read(self, node):
   397         '''Expands keywords when reading filelog.'''
   433         '''Expands keywords when reading filelog.'''
   398         data = super(kwfilelog, self).read(node)
   434         data = super(kwfilelog, self).read(node)
   399         return _kwtemplater.expand(node, data)
   435         return self.kwt.expand(self.path, node, data)
   400 
   436 
   401     def add(self, text, meta, tr, link, p1=None, p2=None):
   437     def add(self, text, meta, tr, link, p1=None, p2=None):
   402         '''Removes keyword substitutions when adding to filelog.'''
   438         '''Removes keyword substitutions when adding to filelog.'''
   403         text = _kwtemplater.shrink(text)
   439         text = self.kwt.shrink(self.path, text)
   404         return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
   440         return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
   405 
   441 
   406     def cmp(self, node, text):
   442     def cmp(self, node, text):
   407         '''Removes keyword substitutions for comparison.'''
   443         '''Removes keyword substitutions for comparison.'''
   408         text = _kwtemplater.shrink(text)
   444         text = self.kwt.shrink(self.path, text)
   409         if self.renamed(node):
   445         if self.renamed(node):
   410             t2 = super(kwfilelog, self).read(node)
   446             t2 = super(kwfilelog, self).read(node)
   411             return t2 != text
   447             return t2 != text
   412         return revlog.revlog.cmp(self, node, text)
   448         return revlog.revlog.cmp(self, node, text)
   413 
   449 
   414 def _iskwfile(f, link):
   450 def _status(ui, repo, kwt, *pats, **opts):
   415     return not link(f) and _kwtemplater.matcher(f)
       
   416 
       
   417 def _status(ui, repo, *pats, **opts):
       
   418     '''Bails out if [keyword] configuration is not active.
   451     '''Bails out if [keyword] configuration is not active.
   419     Returns status of working directory.'''
   452     Returns status of working directory.'''
   420     if _kwtemplater:
   453     if kwt:
   421         files, match, anypats = cmdutil.matchpats(repo, pats, opts)
   454         files, match, anypats = cmdutil.matchpats(repo, pats, opts)
   422         return repo.status(files=files, match=match, list_clean=True)
   455         return repo.status(files=files, match=match, list_clean=True)
   423     if ui.configitems('keyword'):
   456     if ui.configitems('keyword'):
   424         raise util.Abort(_('[keyword] patterns cannot match'))
   457         raise util.Abort(_('[keyword] patterns cannot match'))
   425     raise util.Abort(_('no [keyword] patterns configured'))
   458     raise util.Abort(_('no [keyword] patterns configured'))
   426 
   459 
   427 def _overwrite(ui, repo, node=None, expand=True, files=None):
       
   428     '''Overwrites selected files expanding/shrinking keywords.'''
       
   429     ctx = repo.changectx(node)
       
   430     mf = ctx.manifest()
       
   431     if node is not None:   # commit
       
   432         _kwtemplater.commitnode = node
       
   433         files = [f for f in ctx.files() if f in mf]
       
   434         notify = ui.debug
       
   435     else:                  # kwexpand/kwshrink
       
   436         notify = ui.note
       
   437     candidates = [f for f in files if _iskwfile(f, mf.linkf)]
       
   438     if candidates:
       
   439         overwritten = []
       
   440         candidates.sort()
       
   441         action = expand and 'expanding' or 'shrinking'
       
   442         for f in candidates:
       
   443             fp = repo.file(f, kwmatch=True)
       
   444             data, kwfound = fp.kwctread(mf[f], expand)
       
   445             if kwfound:
       
   446                 notify(_('overwriting %s %s keywords\n') % (f, action))
       
   447                 _wwrite(repo, f, data, mf)
       
   448                 overwritten.append(f)
       
   449         _normal(repo, overwritten)
       
   450 
       
   451 def _kwfwrite(ui, repo, expand, *pats, **opts):
   460 def _kwfwrite(ui, repo, expand, *pats, **opts):
   452     '''Selects files and passes them to _overwrite.'''
   461     '''Selects files and passes them to kwtemplater.overwrite.'''
   453     status = _status(ui, repo, *pats, **opts)
   462     kwt = kwx['templater']
       
   463     status = _status(ui, repo, kwt, *pats, **opts)
   454     modified, added, removed, deleted, unknown, ignored, clean = status
   464     modified, added, removed, deleted, unknown, ignored, clean = status
   455     if modified or added or removed or deleted:
   465     if modified or added or removed or deleted:
   456         raise util.Abort(_('outstanding uncommitted changes in given files'))
   466         raise util.Abort(_('outstanding uncommitted changes in given files'))
   457     wlock = lock = None
   467     wlock = lock = None
   458     try:
   468     try:
   459         wlock = repo.wlock()
   469         wlock = repo.wlock()
   460         lock = repo.lock()
   470         lock = repo.lock()
   461         _overwrite(ui, repo, expand=expand, files=clean)
   471         kwt.overwrite(expand=expand, files=clean)
   462     finally:
   472     finally:
   463         del wlock, lock
   473         del wlock, lock
   464 
   474 
   465 
   475 
   466 def demo(ui, repo, *args, **opts):
   476 def demo(ui, repo, *args, **opts):
   558 
   568 
   559     Crosscheck which files in working directory are potential targets for
   569     Crosscheck which files in working directory are potential targets for
   560     keyword expansion.
   570     keyword expansion.
   561     That is, files matched by [keyword] config patterns but not symlinks.
   571     That is, files matched by [keyword] config patterns but not symlinks.
   562     '''
   572     '''
   563     status = _status(ui, repo, *pats, **opts)
   573     kwt = kwx['templater']
       
   574     status = _status(ui, repo, kwt, *pats, **opts)
   564     modified, added, removed, deleted, unknown, ignored, clean = status
   575     modified, added, removed, deleted, unknown, ignored, clean = status
   565     files = modified + added + clean
   576     files = modified + added + clean
   566     if opts.get('untracked'):
   577     if opts.get('untracked'):
   567         files += unknown
   578         files += unknown
   568     files.sort()
   579     files.sort()
   570     if hasattr(wctx, 'fileflags'):
   581     if hasattr(wctx, 'fileflags'):
   571         islink = lambda p: 'l' in wctx.fileflags(p)
   582         islink = lambda p: 'l' in wctx.fileflags(p)
   572     else:
   583     else:
   573         mf = wctx.manifest()
   584         mf = wctx.manifest()
   574         islink = mf.linkf
   585         islink = mf.linkf
   575     kwfiles = [f for f in files if _iskwfile(f, islink)]
   586     kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
   576     cwd = pats and repo.getcwd() or ''
   587     cwd = pats and repo.getcwd() or ''
   577     kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
   588     kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
   578     if opts.get('all') or opts.get('ignore'):
   589     if opts.get('all') or opts.get('ignore'):
   579         kwfstats += (('I', [f for f in files if f not in kwfiles]),)
   590         kwfstats += (('I', [f for f in files if f not in kwfiles]),)
   580     for char, filenames in kwfstats:
   591     for char, filenames in kwfstats:
   601     Wraps commit to overwrite configured files with updated
   612     Wraps commit to overwrite configured files with updated
   602     keyword substitutions.
   613     keyword substitutions.
   603     This is done for local repos only, and only if there are
   614     This is done for local repos only, and only if there are
   604     files configured at all for keyword substitution.'''
   615     files configured at all for keyword substitution.'''
   605 
   616 
   606     global _kwtemplater
       
   607     hgcmd, hgcmdopts = _cmd, _cmdoptions
       
   608 
       
   609     try:
   617     try:
   610         if (not repo.local() or hgcmd in nokwcommands.split() 
   618         if (not repo.local() or kwx['hgcmd'] in nokwcommands.split() 
   611             or '.hg' in repo.root.split(os.sep)
   619             or '.hg' in repo.root.split(os.sep)
   612             or repo._url.startswith('bundle:')):
   620             or repo._url.startswith('bundle:')):
   613             return
   621             return
   614     except AttributeError:
   622     except AttributeError:
   615         pass
   623         pass
   621         else:
   629         else:
   622             exc.append(pat)
   630             exc.append(pat)
   623     if not inc:
   631     if not inc:
   624         return
   632         return
   625 
   633 
   626     if hgcmd == 'diff':
   634     kwx['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
   627         # only expand if comparing against working dir
       
   628         node1, node2 = cmdutil.revpair(repo, hgcmdopts.get('rev'))
       
   629         if node2 is not None:
       
   630             return
       
   631         # shrink if rev is not current node
       
   632         if node1 is not None and node1 != repo.changectx().node():
       
   633             hgcmd = 'diff1'
       
   634 
       
   635     _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
       
   636 
   635 
   637     class kwrepo(repo.__class__):
   636     class kwrepo(repo.__class__):
   638         def file(self, f, kwmatch=False):
   637         def file(self, f):
   639             if f[0] == '/':
   638             if f[0] == '/':
   640                 f = f[1:]
   639                 f = f[1:]
   641             if kwmatch or _kwtemplater.matcher(f):
   640             return kwfilelog(self.sopener, f)
   642                 return kwfilelog(self.sopener, f)
       
   643             return filelog.filelog(self.sopener, f)
       
   644 
   641 
   645         def wread(self, filename):
   642         def wread(self, filename):
   646             data = super(kwrepo, self).wread(filename)
   643             data = super(kwrepo, self).wread(filename)
   647             if _kwtemplater.restrict and _kwtemplater.matcher(filename):
   644             return kwt.wread(filename, data)
   648                 return _kwtemplater.shrink(data)
       
   649             return data
       
   650 
   645 
   651         def _commit(self, files, text, user, date, match, force, lock, wlock,
   646         def _commit(self, files, text, user, date, match, force, lock, wlock,
   652                     force_editor, p1, p2, extra, empty_ok):
   647                     force_editor, p1, p2, extra, empty_ok):
   653             '''Private commit wrapper for backwards compatibility.'''
   648             '''Private commit wrapper for backwards compatibility.'''
   654             try:
   649             try:
   714 
   709 
   715                 # restore commit hooks
   710                 # restore commit hooks
   716                 for name, cmd in commithooks.iteritems():
   711                 for name, cmd in commithooks.iteritems():
   717                     ui.setconfig('hooks', name, cmd)
   712                     ui.setconfig('hooks', name, cmd)
   718                 if node is not None:
   713                 if node is not None:
   719                     _overwrite(ui, self, node=node)
   714                     kwt.overwrite(node=node)
   720                     repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
   715                     repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
   721                 return node
   716                 return node
   722             finally:
   717             finally:
   723                 del _wlock, _lock
   718                 del _wlock, _lock
   724 
   719 
   725     repo.__class__ = kwrepo
   720     repo.__class__ = kwrepo
   726     try:
   721     patch.diff = _kw_diff
   727         patchfile.__init__ = _kwpatchfile_init
   722     if hasattr(patch, 'patchfile'):
   728     except NameError:
   723         patch.patchfile.__init__ = _kwpatchfile_init
   729         pass
       
   730 
   724 
   731 
   725 
   732 cmdtable = {
   726 cmdtable = {
   733     'kwdemo':
   727     'kwdemo':
   734         (demo,
   728         (demo,