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