hgkw/keyword.py
changeset 290 b05795ad5632
parent 288 80a300088654
child 292 783c1310e20f
equal deleted inserted replaced
288:80a300088654 290:b05795ad5632
    78 Caveat: "hg import" fails if the patch context contains an active
    78 Caveat: "hg import" fails if the patch context contains an active
    79         keyword. In that case run "hg kwshrink", and then reimport.
    79         keyword. In that case run "hg kwshrink", and then reimport.
    80         Or, better, use bundle/unbundle to share changes.
    80         Or, better, use bundle/unbundle to share changes.
    81 '''
    81 '''
    82 
    82 
    83 from mercurial import commands, cmdutil, context, fancyopts
    83 from mercurial import commands, cmdutil, context, dispatch
    84 from mercurial import filelog, localrepo, revlog, templater, util
    84 from mercurial import filelog, localrepo, revlog, templater, util
    85 from mercurial.i18n import gettext as _
    85 from mercurial.i18n import _
    86 import getopt, os.path, re, shutil, sys, tempfile, time
    86 import re, shutil, sys, tempfile, time
    87 
       
    88 # backwards compatibility hacks
       
    89 
       
    90 try:
       
    91     # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
       
    92     # also avoid name conflict with other dispatch package(s)
       
    93     from mercurial.dispatch import _parse
       
    94 except ImportError:
       
    95     try:
       
    96         # commands.parse moves to cmdutil.parse in 0c61124ad877
       
    97         _parse = cmdutil.parse
       
    98     except AttributeError:
       
    99         _parse = commands.parse
       
   100 
       
   101 def _pathto(repo, f, cwd=None):
       
   102     '''kwfiles behaves similar to status, using pathto since 78b6add1f966.'''
       
   103     try:
       
   104         return repo.pathto(f, cwd)
       
   105     except AttributeError:
       
   106         return f
       
   107 
       
   108 # commands.parse/cmdutil.parse returned nothing for
       
   109 # "hg diff --rev" before 88803a69b24a due to bug in fancyopts
       
   110 def _fancyopts(args, options, state):
       
   111     '''Fixed fancyopts from 88803a69b24a.'''
       
   112     long = []
       
   113     short = ''
       
   114     map = {}
       
   115     dt = {}
       
   116     for s, l, d, c in options:
       
   117         pl = l.replace('-', '_')
       
   118         map['-'+s] = map['--'+l] = pl
       
   119         if isinstance(d, list):
       
   120             state[pl] = d[:]
       
   121         else:
       
   122             state[pl] = d
       
   123         dt[pl] = type(d)
       
   124         if (d is not None and d is not True and d is not False and
       
   125             not callable(d)):
       
   126             if s: s += ':'
       
   127             if l: l += '='
       
   128         if s: short = short + s
       
   129         if l: long.append(l)
       
   130     opts, args = getopt.getopt(args, short, long)
       
   131     for opt, arg in opts:
       
   132         if dt[map[opt]] is type(fancyopts): state[map[opt]](state, map[opt], arg)
       
   133         elif dt[map[opt]] is type(1): state[map[opt]] = int(arg)
       
   134         elif dt[map[opt]] is type(''): state[map[opt]] = arg
       
   135         elif dt[map[opt]] is type([]): state[map[opt]].append(arg)
       
   136         elif dt[map[opt]] is type(None): state[map[opt]] = True
       
   137         elif dt[map[opt]] is type(False): state[map[opt]] = True
       
   138     return args
       
   139 
       
   140 fancyopts.fancyopts = _fancyopts
       
   141 
       
   142 
    87 
   143 commands.optionalrepo += ' kwdemo'
    88 commands.optionalrepo += ' kwdemo'
   144 
    89 
   145 def utcdate(date):
    90 def utcdate(date):
   146     '''Returns hgdate in cvs-like UTC format.'''
    91     '''Returns hgdate in cvs-like UTC format.'''
   177         escaped = map(re.escape, self.templates.keys())
   122         escaped = map(re.escape, self.templates.keys())
   178         kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
   123         kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
   179         self.re_kw = re.compile(kwpat)
   124         self.re_kw = re.compile(kwpat)
   180         if self.t:
   125         if self.t:
   181             templater.common_filters['utcdate'] = utcdate
   126             templater.common_filters['utcdate'] = utcdate
   182             self.t = self._changeset_templater()
   127             self.t = cmdutil.changeset_templater(self.ui, self.repo,
   183 
   128                                                  False, '', False)
   184     def _changeset_templater(self):
       
   185         '''Backwards compatible cmdutil.changeset_templater.'''
       
   186         # before 1e0b94cfba0e there was an extra "brinfo" argument
       
   187         try:
       
   188             return cmdutil.changeset_templater(self.ui, self.repo,
       
   189                                                False, '', False)
       
   190         except TypeError:
       
   191             return cmdutil.changeset_templater(self.ui, self.repo,
       
   192                                                False, None, '', False)
       
   193 
       
   194     def _wwrite(self, f, data, man):
       
   195         '''Makes repo.wwrite backwards compatible.'''
       
   196         # 656e06eebda7 removed file descriptor argument
       
   197         # 67982d3ee76c added flags argument
       
   198         try:
       
   199             self.repo.wwrite(f, data, man.flags(f))
       
   200         except (AttributeError, TypeError):
       
   201             self.repo.wwrite(f, data)
       
   202 
       
   203     def _normal(self, files):
       
   204         '''Backwards compatible repo.dirstate.normal/update.'''
       
   205         # 6fd953d5faea introduced dirstate.normal()
       
   206         try:
       
   207             for f in files:
       
   208                 self.repo.dirstate.normal(f)
       
   209         except AttributeError:
       
   210             self.repo.dirstate.update(files, 'n')
       
   211 
   129 
   212     def kwsub(self, mobj):
   130     def kwsub(self, mobj):
   213         '''Substitutes keyword using corresponding template.'''
   131         '''Substitutes keyword using corresponding template.'''
   214         kw = mobj.group(1)
   132         kw = mobj.group(1)
   215         self.t.use_template(self.templates[kw])
   133         self.t.use_template(self.templates[kw])
   236             return data
   154             return data
   237         return self.substitute(node, data, self.re_kw.sub)
   155         return self.substitute(node, data, self.re_kw.sub)
   238 
   156 
   239     def process(self, node, data):
   157     def process(self, node, data):
   240         '''Returns a tuple: data, count.
   158         '''Returns a tuple: data, count.
   241         Count is number of keywords/keyword substitutions.
   159         Count is number of keywords/keyword substitutions, indicates
       
   160         to caller whether to act on file containing data.
   242         Keywords in data are expanded, if templater was initialized.'''
   161         Keywords in data are expanded, if templater was initialized.'''
   243         if util.binary(data):
   162         if util.binary(data):
   244             return data, None
   163             return data, None
   245         if self.t:
   164         if self.t:
   246             return self.substitute(node, data, self.re_kw.subn)
   165             return self.substitute(node, data, self.re_kw.subn)
   257         Keywords are expanded if keyword templater is initialized,
   176         Keywords are expanded if keyword templater is initialized,
   258         otherwise their substitution is removed.'''
   177         otherwise their substitution is removed.'''
   259         expand = self.t is not None
   178         expand = self.t is not None
   260         action = ('shrinking', 'expanding')[expand]
   179         action = ('shrinking', 'expanding')[expand]
   261         notify = (self.ui.note, self.ui.debug)[commit]
   180         notify = (self.ui.note, self.ui.debug)[commit]
   262         overwritten = []
       
   263         for f in candidates:
   181         for f in candidates:
   264             fp = self.repo.file(f, kwexp=expand, kwmatch=True)
   182             fp = self.repo.file(f, kwexp=expand, kwmatch=True)
   265             data, kwfound = fp.kwctread(man[f])
   183             data, kwfound = fp.kwctread(man[f])
   266             if kwfound:
   184             if kwfound:
   267                 notify(_('overwriting %s %s keywords\n') % (f, action))
   185                 notify(_('overwriting %s %s keywords\n') % (f, action))
   268                 self._wwrite(f, data, man)
   186                 self.repo.wwrite(f, data, man.flags(f))
   269                 overwritten.append(f)
   187                 self.repo.dirstate.normal(f)
   270         self._normal(overwritten)
       
   271 
   188 
   272 class kwfilelog(filelog.filelog):
   189 class kwfilelog(filelog.filelog):
   273     '''
   190     '''
   274     Subclass of filelog to hook into its read, add, cmp methods.
   191     Subclass of filelog to hook into its read, add, cmp methods.
   275     Keywords are "stored" unexpanded, and processed on reading.
   192     Keywords are "stored" unexpanded, and processed on reading.
   301             t2 = super(kwfilelog, self).read(node)
   218             t2 = super(kwfilelog, self).read(node)
   302             return t2 != text
   219             return t2 != text
   303         return revlog.revlog.cmp(self, node, text)
   220         return revlog.revlog.cmp(self, node, text)
   304 
   221 
   305 def _status(ui, repo, *pats, **opts):
   222 def _status(ui, repo, *pats, **opts):
       
   223     '''Bails out if [keyword] configuration is not active.
       
   224     Returns status of working directory.'''
   306     if hasattr(ui, 'kwfmatcher'):
   225     if hasattr(ui, 'kwfmatcher'):
   307         files, match, anypats = cmdutil.matchpats(repo, pats, opts)
   226         files, match, anypats = cmdutil.matchpats(repo, pats, opts)
   308         return repo.status(files=files, match=match, list_clean=True)
   227         return repo.status(files=files, match=match, list_clean=True)
   309     if ui.configitems('keyword'):
   228     if ui.configitems('keyword'):
   310         raise util.Abort(_('[keyword] patterns cannot match'))
   229         raise util.Abort(_('[keyword] patterns cannot match'))
   438     if opts['untracked']:
   357     if opts['untracked']:
   439         files = modified + added + unknown + clean
   358         files = modified + added + unknown + clean
   440     else:
   359     else:
   441         files = modified + added + clean
   360         files = modified + added + clean
   442     files.sort()
   361     files.sort()
   443     # use the full definition of repo._link for backwards compatibility
   362     kwfiles = [f for f in files if ui.kwfmatcher(f) and not repo._link(f)]
   444     kwfiles = [f for f in files if ui.kwfmatcher(f)
       
   445                and not os.path.islink(repo.wjoin(f))]
       
   446     cwd = pats and repo.getcwd() or ''
   363     cwd = pats and repo.getcwd() or ''
   447     allf = opts['all']
   364     allf = opts['all']
   448     ignore = opts['ignore']
   365     ignore = opts['ignore']
   449     flag = (allf or ui.verbose) and 1 or 0
   366     flag = (allf or ui.verbose) and 1 or 0
   450     if not ignore:
   367     if not ignore:
   451         format = ('%s\n', 'K %s\n')[flag]
   368         format = ('%s\n', 'K %s\n')[flag]
   452         for k in kwfiles:
   369         for k in kwfiles:
   453             ui.write(format % _pathto(repo, k, cwd))
   370             ui.write(format % repo.pathto(k, cwd))
   454     if allf or ignore:
   371     if allf or ignore:
   455         format = ('%s\n', 'I %s\n')[flag]
   372         format = ('%s\n', 'I %s\n')[flag]
   456         for i in [f for f in files if f not in kwfiles]:
   373         for i in [f for f in files if f not in kwfiles]:
   457             ui.write(format % _pathto(repo, i, cwd))
   374             ui.write(format % repo.pathto(i, cwd))
   458 
   375 
   459 def shrink(ui, repo, *pats, **opts):
   376 def shrink(ui, repo, *pats, **opts):
   460     '''revert expanded keywords in working directory
   377     '''revert expanded keywords in working directory
   461 
   378 
   462     Run before changing/disabling active keywords
   379     Run before changing/disabling active keywords
   479 
   396 
   480     nokwcommands = ['add', 'addremove', 'bundle', 'clone', 'copy', 'export',
   397     nokwcommands = ['add', 'addremove', 'bundle', 'clone', 'copy', 'export',
   481                     'grep', 'identify', 'incoming', 'init', 'outgoing', 'push',
   398                     'grep', 'identify', 'incoming', 'init', 'outgoing', 'push',
   482                     'remove', 'rename', 'rollback', 'convert']
   399                     'remove', 'rename', 'rollback', 'convert']
   483 
   400 
   484     if not repo.local() or _parse(ui, sys.argv[1:])[0] in nokwcommands:
   401     if (not repo.local() or
       
   402         dispatch._parse(ui, sys.argv[1:])[0] in nokwcommands):
   485         return
   403         return
   486 
   404 
   487     inc, exc = [], ['.hgtags']
   405     inc, exc = [], ['.hgtags']
   488     for pat, opt in ui.configitems('keyword'):
   406     for pat, opt in ui.configitems('keyword'):
   489         if opt != 'ignore':
   407         if opt != 'ignore':
   502             if kwmatch or ui.kwfmatcher(f):
   420             if kwmatch or ui.kwfmatcher(f):
   503                 kwt = kwtemplater(ui, self, kwexp, path=f)
   421                 kwt = kwtemplater(ui, self, kwexp, path=f)
   504                 return kwfilelog(self.sopener, f, kwt)
   422                 return kwfilelog(self.sopener, f, kwt)
   505             return filelog.filelog(self.sopener, f)
   423             return filelog.filelog(self.sopener, f)
   506 
   424 
   507         def _commit(self, files, text, user, date, match, force, lock, wlock,
   425         def commit(self, files=None, text='', user=None, date=None,
   508                     force_editor, p1, p2, extra):
   426                    match=util.always, force=False, force_editor=False,
   509             '''Private commit wrapper for backwards compatibility.'''
   427                    p1=None, p2=None, extra={}):
       
   428             wlock = lock = None
   510             try:
   429             try:
   511                 return super(kwrepo, self).commit(files=files, text=text,
   430                 wlock = self.wlock()
   512                                                   user=user, date=date,
   431                 lock = self.lock()
   513                                                   match=match, force=force,
   432                 node = super(kwrepo,
   514                                                   lock=lock, wlock=wlock,
   433                              self).commit(files=files, text=text, user=user,
   515                                                   force_editor=force_editor,
   434                                           date=date, match=match, force=force,
   516                                                   p1=p1, p2=p2, extra=extra)
   435                                           force_editor=force_editor,
   517             except TypeError:
   436                                           p1=p1, p2=p2, extra=extra)
   518                 return super(kwrepo, self).commit(files=files, text=text,
       
   519                                                   user=user, date=date,
       
   520                                                   match=match, force=force,
       
   521                                                   force_editor=force_editor,
       
   522                                                   p1=p1, p2=p2, extra=extra)
       
   523 
       
   524         def commit(self, files=None, text='', user=None, date=None,
       
   525                    match=util.always, force=False, lock=None, wlock=None,
       
   526                    force_editor=False, p1=None, p2=None, extra={}):
       
   527             # (w)lock arguments removed in 126f527b3ba3
       
   528             # so they are None or what was passed to commit
       
   529             # use private _(w)lock for deletion
       
   530             _lock = lock
       
   531             _wlock = wlock
       
   532             del wlock, lock
       
   533             try:
       
   534                 if not _wlock:
       
   535                     _wlock = self.wlock()
       
   536                 if not _lock:
       
   537                     _lock = self.lock()
       
   538                 node = self._commit(files, text, user, date, match, force,
       
   539                                     _lock, _wlock, force_editor, p1, p2, extra)
       
   540                 if node is not None:
   437                 if node is not None:
   541                     cl = self.changelog.read(node)
   438                     cl = self.changelog.read(node)
   542                     mn = self.manifest.read(cl[0])
   439                     mn = self.manifest.read(cl[0])
   543                     candidates = [f for f in cl[3] if mn.has_key(f)
   440                     candidates = [f for f in cl[3] if mn.has_key(f)
   544                                   and _iskwfile(ui, mn, f)]
   441                                   and _iskwfile(ui, mn, f)]
   545                     if candidates:
   442                     if candidates:
   546                         # 6th, 7th arguments set expansion, commit to True
   443                         # 6th, 7th arguments set expansion, commit to True
   547                         _overwrite(ui, self, candidates, node, mn, True, True)
   444                         _overwrite(ui, self, candidates, node, mn, True, True)
   548                 return node
   445                 return node
   549             finally:
   446             finally:
   550                 del _wlock, _lock
   447                 del wlock, lock
   551 
   448 
   552     repo.__class__ = kwrepo
   449     repo.__class__ = kwrepo
   553 
   450 
   554 
   451 
   555 cmdtable = {
   452 cmdtable = {