mercurial/hgweb/webutil.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43087 66f2cc210a29
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    43 
    43 
    44 from ..utils import stringutil
    44 from ..utils import stringutil
    45 
    45 
    46 archivespecs = util.sortdict(
    46 archivespecs = util.sortdict(
    47     (
    47     (
    48         ('zip', ('application/zip', 'zip', '.zip', None)),
    48         (b'zip', (b'application/zip', b'zip', b'.zip', None)),
    49         ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
    49         (b'gz', (b'application/x-gzip', b'tgz', b'.tar.gz', None)),
    50         ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
    50         (b'bz2', (b'application/x-bzip2', b'tbz2', b'.tar.bz2', None)),
    51     )
    51     )
    52 )
    52 )
    53 
    53 
    54 
    54 
    55 def archivelist(ui, nodeid, url=None):
    55 def archivelist(ui, nodeid, url=None):
    56     allowed = ui.configlist('web', 'allow-archive', untrusted=True)
    56     allowed = ui.configlist(b'web', b'allow-archive', untrusted=True)
    57     archives = []
    57     archives = []
    58 
    58 
    59     for typ, spec in archivespecs.iteritems():
    59     for typ, spec in archivespecs.iteritems():
    60         if typ in allowed or ui.configbool(
    60         if typ in allowed or ui.configbool(
    61             'web', 'allow' + typ, untrusted=True
    61             b'web', b'allow' + typ, untrusted=True
    62         ):
    62         ):
    63             archives.append(
    63             archives.append(
    64                 {'type': typ, 'extension': spec[2], 'node': nodeid, 'url': url,}
    64                 {
       
    65                     b'type': typ,
       
    66                     b'extension': spec[2],
       
    67                     b'node': nodeid,
       
    68                     b'url': url,
       
    69                 }
    65             )
    70             )
    66 
    71 
    67     return templateutil.mappinglist(archives)
    72     return templateutil.mappinglist(archives)
    68 
    73 
    69 
    74 
    70 def up(p):
    75 def up(p):
    71     if p[0:1] != "/":
    76     if p[0:1] != b"/":
    72         p = "/" + p
    77         p = b"/" + p
    73     if p[-1:] == "/":
    78     if p[-1:] == b"/":
    74         p = p[:-1]
    79         p = p[:-1]
    75     up = os.path.dirname(p)
    80     up = os.path.dirname(p)
    76     if up == "/":
    81     if up == b"/":
    77         return "/"
    82         return b"/"
    78     return up + "/"
    83     return up + b"/"
    79 
    84 
    80 
    85 
    81 def _navseq(step, firststep=None):
    86 def _navseq(step, firststep=None):
    82     if firststep:
    87     if firststep:
    83         yield firststep
    88         yield firststep
   134         if not self:
   139         if not self:
   135             # empty repo
   140             # empty repo
   136             return templateutil.mappinglist(
   141             return templateutil.mappinglist(
   137                 [
   142                 [
   138                     {
   143                     {
   139                         'before': templateutil.mappinglist([]),
   144                         b'before': templateutil.mappinglist([]),
   140                         'after': templateutil.mappinglist([]),
   145                         b'after': templateutil.mappinglist([]),
   141                     },
   146                     },
   142                 ]
   147                 ]
   143             )
   148             )
   144 
   149 
   145         targets = []
   150         targets = []
   149             targets.append(pos + f)
   154             targets.append(pos + f)
   150             targets.append(pos - f)
   155             targets.append(pos - f)
   151         targets.sort()
   156         targets.sort()
   152 
   157 
   153         first = self._first()
   158         first = self._first()
   154         navbefore = [{'label': '(%i)' % first, 'node': self.hex(first)}]
   159         navbefore = [{b'label': b'(%i)' % first, b'node': self.hex(first)}]
   155         navafter = []
   160         navafter = []
   156         for rev in targets:
   161         for rev in targets:
   157             if rev not in self._revlog:
   162             if rev not in self._revlog:
   158                 continue
   163                 continue
   159             if pos < rev < limit:
   164             if pos < rev < limit:
   160                 navafter.append(
   165                 navafter.append(
   161                     {'label': '+%d' % abs(rev - pos), 'node': self.hex(rev)}
   166                     {b'label': b'+%d' % abs(rev - pos), b'node': self.hex(rev)}
   162                 )
   167                 )
   163             if 0 < rev < pos:
   168             if 0 < rev < pos:
   164                 navbefore.append(
   169                 navbefore.append(
   165                     {'label': '-%d' % abs(rev - pos), 'node': self.hex(rev)}
   170                     {b'label': b'-%d' % abs(rev - pos), b'node': self.hex(rev)}
   166                 )
   171                 )
   167 
   172 
   168         navafter.append({'label': 'tip', 'node': 'tip'})
   173         navafter.append({b'label': b'tip', b'node': b'tip'})
   169 
   174 
   170         # TODO: maybe this can be a scalar object supporting tomap()
   175         # TODO: maybe this can be a scalar object supporting tomap()
   171         return templateutil.mappinglist(
   176         return templateutil.mappinglist(
   172             [
   177             [
   173                 {
   178                 {
   174                     'before': templateutil.mappinglist(navbefore),
   179                     b'before': templateutil.mappinglist(navbefore),
   175                     'after': templateutil.mappinglist(navafter),
   180                     b'after': templateutil.mappinglist(navafter),
   176                 },
   181                 },
   177             ]
   182             ]
   178         )
   183         )
   179 
   184 
   180 
   185 
   197 # TODO: maybe this can be a wrapper class for changectx/filectx list, which
   202 # TODO: maybe this can be a wrapper class for changectx/filectx list, which
   198 # yields {'ctx': ctx}
   203 # yields {'ctx': ctx}
   199 def _ctxsgen(context, ctxs):
   204 def _ctxsgen(context, ctxs):
   200     for s in ctxs:
   205     for s in ctxs:
   201         d = {
   206         d = {
   202             'node': s.hex(),
   207             b'node': s.hex(),
   203             'rev': s.rev(),
   208             b'rev': s.rev(),
   204             'user': s.user(),
   209             b'user': s.user(),
   205             'date': s.date(),
   210             b'date': s.date(),
   206             'description': s.description(),
   211             b'description': s.description(),
   207             'branch': s.branch(),
   212             b'branch': s.branch(),
   208         }
   213         }
   209         if util.safehasattr(s, 'path'):
   214         if util.safehasattr(s, b'path'):
   210             d['file'] = s.path()
   215             d[b'file'] = s.path()
   211         yield d
   216         yield d
   212 
   217 
   213 
   218 
   214 def _siblings(siblings=None, hiderev=None):
   219 def _siblings(siblings=None, hiderev=None):
   215     if siblings is None:
   220     if siblings is None:
   223 def difffeatureopts(req, ui, section):
   228 def difffeatureopts(req, ui, section):
   224     diffopts = diffutil.difffeatureopts(
   229     diffopts = diffutil.difffeatureopts(
   225         ui, untrusted=True, section=section, whitespace=True
   230         ui, untrusted=True, section=section, whitespace=True
   226     )
   231     )
   227 
   232 
   228     for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
   233     for k in (
       
   234         b'ignorews',
       
   235         b'ignorewsamount',
       
   236         b'ignorewseol',
       
   237         b'ignoreblanklines',
       
   238     ):
   229         v = req.qsparams.get(k)
   239         v = req.qsparams.get(k)
   230         if v is not None:
   240         if v is not None:
   231             v = stringutil.parsebool(v)
   241             v = stringutil.parsebool(v)
   232             setattr(diffopts, k, v if v is not None else True)
   242             setattr(diffopts, k, v if v is not None else True)
   233 
   243 
   234     return diffopts
   244     return diffopts
   235 
   245 
   236 
   246 
   237 def annotate(req, fctx, ui):
   247 def annotate(req, fctx, ui):
   238     diffopts = difffeatureopts(req, ui, 'annotate')
   248     diffopts = difffeatureopts(req, ui, b'annotate')
   239     return fctx.annotate(follow=True, diffopts=diffopts)
   249     return fctx.annotate(follow=True, diffopts=diffopts)
   240 
   250 
   241 
   251 
   242 def parents(ctx, hide=None):
   252 def parents(ctx, hide=None):
   243     if isinstance(ctx, context.basefilectx):
   253     if isinstance(ctx, context.basefilectx):
   252 
   262 
   253 
   263 
   254 def renamelink(fctx):
   264 def renamelink(fctx):
   255     r = fctx.renamed()
   265     r = fctx.renamed()
   256     if r:
   266     if r:
   257         return templateutil.mappinglist([{'file': r[0], 'node': hex(r[1])}])
   267         return templateutil.mappinglist([{b'file': r[0], b'node': hex(r[1])}])
   258     return templateutil.mappinglist([])
   268     return templateutil.mappinglist([])
   259 
   269 
   260 
   270 
   261 def nodetagsdict(repo, node):
   271 def nodetagsdict(repo, node):
   262     return templateutil.hybridlist(repo.nodetags(node), name='name')
   272     return templateutil.hybridlist(repo.nodetags(node), name=b'name')
   263 
   273 
   264 
   274 
   265 def nodebookmarksdict(repo, node):
   275 def nodebookmarksdict(repo, node):
   266     return templateutil.hybridlist(repo.nodebookmarks(node), name='name')
   276     return templateutil.hybridlist(repo.nodebookmarks(node), name=b'name')
   267 
   277 
   268 
   278 
   269 def nodebranchdict(repo, ctx):
   279 def nodebranchdict(repo, ctx):
   270     branches = []
   280     branches = []
   271     branch = ctx.branch()
   281     branch = ctx.branch()
   275         branchnode = repo.branchtip(branch)
   285         branchnode = repo.branchtip(branch)
   276     except error.RepoLookupError:
   286     except error.RepoLookupError:
   277         branchnode = None
   287         branchnode = None
   278     if branchnode == ctx.node():
   288     if branchnode == ctx.node():
   279         branches.append(branch)
   289         branches.append(branch)
   280     return templateutil.hybridlist(branches, name='name')
   290     return templateutil.hybridlist(branches, name=b'name')
   281 
   291 
   282 
   292 
   283 def nodeinbranch(repo, ctx):
   293 def nodeinbranch(repo, ctx):
   284     branches = []
   294     branches = []
   285     branch = ctx.branch()
   295     branch = ctx.branch()
   286     try:
   296     try:
   287         branchnode = repo.branchtip(branch)
   297         branchnode = repo.branchtip(branch)
   288     except error.RepoLookupError:
   298     except error.RepoLookupError:
   289         branchnode = None
   299         branchnode = None
   290     if branch != 'default' and branchnode != ctx.node():
   300     if branch != b'default' and branchnode != ctx.node():
   291         branches.append(branch)
   301         branches.append(branch)
   292     return templateutil.hybridlist(branches, name='name')
   302     return templateutil.hybridlist(branches, name=b'name')
   293 
   303 
   294 
   304 
   295 def nodebranchnodefault(ctx):
   305 def nodebranchnodefault(ctx):
   296     branches = []
   306     branches = []
   297     branch = ctx.branch()
   307     branch = ctx.branch()
   298     if branch != 'default':
   308     if branch != b'default':
   299         branches.append(branch)
   309         branches.append(branch)
   300     return templateutil.hybridlist(branches, name='name')
   310     return templateutil.hybridlist(branches, name=b'name')
   301 
   311 
   302 
   312 
   303 def _nodenamesgen(context, f, node, name):
   313 def _nodenamesgen(context, f, node, name):
   304     for t in f(node):
   314     for t in f(node):
   305         yield {name: t}
   315         yield {name: t}
   306 
   316 
   307 
   317 
   308 def showtag(repo, t1, node=nullid):
   318 def showtag(repo, t1, node=nullid):
   309     args = (repo.nodetags, node, 'tag')
   319     args = (repo.nodetags, node, b'tag')
   310     return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
   320     return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
   311 
   321 
   312 
   322 
   313 def showbookmark(repo, t1, node=nullid):
   323 def showbookmark(repo, t1, node=nullid):
   314     args = (repo.nodebookmarks, node, 'bookmark')
   324     args = (repo.nodebookmarks, node, b'bookmark')
   315     return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
   325     return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
   316 
   326 
   317 
   327 
   318 def branchentries(repo, stripecount, limit=0):
   328 def branchentries(repo, stripecount, limit=0):
   319     tips = []
   329     tips = []
   329         for ctx, closed in sorted(tips, key=sortkey, reverse=True):
   339         for ctx, closed in sorted(tips, key=sortkey, reverse=True):
   330             if limit > 0 and count >= limit:
   340             if limit > 0 and count >= limit:
   331                 return
   341                 return
   332             count += 1
   342             count += 1
   333             if closed:
   343             if closed:
   334                 status = 'closed'
   344                 status = b'closed'
   335             elif ctx.node() not in heads:
   345             elif ctx.node() not in heads:
   336                 status = 'inactive'
   346                 status = b'inactive'
   337             else:
   347             else:
   338                 status = 'open'
   348                 status = b'open'
   339             yield {
   349             yield {
   340                 'parity': next(parity),
   350                 b'parity': next(parity),
   341                 'branch': ctx.branch(),
   351                 b'branch': ctx.branch(),
   342                 'status': status,
   352                 b'status': status,
   343                 'node': ctx.hex(),
   353                 b'node': ctx.hex(),
   344                 'date': ctx.date(),
   354                 b'date': ctx.date(),
   345             }
   355             }
   346 
   356 
   347     return templateutil.mappinggenerator(entries)
   357     return templateutil.mappinggenerator(entries)
   348 
   358 
   349 
   359 
   350 def cleanpath(repo, path):
   360 def cleanpath(repo, path):
   351     path = path.lstrip('/')
   361     path = path.lstrip(b'/')
   352     auditor = pathutil.pathauditor(repo.root, realfs=False)
   362     auditor = pathutil.pathauditor(repo.root, realfs=False)
   353     return pathutil.canonpath(repo.root, '', path, auditor=auditor)
   363     return pathutil.canonpath(repo.root, b'', path, auditor=auditor)
   354 
   364 
   355 
   365 
   356 def changectx(repo, req):
   366 def changectx(repo, req):
   357     changeid = "tip"
   367     changeid = b"tip"
   358     if 'node' in req.qsparams:
   368     if b'node' in req.qsparams:
   359         changeid = req.qsparams['node']
   369         changeid = req.qsparams[b'node']
   360         ipos = changeid.find(':')
   370         ipos = changeid.find(b':')
   361         if ipos != -1:
   371         if ipos != -1:
   362             changeid = changeid[(ipos + 1) :]
   372             changeid = changeid[(ipos + 1) :]
   363 
   373 
   364     return scmutil.revsymbol(repo, changeid)
   374     return scmutil.revsymbol(repo, changeid)
   365 
   375 
   366 
   376 
   367 def basechangectx(repo, req):
   377 def basechangectx(repo, req):
   368     if 'node' in req.qsparams:
   378     if b'node' in req.qsparams:
   369         changeid = req.qsparams['node']
   379         changeid = req.qsparams[b'node']
   370         ipos = changeid.find(':')
   380         ipos = changeid.find(b':')
   371         if ipos != -1:
   381         if ipos != -1:
   372             changeid = changeid[:ipos]
   382             changeid = changeid[:ipos]
   373             return scmutil.revsymbol(repo, changeid)
   383             return scmutil.revsymbol(repo, changeid)
   374 
   384 
   375     return None
   385     return None
   376 
   386 
   377 
   387 
   378 def filectx(repo, req):
   388 def filectx(repo, req):
   379     if 'file' not in req.qsparams:
   389     if b'file' not in req.qsparams:
   380         raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
   390         raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
   381     path = cleanpath(repo, req.qsparams['file'])
   391     path = cleanpath(repo, req.qsparams[b'file'])
   382     if 'node' in req.qsparams:
   392     if b'node' in req.qsparams:
   383         changeid = req.qsparams['node']
   393         changeid = req.qsparams[b'node']
   384     elif 'filenode' in req.qsparams:
   394     elif b'filenode' in req.qsparams:
   385         changeid = req.qsparams['filenode']
   395         changeid = req.qsparams[b'filenode']
   386     else:
   396     else:
   387         raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
   397         raise ErrorResponse(HTTP_NOT_FOUND, b'node or filenode not given')
   388     try:
   398     try:
   389         fctx = scmutil.revsymbol(repo, changeid)[path]
   399         fctx = scmutil.revsymbol(repo, changeid)[path]
   390     except error.RepoError:
   400     except error.RepoError:
   391         fctx = repo.filectx(path, fileid=changeid)
   401         fctx = repo.filectx(path, fileid=changeid)
   392 
   402 
   393     return fctx
   403     return fctx
   394 
   404 
   395 
   405 
   396 def linerange(req):
   406 def linerange(req):
   397     linerange = req.qsparams.getall('linerange')
   407     linerange = req.qsparams.getall(b'linerange')
   398     if not linerange:
   408     if not linerange:
   399         return None
   409         return None
   400     if len(linerange) > 1:
   410     if len(linerange) > 1:
   401         raise ErrorResponse(HTTP_BAD_REQUEST, 'redundant linerange parameter')
   411         raise ErrorResponse(HTTP_BAD_REQUEST, b'redundant linerange parameter')
   402     try:
   412     try:
   403         fromline, toline = map(int, linerange[0].split(':', 1))
   413         fromline, toline = map(int, linerange[0].split(b':', 1))
   404     except ValueError:
   414     except ValueError:
   405         raise ErrorResponse(HTTP_BAD_REQUEST, 'invalid linerange parameter')
   415         raise ErrorResponse(HTTP_BAD_REQUEST, b'invalid linerange parameter')
   406     try:
   416     try:
   407         return util.processlinerange(fromline, toline)
   417         return util.processlinerange(fromline, toline)
   408     except error.ParseError as exc:
   418     except error.ParseError as exc:
   409         raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
   419         raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
   410 
   420 
   411 
   421 
   412 def formatlinerange(fromline, toline):
   422 def formatlinerange(fromline, toline):
   413     return '%d:%d' % (fromline + 1, toline)
   423     return b'%d:%d' % (fromline + 1, toline)
   414 
   424 
   415 
   425 
   416 def _succsandmarkersgen(context, mapping):
   426 def _succsandmarkersgen(context, mapping):
   417     repo = context.resource(mapping, 'repo')
   427     repo = context.resource(mapping, b'repo')
   418     itemmappings = templatekw.showsuccsandmarkers(context, mapping)
   428     itemmappings = templatekw.showsuccsandmarkers(context, mapping)
   419     for item in itemmappings.tovalue(context, mapping):
   429     for item in itemmappings.tovalue(context, mapping):
   420         item['successors'] = _siblings(
   430         item[b'successors'] = _siblings(
   421             repo[successor] for successor in item['successors']
   431             repo[successor] for successor in item[b'successors']
   422         )
   432         )
   423         yield item
   433         yield item
   424 
   434 
   425 
   435 
   426 def succsandmarkers(context, mapping):
   436 def succsandmarkers(context, mapping):
   427     return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
   437     return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
   428 
   438 
   429 
   439 
   430 # teach templater succsandmarkers is switched to (context, mapping) API
   440 # teach templater succsandmarkers is switched to (context, mapping) API
   431 succsandmarkers._requires = {'repo', 'ctx'}
   441 succsandmarkers._requires = {b'repo', b'ctx'}
   432 
   442 
   433 
   443 
   434 def _whyunstablegen(context, mapping):
   444 def _whyunstablegen(context, mapping):
   435     repo = context.resource(mapping, 'repo')
   445     repo = context.resource(mapping, b'repo')
   436     ctx = context.resource(mapping, 'ctx')
   446     ctx = context.resource(mapping, b'ctx')
   437 
   447 
   438     entries = obsutil.whyunstable(repo, ctx)
   448     entries = obsutil.whyunstable(repo, ctx)
   439     for entry in entries:
   449     for entry in entries:
   440         if entry.get('divergentnodes'):
   450         if entry.get(b'divergentnodes'):
   441             entry['divergentnodes'] = _siblings(entry['divergentnodes'])
   451             entry[b'divergentnodes'] = _siblings(entry[b'divergentnodes'])
   442         yield entry
   452         yield entry
   443 
   453 
   444 
   454 
   445 def whyunstable(context, mapping):
   455 def whyunstable(context, mapping):
   446     return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
   456     return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
   447 
   457 
   448 
   458 
   449 whyunstable._requires = {'repo', 'ctx'}
   459 whyunstable._requires = {b'repo', b'ctx'}
   450 
   460 
   451 
   461 
   452 def commonentry(repo, ctx):
   462 def commonentry(repo, ctx):
   453     node = scmutil.binnode(ctx)
   463     node = scmutil.binnode(ctx)
   454     return {
   464     return {
   455         # TODO: perhaps ctx.changectx() should be assigned if ctx is a
   465         # TODO: perhaps ctx.changectx() should be assigned if ctx is a
   456         # filectx, but I'm not pretty sure if that would always work because
   466         # filectx, but I'm not pretty sure if that would always work because
   457         # fctx.parents() != fctx.changectx.parents() for example.
   467         # fctx.parents() != fctx.changectx.parents() for example.
   458         'ctx': ctx,
   468         b'ctx': ctx,
   459         'rev': ctx.rev(),
   469         b'rev': ctx.rev(),
   460         'node': hex(node),
   470         b'node': hex(node),
   461         'author': ctx.user(),
   471         b'author': ctx.user(),
   462         'desc': ctx.description(),
   472         b'desc': ctx.description(),
   463         'date': ctx.date(),
   473         b'date': ctx.date(),
   464         'extra': ctx.extra(),
   474         b'extra': ctx.extra(),
   465         'phase': ctx.phasestr(),
   475         b'phase': ctx.phasestr(),
   466         'obsolete': ctx.obsolete(),
   476         b'obsolete': ctx.obsolete(),
   467         'succsandmarkers': succsandmarkers,
   477         b'succsandmarkers': succsandmarkers,
   468         'instabilities': templateutil.hybridlist(
   478         b'instabilities': templateutil.hybridlist(
   469             ctx.instabilities(), name='instability'
   479             ctx.instabilities(), name=b'instability'
   470         ),
   480         ),
   471         'whyunstable': whyunstable,
   481         b'whyunstable': whyunstable,
   472         'branch': nodebranchnodefault(ctx),
   482         b'branch': nodebranchnodefault(ctx),
   473         'inbranch': nodeinbranch(repo, ctx),
   483         b'inbranch': nodeinbranch(repo, ctx),
   474         'branches': nodebranchdict(repo, ctx),
   484         b'branches': nodebranchdict(repo, ctx),
   475         'tags': nodetagsdict(repo, node),
   485         b'tags': nodetagsdict(repo, node),
   476         'bookmarks': nodebookmarksdict(repo, node),
   486         b'bookmarks': nodebookmarksdict(repo, node),
   477         'parent': lambda context, mapping: parents(ctx),
   487         b'parent': lambda context, mapping: parents(ctx),
   478         'child': lambda context, mapping: children(ctx),
   488         b'child': lambda context, mapping: children(ctx),
   479     }
   489     }
   480 
   490 
   481 
   491 
   482 def changelistentry(web, ctx):
   492 def changelistentry(web, ctx):
   483     '''Obtain a dictionary to be used for entries in a changelist.
   493     '''Obtain a dictionary to be used for entries in a changelist.
   486     to the "shortlog" and "changelog" templates.
   496     to the "shortlog" and "changelog" templates.
   487     '''
   497     '''
   488     repo = web.repo
   498     repo = web.repo
   489     rev = ctx.rev()
   499     rev = ctx.rev()
   490     n = scmutil.binnode(ctx)
   500     n = scmutil.binnode(ctx)
   491     showtags = showtag(repo, 'changelogtag', n)
   501     showtags = showtag(repo, b'changelogtag', n)
   492     files = listfilediffs(ctx.files(), n, web.maxfiles)
   502     files = listfilediffs(ctx.files(), n, web.maxfiles)
   493 
   503 
   494     entry = commonentry(repo, ctx)
   504     entry = commonentry(repo, ctx)
   495     entry.update(
   505     entry.update(
   496         {
   506         {
   497             'allparents': lambda context, mapping: parents(ctx),
   507             b'allparents': lambda context, mapping: parents(ctx),
   498             'parent': lambda context, mapping: parents(ctx, rev - 1),
   508             b'parent': lambda context, mapping: parents(ctx, rev - 1),
   499             'child': lambda context, mapping: children(ctx, rev + 1),
   509             b'child': lambda context, mapping: children(ctx, rev + 1),
   500             'changelogtag': showtags,
   510             b'changelogtag': showtags,
   501             'files': files,
   511             b'files': files,
   502         }
   512         }
   503     )
   513     )
   504     return entry
   514     return entry
   505 
   515 
   506 
   516 
   514             break
   524             break
   515 
   525 
   516         count += 1
   526         count += 1
   517 
   527 
   518         entry = changelistentry(web, repo[rev])
   528         entry = changelistentry(web, repo[rev])
   519         entry['parity'] = next(parityfn)
   529         entry[b'parity'] = next(parityfn)
   520 
   530 
   521         yield entry
   531         yield entry
   522 
   532 
   523 
   533 
   524 def symrevorshortnode(req, ctx):
   534 def symrevorshortnode(req, ctx):
   525     if 'node' in req.qsparams:
   535     if b'node' in req.qsparams:
   526         return templatefilters.revescape(req.qsparams['node'])
   536         return templatefilters.revescape(req.qsparams[b'node'])
   527     else:
   537     else:
   528         return short(scmutil.binnode(ctx))
   538         return short(scmutil.binnode(ctx))
   529 
   539 
   530 
   540 
   531 def _listfilesgen(context, ctx, stripecount):
   541 def _listfilesgen(context, ctx, stripecount):
   532     parity = paritygen(stripecount)
   542     parity = paritygen(stripecount)
   533     for blockno, f in enumerate(ctx.files()):
   543     for blockno, f in enumerate(ctx.files()):
   534         template = 'filenodelink' if f in ctx else 'filenolink'
   544         template = b'filenodelink' if f in ctx else b'filenolink'
   535         yield context.process(
   545         yield context.process(
   536             template,
   546             template,
   537             {
   547             {
   538                 'node': ctx.hex(),
   548                 b'node': ctx.hex(),
   539                 'file': f,
   549                 b'file': f,
   540                 'blockno': blockno + 1,
   550                 b'blockno': blockno + 1,
   541                 'parity': next(parity),
   551                 b'parity': next(parity),
   542             },
   552             },
   543         )
   553         )
   544 
   554 
   545 
   555 
   546 def changesetentry(web, ctx):
   556 def changesetentry(web, ctx):
   547     '''Obtain a dictionary to be used to render the "changeset" template.'''
   557     '''Obtain a dictionary to be used to render the "changeset" template.'''
   548 
   558 
   549     showtags = showtag(web.repo, 'changesettag', scmutil.binnode(ctx))
   559     showtags = showtag(web.repo, b'changesettag', scmutil.binnode(ctx))
   550     showbookmarks = showbookmark(
   560     showbookmarks = showbookmark(
   551         web.repo, 'changesetbookmark', scmutil.binnode(ctx)
   561         web.repo, b'changesetbookmark', scmutil.binnode(ctx)
   552     )
   562     )
   553     showbranch = nodebranchnodefault(ctx)
   563     showbranch = nodebranchnodefault(ctx)
   554 
   564 
   555     basectx = basechangectx(web.repo, web.req)
   565     basectx = basechangectx(web.repo, web.req)
   556     if basectx is None:
   566     if basectx is None:
   557         basectx = ctx.p1()
   567         basectx = ctx.p1()
   558 
   568 
   559     style = web.config('web', 'style')
   569     style = web.config(b'web', b'style')
   560     if 'style' in web.req.qsparams:
   570     if b'style' in web.req.qsparams:
   561         style = web.req.qsparams['style']
   571         style = web.req.qsparams[b'style']
   562 
   572 
   563     diff = diffs(web, ctx, basectx, None, style)
   573     diff = diffs(web, ctx, basectx, None, style)
   564 
   574 
   565     parity = paritygen(web.stripecount)
   575     parity = paritygen(web.stripecount)
   566     diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
   576     diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
   583     )
   593     )
   584 
   594 
   585 
   595 
   586 def _listfilediffsgen(context, files, node, max):
   596 def _listfilediffsgen(context, files, node, max):
   587     for f in files[:max]:
   597     for f in files[:max]:
   588         yield context.process('filedifflink', {'node': hex(node), 'file': f})
   598         yield context.process(b'filedifflink', {b'node': hex(node), b'file': f})
   589     if len(files) > max:
   599     if len(files) > max:
   590         yield context.process('fileellipses', {})
   600         yield context.process(b'fileellipses', {})
   591 
   601 
   592 
   602 
   593 def listfilediffs(files, node, max):
   603 def listfilediffs(files, node, max):
   594     return templateutil.mappedgenerator(
   604     return templateutil.mappedgenerator(
   595         _listfilediffsgen, args=(files, node, max)
   605         _listfilediffsgen, args=(files, node, max)
   596     )
   606     )
   597 
   607 
   598 
   608 
   599 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
   609 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
   600     for lineno, l in enumerate(lines, 1):
   610     for lineno, l in enumerate(lines, 1):
   601         difflineno = "%d.%d" % (blockno, lineno)
   611         difflineno = b"%d.%d" % (blockno, lineno)
   602         if l.startswith('+'):
   612         if l.startswith(b'+'):
   603             ltype = "difflineplus"
   613             ltype = b"difflineplus"
   604         elif l.startswith('-'):
   614         elif l.startswith(b'-'):
   605             ltype = "difflineminus"
   615             ltype = b"difflineminus"
   606         elif l.startswith('@'):
   616         elif l.startswith(b'@'):
   607             ltype = "difflineat"
   617             ltype = b"difflineat"
   608         else:
   618         else:
   609             ltype = "diffline"
   619             ltype = b"diffline"
   610         yield context.process(
   620         yield context.process(
   611             ltype,
   621             ltype,
   612             {
   622             {
   613                 'line': l,
   623                 b'line': l,
   614                 'lineno': lineno,
   624                 b'lineno': lineno,
   615                 'lineid': lineidprefix + "l%s" % difflineno,
   625                 b'lineid': lineidprefix + b"l%s" % difflineno,
   616                 'linenumber': "% 8s" % difflineno,
   626                 b'linenumber': b"% 8s" % difflineno,
   617             },
   627             },
   618         )
   628         )
   619 
   629 
   620 
   630 
   621 def _diffsgen(
   631 def _diffsgen(
   637     diffopts = patch.diffopts(repo.ui, untrusted=True)
   647     diffopts = patch.diffopts(repo.ui, untrusted=True)
   638     parity = paritygen(stripecount)
   648     parity = paritygen(stripecount)
   639 
   649 
   640     diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
   650     diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
   641     for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
   651     for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
   642         if style != 'raw':
   652         if style != b'raw':
   643             header = header[1:]
   653             header = header[1:]
   644         lines = [h + '\n' for h in header]
   654         lines = [h + b'\n' for h in header]
   645         for hunkrange, hunklines in hunks:
   655         for hunkrange, hunklines in hunks:
   646             if linerange is not None and hunkrange is not None:
   656             if linerange is not None and hunkrange is not None:
   647                 s1, l1, s2, l2 = hunkrange
   657                 s1, l1, s2, l2 = hunkrange
   648                 if not mdiff.hunkinrange((s2, l2), linerange):
   658                 if not mdiff.hunkinrange((s2, l2), linerange):
   649                     continue
   659                     continue
   651         if lines:
   661         if lines:
   652             l = templateutil.mappedgenerator(
   662             l = templateutil.mappedgenerator(
   653                 _prettyprintdifflines, args=(lines, blockno, lineidprefix)
   663                 _prettyprintdifflines, args=(lines, blockno, lineidprefix)
   654             )
   664             )
   655             yield {
   665             yield {
   656                 'parity': next(parity),
   666                 b'parity': next(parity),
   657                 'blockno': blockno,
   667                 b'blockno': blockno,
   658                 'lines': l,
   668                 b'lines': l,
   659             }
   669             }
   660 
   670 
   661 
   671 
   662 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=''):
   672 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=b''):
   663     args = (
   673     args = (
   664         web.repo,
   674         web.repo,
   665         ctx,
   675         ctx,
   666         basectx,
   676         basectx,
   667         files,
   677         files,
   668         style,
   678         style,
   669         web.stripecount,
   679         web.stripecount,
   670         linerange,
   680         linerange,
   671         lineidprefix,
   681         lineidprefix,
   672     )
   682     )
   673     return templateutil.mappinggenerator(_diffsgen, args=args, name='diffblock')
   683     return templateutil.mappinggenerator(
       
   684         _diffsgen, args=args, name=b'diffblock'
       
   685     )
   674 
   686 
   675 
   687 
   676 def _compline(type, leftlineno, leftline, rightlineno, rightline):
   688 def _compline(type, leftlineno, leftline, rightlineno, rightline):
   677     lineid = leftlineno and ("l%d" % leftlineno) or ''
   689     lineid = leftlineno and (b"l%d" % leftlineno) or b''
   678     lineid += rightlineno and ("r%d" % rightlineno) or ''
   690     lineid += rightlineno and (b"r%d" % rightlineno) or b''
   679     llno = '%d' % leftlineno if leftlineno else ''
   691     llno = b'%d' % leftlineno if leftlineno else b''
   680     rlno = '%d' % rightlineno if rightlineno else ''
   692     rlno = b'%d' % rightlineno if rightlineno else b''
   681     return {
   693     return {
   682         'type': type,
   694         b'type': type,
   683         'lineid': lineid,
   695         b'lineid': lineid,
   684         'leftlineno': leftlineno,
   696         b'leftlineno': leftlineno,
   685         'leftlinenumber': "% 6s" % llno,
   697         b'leftlinenumber': b"% 6s" % llno,
   686         'leftline': leftline or '',
   698         b'leftline': leftline or b'',
   687         'rightlineno': rightlineno,
   699         b'rightlineno': rightlineno,
   688         'rightlinenumber': "% 6s" % rlno,
   700         b'rightlinenumber': b"% 6s" % rlno,
   689         'rightline': rightline or '',
   701         b'rightline': rightline or b'',
   690     }
   702     }
   691 
   703 
   692 
   704 
   693 def _getcompblockgen(context, leftlines, rightlines, opcodes):
   705 def _getcompblockgen(context, leftlines, rightlines, opcodes):
   694     for type, llo, lhi, rlo, rhi in opcodes:
   706     for type, llo, lhi, rlo, rhi in opcodes:
   725 
   737 
   726 
   738 
   727 def _getcompblock(leftlines, rightlines, opcodes):
   739 def _getcompblock(leftlines, rightlines, opcodes):
   728     args = (leftlines, rightlines, opcodes)
   740     args = (leftlines, rightlines, opcodes)
   729     return templateutil.mappinggenerator(
   741     return templateutil.mappinggenerator(
   730         _getcompblockgen, args=args, name='comparisonline'
   742         _getcompblockgen, args=args, name=b'comparisonline'
   731     )
   743     )
   732 
   744 
   733 
   745 
   734 def _comparegen(context, contextnum, leftlines, rightlines):
   746 def _comparegen(context, contextnum, leftlines, rightlines):
   735     '''Generator function that provides side-by-side comparison data.'''
   747     '''Generator function that provides side-by-side comparison data.'''
   736     s = difflib.SequenceMatcher(None, leftlines, rightlines)
   748     s = difflib.SequenceMatcher(None, leftlines, rightlines)
   737     if contextnum < 0:
   749     if contextnum < 0:
   738         l = _getcompblock(leftlines, rightlines, s.get_opcodes())
   750         l = _getcompblock(leftlines, rightlines, s.get_opcodes())
   739         yield {'lines': l}
   751         yield {b'lines': l}
   740     else:
   752     else:
   741         for oc in s.get_grouped_opcodes(n=contextnum):
   753         for oc in s.get_grouped_opcodes(n=contextnum):
   742             l = _getcompblock(leftlines, rightlines, oc)
   754             l = _getcompblock(leftlines, rightlines, oc)
   743             yield {'lines': l}
   755             yield {b'lines': l}
   744 
   756 
   745 
   757 
   746 def compare(contextnum, leftlines, rightlines):
   758 def compare(contextnum, leftlines, rightlines):
   747     args = (contextnum, leftlines, rightlines)
   759     args = (contextnum, leftlines, rightlines)
   748     return templateutil.mappinggenerator(
   760     return templateutil.mappinggenerator(
   749         _comparegen, args=args, name='comparisonblock'
   761         _comparegen, args=args, name=b'comparisonblock'
   750     )
   762     )
   751 
   763 
   752 
   764 
   753 def diffstatgen(ui, ctx, basectx):
   765 def diffstatgen(ui, ctx, basectx):
   754     '''Generator function that provides the diffstat data.'''
   766     '''Generator function that provides the diffstat data.'''
   755 
   767 
   756     diffopts = patch.diffopts(ui, {'noprefix': False})
   768     diffopts = patch.diffopts(ui, {b'noprefix': False})
   757     stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
   769     stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
   758     maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
   770     maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
   759     while True:
   771     while True:
   760         yield stats, maxname, maxtotal, addtotal, removetotal, binary
   772         yield stats, maxname, maxtotal, addtotal, removetotal, binary
   761 
   773 
   762 
   774 
   763 def diffsummary(statgen):
   775 def diffsummary(statgen):
   764     '''Return a short summary of the diff.'''
   776     '''Return a short summary of the diff.'''
   765 
   777 
   766     stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
   778     stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
   767     return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
   779     return _(b' %d files changed, %d insertions(+), %d deletions(-)\n') % (
   768         len(stats),
   780         len(stats),
   769         addtotal,
   781         addtotal,
   770         removetotal,
   782         removetotal,
   771     )
   783     )
   772 
   784 
   780             return 0
   792             return 0
   781         return (float(i) / maxtotal) * 100
   793         return (float(i) / maxtotal) * 100
   782 
   794 
   783     fileno = 0
   795     fileno = 0
   784     for filename, adds, removes, isbinary in stats:
   796     for filename, adds, removes, isbinary in stats:
   785         template = 'diffstatlink' if filename in files else 'diffstatnolink'
   797         template = b'diffstatlink' if filename in files else b'diffstatnolink'
   786         total = adds + removes
   798         total = adds + removes
   787         fileno += 1
   799         fileno += 1
   788         yield context.process(
   800         yield context.process(
   789             template,
   801             template,
   790             {
   802             {
   791                 'node': ctx.hex(),
   803                 b'node': ctx.hex(),
   792                 'file': filename,
   804                 b'file': filename,
   793                 'fileno': fileno,
   805                 b'fileno': fileno,
   794                 'total': total,
   806                 b'total': total,
   795                 'addpct': pct(adds),
   807                 b'addpct': pct(adds),
   796                 'removepct': pct(removes),
   808                 b'removepct': pct(removes),
   797                 'parity': next(parity),
   809                 b'parity': next(parity),
   798             },
   810             },
   799         )
   811         )
   800 
   812 
   801 
   813 
   802 def diffstat(ctx, statgen, parity):
   814 def diffstat(ctx, statgen, parity):
   804     args = (ctx, statgen, parity)
   816     args = (ctx, statgen, parity)
   805     return templateutil.mappedgenerator(_diffstattmplgen, args=args)
   817     return templateutil.mappedgenerator(_diffstattmplgen, args=args)
   806 
   818 
   807 
   819 
   808 class sessionvars(templateutil.wrapped):
   820 class sessionvars(templateutil.wrapped):
   809     def __init__(self, vars, start='?'):
   821     def __init__(self, vars, start=b'?'):
   810         self._start = start
   822         self._start = start
   811         self._vars = vars
   823         self._vars = vars
   812 
   824 
   813     def __getitem__(self, key):
   825     def __getitem__(self, key):
   814         return self._vars[key]
   826         return self._vars[key]
   826     def getmember(self, context, mapping, key):
   838     def getmember(self, context, mapping, key):
   827         key = templateutil.unwrapvalue(context, mapping, key)
   839         key = templateutil.unwrapvalue(context, mapping, key)
   828         return self._vars.get(key)
   840         return self._vars.get(key)
   829 
   841 
   830     def getmin(self, context, mapping):
   842     def getmin(self, context, mapping):
   831         raise error.ParseError(_('not comparable'))
   843         raise error.ParseError(_(b'not comparable'))
   832 
   844 
   833     def getmax(self, context, mapping):
   845     def getmax(self, context, mapping):
   834         raise error.ParseError(_('not comparable'))
   846         raise error.ParseError(_(b'not comparable'))
   835 
   847 
   836     def filter(self, context, mapping, select):
   848     def filter(self, context, mapping, select):
   837         # implement if necessary
   849         # implement if necessary
   838         raise error.ParseError(_('not filterable'))
   850         raise error.ParseError(_(b'not filterable'))
   839 
   851 
   840     def itermaps(self, context):
   852     def itermaps(self, context):
   841         separator = self._start
   853         separator = self._start
   842         for key, value in sorted(self._vars.iteritems()):
   854         for key, value in sorted(self._vars.iteritems()):
   843             yield {
   855             yield {
   844                 'name': key,
   856                 b'name': key,
   845                 'value': pycompat.bytestr(value),
   857                 b'value': pycompat.bytestr(value),
   846                 'separator': separator,
   858                 b'separator': separator,
   847             }
   859             }
   848             separator = '&'
   860             separator = b'&'
   849 
   861 
   850     def join(self, context, mapping, sep):
   862     def join(self, context, mapping, sep):
   851         # could be '{separator}{name}={value|urlescape}'
   863         # could be '{separator}{name}={value|urlescape}'
   852         raise error.ParseError(_('not displayable without template'))
   864         raise error.ParseError(_(b'not displayable without template'))
   853 
   865 
   854     def show(self, context, mapping):
   866     def show(self, context, mapping):
   855         return self.join(context, '')
   867         return self.join(context, b'')
   856 
   868 
   857     def tobool(self, context, mapping):
   869     def tobool(self, context, mapping):
   858         return bool(self._vars)
   870         return bool(self._vars)
   859 
   871 
   860     def tovalue(self, context, mapping):
   872     def tovalue(self, context, mapping):
   867         return 80
   879         return 80
   868 
   880 
   869 
   881 
   870 def getwebsubs(repo):
   882 def getwebsubs(repo):
   871     websubtable = []
   883     websubtable = []
   872     websubdefs = repo.ui.configitems('websub')
   884     websubdefs = repo.ui.configitems(b'websub')
   873     # we must maintain interhg backwards compatibility
   885     # we must maintain interhg backwards compatibility
   874     websubdefs += repo.ui.configitems('interhg')
   886     websubdefs += repo.ui.configitems(b'interhg')
   875     for key, pattern in websubdefs:
   887     for key, pattern in websubdefs:
   876         # grab the delimiter from the character after the "s"
   888         # grab the delimiter from the character after the "s"
   877         unesc = pattern[1:2]
   889         unesc = pattern[1:2]
   878         delim = stringutil.reescape(unesc)
   890         delim = stringutil.reescape(unesc)
   879 
   891 
   885             % (delim, delim, delim),
   897             % (delim, delim, delim),
   886             pattern,
   898             pattern,
   887         )
   899         )
   888         if not match:
   900         if not match:
   889             repo.ui.warn(
   901             repo.ui.warn(
   890                 _("websub: invalid pattern for %s: %s\n") % (key, pattern)
   902                 _(b"websub: invalid pattern for %s: %s\n") % (key, pattern)
   891             )
   903             )
   892             continue
   904             continue
   893 
   905 
   894         # we need to unescape the delimiter for regexp and format
   906         # we need to unescape the delimiter for regexp and format
   895         delim_re = re.compile(br'(?<!\\)\\%s' % delim)
   907         delim_re = re.compile(br'(?<!\\)\\%s' % delim)
   906         try:
   918         try:
   907             regexp = re.compile(regexp, flags)
   919             regexp = re.compile(regexp, flags)
   908             websubtable.append((regexp, format))
   920             websubtable.append((regexp, format))
   909         except re.error:
   921         except re.error:
   910             repo.ui.warn(
   922             repo.ui.warn(
   911                 _("websub: invalid regexp for %s: %s\n") % (key, regexp)
   923                 _(b"websub: invalid regexp for %s: %s\n") % (key, regexp)
   912             )
   924             )
   913     return websubtable
   925     return websubtable
   914 
   926 
   915 
   927 
   916 def getgraphnode(repo, ctx):
   928 def getgraphnode(repo, ctx):