mercurial/templatekw.py
changeset 43076 2372284d9457
parent 42518 88ba0ff94605
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    27     registrar,
    27     registrar,
    28     scmutil,
    28     scmutil,
    29     templateutil,
    29     templateutil,
    30     util,
    30     util,
    31 )
    31 )
    32 from .utils import (
    32 from .utils import stringutil
    33     stringutil,
       
    34 )
       
    35 
    33 
    36 _hybrid = templateutil.hybrid
    34 _hybrid = templateutil.hybrid
    37 hybriddict = templateutil.hybriddict
    35 hybriddict = templateutil.hybriddict
    38 hybridlist = templateutil.hybridlist
    36 hybridlist = templateutil.hybridlist
    39 compatdict = templateutil.compatdict
    37 compatdict = templateutil.compatdict
    40 compatlist = templateutil.compatlist
    38 compatlist = templateutil.compatlist
    41 _showcompatlist = templateutil._showcompatlist
    39 _showcompatlist = templateutil._showcompatlist
       
    40 
    42 
    41 
    43 def getlatesttags(context, mapping, pattern=None):
    42 def getlatesttags(context, mapping, pattern=None):
    44     '''return date, distance and name for the latest tag of rev'''
    43     '''return date, distance and name for the latest tag of rev'''
    45     repo = context.resource(mapping, 'repo')
    44     repo = context.resource(mapping, 'repo')
    46     ctx = context.resource(mapping, 'ctx')
    45     ctx = context.resource(mapping, 'ctx')
    64     while todo:
    63     while todo:
    65         rev = todo.pop()
    64         rev = todo.pop()
    66         if rev in latesttags:
    65         if rev in latesttags:
    67             continue
    66             continue
    68         ctx = repo[rev]
    67         ctx = repo[rev]
    69         tags = [t for t in ctx.tags()
    68         tags = [
    70                 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
    69             t
    71                     and match(t))]
    70             for t in ctx.tags()
       
    71             if (repo.tagtype(t) and repo.tagtype(t) != 'local' and match(t))
       
    72         ]
    72         if tags:
    73         if tags:
    73             latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
    74             latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
    74             continue
    75             continue
    75         try:
    76         try:
    76             ptags = [latesttags[p.rev()] for p in ctx.parents()]
    77             ptags = [latesttags[p.rev()] for p in ctx.parents()]
    78                 if ptags[0][2] == ptags[1][2]:
    79                 if ptags[0][2] == ptags[1][2]:
    79                     # The tuples are laid out so the right one can be found by
    80                     # The tuples are laid out so the right one can be found by
    80                     # comparison in this case.
    81                     # comparison in this case.
    81                     pdate, pdist, ptag = max(ptags)
    82                     pdate, pdist, ptag = max(ptags)
    82                 else:
    83                 else:
       
    84 
    83                     def key(x):
    85                     def key(x):
    84                         tag = x[2][0]
    86                         tag = x[2][0]
    85                         if ctx.rev() is None:
    87                         if ctx.rev() is None:
    86                             # only() doesn't support wdir
    88                             # only() doesn't support wdir
    87                             prevs = [c.rev() for c in ctx.parents()]
    89                             prevs = [c.rev() for c in ctx.parents()]
    91                             changes = repo.revs('only(%d, %s)', ctx.rev(), tag)
    93                             changes = repo.revs('only(%d, %s)', ctx.rev(), tag)
    92                             changessincetag = len(changes)
    94                             changessincetag = len(changes)
    93                         # Smallest number of changes since tag wins. Date is
    95                         # Smallest number of changes since tag wins. Date is
    94                         # used as tiebreaker.
    96                         # used as tiebreaker.
    95                         return [-changessincetag, x[0]]
    97                         return [-changessincetag, x[0]]
       
    98 
    96                     pdate, pdist, ptag = max(ptags, key=key)
    99                     pdate, pdist, ptag = max(ptags, key=key)
    97             else:
   100             else:
    98                 pdate, pdist, ptag = ptags[0]
   101                 pdate, pdist, ptag = ptags[0]
    99         except KeyError:
   102         except KeyError:
   100             # Cache miss - recurse
   103             # Cache miss - recurse
   102             todo.extend(p.rev() for p in ctx.parents())
   105             todo.extend(p.rev() for p in ctx.parents())
   103             continue
   106             continue
   104         latesttags[rev] = pdate, pdist + 1, ptag
   107         latesttags[rev] = pdate, pdist + 1, ptag
   105     return latesttags[rev]
   108     return latesttags[rev]
   106 
   109 
       
   110 
   107 def getlogcolumns():
   111 def getlogcolumns():
   108     """Return a dict of log column labels"""
   112     """Return a dict of log column labels"""
   109     _ = pycompat.identity  # temporarily disable gettext
   113     _ = pycompat.identity  # temporarily disable gettext
   110     # i18n: column positioning for "hg log"
   114     # i18n: column positioning for "hg log"
   111     columns = _('bookmark:    %s\n'
   115     columns = _(
   112                 'branch:      %s\n'
   116         'bookmark:    %s\n'
   113                 'changeset:   %s\n'
   117         'branch:      %s\n'
   114                 'copies:      %s\n'
   118         'changeset:   %s\n'
   115                 'date:        %s\n'
   119         'copies:      %s\n'
   116                 'extra:       %s=%s\n'
   120         'date:        %s\n'
   117                 'files+:      %s\n'
   121         'extra:       %s=%s\n'
   118                 'files-:      %s\n'
   122         'files+:      %s\n'
   119                 'files:       %s\n'
   123         'files-:      %s\n'
   120                 'instability: %s\n'
   124         'files:       %s\n'
   121                 'manifest:    %s\n'
   125         'instability: %s\n'
   122                 'obsolete:    %s\n'
   126         'manifest:    %s\n'
   123                 'parent:      %s\n'
   127         'obsolete:    %s\n'
   124                 'phase:       %s\n'
   128         'parent:      %s\n'
   125                 'summary:     %s\n'
   129         'phase:       %s\n'
   126                 'tag:         %s\n'
   130         'summary:     %s\n'
   127                 'user:        %s\n')
   131         'tag:         %s\n'
   128     return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
   132         'user:        %s\n'
   129                     i18n._(columns).splitlines(True)))
   133     )
       
   134     return dict(
       
   135         zip(
       
   136             [s.split(':', 1)[0] for s in columns.splitlines()],
       
   137             i18n._(columns).splitlines(True),
       
   138         )
       
   139     )
       
   140 
   130 
   141 
   131 # basic internal templates
   142 # basic internal templates
   132 _changeidtmpl = '{rev}:{node|formatnode}'
   143 _changeidtmpl = '{rev}:{node|formatnode}'
   133 
   144 
   134 # default templates internally used for rendering of lists
   145 # default templates internally used for rendering of lists
   135 defaulttempl = {
   146 defaulttempl = {
   136     'parent': _changeidtmpl + ' ',
   147     'parent': _changeidtmpl + ' ',
   137     'manifest': _changeidtmpl,
   148     'manifest': _changeidtmpl,
   138     'file_copy': '{name} ({source})',
   149     'file_copy': '{name} ({source})',
   139     'envvar': '{key}={value}',
   150     'envvar': '{key}={value}',
   140     'extra': '{key}={value|stringescape}'
   151     'extra': '{key}={value|stringescape}',
   141 }
   152 }
   142 # filecopy is preserved for compatibility reasons
   153 # filecopy is preserved for compatibility reasons
   143 defaulttempl['filecopy'] = defaulttempl['file_copy']
   154 defaulttempl['filecopy'] = defaulttempl['file_copy']
   144 
   155 
   145 # keywords are callables (see registrar.templatekeyword for details)
   156 # keywords are callables (see registrar.templatekeyword for details)
   146 keywords = {}
   157 keywords = {}
   147 templatekeyword = registrar.templatekeyword(keywords)
   158 templatekeyword = registrar.templatekeyword(keywords)
       
   159 
   148 
   160 
   149 @templatekeyword('author', requires={'ctx'})
   161 @templatekeyword('author', requires={'ctx'})
   150 def showauthor(context, mapping):
   162 def showauthor(context, mapping):
   151     """Alias for ``{user}``"""
   163     """Alias for ``{user}``"""
   152     return showuser(context, mapping)
   164     return showuser(context, mapping)
   153 
   165 
       
   166 
   154 @templatekeyword('bisect', requires={'repo', 'ctx'})
   167 @templatekeyword('bisect', requires={'repo', 'ctx'})
   155 def showbisect(context, mapping):
   168 def showbisect(context, mapping):
   156     """String. The changeset bisection status."""
   169     """String. The changeset bisection status."""
   157     repo = context.resource(mapping, 'repo')
   170     repo = context.resource(mapping, 'repo')
   158     ctx = context.resource(mapping, 'ctx')
   171     ctx = context.resource(mapping, 'ctx')
   159     return hbisect.label(repo, ctx.node())
   172     return hbisect.label(repo, ctx.node())
       
   173 
   160 
   174 
   161 @templatekeyword('branch', requires={'ctx'})
   175 @templatekeyword('branch', requires={'ctx'})
   162 def showbranch(context, mapping):
   176 def showbranch(context, mapping):
   163     """String. The name of the branch on which the changeset was
   177     """String. The name of the branch on which the changeset was
   164     committed.
   178     committed.
   165     """
   179     """
   166     ctx = context.resource(mapping, 'ctx')
   180     ctx = context.resource(mapping, 'ctx')
   167     return ctx.branch()
   181     return ctx.branch()
       
   182 
   168 
   183 
   169 @templatekeyword('branches', requires={'ctx'})
   184 @templatekeyword('branches', requires={'ctx'})
   170 def showbranches(context, mapping):
   185 def showbranches(context, mapping):
   171     """List of strings. The name of the branch on which the
   186     """List of strings. The name of the branch on which the
   172     changeset was committed. Will be empty if the branch name was
   187     changeset was committed. Will be empty if the branch name was
   173     default. (DEPRECATED)
   188     default. (DEPRECATED)
   174     """
   189     """
   175     ctx = context.resource(mapping, 'ctx')
   190     ctx = context.resource(mapping, 'ctx')
   176     branch = ctx.branch()
   191     branch = ctx.branch()
   177     if branch != 'default':
   192     if branch != 'default':
   178         return compatlist(context, mapping, 'branch', [branch],
   193         return compatlist(
   179                           plural='branches')
   194             context, mapping, 'branch', [branch], plural='branches'
       
   195         )
   180     return compatlist(context, mapping, 'branch', [], plural='branches')
   196     return compatlist(context, mapping, 'branch', [], plural='branches')
       
   197 
   181 
   198 
   182 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
   199 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
   183 def showbookmarks(context, mapping):
   200 def showbookmarks(context, mapping):
   184     """List of strings. Any bookmarks associated with the
   201     """List of strings. Any bookmarks associated with the
   185     changeset. Also sets 'active', the name of the active bookmark.
   202     changeset. Also sets 'active', the name of the active bookmark.
   190     active = repo._activebookmark
   207     active = repo._activebookmark
   191     makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
   208     makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
   192     f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
   209     f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
   193     return _hybrid(f, bookmarks, makemap, pycompat.identity)
   210     return _hybrid(f, bookmarks, makemap, pycompat.identity)
   194 
   211 
       
   212 
   195 @templatekeyword('children', requires={'ctx'})
   213 @templatekeyword('children', requires={'ctx'})
   196 def showchildren(context, mapping):
   214 def showchildren(context, mapping):
   197     """List of strings. The children of the changeset."""
   215     """List of strings. The children of the changeset."""
   198     ctx = context.resource(mapping, 'ctx')
   216     ctx = context.resource(mapping, 'ctx')
   199     childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
   217     childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
   200     return compatlist(context, mapping, 'children', childrevs, element='child')
   218     return compatlist(context, mapping, 'children', childrevs, element='child')
       
   219 
   201 
   220 
   202 # Deprecated, but kept alive for help generation a purpose.
   221 # Deprecated, but kept alive for help generation a purpose.
   203 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
   222 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
   204 def showcurrentbookmark(context, mapping):
   223 def showcurrentbookmark(context, mapping):
   205     """String. The active bookmark, if it is associated with the changeset.
   224     """String. The active bookmark, if it is associated with the changeset.
   206     (DEPRECATED)"""
   225     (DEPRECATED)"""
   207     return showactivebookmark(context, mapping)
   226     return showactivebookmark(context, mapping)
   208 
   227 
       
   228 
   209 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
   229 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
   210 def showactivebookmark(context, mapping):
   230 def showactivebookmark(context, mapping):
   211     """String. The active bookmark, if it is associated with the changeset."""
   231     """String. The active bookmark, if it is associated with the changeset."""
   212     repo = context.resource(mapping, 'repo')
   232     repo = context.resource(mapping, 'repo')
   213     ctx = context.resource(mapping, 'ctx')
   233     ctx = context.resource(mapping, 'ctx')
   214     active = repo._activebookmark
   234     active = repo._activebookmark
   215     if active and active in ctx.bookmarks():
   235     if active and active in ctx.bookmarks():
   216         return active
   236         return active
   217     return ''
   237     return ''
   218 
   238 
       
   239 
   219 @templatekeyword('date', requires={'ctx'})
   240 @templatekeyword('date', requires={'ctx'})
   220 def showdate(context, mapping):
   241 def showdate(context, mapping):
   221     """Date information. The date when the changeset was committed."""
   242     """Date information. The date when the changeset was committed."""
   222     ctx = context.resource(mapping, 'ctx')
   243     ctx = context.resource(mapping, 'ctx')
   223     # the default string format is '<float(unixtime)><tzoffset>' because
   244     # the default string format is '<float(unixtime)><tzoffset>' because
   224     # python-hglib splits date at decimal separator.
   245     # python-hglib splits date at decimal separator.
   225     return templateutil.date(ctx.date(), showfmt='%d.0%d')
   246     return templateutil.date(ctx.date(), showfmt='%d.0%d')
       
   247 
   226 
   248 
   227 @templatekeyword('desc', requires={'ctx'})
   249 @templatekeyword('desc', requires={'ctx'})
   228 def showdescription(context, mapping):
   250 def showdescription(context, mapping):
   229     """String. The text of the changeset description."""
   251     """String. The text of the changeset description."""
   230     ctx = context.resource(mapping, 'ctx')
   252     ctx = context.resource(mapping, 'ctx')
   235     elif isinstance(s, encoding.safelocalstr):
   257     elif isinstance(s, encoding.safelocalstr):
   236         return encoding.safelocalstr(s.strip())
   258         return encoding.safelocalstr(s.strip())
   237     else:
   259     else:
   238         return s.strip()
   260         return s.strip()
   239 
   261 
       
   262 
   240 @templatekeyword('diffstat', requires={'ui', 'ctx'})
   263 @templatekeyword('diffstat', requires={'ui', 'ctx'})
   241 def showdiffstat(context, mapping):
   264 def showdiffstat(context, mapping):
   242     """String. Statistics of changes with the following format:
   265     """String. Statistics of changes with the following format:
   243     "modified files: +added/-removed lines"
   266     "modified files: +added/-removed lines"
   244     """
   267     """
   247     diffopts = diffutil.diffallopts(ui, {'noprefix': False})
   270     diffopts = diffutil.diffallopts(ui, {'noprefix': False})
   248     diff = ctx.diff(opts=diffopts)
   271     diff = ctx.diff(opts=diffopts)
   249     stats = patch.diffstatdata(util.iterlines(diff))
   272     stats = patch.diffstatdata(util.iterlines(diff))
   250     maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
   273     maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
   251     return '%d: +%d/-%d' % (len(stats), adds, removes)
   274     return '%d: +%d/-%d' % (len(stats), adds, removes)
       
   275 
   252 
   276 
   253 @templatekeyword('envvars', requires={'ui'})
   277 @templatekeyword('envvars', requires={'ui'})
   254 def showenvvars(context, mapping):
   278 def showenvvars(context, mapping):
   255     """A dictionary of environment variables. (EXPERIMENTAL)"""
   279     """A dictionary of environment variables. (EXPERIMENTAL)"""
   256     ui = context.resource(mapping, 'ui')
   280     ui = context.resource(mapping, 'ui')
   257     env = ui.exportableenviron()
   281     env = ui.exportableenviron()
   258     env = util.sortdict((k, env[k]) for k in sorted(env))
   282     env = util.sortdict((k, env[k]) for k in sorted(env))
   259     return compatdict(context, mapping, 'envvar', env, plural='envvars')
   283     return compatdict(context, mapping, 'envvar', env, plural='envvars')
   260 
   284 
       
   285 
   261 @templatekeyword('extras', requires={'ctx'})
   286 @templatekeyword('extras', requires={'ctx'})
   262 def showextras(context, mapping):
   287 def showextras(context, mapping):
   263     """List of dicts with key, value entries of the 'extras'
   288     """List of dicts with key, value entries of the 'extras'
   264     field of this changeset."""
   289     field of this changeset."""
   265     ctx = context.resource(mapping, 'ctx')
   290     ctx = context.resource(mapping, 'ctx')
   266     extras = ctx.extra()
   291     extras = ctx.extra()
   267     extras = util.sortdict((k, extras[k]) for k in sorted(extras))
   292     extras = util.sortdict((k, extras[k]) for k in sorted(extras))
   268     makemap = lambda k: {'key': k, 'value': extras[k]}
   293     makemap = lambda k: {'key': k, 'value': extras[k]}
   269     c = [makemap(k) for k in extras]
   294     c = [makemap(k) for k in extras]
   270     f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
   295     f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
   271     return _hybrid(f, extras, makemap,
   296     return _hybrid(
   272                    lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
   297         f,
       
   298         extras,
       
   299         makemap,
       
   300         lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])),
       
   301     )
       
   302 
   273 
   303 
   274 def _getfilestatus(context, mapping, listall=False):
   304 def _getfilestatus(context, mapping, listall=False):
   275     ctx = context.resource(mapping, 'ctx')
   305     ctx = context.resource(mapping, 'ctx')
   276     revcache = context.resource(mapping, 'revcache')
   306     revcache = context.resource(mapping, 'revcache')
   277     if 'filestatus' not in revcache or revcache['filestatusall'] < listall:
   307     if 'filestatus' not in revcache or revcache['filestatusall'] < listall:
   278         stat = ctx.p1().status(ctx, listignored=listall, listclean=listall,
   308         stat = ctx.p1().status(
   279                                listunknown=listall)
   309             ctx, listignored=listall, listclean=listall, listunknown=listall
       
   310         )
   280         revcache['filestatus'] = stat
   311         revcache['filestatus'] = stat
   281         revcache['filestatusall'] = listall
   312         revcache['filestatusall'] = listall
   282     return revcache['filestatus']
   313     return revcache['filestatus']
       
   314 
   283 
   315 
   284 def _getfilestatusmap(context, mapping, listall=False):
   316 def _getfilestatusmap(context, mapping, listall=False):
   285     revcache = context.resource(mapping, 'revcache')
   317     revcache = context.resource(mapping, 'revcache')
   286     if 'filestatusmap' not in revcache or revcache['filestatusall'] < listall:
   318     if 'filestatusmap' not in revcache or revcache['filestatusall'] < listall:
   287         stat = _getfilestatus(context, mapping, listall=listall)
   319         stat = _getfilestatus(context, mapping, listall=listall)
   288         revcache['filestatusmap'] = statmap = {}
   320         revcache['filestatusmap'] = statmap = {}
   289         for char, files in zip(pycompat.iterbytestr('MAR!?IC'), stat):
   321         for char, files in zip(pycompat.iterbytestr('MAR!?IC'), stat):
   290             statmap.update((f, char) for f in files)
   322             statmap.update((f, char) for f in files)
   291     return revcache['filestatusmap']  # {path: statchar}
   323     return revcache['filestatusmap']  # {path: statchar}
   292 
   324 
   293 @templatekeyword('file_copies',
   325 
   294                  requires={'repo', 'ctx', 'cache', 'revcache'})
   326 @templatekeyword('file_copies', requires={'repo', 'ctx', 'cache', 'revcache'})
   295 def showfilecopies(context, mapping):
   327 def showfilecopies(context, mapping):
   296     """List of strings. Files copied in this changeset with
   328     """List of strings. Files copied in this changeset with
   297     their sources.
   329     their sources.
   298     """
   330     """
   299     repo = context.resource(mapping, 'repo')
   331     repo = context.resource(mapping, 'repo')
   303     if copies is None:
   335     if copies is None:
   304         if 'getcopies' not in cache:
   336         if 'getcopies' not in cache:
   305             cache['getcopies'] = scmutil.getcopiesfn(repo)
   337             cache['getcopies'] = scmutil.getcopiesfn(repo)
   306         getcopies = cache['getcopies']
   338         getcopies = cache['getcopies']
   307         copies = getcopies(ctx)
   339         copies = getcopies(ctx)
   308     return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
   340     return templateutil.compatfilecopiesdict(
   309                                              copies)
   341         context, mapping, 'file_copy', copies
       
   342     )
       
   343 
   310 
   344 
   311 # showfilecopiesswitch() displays file copies only if copy records are
   345 # showfilecopiesswitch() displays file copies only if copy records are
   312 # provided before calling the templater, usually with a --copies
   346 # provided before calling the templater, usually with a --copies
   313 # command line switch.
   347 # command line switch.
   314 @templatekeyword('file_copies_switch', requires={'revcache'})
   348 @templatekeyword('file_copies_switch', requires={'revcache'})
   315 def showfilecopiesswitch(context, mapping):
   349 def showfilecopiesswitch(context, mapping):
   316     """List of strings. Like "file_copies" but displayed
   350     """List of strings. Like "file_copies" but displayed
   317     only if the --copied switch is set.
   351     only if the --copied switch is set.
   318     """
   352     """
   319     copies = context.resource(mapping, 'revcache').get('copies') or []
   353     copies = context.resource(mapping, 'revcache').get('copies') or []
   320     return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
   354     return templateutil.compatfilecopiesdict(
   321                                              copies)
   355         context, mapping, 'file_copy', copies
       
   356     )
       
   357 
   322 
   358 
   323 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
   359 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
   324 def showfileadds(context, mapping):
   360 def showfileadds(context, mapping):
   325     """List of strings. Files added by this changeset."""
   361     """List of strings. Files added by this changeset."""
   326     ctx = context.resource(mapping, 'ctx')
   362     ctx = context.resource(mapping, 'ctx')
   327     return templateutil.compatfileslist(context, mapping, 'file_add',
   363     return templateutil.compatfileslist(
   328                                         ctx.filesadded())
   364         context, mapping, 'file_add', ctx.filesadded()
       
   365     )
       
   366 
   329 
   367 
   330 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
   368 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
   331 def showfiledels(context, mapping):
   369 def showfiledels(context, mapping):
   332     """List of strings. Files removed by this changeset."""
   370     """List of strings. Files removed by this changeset."""
   333     ctx = context.resource(mapping, 'ctx')
   371     ctx = context.resource(mapping, 'ctx')
   334     return templateutil.compatfileslist(context, mapping, 'file_del',
   372     return templateutil.compatfileslist(
   335                                         ctx.filesremoved())
   373         context, mapping, 'file_del', ctx.filesremoved()
       
   374     )
       
   375 
   336 
   376 
   337 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
   377 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
   338 def showfilemods(context, mapping):
   378 def showfilemods(context, mapping):
   339     """List of strings. Files modified by this changeset."""
   379     """List of strings. Files modified by this changeset."""
   340     ctx = context.resource(mapping, 'ctx')
   380     ctx = context.resource(mapping, 'ctx')
   341     return templateutil.compatfileslist(context, mapping, 'file_mod',
   381     return templateutil.compatfileslist(
   342                                         ctx.filesmodified())
   382         context, mapping, 'file_mod', ctx.filesmodified()
       
   383     )
       
   384 
   343 
   385 
   344 @templatekeyword('files', requires={'ctx'})
   386 @templatekeyword('files', requires={'ctx'})
   345 def showfiles(context, mapping):
   387 def showfiles(context, mapping):
   346     """List of strings. All files modified, added, or removed by this
   388     """List of strings. All files modified, added, or removed by this
   347     changeset.
   389     changeset.
   348     """
   390     """
   349     ctx = context.resource(mapping, 'ctx')
   391     ctx = context.resource(mapping, 'ctx')
   350     return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
   392     return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
   351 
   393 
       
   394 
   352 @templatekeyword('graphnode', requires={'repo', 'ctx'})
   395 @templatekeyword('graphnode', requires={'repo', 'ctx'})
   353 def showgraphnode(context, mapping):
   396 def showgraphnode(context, mapping):
   354     """String. The character representing the changeset node in an ASCII
   397     """String. The character representing the changeset node in an ASCII
   355     revision graph."""
   398     revision graph."""
   356     repo = context.resource(mapping, 'repo')
   399     repo = context.resource(mapping, 'repo')
   357     ctx = context.resource(mapping, 'ctx')
   400     ctx = context.resource(mapping, 'ctx')
   358     return getgraphnode(repo, ctx)
   401     return getgraphnode(repo, ctx)
   359 
   402 
       
   403 
   360 def getgraphnode(repo, ctx):
   404 def getgraphnode(repo, ctx):
   361     return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
   405     return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
       
   406 
   362 
   407 
   363 def getgraphnodecurrent(repo, ctx):
   408 def getgraphnodecurrent(repo, ctx):
   364     wpnodes = repo.dirstate.parents()
   409     wpnodes = repo.dirstate.parents()
   365     if wpnodes[1] == nullid:
   410     if wpnodes[1] == nullid:
   366         wpnodes = wpnodes[:1]
   411         wpnodes = wpnodes[:1]
   367     if ctx.node() in wpnodes:
   412     if ctx.node() in wpnodes:
   368         return '@'
   413         return '@'
   369     else:
   414     else:
   370         return ''
   415         return ''
       
   416 
   371 
   417 
   372 def getgraphnodesymbol(ctx):
   418 def getgraphnodesymbol(ctx):
   373     if ctx.obsolete():
   419     if ctx.obsolete():
   374         return 'x'
   420         return 'x'
   375     elif ctx.isunstable():
   421     elif ctx.isunstable():
   377     elif ctx.closesbranch():
   423     elif ctx.closesbranch():
   378         return '_'
   424         return '_'
   379     else:
   425     else:
   380         return 'o'
   426         return 'o'
   381 
   427 
       
   428 
   382 @templatekeyword('graphwidth', requires=())
   429 @templatekeyword('graphwidth', requires=())
   383 def showgraphwidth(context, mapping):
   430 def showgraphwidth(context, mapping):
   384     """Integer. The width of the graph drawn by 'log --graph' or zero."""
   431     """Integer. The width of the graph drawn by 'log --graph' or zero."""
   385     # just hosts documentation; should be overridden by template mapping
   432     # just hosts documentation; should be overridden by template mapping
   386     return 0
   433     return 0
   387 
   434 
       
   435 
   388 @templatekeyword('index', requires=())
   436 @templatekeyword('index', requires=())
   389 def showindex(context, mapping):
   437 def showindex(context, mapping):
   390     """Integer. The current iteration of the loop. (0 indexed)"""
   438     """Integer. The current iteration of the loop. (0 indexed)"""
   391     # just hosts documentation; should be overridden by template mapping
   439     # just hosts documentation; should be overridden by template mapping
   392     raise error.Abort(_("can't use index in this context"))
   440     raise error.Abort(_("can't use index in this context"))
   393 
   441 
       
   442 
   394 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
   443 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
   395 def showlatesttag(context, mapping):
   444 def showlatesttag(context, mapping):
   396     """List of strings. The global tags on the most recent globally
   445     """List of strings. The global tags on the most recent globally
   397     tagged ancestor of this changeset.  If no such tags exist, the list
   446     tagged ancestor of this changeset.  If no such tags exist, the list
   398     consists of the single string "null".
   447     consists of the single string "null".
   399     """
   448     """
   400     return showlatesttags(context, mapping, None)
   449     return showlatesttags(context, mapping, None)
       
   450 
   401 
   451 
   402 def showlatesttags(context, mapping, pattern):
   452 def showlatesttags(context, mapping, pattern):
   403     """helper method for the latesttag keyword and function"""
   453     """helper method for the latesttag keyword and function"""
   404     latesttags = getlatesttags(context, mapping, pattern)
   454     latesttags = getlatesttags(context, mapping, pattern)
   405 
   455 
   407     # branches in a stable manner- it is the date the tagged cset was created,
   457     # branches in a stable manner- it is the date the tagged cset was created,
   408     # not the date the tag was created.  Therefore it isn't made visible here.
   458     # not the date the tag was created.  Therefore it isn't made visible here.
   409     makemap = lambda v: {
   459     makemap = lambda v: {
   410         'changes': _showchangessincetag,
   460         'changes': _showchangessincetag,
   411         'distance': latesttags[1],
   461         'distance': latesttags[1],
   412         'latesttag': v,   # BC with {latesttag % '{latesttag}'}
   462         'latesttag': v,  # BC with {latesttag % '{latesttag}'}
   413         'tag': v
   463         'tag': v,
   414     }
   464     }
   415 
   465 
   416     tags = latesttags[2]
   466     tags = latesttags[2]
   417     f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
   467     f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
   418     return _hybrid(f, tags, makemap, pycompat.identity)
   468     return _hybrid(f, tags, makemap, pycompat.identity)
       
   469 
   419 
   470 
   420 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
   471 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
   421 def showlatesttagdistance(context, mapping):
   472 def showlatesttagdistance(context, mapping):
   422     """Integer. Longest path to the latest tag."""
   473     """Integer. Longest path to the latest tag."""
   423     return getlatesttags(context, mapping)[1]
   474     return getlatesttags(context, mapping)[1]
       
   475 
   424 
   476 
   425 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
   477 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
   426 def showchangessincelatesttag(context, mapping):
   478 def showchangessincelatesttag(context, mapping):
   427     """Integer. All ancestors not in the latest tag."""
   479     """Integer. All ancestors not in the latest tag."""
   428     tag = getlatesttags(context, mapping)[2][0]
   480     tag = getlatesttags(context, mapping)[2][0]
   429     mapping = context.overlaymap(mapping, {'tag': tag})
   481     mapping = context.overlaymap(mapping, {'tag': tag})
   430     return _showchangessincetag(context, mapping)
   482     return _showchangessincetag(context, mapping)
   431 
   483 
       
   484 
   432 def _showchangessincetag(context, mapping):
   485 def _showchangessincetag(context, mapping):
   433     repo = context.resource(mapping, 'repo')
   486     repo = context.resource(mapping, 'repo')
   434     ctx = context.resource(mapping, 'ctx')
   487     ctx = context.resource(mapping, 'ctx')
   435     offset = 0
   488     offset = 0
   436     revs = [ctx.rev()]
   489     revs = [ctx.rev()]
   441         offset = 1
   494         offset = 1
   442         revs = [p.rev() for p in ctx.parents()]
   495         revs = [p.rev() for p in ctx.parents()]
   443 
   496 
   444     return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
   497     return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
   445 
   498 
       
   499 
   446 # teach templater latesttags.changes is switched to (context, mapping) API
   500 # teach templater latesttags.changes is switched to (context, mapping) API
   447 _showchangessincetag._requires = {'repo', 'ctx'}
   501 _showchangessincetag._requires = {'repo', 'ctx'}
       
   502 
   448 
   503 
   449 @templatekeyword('manifest', requires={'repo', 'ctx'})
   504 @templatekeyword('manifest', requires={'repo', 'ctx'})
   450 def showmanifest(context, mapping):
   505 def showmanifest(context, mapping):
   451     repo = context.resource(mapping, 'repo')
   506     repo = context.resource(mapping, 'repo')
   452     ctx = context.resource(mapping, 'ctx')
   507     ctx = context.resource(mapping, 'ctx')
   457     else:
   512     else:
   458         mrev = repo.manifestlog.rev(mnode)
   513         mrev = repo.manifestlog.rev(mnode)
   459     mhex = hex(mnode)
   514     mhex = hex(mnode)
   460     mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
   515     mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
   461     f = context.process('manifest', mapping)
   516     f = context.process('manifest', mapping)
   462     return templateutil.hybriditem(f, None, f,
   517     return templateutil.hybriditem(
   463                                    lambda x: {'rev': mrev, 'node': mhex})
   518         f, None, f, lambda x: {'rev': mrev, 'node': mhex}
       
   519     )
       
   520 
   464 
   521 
   465 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
   522 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
   466 def showobsfate(context, mapping):
   523 def showobsfate(context, mapping):
   467     # this function returns a list containing pre-formatted obsfate strings.
   524     # this function returns a list containing pre-formatted obsfate strings.
   468     #
   525     #
   473     ui = context.resource(mapping, 'ui')
   530     ui = context.resource(mapping, 'ui')
   474     repo = context.resource(mapping, 'repo')
   531     repo = context.resource(mapping, 'repo')
   475     values = []
   532     values = []
   476 
   533 
   477     for x in succsandmarkers.tovalue(context, mapping):
   534     for x in succsandmarkers.tovalue(context, mapping):
   478         v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
   535         v = obsutil.obsfateprinter(
   479                                    scmutil.formatchangeid)
   536             ui, repo, x['successors'], x['markers'], scmutil.formatchangeid
       
   537         )
   480         values.append(v)
   538         values.append(v)
   481 
   539 
   482     return compatlist(context, mapping, "fate", values)
   540     return compatlist(context, mapping, "fate", values)
       
   541 
   483 
   542 
   484 def shownames(context, mapping, namespace):
   543 def shownames(context, mapping, namespace):
   485     """helper method to generate a template keyword for a namespace"""
   544     """helper method to generate a template keyword for a namespace"""
   486     repo = context.resource(mapping, 'repo')
   545     repo = context.resource(mapping, 'repo')
   487     ctx = context.resource(mapping, 'ctx')
   546     ctx = context.resource(mapping, 'ctx')
   488     ns = repo.names[namespace]
   547     ns = repo.names[namespace]
   489     names = ns.names(repo, ctx.node())
   548     names = ns.names(repo, ctx.node())
   490     return compatlist(context, mapping, ns.templatename, names,
   549     return compatlist(
   491                       plural=namespace)
   550         context, mapping, ns.templatename, names, plural=namespace
       
   551     )
       
   552 
   492 
   553 
   493 @templatekeyword('namespaces', requires={'repo', 'ctx'})
   554 @templatekeyword('namespaces', requires={'repo', 'ctx'})
   494 def shownamespaces(context, mapping):
   555 def shownamespaces(context, mapping):
   495     """Dict of lists. Names attached to this changeset per
   556     """Dict of lists. Names attached to this changeset per
   496     namespace."""
   557     namespace."""
   497     repo = context.resource(mapping, 'repo')
   558     repo = context.resource(mapping, 'repo')
   498     ctx = context.resource(mapping, 'ctx')
   559     ctx = context.resource(mapping, 'ctx')
   499 
   560 
   500     namespaces = util.sortdict()
   561     namespaces = util.sortdict()
       
   562 
   501     def makensmapfn(ns):
   563     def makensmapfn(ns):
   502         # 'name' for iterating over namespaces, templatename for local reference
   564         # 'name' for iterating over namespaces, templatename for local reference
   503         return lambda v: {'name': v, ns.templatename: v}
   565         return lambda v: {'name': v, ns.templatename: v}
   504 
   566 
   505     for k, ns in repo.names.iteritems():
   567     for k, ns in repo.names.iteritems():
   517             'colorname': repo.names[ns].colorname,
   579             'colorname': repo.names[ns].colorname,
   518         }
   580         }
   519 
   581 
   520     return _hybrid(f, namespaces, makemap, pycompat.identity)
   582     return _hybrid(f, namespaces, makemap, pycompat.identity)
   521 
   583 
       
   584 
   522 @templatekeyword('negrev', requires={'repo', 'ctx'})
   585 @templatekeyword('negrev', requires={'repo', 'ctx'})
   523 def shownegrev(context, mapping):
   586 def shownegrev(context, mapping):
   524     """Integer. The repository-local changeset negative revision number,
   587     """Integer. The repository-local changeset negative revision number,
   525     which counts in the opposite direction."""
   588     which counts in the opposite direction."""
   526     ctx = context.resource(mapping, 'ctx')
   589     ctx = context.resource(mapping, 'ctx')
   528     if rev is None or rev < 0:  # wdir() or nullrev?
   591     if rev is None or rev < 0:  # wdir() or nullrev?
   529         return None
   592         return None
   530     repo = context.resource(mapping, 'repo')
   593     repo = context.resource(mapping, 'repo')
   531     return rev - len(repo)
   594     return rev - len(repo)
   532 
   595 
       
   596 
   533 @templatekeyword('node', requires={'ctx'})
   597 @templatekeyword('node', requires={'ctx'})
   534 def shownode(context, mapping):
   598 def shownode(context, mapping):
   535     """String. The changeset identification hash, as a 40 hexadecimal
   599     """String. The changeset identification hash, as a 40 hexadecimal
   536     digit string.
   600     digit string.
   537     """
   601     """
   538     ctx = context.resource(mapping, 'ctx')
   602     ctx = context.resource(mapping, 'ctx')
   539     return ctx.hex()
   603     return ctx.hex()
   540 
   604 
       
   605 
   541 @templatekeyword('obsolete', requires={'ctx'})
   606 @templatekeyword('obsolete', requires={'ctx'})
   542 def showobsolete(context, mapping):
   607 def showobsolete(context, mapping):
   543     """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
   608     """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
   544     ctx = context.resource(mapping, 'ctx')
   609     ctx = context.resource(mapping, 'ctx')
   545     if ctx.obsolete():
   610     if ctx.obsolete():
   546         return 'obsolete'
   611         return 'obsolete'
   547     return ''
   612     return ''
       
   613 
   548 
   614 
   549 @templatekeyword('path', requires={'fctx'})
   615 @templatekeyword('path', requires={'fctx'})
   550 def showpath(context, mapping):
   616 def showpath(context, mapping):
   551     """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
   617     """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
   552     fctx = context.resource(mapping, 'fctx')
   618     fctx = context.resource(mapping, 'fctx')
   553     return fctx.path()
   619     return fctx.path()
   554 
   620 
       
   621 
   555 @templatekeyword('peerurls', requires={'repo'})
   622 @templatekeyword('peerurls', requires={'repo'})
   556 def showpeerurls(context, mapping):
   623 def showpeerurls(context, mapping):
   557     """A dictionary of repository locations defined in the [paths] section
   624     """A dictionary of repository locations defined in the [paths] section
   558     of your configuration file."""
   625     of your configuration file."""
   559     repo = context.resource(mapping, 'repo')
   626     repo = context.resource(mapping, 'repo')
   560     # see commands.paths() for naming of dictionary keys
   627     # see commands.paths() for naming of dictionary keys
   561     paths = repo.ui.paths
   628     paths = repo.ui.paths
   562     urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
   629     urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
       
   630 
   563     def makemap(k):
   631     def makemap(k):
   564         p = paths[k]
   632         p = paths[k]
   565         d = {'name': k, 'url': p.rawloc}
   633         d = {'name': k, 'url': p.rawloc}
   566         d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
   634         d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
   567         return d
   635         return d
       
   636 
   568     return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
   637     return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
       
   638 
   569 
   639 
   570 @templatekeyword("predecessors", requires={'repo', 'ctx'})
   640 @templatekeyword("predecessors", requires={'repo', 'ctx'})
   571 def showpredecessors(context, mapping):
   641 def showpredecessors(context, mapping):
   572     """Returns the list of the closest visible predecessors. (EXPERIMENTAL)"""
   642     """Returns the list of the closest visible predecessors. (EXPERIMENTAL)"""
   573     repo = context.resource(mapping, 'repo')
   643     repo = context.resource(mapping, 'repo')
   574     ctx = context.resource(mapping, 'ctx')
   644     ctx = context.resource(mapping, 'ctx')
   575     predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
   645     predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
   576     predecessors = pycompat.maplist(hex, predecessors)
   646     predecessors = pycompat.maplist(hex, predecessors)
   577 
   647 
   578     return _hybrid(None, predecessors,
   648     return _hybrid(
   579                    lambda x: {'ctx': repo[x]},
   649         None,
   580                    lambda x: scmutil.formatchangeid(repo[x]))
   650         predecessors,
       
   651         lambda x: {'ctx': repo[x]},
       
   652         lambda x: scmutil.formatchangeid(repo[x]),
       
   653     )
       
   654 
   581 
   655 
   582 @templatekeyword('reporoot', requires={'repo'})
   656 @templatekeyword('reporoot', requires={'repo'})
   583 def showreporoot(context, mapping):
   657 def showreporoot(context, mapping):
   584     """String. The root directory of the current repository."""
   658     """String. The root directory of the current repository."""
   585     repo = context.resource(mapping, 'repo')
   659     repo = context.resource(mapping, 'repo')
   586     return repo.root
   660     return repo.root
       
   661 
   587 
   662 
   588 @templatekeyword('size', requires={'fctx'})
   663 @templatekeyword('size', requires={'fctx'})
   589 def showsize(context, mapping):
   664 def showsize(context, mapping):
   590     """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
   665     """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
   591     fctx = context.resource(mapping, 'fctx')
   666     fctx = context.resource(mapping, 'fctx')
   592     return fctx.size()
   667     return fctx.size()
       
   668 
   593 
   669 
   594 # requires 'fctx' to denote {status} depends on (ctx, path) pair
   670 # requires 'fctx' to denote {status} depends on (ctx, path) pair
   595 @templatekeyword('status', requires={'ctx', 'fctx', 'revcache'})
   671 @templatekeyword('status', requires={'ctx', 'fctx', 'revcache'})
   596 def showstatus(context, mapping):
   672 def showstatus(context, mapping):
   597     """String. Status code of the current file. (EXPERIMENTAL)"""
   673     """String. Status code of the current file. (EXPERIMENTAL)"""
   602     statmap = _getfilestatusmap(context, mapping)
   678     statmap = _getfilestatusmap(context, mapping)
   603     if path not in statmap:
   679     if path not in statmap:
   604         statmap = _getfilestatusmap(context, mapping, listall=True)
   680         statmap = _getfilestatusmap(context, mapping, listall=True)
   605     return statmap.get(path)
   681     return statmap.get(path)
   606 
   682 
       
   683 
   607 @templatekeyword("successorssets", requires={'repo', 'ctx'})
   684 @templatekeyword("successorssets", requires={'repo', 'ctx'})
   608 def showsuccessorssets(context, mapping):
   685 def showsuccessorssets(context, mapping):
   609     """Returns a string of sets of successors for a changectx. Format used
   686     """Returns a string of sets of successors for a changectx. Format used
   610     is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
   687     is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
   611     while also diverged into ctx3. (EXPERIMENTAL)"""
   688     while also diverged into ctx3. (EXPERIMENTAL)"""
   617     ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
   694     ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
   618     ssets = [[hex(n) for n in ss] for ss in ssets]
   695     ssets = [[hex(n) for n in ss] for ss in ssets]
   619 
   696 
   620     data = []
   697     data = []
   621     for ss in ssets:
   698     for ss in ssets:
   622         h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
   699         h = _hybrid(
   623                     lambda x: scmutil.formatchangeid(repo[x]))
   700             None,
       
   701             ss,
       
   702             lambda x: {'ctx': repo[x]},
       
   703             lambda x: scmutil.formatchangeid(repo[x]),
       
   704         )
   624         data.append(h)
   705         data.append(h)
   625 
   706 
   626     # Format the successorssets
   707     # Format the successorssets
   627     def render(d):
   708     def render(d):
   628         return templateutil.stringify(context, mapping, d)
   709         return templateutil.stringify(context, mapping, d)
   629 
   710 
   630     def gen(data):
   711     def gen(data):
   631         yield "; ".join(render(d) for d in data)
   712         yield "; ".join(render(d) for d in data)
   632 
   713 
   633     return _hybrid(gen(data), data, lambda x: {'successorset': x},
   714     return _hybrid(
   634                    pycompat.identity)
   715         gen(data), data, lambda x: {'successorset': x}, pycompat.identity
       
   716     )
       
   717 
   635 
   718 
   636 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
   719 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
   637 def showsuccsandmarkers(context, mapping):
   720 def showsuccsandmarkers(context, mapping):
   638     """Returns a list of dict for each final successor of ctx. The dict
   721     """Returns a list of dict for each final successor of ctx. The dict
   639     contains successors node id in "successors" keys and the list of
   722     contains successors node id in "successors" keys and the list of
   653     for i in values:
   736     for i in values:
   654         # Format successors
   737         # Format successors
   655         successors = i['successors']
   738         successors = i['successors']
   656 
   739 
   657         successors = [hex(n) for n in successors]
   740         successors = [hex(n) for n in successors]
   658         successors = _hybrid(None, successors,
   741         successors = _hybrid(
   659                              lambda x: {'ctx': repo[x]},
   742             None,
   660                              lambda x: scmutil.formatchangeid(repo[x]))
   743             successors,
       
   744             lambda x: {'ctx': repo[x]},
       
   745             lambda x: scmutil.formatchangeid(repo[x]),
       
   746         )
   661 
   747 
   662         # Format markers
   748         # Format markers
   663         finalmarkers = []
   749         finalmarkers = []
   664         for m in i['markers']:
   750         for m in i['markers']:
   665             hexprec = hex(m[0])
   751             hexprec = hex(m[0])
   672 
   758 
   673         data.append({'successors': successors, 'markers': finalmarkers})
   759         data.append({'successors': successors, 'markers': finalmarkers})
   674 
   760 
   675     return templateutil.mappinglist(data)
   761     return templateutil.mappinglist(data)
   676 
   762 
       
   763 
   677 @templatekeyword('p1', requires={'ctx'})
   764 @templatekeyword('p1', requires={'ctx'})
   678 def showp1(context, mapping):
   765 def showp1(context, mapping):
   679     """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
   766     """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
   680     number, and ``{p1.node}`` for the identification hash."""
   767     number, and ``{p1.node}`` for the identification hash."""
   681     ctx = context.resource(mapping, 'ctx')
   768     ctx = context.resource(mapping, 'ctx')
   682     return templateutil.mappingdict({'ctx': ctx.p1()}, tmpl=_changeidtmpl)
   769     return templateutil.mappingdict({'ctx': ctx.p1()}, tmpl=_changeidtmpl)
   683 
   770 
       
   771 
   684 @templatekeyword('p2', requires={'ctx'})
   772 @templatekeyword('p2', requires={'ctx'})
   685 def showp2(context, mapping):
   773 def showp2(context, mapping):
   686     """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
   774     """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
   687     number, and ``{p2.node}`` for the identification hash."""
   775     number, and ``{p2.node}`` for the identification hash."""
   688     ctx = context.resource(mapping, 'ctx')
   776     ctx = context.resource(mapping, 'ctx')
   689     return templateutil.mappingdict({'ctx': ctx.p2()}, tmpl=_changeidtmpl)
   777     return templateutil.mappingdict({'ctx': ctx.p2()}, tmpl=_changeidtmpl)
   690 
   778 
       
   779 
   691 @templatekeyword('p1rev', requires={'ctx'})
   780 @templatekeyword('p1rev', requires={'ctx'})
   692 def showp1rev(context, mapping):
   781 def showp1rev(context, mapping):
   693     """Integer. The repository-local revision number of the changeset's
   782     """Integer. The repository-local revision number of the changeset's
   694     first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
   783     first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
   695     ctx = context.resource(mapping, 'ctx')
   784     ctx = context.resource(mapping, 'ctx')
   696     return ctx.p1().rev()
   785     return ctx.p1().rev()
   697 
   786 
       
   787 
   698 @templatekeyword('p2rev', requires={'ctx'})
   788 @templatekeyword('p2rev', requires={'ctx'})
   699 def showp2rev(context, mapping):
   789 def showp2rev(context, mapping):
   700     """Integer. The repository-local revision number of the changeset's
   790     """Integer. The repository-local revision number of the changeset's
   701     second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
   791     second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
   702     ctx = context.resource(mapping, 'ctx')
   792     ctx = context.resource(mapping, 'ctx')
   703     return ctx.p2().rev()
   793     return ctx.p2().rev()
       
   794 
   704 
   795 
   705 @templatekeyword('p1node', requires={'ctx'})
   796 @templatekeyword('p1node', requires={'ctx'})
   706 def showp1node(context, mapping):
   797 def showp1node(context, mapping):
   707     """String. The identification hash of the changeset's first parent,
   798     """String. The identification hash of the changeset's first parent,
   708     as a 40 digit hexadecimal string. If the changeset has no parents, all
   799     as a 40 digit hexadecimal string. If the changeset has no parents, all
   709     digits are 0. (DEPRECATED)"""
   800     digits are 0. (DEPRECATED)"""
   710     ctx = context.resource(mapping, 'ctx')
   801     ctx = context.resource(mapping, 'ctx')
   711     return ctx.p1().hex()
   802     return ctx.p1().hex()
   712 
   803 
       
   804 
   713 @templatekeyword('p2node', requires={'ctx'})
   805 @templatekeyword('p2node', requires={'ctx'})
   714 def showp2node(context, mapping):
   806 def showp2node(context, mapping):
   715     """String. The identification hash of the changeset's second
   807     """String. The identification hash of the changeset's second
   716     parent, as a 40 digit hexadecimal string. If the changeset has no second
   808     parent, as a 40 digit hexadecimal string. If the changeset has no second
   717     parent, all digits are 0. (DEPRECATED)"""
   809     parent, all digits are 0. (DEPRECATED)"""
   718     ctx = context.resource(mapping, 'ctx')
   810     ctx = context.resource(mapping, 'ctx')
   719     return ctx.p2().hex()
   811     return ctx.p2().hex()
   720 
   812 
       
   813 
   721 @templatekeyword('parents', requires={'repo', 'ctx'})
   814 @templatekeyword('parents', requires={'repo', 'ctx'})
   722 def showparents(context, mapping):
   815 def showparents(context, mapping):
   723     """List of strings. The parents of the changeset in "rev:node"
   816     """List of strings. The parents of the changeset in "rev:node"
   724     format. If the changeset has only one "natural" parent (the predecessor
   817     format. If the changeset has only one "natural" parent (the predecessor
   725     revision) nothing is shown."""
   818     revision) nothing is shown."""
   726     repo = context.resource(mapping, 'repo')
   819     repo = context.resource(mapping, 'repo')
   727     ctx = context.resource(mapping, 'ctx')
   820     ctx = context.resource(mapping, 'ctx')
   728     pctxs = scmutil.meaningfulparents(repo, ctx)
   821     pctxs = scmutil.meaningfulparents(repo, ctx)
   729     prevs = [p.rev() for p in pctxs]
   822     prevs = [p.rev() for p in pctxs]
   730     parents = [[('rev', p.rev()),
   823     parents = [
   731                 ('node', p.hex()),
   824         [('rev', p.rev()), ('node', p.hex()), ('phase', p.phasestr())]
   732                 ('phase', p.phasestr())]
   825         for p in pctxs
   733                for p in pctxs]
   826     ]
   734     f = _showcompatlist(context, mapping, 'parent', parents)
   827     f = _showcompatlist(context, mapping, 'parent', parents)
   735     return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
   828     return _hybrid(
   736                    lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
   829         f,
       
   830         prevs,
       
   831         lambda x: {'ctx': repo[x]},
       
   832         lambda x: scmutil.formatchangeid(repo[x]),
       
   833         keytype=int,
       
   834     )
       
   835 
   737 
   836 
   738 @templatekeyword('phase', requires={'ctx'})
   837 @templatekeyword('phase', requires={'ctx'})
   739 def showphase(context, mapping):
   838 def showphase(context, mapping):
   740     """String. The changeset phase name."""
   839     """String. The changeset phase name."""
   741     ctx = context.resource(mapping, 'ctx')
   840     ctx = context.resource(mapping, 'ctx')
   742     return ctx.phasestr()
   841     return ctx.phasestr()
   743 
   842 
       
   843 
   744 @templatekeyword('phaseidx', requires={'ctx'})
   844 @templatekeyword('phaseidx', requires={'ctx'})
   745 def showphaseidx(context, mapping):
   845 def showphaseidx(context, mapping):
   746     """Integer. The changeset phase index. (ADVANCED)"""
   846     """Integer. The changeset phase index. (ADVANCED)"""
   747     ctx = context.resource(mapping, 'ctx')
   847     ctx = context.resource(mapping, 'ctx')
   748     return ctx.phase()
   848     return ctx.phase()
   749 
   849 
       
   850 
   750 @templatekeyword('rev', requires={'ctx'})
   851 @templatekeyword('rev', requires={'ctx'})
   751 def showrev(context, mapping):
   852 def showrev(context, mapping):
   752     """Integer. The repository-local changeset revision number."""
   853     """Integer. The repository-local changeset revision number."""
   753     ctx = context.resource(mapping, 'ctx')
   854     ctx = context.resource(mapping, 'ctx')
   754     return scmutil.intrev(ctx)
   855     return scmutil.intrev(ctx)
       
   856 
   755 
   857 
   756 def showrevslist(context, mapping, name, revs):
   858 def showrevslist(context, mapping, name, revs):
   757     """helper to generate a list of revisions in which a mapped template will
   859     """helper to generate a list of revisions in which a mapped template will
   758     be evaluated"""
   860     be evaluated"""
   759     repo = context.resource(mapping, 'repo')
   861     repo = context.resource(mapping, 'repo')
   760     # revs may be a smartset; don't compute it until f() has to be evaluated
   862     # revs may be a smartset; don't compute it until f() has to be evaluated
   761     def f():
   863     def f():
   762         srevs = ['%d' % r for r in revs]
   864         srevs = ['%d' % r for r in revs]
   763         return _showcompatlist(context, mapping, name, srevs)
   865         return _showcompatlist(context, mapping, name, srevs)
   764     return _hybrid(f, revs,
   866 
   765                    lambda x: {name: x, 'ctx': repo[x]},
   867     return _hybrid(
   766                    pycompat.identity, keytype=int)
   868         f,
       
   869         revs,
       
   870         lambda x: {name: x, 'ctx': repo[x]},
       
   871         pycompat.identity,
       
   872         keytype=int,
       
   873     )
       
   874 
   767 
   875 
   768 @templatekeyword('subrepos', requires={'ctx'})
   876 @templatekeyword('subrepos', requires={'ctx'})
   769 def showsubrepos(context, mapping):
   877 def showsubrepos(context, mapping):
   770     """List of strings. Updated subrepositories in the changeset."""
   878     """List of strings. Updated subrepositories in the changeset."""
   771     ctx = context.resource(mapping, 'ctx')
   879     ctx = context.resource(mapping, 'ctx')
   774         return compatlist(context, mapping, 'subrepo', [])
   882         return compatlist(context, mapping, 'subrepo', [])
   775     psubstate = ctx.p1().substate or {}
   883     psubstate = ctx.p1().substate or {}
   776     subrepos = []
   884     subrepos = []
   777     for sub in substate:
   885     for sub in substate:
   778         if sub not in psubstate or substate[sub] != psubstate[sub]:
   886         if sub not in psubstate or substate[sub] != psubstate[sub]:
   779             subrepos.append(sub) # modified or newly added in ctx
   887             subrepos.append(sub)  # modified or newly added in ctx
   780     for sub in psubstate:
   888     for sub in psubstate:
   781         if sub not in substate:
   889         if sub not in substate:
   782             subrepos.append(sub) # removed in ctx
   890             subrepos.append(sub)  # removed in ctx
   783     return compatlist(context, mapping, 'subrepo', sorted(subrepos))
   891     return compatlist(context, mapping, 'subrepo', sorted(subrepos))
       
   892 
   784 
   893 
   785 # don't remove "showtags" definition, even though namespaces will put
   894 # don't remove "showtags" definition, even though namespaces will put
   786 # a helper function for "tags" keyword into "keywords" map automatically,
   895 # a helper function for "tags" keyword into "keywords" map automatically,
   787 # because online help text is built without namespaces initialization
   896 # because online help text is built without namespaces initialization
   788 @templatekeyword('tags', requires={'repo', 'ctx'})
   897 @templatekeyword('tags', requires={'repo', 'ctx'})
   789 def showtags(context, mapping):
   898 def showtags(context, mapping):
   790     """List of strings. Any tags associated with the changeset."""
   899     """List of strings. Any tags associated with the changeset."""
   791     return shownames(context, mapping, 'tags')
   900     return shownames(context, mapping, 'tags')
   792 
   901 
       
   902 
   793 @templatekeyword('termwidth', requires={'ui'})
   903 @templatekeyword('termwidth', requires={'ui'})
   794 def showtermwidth(context, mapping):
   904 def showtermwidth(context, mapping):
   795     """Integer. The width of the current terminal."""
   905     """Integer. The width of the current terminal."""
   796     ui = context.resource(mapping, 'ui')
   906     ui = context.resource(mapping, 'ui')
   797     return ui.termwidth()
   907     return ui.termwidth()
   798 
   908 
       
   909 
   799 @templatekeyword('user', requires={'ctx'})
   910 @templatekeyword('user', requires={'ctx'})
   800 def showuser(context, mapping):
   911 def showuser(context, mapping):
   801     """String. The unmodified author of the changeset."""
   912     """String. The unmodified author of the changeset."""
   802     ctx = context.resource(mapping, 'ctx')
   913     ctx = context.resource(mapping, 'ctx')
   803     return ctx.user()
   914     return ctx.user()
       
   915 
   804 
   916 
   805 @templatekeyword('instabilities', requires={'ctx'})
   917 @templatekeyword('instabilities', requires={'ctx'})
   806 def showinstabilities(context, mapping):
   918 def showinstabilities(context, mapping):
   807     """List of strings. Evolution instabilities affecting the changeset.
   919     """List of strings. Evolution instabilities affecting the changeset.
   808     (EXPERIMENTAL)
   920     (EXPERIMENTAL)
   809     """
   921     """
   810     ctx = context.resource(mapping, 'ctx')
   922     ctx = context.resource(mapping, 'ctx')
   811     return compatlist(context, mapping, 'instability', ctx.instabilities(),
   923     return compatlist(
   812                       plural='instabilities')
   924         context,
       
   925         mapping,
       
   926         'instability',
       
   927         ctx.instabilities(),
       
   928         plural='instabilities',
       
   929     )
       
   930 
   813 
   931 
   814 @templatekeyword('verbosity', requires={'ui'})
   932 @templatekeyword('verbosity', requires={'ui'})
   815 def showverbosity(context, mapping):
   933 def showverbosity(context, mapping):
   816     """String. The current output verbosity in 'debug', 'quiet', 'verbose',
   934     """String. The current output verbosity in 'debug', 'quiet', 'verbose',
   817     or ''."""
   935     or ''."""
   823         return 'quiet'
   941         return 'quiet'
   824     elif ui.verbose:
   942     elif ui.verbose:
   825         return 'verbose'
   943         return 'verbose'
   826     return ''
   944     return ''
   827 
   945 
       
   946 
   828 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
   947 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
   829 def showwhyunstable(context, mapping):
   948 def showwhyunstable(context, mapping):
   830     """List of dicts explaining all instabilities of a changeset.
   949     """List of dicts explaining all instabilities of a changeset.
   831     (EXPERIMENTAL)
   950     (EXPERIMENTAL)
   832     """
   951     """
   839     entries = obsutil.whyunstable(repo, ctx)
   958     entries = obsutil.whyunstable(repo, ctx)
   840 
   959 
   841     for entry in entries:
   960     for entry in entries:
   842         if entry.get('divergentnodes'):
   961         if entry.get('divergentnodes'):
   843             dnodes = entry['divergentnodes']
   962             dnodes = entry['divergentnodes']
   844             dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
   963             dnhybrid = _hybrid(
   845                                lambda x: {'ctx': repo[x]},
   964                 None,
   846                                lambda x: formatnode(repo[x]))
   965                 [dnode.hex() for dnode in dnodes],
       
   966                 lambda x: {'ctx': repo[x]},
       
   967                 lambda x: formatnode(repo[x]),
       
   968             )
   847             entry['divergentnodes'] = dnhybrid
   969             entry['divergentnodes'] = dnhybrid
   848 
   970 
   849     tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
   971     tmpl = (
   850             '{reason} {node|short}')
   972         '{instability}:{if(divergentnodes, " ")}{divergentnodes} '
       
   973         '{reason} {node|short}'
       
   974     )
   851     return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
   975     return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
       
   976 
   852 
   977 
   853 def loadkeyword(ui, extname, registrarobj):
   978 def loadkeyword(ui, extname, registrarobj):
   854     """Load template keyword from specified registrarobj
   979     """Load template keyword from specified registrarobj
   855     """
   980     """
   856     for name, func in registrarobj._table.iteritems():
   981     for name, func in registrarobj._table.iteritems():
   857         keywords[name] = func
   982         keywords[name] = func
   858 
   983 
       
   984 
   859 # tell hggettext to extract docstrings from these functions:
   985 # tell hggettext to extract docstrings from these functions:
   860 i18nfunctions = keywords.values()
   986 i18nfunctions = keywords.values()