hgkw/keyword.py
branchstable
changeset 445 0c91995647e9
parent 431 4fb8a2e526ff
child 446 1c3a9f84dbbc
child 447 b739aa437c57
equal deleted inserted replaced
431:4fb8a2e526ff 445:0c91995647e9
   100 
   100 
   101 def utcdate(date):
   101 def utcdate(date):
   102     '''Returns hgdate in cvs-like UTC format.'''
   102     '''Returns hgdate in cvs-like UTC format.'''
   103     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   103     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
   104 
   104 
   105 
       
   106 # make keyword tools accessible
   105 # make keyword tools accessible
   107 kwtools = {'templater': None, 'hgcmd': None}
   106 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
   108 
       
   109 # store originals of monkeypatches
       
   110 _patchfile_init = patch.patchfile.__init__
       
   111 _patch_diff = patch.diff
       
   112 _dispatch_parse = dispatch._parse
       
   113 
       
   114 def _kwpatchfile_init(self, ui, fname, missing=False):
       
   115     '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
       
   116     rejects or conflicts due to expanded keywords in working dir.'''
       
   117     _patchfile_init(self, ui, fname, missing=missing)
       
   118     # shrink keywords read from working dir
       
   119     kwt = kwtools['templater']
       
   120     self.lines = kwt.shrinklines(self.fname, self.lines)
       
   121 
       
   122 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
       
   123              fp=None, changes=None, opts=None):
       
   124     '''Monkeypatch patch.diff to avoid expansion except when
       
   125     comparing against working dir.'''
       
   126     if node2 is not None:
       
   127         kwtools['templater'].matcher = util.never
       
   128     elif node1 is not None and node1 != repo.changectx().node():
       
   129         kwtools['templater'].restrict = True
       
   130     _patch_diff(repo, node1=node1, node2=node2, files=files, match=match,
       
   131                 fp=fp, changes=changes, opts=opts)
       
   132 
       
   133 # monkeypatching hgweb functions changeset and filediff
       
   134 # actual monkeypatching is done at the bottom of reposetup()
       
   135 
       
   136 web_changeset = webcommands.changeset
       
   137 web_filediff = webcommands.filediff
       
   138 
       
   139 def _kwweb_changeset(web, req, tmpl):
       
   140     '''Wraps webcommands.changeset turning off keyword expansion.'''
       
   141     kwtools['templater'].matcher = util.never
       
   142     return web_changeset(web, req, tmpl)
       
   143 
       
   144 def _kwweb_filediff(web, req, tmpl):
       
   145     '''Wraps webcommands.filediff turning off keyword expansion.'''
       
   146     kwtools['templater'].matcher = util.never
       
   147     return web_filediff(web, req, tmpl)
       
   148 
       
   149 def _kwdispatch_parse(ui, args):
       
   150     '''Monkeypatch dispatch._parse to obtain running hg command.'''
       
   151     cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args)
       
   152     kwtools['hgcmd'] = cmd
       
   153     return cmd, func, args, options, cmdoptions
       
   154 
       
   155 # dispatch._parse is run before reposetup, so wrap it here
       
   156 dispatch._parse = _kwdispatch_parse
       
   157 
   107 
   158 
   108 
   159 class kwtemplater(object):
   109 class kwtemplater(object):
   160     '''
   110     '''
   161     Sets up keyword templates, corresponding keyword regex, and
   111     Sets up keyword templates, corresponding keyword regex, and
   169         'Source': '{root}/{file},v',
   119         'Source': '{root}/{file},v',
   170         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   120         'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
   171         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   121         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
   172     }
   122     }
   173 
   123 
   174     def __init__(self, ui, repo, inc, exc):
   124     def __init__(self, ui, repo):
   175         self.ui = ui
   125         self.ui = ui
   176         self.repo = repo
   126         self.repo = repo
   177         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
   127         self.matcher = util.matcher(repo.root,
       
   128                                     inc=kwtools['inc'], exc=kwtools['exc'])[1]
   178         self.restrict = kwtools['hgcmd'] in restricted.split()
   129         self.restrict = kwtools['hgcmd'] in restricted.split()
   179 
   130 
   180         kwmaps = self.ui.configitems('keywordmaps')
   131         kwmaps = self.ui.configitems('keywordmaps')
   181         if kwmaps: # override default templates
   132         if kwmaps: # override default templates
   182             kwmaps = [(k, templater.parsestring(v, quoted=False))
   133             kwmaps = [(k, templater.parsestring(v, False))
   183                       for (k, v) in kwmaps]
   134                       for (k, v) in kwmaps]
   184             self.templates = dict(kwmaps)
   135             self.templates = dict(kwmaps)
   185         escaped = map(re.escape, self.templates.keys())
   136         escaped = map(re.escape, self.templates.keys())
   186         kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
   137         kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
   187         self.re_kw = re.compile(kwpat)
   138         self.re_kw = re.compile(kwpat)
   218         '''Returns true if path matches [keyword] pattern
   169         '''Returns true if path matches [keyword] pattern
   219         and is not a symbolic link.
   170         and is not a symbolic link.
   220         Caveat: localrepository._link fails on Windows.'''
   171         Caveat: localrepository._link fails on Windows.'''
   221         return self.matcher(path) and not islink(path)
   172         return self.matcher(path) and not islink(path)
   222 
   173 
   223     def overwrite(self, node=None, expand=True, files=None):
   174     def overwrite(self, node, expand, files):
   224         '''Overwrites selected files expanding/shrinking keywords.'''
   175         '''Overwrites selected files expanding/shrinking keywords.'''
   225         ctx = self.repo.changectx(node)
   176         ctx = self.repo.changectx(node)
   226         mf = ctx.manifest()
   177         mf = ctx.manifest()
   227         if node is not None:     # commit
   178         if node is not None:     # commit
   228             files = [f for f in ctx.files() if f in mf]
   179             files = [f for f in ctx.files() if f in mf]
   277 class kwfilelog(filelog.filelog):
   228 class kwfilelog(filelog.filelog):
   278     '''
   229     '''
   279     Subclass of filelog to hook into its read, add, cmp methods.
   230     Subclass of filelog to hook into its read, add, cmp methods.
   280     Keywords are "stored" unexpanded, and processed on reading.
   231     Keywords are "stored" unexpanded, and processed on reading.
   281     '''
   232     '''
   282     def __init__(self, opener, path):
   233     def __init__(self, opener, kwt, path):
   283         super(kwfilelog, self).__init__(opener, path)
   234         super(kwfilelog, self).__init__(opener, path)
   284         self.kwt = kwtools['templater']
   235         self.kwt = kwt
   285         self.path = path
   236         self.path = path
   286 
   237 
   287     def read(self, node):
   238     def read(self, node):
   288         '''Expands keywords when reading filelog.'''
   239         '''Expands keywords when reading filelog.'''
   289         data = super(kwfilelog, self).read(node)
   240         data = super(kwfilelog, self).read(node)
   290         return self.kwt.expand(self.path, node, data)
   241         return self.kwt.expand(self.path, node, data)
   291 
   242 
   292     def add(self, text, meta, tr, link, p1=None, p2=None):
   243     def add(self, text, meta, tr, link, p1=None, p2=None):
   293         '''Removes keyword substitutions when adding to filelog.'''
   244         '''Removes keyword substitutions when adding to filelog.'''
   294         text = self.kwt.shrink(self.path, text)
   245         text = self.kwt.shrink(self.path, text)
   295         return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
   246         return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
   296 
   247 
   297     def cmp(self, node, text):
   248     def cmp(self, node, text):
   298         '''Removes keyword substitutions for comparison.'''
   249         '''Removes keyword substitutions for comparison.'''
   299         text = self.kwt.shrink(self.path, text)
   250         text = self.kwt.shrink(self.path, text)
   300         if self.renamed(node):
   251         if self.renamed(node):
   321         raise util.Abort(_('outstanding uncommitted changes in given files'))
   272         raise util.Abort(_('outstanding uncommitted changes in given files'))
   322     wlock = lock = None
   273     wlock = lock = None
   323     try:
   274     try:
   324         wlock = repo.wlock()
   275         wlock = repo.wlock()
   325         lock = repo.lock()
   276         lock = repo.lock()
   326         kwt.overwrite(expand=expand, files=clean)
   277         kwt.overwrite(None, expand, clean)
   327     finally:
   278     finally:
   328         del wlock, lock
   279         del wlock, lock
   329 
   280 
   330 
   281 
   331 def demo(ui, repo, *args, **opts):
   282 def demo(ui, repo, *args, **opts):
   351     kwstatus = 'current'
   302     kwstatus = 'current'
   352     fn = 'demo.txt'
   303     fn = 'demo.txt'
   353     branchname = 'demobranch'
   304     branchname = 'demobranch'
   354     tmpdir = tempfile.mkdtemp('', 'kwdemo.')
   305     tmpdir = tempfile.mkdtemp('', 'kwdemo.')
   355     ui.note(_('creating temporary repo at %s\n') % tmpdir)
   306     ui.note(_('creating temporary repo at %s\n') % tmpdir)
   356     repo = localrepo.localrepository(ui, path=tmpdir, create=True)
   307     repo = localrepo.localrepository(ui, tmpdir, True)
   357     ui.setconfig('keyword', fn, '')
   308     ui.setconfig('keyword', fn, '')
   358     if args or opts.get('rcfile'):
   309     if args or opts.get('rcfile'):
   359         kwstatus = 'custom'
   310         kwstatus = 'custom'
   360     if opts.get('rcfile'):
   311     if opts.get('rcfile'):
   361         ui.readconfig(opts.get('rcfile'))
   312         ui.readconfig(opts.get('rcfile'))
   373         fp.writelines(rcmaps)
   324         fp.writelines(rcmaps)
   374         fp.close()
   325         fp.close()
   375         ui.readconfig(repo.join('hgrc'))
   326         ui.readconfig(repo.join('hgrc'))
   376     if not opts.get('default'):
   327     if not opts.get('default'):
   377         kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
   328         kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
       
   329     uisetup(ui)
   378     reposetup(ui, repo)
   330     reposetup(ui, repo)
   379     for k, v in ui.configitems('extensions'):
   331     for k, v in ui.configitems('extensions'):
   380         if k.endswith('keyword'):
   332         if k.endswith('keyword'):
   381             extension = '%s = %s' % (k, v)
   333             extension = '%s = %s' % (k, v)
   382             break
   334             break
   454     '''
   406     '''
   455     # 3rd argument sets expansion to False
   407     # 3rd argument sets expansion to False
   456     _kwfwrite(ui, repo, False, *pats, **opts)
   408     _kwfwrite(ui, repo, False, *pats, **opts)
   457 
   409 
   458 
   410 
       
   411 def uisetup(ui):
       
   412     '''Collects [keyword] config in kwtools.
       
   413     Monkeypatches dispatch._parse if needed.'''
       
   414 
       
   415     for pat, opt in ui.configitems('keyword'):
       
   416         if opt != 'ignore':
       
   417             kwtools['inc'].append(pat)
       
   418         else:
       
   419             kwtools['exc'].append(pat)
       
   420 
       
   421     if kwtools['inc']:
       
   422         def kwdispatch_parse(ui, args):
       
   423             '''Monkeypatch dispatch._parse to obtain running hg command.'''
       
   424             cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
       
   425             kwtools['hgcmd'] = cmd
       
   426             return cmd, func, args, options, cmdoptions
       
   427 
       
   428         dispatch_parse = dispatch._parse
       
   429         dispatch._parse = kwdispatch_parse
       
   430 
   459 def reposetup(ui, repo):
   431 def reposetup(ui, repo):
   460     '''Sets up repo as kwrepo for keyword substitution.
   432     '''Sets up repo as kwrepo for keyword substitution.
   461     Overrides file method to return kwfilelog instead of filelog
   433     Overrides file method to return kwfilelog instead of filelog
   462     if file matches user configuration.
   434     if file matches user configuration.
   463     Wraps commit to overwrite configured files with updated
   435     Wraps commit to overwrite configured files with updated
   464     keyword substitutions.
   436     keyword substitutions.
   465     This is done for local repos only, and only if there are
   437     Monkeypatches patch and webcommands.'''
   466     files configured at all for keyword substitution.'''
       
   467 
   438 
   468     try:
   439     try:
   469         if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
   440         if (not repo.local() or not kwtools['inc']
       
   441             or kwtools['hgcmd'] in nokwcommands.split()
   470             or '.hg' in util.splitpath(repo.root)
   442             or '.hg' in util.splitpath(repo.root)
   471             or repo._url.startswith('bundle:')):
   443             or repo._url.startswith('bundle:')):
   472             return
   444             return
   473     except AttributeError:
   445     except AttributeError:
   474         pass
   446         pass
   475 
   447 
   476     inc, exc = [], ['.hg*']
   448     kwtools['templater'] = kwt = kwtemplater(ui, repo)
   477     for pat, opt in ui.configitems('keyword'):
       
   478         if opt != 'ignore':
       
   479             inc.append(pat)
       
   480         else:
       
   481             exc.append(pat)
       
   482     if not inc:
       
   483         return
       
   484 
       
   485     kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
       
   486 
   449 
   487     class kwrepo(repo.__class__):
   450     class kwrepo(repo.__class__):
   488         def file(self, f):
   451         def file(self, f):
   489             if f[0] == '/':
   452             if f[0] == '/':
   490                 f = f[1:]
   453                 f = f[1:]
   491             return kwfilelog(self.sopener, f)
   454             return kwfilelog(self.sopener, kwt, f)
   492 
   455 
   493         def wread(self, filename):
   456         def wread(self, filename):
   494             data = super(kwrepo, self).wread(filename)
   457             data = super(kwrepo, self).wread(filename)
   495             return kwt.wread(filename, data)
   458             return kwt.wread(filename, data)
   496 
   459 
   518                     if _p2 == nullid:
   481                     if _p2 == nullid:
   519                         _p2 = ''
   482                         _p2 = ''
   520                     else:
   483                     else:
   521                         _p2 = hex(_p2)
   484                         _p2 = hex(_p2)
   522 
   485 
   523                 node = super(kwrepo,
   486                 n = super(kwrepo, self).commit(files, text, user, date, match,
   524                              self).commit(files=files, text=text, user=user,
   487                                                force, force_editor, p1, p2,
   525                                           date=date, match=match, force=force,
   488                                                extra, empty_ok)
   526                                           force_editor=force_editor,
       
   527                                           p1=p1, p2=p2, extra=extra,
       
   528                                           empty_ok=empty_ok)
       
   529 
   489 
   530                 # restore commit hooks
   490                 # restore commit hooks
   531                 for name, cmd in commithooks.iteritems():
   491                 for name, cmd in commithooks.iteritems():
   532                     ui.setconfig('hooks', name, cmd)
   492                     ui.setconfig('hooks', name, cmd)
   533                 if node is not None:
   493                 if n is not None:
   534                     kwt.overwrite(node=node)
   494                     kwt.overwrite(n, True, None)
   535                     repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
   495                     repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
   536                 return node
   496                 return n
   537             finally:
   497             finally:
   538                 del wlock, lock
   498                 del wlock, lock
   539 
   499 
       
   500     # monkeypatches
       
   501     def kwpatchfile_init(self, ui, fname, missing=False):
       
   502         '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
       
   503         rejects or conflicts due to expanded keywords in working dir.'''
       
   504         patchfile_init(self, ui, fname, missing)
       
   505         # shrink keywords read from working dir
       
   506         self.lines = kwt.shrinklines(self.fname, self.lines)
       
   507 
       
   508     def kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
       
   509                 fp=None, changes=None, opts=None):
       
   510         '''Monkeypatch patch.diff to avoid expansion except when
       
   511         comparing against working dir.'''
       
   512         if node2 is not None:
       
   513             kwt.matcher = util.never
       
   514         elif node1 is not None and node1 != repo.changectx().node():
       
   515             kwt.restrict = True
       
   516         patch_diff(repo, node1, node2, files, match, fp, changes, opts)
       
   517 
       
   518     def kwweb_changeset(web, req, tmpl):
       
   519         '''Wraps webcommands.changeset turning off keyword expansion.'''
       
   520         kwt.matcher = util.never
       
   521         return webcommands_changeset(web, req, tmpl)
       
   522 
       
   523     def kwweb_filediff(web, req, tmpl):
       
   524         '''Wraps webcommands.filediff turning off keyword expansion.'''
       
   525         kwt.matcher = util.never
       
   526         return webcommands_filediff(web, req, tmpl)
       
   527 
   540     repo.__class__ = kwrepo
   528     repo.__class__ = kwrepo
   541     patch.patchfile.__init__ = _kwpatchfile_init
   529 
   542     patch.diff = _kw_diff
   530     patchfile_init = patch.patchfile.__init__
   543     webcommands.changeset = webcommands.rev = _kwweb_changeset
   531     patch_diff = patch.diff
   544     webcommands.filediff = webcommands.diff = _kwweb_filediff
   532     webcommands_changeset = webcommands.changeset
       
   533     webcommands_filediff = webcommands.filediff
       
   534 
       
   535     patch.patchfile.__init__ = kwpatchfile_init
       
   536     patch.diff = kw_diff
       
   537     webcommands.changeset = webcommands.rev = kwweb_changeset
       
   538     webcommands.filediff = webcommands.diff = kwweb_filediff
   545 
   539 
   546 
   540 
   547 cmdtable = {
   541 cmdtable = {
   548     'kwdemo':
   542     'kwdemo':
   549         (demo,
   543         (demo,