hgkw/keyword.py
branchkwmap-templates
changeset 185 bc5cd6cf69bc
parent 184 30b3e6a09a9d
child 186 c1b7b1d052de
equal deleted inserted replaced
184:30b3e6a09a9d 185:bc5cd6cf69bc
    18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
    18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
    19 #
    19 #
    20 # Keyword expansion is based on Mercurial's changeset template mappings.
    20 # Keyword expansion is based on Mercurial's changeset template mappings.
    21 # The extension provides an additional UTC-date filter ({date|utcdate}).
    21 # The extension provides an additional UTC-date filter ({date|utcdate}).
    22 #
    22 #
    23 # The user has the choice either to create his own keywords and their
       
    24 # expansions or to use the CVS-like default ones.
       
    25 #
       
    26 # Expansions spanning more than one line are truncated to their first line.
    23 # Expansions spanning more than one line are truncated to their first line.
    27 # Incremental expansion (like CVS' $Log$) is not supported.
    24 # Incremental expansion (like CVS' $Log$) is not supported.
    28 #
    25 #
    29 # Binary files are not touched.
    26 # Binary files are not touched.
    30 #
    27 #
    35 #     # or, if script in hgext folder:
    32 #     # or, if script in hgext folder:
    36 #     # hgext.keyword =
    33 #     # hgext.keyword =
    37 
    34 
    38 '''keyword expansion in local repositories
    35 '''keyword expansion in local repositories
    39 
    36 
    40 This extension expands RCS/CVS-like or self-customized keywords in
    37 This extension expands RCS/CVS-like or self-customized $Keywords$
    41 the text files selected by your configuration.
    38 in the text files selected by your configuration.
    42 
    39 
    43 Keywords are only expanded in local repositories and not logged by
    40 Keywords are only expanded in local repositories and not logged by
    44 Mercurial internally. The mechanism can be regarded as a convenience
    41 Mercurial internally. The mechanism can be regarded as a convenience
    45 for the current user and may be turned off anytime.
    42 for the current user or archive distribution.
    46 
       
    47 An additional date template filter {date|utcdate} is provided.
       
    48 
       
    49 Caveat: "hg import" might fail if the patches were exported from a
       
    50 repo with a different/no keyword setup, whereas "hg unbundle" is
       
    51 safe.
       
    52 
    43 
    53 Configuration is done in the [keyword] and [keywordmaps] sections of
    44 Configuration is done in the [keyword] and [keywordmaps] sections of
    54 hgrc files.
    45 hgrc files.
    55 
    46 
    56 Example:
    47 Example:
    57     [extensions]
    48     [extensions]
    58     hgext.keyword =
    49     hgext.keyword =
    59 
    50 
    60     [keyword]
    51     [keyword]
    61     # expand keywords in every python file,
    52     # expand keywords in every python file except those matching "x*"
    62     # except those matching "x*"
       
    63     **.py =
    53     **.py =
    64     x* = ignore
    54     x* = ignore
    65 
    55 
    66 For [keywordmaps] demonstration run "hg kwdemo".
    56 Note: the more specific you are in your [keyword] filename patterns
       
    57       the less you lose speed in huge repos.
       
    58 
       
    59 For a [keywordmaps] template mapping and expansion demonstration
       
    60 run "hg kwdemo".
       
    61 
       
    62 An additional date template filter {date|utcdate} is provided.
       
    63 
       
    64 You can replace the default template mappings with customized keywords
       
    65 and templates of your choice.
       
    66 Again, run "hg kwdemo" to control the results of your config changes.
       
    67 
       
    68 When you change keyword configuration, especially the active keywords,
       
    69 and do not want to store expanded keywords in change history, run
       
    70 "hg kwshrink", and then change configuration.
       
    71 
       
    72 Caveat: "hg import" fails if the patch context contains an active
       
    73         keyword. In that case run "hg kwshrink", reimport, and then
       
    74         "hg kwexpand".
       
    75         Or, better, use bundle/unbundle to share changes.
    67 '''
    76 '''
    68 
    77 
       
    78 from mercurial import commands, cmdutil, context, fancyopts
       
    79 from mercurial import filelog, localrepo, templater, util, hg
    69 from mercurial.i18n import gettext as _
    80 from mercurial.i18n import gettext as _
    70 from mercurial import commands, fancyopts, templater, util
       
    71 from mercurial import cmdutil, context, filelog, localrepo
       
    72 # findcmd might be in cmdutil or commands
    81 # findcmd might be in cmdutil or commands
    73 # depending on mercurial version
    82 # depending on mercurial version
    74 if hasattr(cmdutil, 'findcmd'):
    83 if hasattr(cmdutil, 'findcmd'):
    75     findcmd = cmdutil.findcmd
    84     findcmd = cmdutil.findcmd
    76 else:
    85 else:
    77     findcmd = commands.findcmd
    86     findcmd = commands.findcmd
    78 import os, re, shutil, sys, tempfile, time
    87 import os, re, shutil, sys, tempfile, time
       
    88 
       
    89 commands.optionalrepo += ' kwdemo'
    79 
    90 
    80 deftemplates = {
    91 deftemplates = {
    81     'Revision': '{node|short}',
    92     'Revision': '{node|short}',
    82     'Author': '{author|user}',
    93     'Author': '{author|user}',
    83     'Date': '{date|utcdate}',
    94     'Date': '{date|utcdate}',
   168         '''Returns text with all keyword substitutions removed.'''
   179         '''Returns text with all keyword substitutions removed.'''
   169         if util.binary(text):
   180         if util.binary(text):
   170             return text
   181             return text
   171         return self.re_kw.sub(r'$\1$', text)
   182         return self.re_kw.sub(r'$\1$', text)
   172 
   183 
   173     def overwrite(self, candidates, mn):
   184     def overwrite(self, candidates, manifest, expand=True, commit=True):
   174         '''Overwrites candidates in working dir expanding keywords.'''
   185         '''Overwrites candidates in working dir expanding keywords.'''
       
   186         if expand:
       
   187             sub = self.kwsub
       
   188             action = 'expanding'
       
   189         else:
       
   190             sub = r'$\1$'
       
   191             action = 'shrinking'
       
   192         if not commit:
       
   193             notify = self.ui.note
       
   194         else:
       
   195             notify = self.ui.debug
   175         files = []
   196         files = []
   176         m = self.repo.manifest.read(mn)
       
   177         for f in candidates:
   197         for f in candidates:
   178             data = self.repo.wread(f)
   198             data = self.repo.wread(f)
   179             if not util.binary(data):
   199             if not util.binary(data):
   180                 self.path = f
   200                 self.path = f
   181                 data, kwct = self.re_kw.subn(self.kwsub, data)
   201                 data, kwct = self.re_kw.subn(sub, data)
   182                 if kwct:
   202                 if kwct:
   183                     self.ui.debug(_('overwriting %s expanding keywords\n') % f)
   203                     notify(_('overwriting %s %s keywords\n') % (f, action))
   184                     self.repo.wwrite(f, data, m.flags(f))
   204                     self.repo.wwrite(f, data, manifest.flags(f))
   185                     files.append(f)
   205                     files.append(f)
   186         if files:
   206         if files:
   187             self.repo.dirstate.update(files, 'n')
   207             self.repo.dirstate.update(files, 'n')
   188 
   208 
   189 class kwfilelog(filelog.filelog):
   209 class kwfilelog(filelog.filelog):
   211         if self.renamed(node):
   231         if self.renamed(node):
   212             t2 = super(kwfilelog, self).read(node)
   232             t2 = super(kwfilelog, self).read(node)
   213             return t2 != text
   233             return t2 != text
   214         return super(kwfilelog, self).cmp(node, text)
   234         return super(kwfilelog, self).cmp(node, text)
   215 
   235 
       
   236 def overwrite(ui, repo, files=None, expand=False):
       
   237     '''Expands/shrinks keywords in working directory.'''
       
   238     wlock = repo.wlock()
       
   239     try:
       
   240         ctx = repo.changectx()
       
   241         if not ctx:
       
   242             raise hg.RepoError(_('no changeset found'))
       
   243         for changed in repo.status()[:4]:
       
   244             if changed:
       
   245                 raise util.Abort(_('local changes detected'))
       
   246         kwfmatcher = keywordmatcher(ui, repo)
       
   247         if kwfmatcher is None:
       
   248             ui.warn(_('no files configured for keyword expansion\n'))
       
   249             return
       
   250         m = ctx.manifest()
       
   251         if files:
       
   252             files = [f for f in files if f in m.keys()]
       
   253         else:
       
   254             files = m.keys()
       
   255         files = [f for f in files if kwfmatcher(f) and not os.path.islink(f)]
       
   256         if not files:
       
   257             ui.warn(_('given files not tracked or '
       
   258                       'not configured for expansion\n'))
       
   259             return
       
   260         kwt = kwtemplater(ui, repo, node=ctx.node())
       
   261         kwt.overwrite(files, m, expand=expand, commit=False)
       
   262     finally:
       
   263         wlock.release()
       
   264 
       
   265 
       
   266 def shrink(ui, repo, *args):
       
   267     '''revert expanded keywords in working directory
       
   268 
       
   269     run before:
       
   270                disabling keyword expansion
       
   271                changing keyword expansion configuration
       
   272     or if you experience problems with "hg import"
       
   273     '''
       
   274     overwrite(ui, repo, args, expand=False)
       
   275 
       
   276 def expand(ui, repo, *args):
       
   277     '''expand keywords in working directory
       
   278 
       
   279     run after (re)enabling keyword expansion
       
   280     '''
       
   281     overwrite(ui, repo, args, expand=True)
   216 
   282 
   217 def demo(ui, repo, **opts):
   283 def demo(ui, repo, **opts):
   218     '''print [keywordmaps] configuration and an expansion example
   284     '''print [keywordmaps] configuration and an expansion example
       
   285 
       
   286     Show current or default keyword template maps and their expansion
   219     '''
   287     '''
   220     msg = 'hg keyword config and expansion example'
   288     msg = 'hg keyword config and expansion example'
   221     fn = 'demo.txt'
   289     fn = 'demo.txt'
   222     tmpdir = tempfile.mkdtemp('', 'kwdemo.')
   290     tmpdir = tempfile.mkdtemp('', 'kwdemo.')
   223     ui.note(_('creating temporary repo at %s\n') % tmpdir)
   291     ui.note(_('creating temporary repo at %s\n') % tmpdir)
   306                 cl = self.changelog.read(node)
   374                 cl = self.changelog.read(node)
   307                 candidates = [f for f in cl[3] if kwfmatcher(f)
   375                 candidates = [f for f in cl[3] if kwfmatcher(f)
   308                               and f not in removed
   376                               and f not in removed
   309                               and not os.path.islink(self.wjoin(f))]
   377                               and not os.path.islink(self.wjoin(f))]
   310                 if candidates:
   378                 if candidates:
       
   379                     m = self.manifest.read(cl[0])
   311                     kwt = kwtemplater(ui, self, node=node)
   380                     kwt = kwtemplater(ui, self, node=node)
   312                     kwt.overwrite(candidates, cl[0])
   381                     kwt.overwrite(candidates, m)
   313                 return node
   382                 return node
   314             finally:
   383             finally:
   315                 if wrelease:
   384                 if wrelease:
   316                     wlock.release()
   385                     wlock.release()
   317 
   386 
   319 
   388 
   320 
   389 
   321 cmdtable = {
   390 cmdtable = {
   322     'kwdemo':
   391     'kwdemo':
   323         (demo,
   392         (demo,
   324          [('d', 'default', None, _('use default keyword maps'))],
   393          [('d', 'default', None, _('show default keyword template maps'))],
   325          _('hg kwdemo [-d]')),
   394          _('hg kwdemo [-d]')),
       
   395     'kwshrink': (shrink, [], _('hg kwshrink [NAME] ...')),
       
   396     'kwexpand': (expand, [], _('hg kwexpand [NAME] ...')),
   326 }
   397 }