hgkw/keyword.py
branch0.9.2compat
changeset 359 4ac7e51443fe
parent 352 a71e2086fe6e
child 360 ed06223f88fc
equal deleted inserted replaced
352:a71e2086fe6e 359:4ac7e51443fe
    82 Caveat: "hg import" fails if the patch context contains an active
    82 Caveat: "hg import" fails if the patch context contains an active
    83         keyword. In that case run "hg kwshrink", and then reimport.
    83         keyword. In that case run "hg kwshrink", and then reimport.
    84         Or, better, use bundle/unbundle to share changes.
    84         Or, better, use bundle/unbundle to share changes.
    85 '''
    85 '''
    86 
    86 
    87 from mercurial import commands, cmdutil, context, fancyopts
    87 from mercurial import commands, cmdutil, context, filelog, localrepo
    88 from mercurial import filelog, localrepo, revlog, templater, util
    88 from mercurial import patch, revlog, templater, util
    89 from mercurial.node import *
    89 from mercurial.node import *
    90 from mercurial.i18n import gettext as _
    90 from mercurial.i18n import gettext as _
    91 import getopt, os.path, re, shutil, sys, tempfile, time
    91 import os.path, re, shutil, tempfile, time
    92 
    92 
    93 # 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
       
   105 
       
   106 def _wwrite(repo, f, data, mf):
       
   107     '''Makes repo.wwrite backwards compatible.'''
       
   108     # 656e06eebda7 removed file descriptor argument
       
   109     # 67982d3ee76c added flags argument
       
   110     try:
       
   111         repo.wwrite(f, data, mf.flags(f))
       
   112     except (AttributeError, TypeError):
       
   113         repo.wwrite(f, data)
       
   114 
    94 
   115 def _normal(repo, files):
    95 def _normal(repo, files):
   116     '''Backwards compatible repo.dirstate.normal/update.'''
    96     '''Backwards compatible repo.dirstate.normal/update.'''
   117     # 6fd953d5faea introduced dirstate.normal()
    97     # 6fd953d5faea introduced dirstate.normal()
   118     try:
    98     try:
   119         for f in files:
    99         for f in files:
   120             repo.dirstate.normal(f)
   100             repo.dirstate.normal(f)
   121     except AttributeError:
   101     except AttributeError:
   122         repo.dirstate.update(files, 'n')
   102         repo.dirstate.update(files, 'n')
   123 
   103 
       
   104 def _link(repo, f):
       
   105     try:
       
   106         return repo._link(f)
       
   107     except AttributeError:
       
   108         return os.path.islink(repo.wjoin(f))
       
   109 
   124 def _pathto(repo, f, cwd=None):
   110 def _pathto(repo, f, cwd=None):
   125     '''kwfiles behaves similar to status, using pathto since 78b6add1f966.'''
   111     '''kwfiles behaves similar to status, using pathto since 78b6add1f966.'''
   126     try:
   112     try:
   127         return repo.pathto(f, cwd)
   113         return repo.pathto(f, cwd)
   128     except AttributeError:
   114     except AttributeError:
   129         return f
   115         return f
   130 
   116 
   131 # commands.parse/cmdutil.parse returned nothing for
       
   132 # "hg diff --rev" before 88803a69b24a due to bug in fancyopts
       
   133 def _fancyopts(args, options, state):
       
   134     '''Fixed fancyopts from a9b7e425674f.'''
       
   135     namelist = []
       
   136     shortlist = ''
       
   137     argmap = {}
       
   138     defmap = {}
       
   139 
       
   140     for short, name, default, comment in options:
       
   141         # convert opts to getopt format
       
   142         oname = name
       
   143         name = name.replace('-', '_')
       
   144 
       
   145         argmap['-' + short] = argmap['--' + oname] = name
       
   146         defmap[name] = default
       
   147 
       
   148         # copy defaults to state
       
   149         if isinstance(default, list):
       
   150             state[name] = default[:]
       
   151         elif callable(default):
       
   152             print "whoa", name, default
       
   153             state[name] = None
       
   154         else:
       
   155             state[name] = default
       
   156 
       
   157         # does it take a parameter?
       
   158         if not (default is None or default is True or default is False):
       
   159             if short: short += ':'
       
   160             if oname: oname += '='
       
   161         if short:
       
   162             shortlist += short
       
   163         if name:
       
   164             namelist.append(oname)
       
   165 
       
   166     # parse arguments
       
   167     opts, args = getopt.getopt(args, shortlist, namelist)
       
   168 
       
   169     # transfer result to state
       
   170     for opt, val in opts:
       
   171         name = argmap[opt]
       
   172         t = type(defmap[name])
       
   173         if t is type(fancyopts):
       
   174             state[name] = defmap[name](val)
       
   175         elif t is type(1):
       
   176             state[name] = int(val)
       
   177         elif t is type(''):
       
   178             state[name] = val
       
   179         elif t is type([]):
       
   180             state[name].append(val)
       
   181         elif t is type(None) or t is type(False):
       
   182             state[name] = True
       
   183 
       
   184     # return unparsed args
       
   185     return args
       
   186 
       
   187 fancyopts.fancyopts = _fancyopts
       
   188 
       
   189 
   117 
   190 commands.optionalrepo += ' kwdemo'
   118 commands.optionalrepo += ' kwdemo'
   191 
       
   192 # handle for external callers
       
   193 externalcall = None, None, {}
       
   194 
       
   195 def externalcmdhook(hgcmd, *args, **opts):
       
   196     '''Hook for external callers to pass hg commands to keyword.
       
   197 
       
   198     Caveat: hgcmd, args, opts are not checked for validity.
       
   199     This is the responsibility of the caller.
       
   200 
       
   201     hgmcd can be either the hg function object, eg diff or patch,
       
   202     or its string represenation, eg 'diff' or 'patch'.'''
       
   203     global externalcall
       
   204     if not isinstance(hgcmd, str):
       
   205         hgcmd = hgcmd.__name__.split('.')[-1]
       
   206     externalcall = hgcmd, args, opts
       
   207 
       
   208 # hg commands that trigger expansion only when writing to working dir,
       
   209 # not when reading filelog, and unexpand when reading from working dir
       
   210 restricted = ('diff1', 'record',
       
   211               'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
       
   212 
   119 
   213 def utcdate(date):
   120 def utcdate(date):
   214     '''Returns hgdate in cvs-like UTC format.'''
   121     '''Returns hgdate in cvs-like UTC format.'''
   215     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   122     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   216 
       
   217 
       
   218 _kwtemplater = None
       
   219 
   123 
   220 class kwtemplater(object):
   124 class kwtemplater(object):
   221     '''
   125     '''
   222     Sets up keyword templates, corresponding keyword regex, and
   126     Sets up keyword templates, corresponding keyword regex, and
   223     provides keyword substitution functions.
   127     provides keyword substitution functions.
   230         'Source': '{root}/{file},v',
   134         'Source': '{root}/{file},v',
   231         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   135         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   232         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   136         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   233     }
   137     }
   234 
   138 
   235     def __init__(self, ui, repo, inc, exc, hgcmd):
   139     def __init__(self, ui, repo, inc, exc):
   236         self.ui = ui
   140         self.ui = ui
   237         self.repo = repo
   141         self.repo = repo
   238         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   142         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   239         self.hgcmd = hgcmd
   143         self.ctx = None
   240         self.commitnode = None
       
   241         self.path = ''
       
   242 
   144 
   243         kwmaps = self.ui.configitems('keywordmaps')
   145         kwmaps = self.ui.configitems('keywordmaps')
   244         if kwmaps: # override default templates
   146         if kwmaps: # override default templates
   245             kwmaps = [(k, templater.parsestring(v, quoted=False))
   147             kwmaps = [(k, templater.parsestring(v, quoted=False))
   246                       for (k, v) in kwmaps]
   148                       for (k, v) in kwmaps]
   260                                                False, '', False)
   162                                                False, '', False)
   261         except TypeError:
   163         except TypeError:
   262             return cmdutil.changeset_templater(self.ui, self.repo,
   164             return cmdutil.changeset_templater(self.ui, self.repo,
   263                                                False, None, '', False)
   165                                                False, None, '', False)
   264 
   166 
   265     def substitute(self, node, data, subfunc):
   167     def substitute(self, path, data, node, subfunc):
   266         '''Obtains file's changenode if commit node not given,
   168         '''Obtains file's changenode if node not given,
   267         and calls given substitution function.'''
   169         and calls given substitution function.'''
   268         if self.commitnode:
   170         if node is None:
   269             fnode = self.commitnode
   171             # kwrepo.wwrite except when overwriting on commit
   270         else:
   172             if self.ctx is None:
   271             c = context.filectx(self.repo, self.path, fileid=node)
   173                 self.ctx = self.repo.changectx()
   272             fnode = c.node()
   174             try:
       
   175                 fnode = self.ctx.filenode(path)
       
   176                 fl = self.repo.file(path)
       
   177                 c = context.filectx(self.repo, path, fileid=fnode, filelog=fl)
       
   178                 node = c.node()
       
   179             except revlog.LookupError:
       
   180                 # eg: convert
       
   181                 return subfunc == self.re_kw.sub and data or (data, None)
       
   182         elif subfunc == self.re_kw.sub:
       
   183             # hg kwcat using kwfilelog.read
       
   184             c = context.filectx(self.repo, path, fileid=node)
       
   185             node = c.node()
   273 
   186 
   274         def kwsub(mobj):
   187         def kwsub(mobj):
   275             '''Substitutes keyword using corresponding template.'''
   188             '''Substitutes keyword using corresponding template.'''
   276             kw = mobj.group(1)
   189             kw = mobj.group(1)
   277             self.ct.use_template(self.templates[kw])
   190             self.ct.use_template(self.templates[kw])
   278             self.ui.pushbuffer()
   191             self.ui.pushbuffer()
   279             self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
   192             self.ct.show(changenode=node, root=self.repo.root, file=path)
   280             return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
   193             return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
   281 
   194 
   282         return subfunc(kwsub, data)
   195         return subfunc(kwsub, data)
   283 
   196 
   284     def expand(self, node, data):
   197     def expand(self, path, data, node):
   285         '''Returns data with keywords expanded.'''
   198         '''Returns data with keywords expanded.'''
   286         if util.binary(data) or self.hgcmd in restricted:
   199         if util.binary(data):
   287             return data
   200             return data
   288         return self.substitute(node, data, self.re_kw.sub)
   201         return self.substitute(path, data, node, self.re_kw.sub)
   289 
   202 
   290     def process(self, node, data, expand):
   203     def process(self, path, data, expand, ctx, node):
   291         '''Returns a tuple: data, count.
   204         '''Returns a tuple: data, count.
   292         Count is number of keywords/keyword substitutions,
   205         Count is number of keywords/keyword substitutions,
   293         telling caller whether to act on file containing data.'''
   206         telling caller whether to act on file containing data.'''
   294         if util.binary(data):
   207         if util.binary(data):
   295             return data, None
   208             return data, None
   296         if expand:
   209         if expand:
   297             return self.substitute(node, data, self.re_kw.subn)
   210             self.ctx = ctx
   298         return data, self.re_kw.search(data)
   211             return self.substitute(path, data, node, self.re_kw.subn)
   299 
   212         return self.re_kw.subn(r'$\1$', data)
   300     def shrink(self, text):
   213 
       
   214     def shrink(self, data):
   301         '''Returns text with all keyword substitutions removed.'''
   215         '''Returns text with all keyword substitutions removed.'''
   302         if util.binary(text):
   216         if util.binary(data):
   303             return text
   217             return data
   304         return self.re_kw.sub(r'$\1$', text)
   218         return self.re_kw.sub(r'$\1$', data)
   305 
   219 
   306 class kwfilelog(filelog.filelog):
   220 class kwfilelog(filelog.filelog):
   307     '''
   221     '''
   308     Subclass of filelog to hook into its read, add, cmp methods.
   222     Subclass of filelog to hook into its read method for kwcat.
   309     Keywords are "stored" unexpanded, and processed on reading.
   223     '''
   310     '''
   224     def __init__(self, opener, path, kwt):
   311     def __init__(self, opener, path):
       
   312         super(kwfilelog, self).__init__(opener, path)
   225         super(kwfilelog, self).__init__(opener, path)
   313         _kwtemplater.path = path
   226         self._kwt = kwt
   314 
   227         self._path = path
   315     def kwctread(self, node, expand):
       
   316         '''Reads expanding and counting keywords, called from _overwrite.'''
       
   317         data = super(kwfilelog, self).read(node)
       
   318         return _kwtemplater.process(node, data, expand)
       
   319 
   228 
   320     def read(self, node):
   229     def read(self, node):
   321         '''Expands keywords when reading filelog.'''
   230         '''Expands keywords when reading filelog.'''
   322         data = super(kwfilelog, self).read(node)
   231         data = super(kwfilelog, self).read(node)
   323         return _kwtemplater.expand(node, data)
   232         return self._kwt.expand(self._path, data, node)
   324 
   233 
   325     def add(self, text, meta, tr, link, p1=None, p2=None):
       
   326         '''Removes keyword substitutions when adding to filelog.'''
       
   327         text = _kwtemplater.shrink(text)
       
   328         return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
       
   329 
       
   330     def cmp(self, node, text):
       
   331         '''Removes keyword substitutions for comparison.'''
       
   332         text = _kwtemplater.shrink(text)
       
   333         if self.renamed(node):
       
   334             t2 = super(kwfilelog, self).read(node)
       
   335             return t2 != text
       
   336         return revlog.revlog.cmp(self, node, text)
       
   337 
       
   338 def _iskwfile(f, link):
       
   339     return not link(f) and _kwtemplater.matcher(f)
       
   340 
   234 
   341 def _status(ui, repo, *pats, **opts):
   235 def _status(ui, repo, *pats, **opts):
   342     '''Bails out if [keyword] configuration is not active.
   236     '''Bails out if [keyword] configuration is not active.
   343     Returns status of working directory.'''
   237     Returns status of working directory.'''
   344     if _kwtemplater:
   238     if hasattr(repo, '_kwt'):
   345         files, match, anypats = cmdutil.matchpats(repo, pats, opts)
   239         files, match, anypats = cmdutil.matchpats(repo, pats, opts)
   346         return repo.status(files=files, match=match, list_clean=True)
   240         return repo.status(files=files, match=match, list_clean=True)
   347     if ui.configitems('keyword'):
   241     if ui.configitems('keyword'):
   348         raise util.Abort(_('[keyword] patterns cannot match'))
   242         raise util.Abort(_('[keyword] patterns cannot match'))
   349     raise util.Abort(_('no [keyword] patterns configured'))
   243     raise util.Abort(_('no [keyword] patterns configured'))
   350 
   244 
   351 def _overwrite(ui, repo, node=None, expand=True, files=None):
   245 def _overwrite(ui, repo, node=None, expand=True, files=None):
   352     '''Overwrites selected files expanding/shrinking keywords.'''
   246     '''Overwrites selected files expanding/shrinking keywords.'''
   353     ctx = repo.changectx(node)
   247     ctx = repo.changectx(node)
   354     mf = ctx.manifest()
   248     mf = ctx.manifest()
   355     if node is not None:   # commit
   249     if node is not None:
   356         _kwtemplater.commitnode = node
   250         # commit
   357         files = [f for f in ctx.files() if f in mf]
   251         files = [f for f in ctx.files() if f in mf]
   358         notify = ui.debug
   252         notify = ui.debug
   359     else:                  # kwexpand/kwshrink
   253     else:
       
   254         # kwexpand/kwshrink
   360         notify = ui.note
   255         notify = ui.note
   361     candidates = [f for f in files if _iskwfile(f, mf.linkf)]
   256     candidates = [f for f in files if not mf.linkf(f) and repo._kwt.matcher(f)]
   362     if candidates:
   257     if candidates:
   363         overwritten = []
   258         overwritten = []
   364         candidates.sort()
   259         candidates.sort()
   365         action = expand and 'expanding' or 'shrinking'
   260         action = expand and 'expanding' or 'shrinking'
   366         for f in candidates:
   261         for f in candidates:
   367             fp = repo.file(f, kwmatch=True)
   262             data, kwfound = repo._wreadkwct(f, expand, ctx, node)
   368             data, kwfound = fp.kwctread(mf[f], expand)
       
   369             if kwfound:
   263             if kwfound:
   370                 notify(_('overwriting %s %s keywords\n') % (f, action))
   264                 notify(_('overwriting %s %s keywords\n') % (f, action))
   371                 _wwrite(repo, f, data, mf)
   265                 repo.wwrite(f, data, mf.flags(f), overwrite=True)
   372                 overwritten.append(f)
   266                 overwritten.append(f)
   373         _normal(repo, overwritten)
   267         _normal(repo, overwritten)
   374 
   268 
   375 def _kwfwrite(ui, repo, expand, *pats, **opts):
   269 def _kwfwrite(ui, repo, expand, *pats, **opts):
   376     '''Selects files and passes them to _overwrite.'''
   270     '''Selects files and passes them to _overwrite.'''
   384         lock = repo.lock()
   278         lock = repo.lock()
   385         _overwrite(ui, repo, expand=expand, files=clean)
   279         _overwrite(ui, repo, expand=expand, files=clean)
   386     finally:
   280     finally:
   387         del wlock, lock
   281         del wlock, lock
   388 
   282 
       
   283 def cat(ui, repo, file1, *pats, **opts):
       
   284     '''output the current or given revision of files expanding keywords
       
   285 
       
   286     Print the specified files as they were at the given revision.
       
   287     If no revision is given, the parent of the working directory is used,
       
   288     or tip if no revision is checked out.
       
   289 
       
   290     Output may be to a file, in which case the name of the file is
       
   291     given using a format string.  The formatting rules are the same as
       
   292     for the export command, with the following additions:
       
   293 
       
   294     %s   basename of file being printed
       
   295     %d   dirname of file being printed, or '.' if in repo root
       
   296     %p   root-relative path name of file being printed
       
   297     '''
       
   298     try:
       
   299         repo.file = repo._kwfile
       
   300     except AttributeError:
       
   301         pass
       
   302     commands.cat(ui, repo, file1, *pats, **opts)
   389 
   303 
   390 def demo(ui, repo, *args, **opts):
   304 def demo(ui, repo, *args, **opts):
   391     '''print [keywordmaps] configuration and an expansion example
   305     '''print [keywordmaps] configuration and an expansion example
   392 
   306 
   393     Show current, custom, or default keyword template maps
   307     Show current, custom, or default keyword template maps
   461     ui.note(_('unhooked all commit hooks\n'))
   375     ui.note(_('unhooked all commit hooks\n'))
   462     ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
   376     ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
   463     repo.commit(text=msg)
   377     repo.commit(text=msg)
   464     format = ui.verbose and ' in %s' % path or ''
   378     format = ui.verbose and ' in %s' % path or ''
   465     demostatus('%s keywords expanded%s' % (kwstatus, format))
   379     demostatus('%s keywords expanded%s' % (kwstatus, format))
   466     ui.write(repo.wread(fn))
   380     ui.write(repo.wopener(fn).read())
   467     ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
   381     ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
   468     shutil.rmtree(tmpdir, ignore_errors=True)
   382     shutil.rmtree(tmpdir, ignore_errors=True)
   469 
   383 
   470 def expand(ui, repo, *pats, **opts):
   384 def expand(ui, repo, *pats, **opts):
   471     '''expand keywords in working directory
   385     '''expand keywords in working directory
   488     modified, added, removed, deleted, unknown, ignored, clean = status
   402     modified, added, removed, deleted, unknown, ignored, clean = status
   489     files = modified + added + clean
   403     files = modified + added + clean
   490     if opts.get('untracked'):
   404     if opts.get('untracked'):
   491         files += unknown
   405         files += unknown
   492     files.sort()
   406     files.sort()
   493     # use the full definition of repo._link for backwards compatibility
   407     kwfiles = [f for f in files if not _link(repo, f) and repo._kwt.matcher(f)]
   494     kwfiles = [f for f in files if _kwtemplater.matcher(f)
       
   495                and not os.path.islink(repo.wjoin(f))]
       
   496     cwd = pats and repo.getcwd() or ''
   408     cwd = pats and repo.getcwd() or ''
   497     kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
   409     kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
   498     if opts.get('all') or opts.get('ignore'):
   410     if opts.get('all') or opts.get('ignore'):
   499         kwfstats += (('I', [f for f in files if f not in kwfiles]),)
   411         kwfstats += (('I', [f for f in files if f not in kwfiles]),)
   500     for char, filenames in kwfstats:
   412     for char, filenames in kwfstats:
   513     # 3rd argument sets expansion to False
   425     # 3rd argument sets expansion to False
   514     _kwfwrite(ui, repo, False, *pats, **opts)
   426     _kwfwrite(ui, repo, False, *pats, **opts)
   515 
   427 
   516 
   428 
   517 def reposetup(ui, repo):
   429 def reposetup(ui, repo):
   518     '''Sets up repo as kwrepo for keyword substitution.
       
   519     Overrides file method to return kwfilelog instead of filelog
       
   520     if file matches user configuration.
       
   521     Wraps commit to overwrite configured files with updated
       
   522     keyword substitutions.
       
   523     This is done for local repos only, and only if there are
       
   524     files configured at all for keyword substitution.'''
       
   525 
       
   526     if not repo.local():
   430     if not repo.local():
   527         return
   431         return
   528 
   432 
   529     nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
   433     inc, exc = [], ['.hgtags', '.hg_archival.txt']
   530                     'export', 'grep', 'identify', 'incoming', 'init',
       
   531                     'log', 'outgoing', 'push', 'remove', 'rename',
       
   532                     'rollback', 'tip',
       
   533                     'convert')
       
   534     try:
       
   535         hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
       
   536     except (cmdutil.UnknownCommand, dispatch.ParseError):
       
   537         # must be an external caller, otherwise Exception would have been
       
   538         # raised at core command line parsing
       
   539         hgcmd, args, cmdopts = externalcall
       
   540         if hgcmd is None:
       
   541             # not an "official" hg command as from command line
       
   542             return
       
   543     if hgcmd in nokwcommands:
       
   544         return
       
   545 
       
   546     if hgcmd == 'diff':
       
   547         # only expand if comparing against working dir
       
   548         node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
       
   549         if node2 is not None:
       
   550             return
       
   551         # shrink if rev is not current node
       
   552         if node1 is not None and node1 != repo.changectx().node():
       
   553             hgcmd = 'diff1'
       
   554 
       
   555     inc, exc = [], ['.hgtags']
       
   556     for pat, opt in ui.configitems('keyword'):
   434     for pat, opt in ui.configitems('keyword'):
   557         if opt != 'ignore':
   435         if opt != 'ignore':
   558             inc.append(pat)
   436             inc.append(pat)
   559         else:
   437         else:
   560             exc.append(pat)
   438             exc.append(pat)
   561     if not inc:
   439     if not inc:
   562         return
   440         return
   563 
   441 
   564     global _kwtemplater
       
   565     _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
       
   566 
       
   567     class kwrepo(repo.__class__):
   442     class kwrepo(repo.__class__):
   568         def file(self, f, kwmatch=False):
   443         def _kwfile(self, f):
       
   444             '''Returns filelog expanding keywords on read (for kwcat).'''
   569             if f[0] == '/':
   445             if f[0] == '/':
   570                 f = f[1:]
   446                 f = f[1:]
   571             if kwmatch or _kwtemplater.matcher(f):
   447             if self._kwt.matcher(f):
   572                 return kwfilelog(self.sopener, f)
   448                 return kwfilelog(self.sopener, f, self._kwt)
   573             return filelog.filelog(self.sopener, f)
   449             return filelog.filelog(self.sopener, f)
       
   450 
       
   451         def _wreadkwct(self, filename, expand, ctx, node):
       
   452             '''Reads filename and returns tuple of data with keywords
       
   453             expanded/shrunk and count of keywords (for _overwrite).'''
       
   454             data = super(kwrepo, self).wread(filename)
       
   455             return self._kwt.process(filename, data, expand, ctx, node)
   574 
   456 
   575         def wread(self, filename):
   457         def wread(self, filename):
   576             data = super(kwrepo, self).wread(filename)
   458             data = super(kwrepo, self).wread(filename)
   577             if hgcmd in restricted and _kwtemplater.matcher(filename):
   459             if self._kwt.matcher(filename):
   578                 return _kwtemplater.shrink(data)
   460                 return self._kwt.shrink(data)
   579             return data
   461             return data
   580 
   462 
       
   463         def wwrite(self, filename, data, flags=None, overwrite=False):
       
   464             if not overwrite and self._kwt.matcher(filename):
       
   465                 data = self._kwt.expand(filename, data, None)
       
   466             try:
       
   467                 super(kwrepo, self).wwrite(filename, data, flags)
       
   468             except (AttributeError, TypeError):
       
   469                 # 656e06eebda7 removed file descriptor argument
       
   470                 # 67982d3ee76c added flags argument
       
   471                 super(kwrepo, self).wwrite(filename, data)
       
   472 
       
   473         def wwritedata(self, filename, data):
       
   474             if self._kwt.matcher(filename):
       
   475                 data = self._kwt.expand(filename, data, None)
       
   476             return super(kwrepo, self).wwritedata(filename, data)
       
   477 
   581         def _commit(self, files, text, user, date, match, force, lock, wlock,
   478         def _commit(self, files, text, user, date, match, force, lock, wlock,
   582                     force_editor, p1, p2, extra):
   479                     force_editor, p1, p2, extra, empty_ok):
   583             '''Private commit wrapper for backwards compatibility.'''
   480             '''Private commit wrapper for backwards compatibility.'''
   584             try:
   481             try:
   585                 return super(kwrepo, self).commit(files=files, text=text,
   482                 return super(kwrepo, self).commit(files=files, text=text,
   586                                                   user=user, date=date,
   483                                                   user=user, date=date,
   587                                                   match=match, force=force,
   484                                                   match=match, force=force,
   588                                                   lock=lock, wlock=wlock,
   485                                                   lock=lock, wlock=wlock,
   589                                                   force_editor=force_editor,
   486                                                   force_editor=force_editor,
   590                                                   p1=p1, p2=p2, extra=extra)
   487                                                   p1=p1, p2=p2, extra=extra)
   591             except TypeError:
   488             except TypeError:
   592                 return super(kwrepo, self).commit(files=files, text=text,
   489                 try:
   593                                                   user=user, date=date,
   490                     return super(kwrepo, self).commit(files=files, text=text,
   594                                                   match=match, force=force,
   491                                                       user=user, date=date,
   595                                                   force_editor=force_editor,
   492                                                       match=match, force=force,
   596                                                   p1=p1, p2=p2, extra=extra)
   493                                                       force_editor=force_editor,
       
   494                                                       p1=p1, p2=p2,
       
   495                                                       extra=extra,
       
   496                                                       empty_ok=empty_ok)
       
   497                 except TypeError:
       
   498                     return super(kwrepo, self).commit(files=files, text=text,
       
   499                                                       user=user, date=date,
       
   500                                                       match=match, force=force,
       
   501                                                       force_editor=force_editor,
       
   502                                                       p1=p1, p2=p2, extra=extra)
   597 
   503 
   598         def commit(self, files=None, text='', user=None, date=None,
   504         def commit(self, files=None, text='', user=None, date=None,
   599                    match=util.always, force=False, lock=None, wlock=None,
   505                    match=util.always, force=False, lock=None, wlock=None,
   600                    force_editor=False, p1=None, p2=None, extra={}):
   506                    force_editor=False, p1=None, p2=None, extra={},
       
   507                    empty_ok=False):
   601             # (w)lock arguments removed in 126f527b3ba3
   508             # (w)lock arguments removed in 126f527b3ba3
   602             # so they are None or what was passed to commit
   509             # so they are None or what was passed to commit
   603             # use private _(w)lock for deletion
   510             # use private _(w)lock for deletion
   604             _lock = lock
   511             _lock = lock
   605             _wlock = wlock
   512             _wlock = wlock
   627                         _p2 = ''
   534                         _p2 = ''
   628                     else:
   535                     else:
   629                         _p2 = hex(_p2)
   536                         _p2 = hex(_p2)
   630 
   537 
   631                 node = self._commit(files, text, user, date, match, force,
   538                 node = self._commit(files, text, user, date, match, force,
   632                                     _lock, _wlock, force_editor, p1, p2, extra)
   539                                     _lock, _wlock, force_editor, p1, p2, extra,
       
   540                                     empty_ok)
   633 
   541 
   634                 # restore commit hooks
   542                 # restore commit hooks
   635                 for name, cmd in commithooks.iteritems():
   543                 for name, cmd in commithooks.iteritems():
   636                     ui.setconfig('hooks', name, cmd)
   544                     ui.setconfig('hooks', name, cmd)
   637                 if node is not None:
   545                 if node is not None:
   639                     repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
   547                     repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
   640                 return node
   548                 return node
   641             finally:
   549             finally:
   642                 del _wlock, _lock
   550                 del _wlock, _lock
   643 
   551 
       
   552     kwrepo._kwt = kwtemplater(ui, repo, inc, exc)
   644     repo.__class__ = kwrepo
   553     repo.__class__ = kwrepo
   645 
   554 
   646 
   555 
   647 cmdtable = {
   556 cmdtable = {
       
   557     'kwcat':
       
   558         (cat, commands.table['cat'][1],
       
   559          _('hg kwcat [OPTION]... FILE...')),
   648     'kwdemo':
   560     'kwdemo':
   649         (demo,
   561         (demo,
   650          [('d', 'default', None, _('show default keyword template maps')),
   562          [('d', 'default', None, _('show default keyword template maps')),
   651           ('f', 'rcfile', [], _('read maps from rcfile'))],
   563           ('f', 'rcfile', [], _('read maps from rcfile'))],
   652          _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
   564          _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),