comparison mercurial/hgweb/webcommands.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children c59eb1560c44
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
77 __all__.append(self.name) 77 __all__.append(self.name)
78 commands[self.name] = func 78 commands[self.name] = func
79 return func 79 return func
80 80
81 81
82 @webcommand('log') 82 @webcommand(b'log')
83 def log(web): 83 def log(web):
84 """ 84 """
85 /log[/{revision}[/{path}]] 85 /log[/{revision}[/{path}]]
86 -------------------------- 86 --------------------------
87 87
94 94
95 For URLs of the form ``/log/{revision}/{file}``, the history for a specific 95 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
96 file will be shown. This form is equivalent to the ``filelog`` handler. 96 file will be shown. This form is equivalent to the ``filelog`` handler.
97 """ 97 """
98 98
99 if web.req.qsparams.get('file'): 99 if web.req.qsparams.get(b'file'):
100 return filelog(web) 100 return filelog(web)
101 else: 101 else:
102 return changelog(web) 102 return changelog(web)
103 103
104 104
105 @webcommand('rawfile') 105 @webcommand(b'rawfile')
106 def rawfile(web): 106 def rawfile(web):
107 guessmime = web.configbool('web', 'guessmime') 107 guessmime = web.configbool(b'web', b'guessmime')
108 108
109 path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', '')) 109 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
110 if not path: 110 if not path:
111 return manifest(web) 111 return manifest(web)
112 112
113 try: 113 try:
114 fctx = webutil.filectx(web.repo, web.req) 114 fctx = webutil.filectx(web.repo, web.req)
118 except ErrorResponse: 118 except ErrorResponse:
119 raise inst 119 raise inst
120 120
121 path = fctx.path() 121 path = fctx.path()
122 text = fctx.data() 122 text = fctx.data()
123 mt = 'application/binary' 123 mt = b'application/binary'
124 if guessmime: 124 if guessmime:
125 mt = mimetypes.guess_type(pycompat.fsdecode(path))[0] 125 mt = mimetypes.guess_type(pycompat.fsdecode(path))[0]
126 if mt is None: 126 if mt is None:
127 if stringutil.binary(text): 127 if stringutil.binary(text):
128 mt = 'application/binary' 128 mt = b'application/binary'
129 else: 129 else:
130 mt = 'text/plain' 130 mt = b'text/plain'
131 else: 131 else:
132 mt = pycompat.sysbytes(mt) 132 mt = pycompat.sysbytes(mt)
133 133
134 if mt.startswith('text/'): 134 if mt.startswith(b'text/'):
135 mt += '; charset="%s"' % encoding.encoding 135 mt += b'; charset="%s"' % encoding.encoding
136 136
137 web.res.headers['Content-Type'] = mt 137 web.res.headers[b'Content-Type'] = mt
138 filename = ( 138 filename = (
139 path.rpartition('/')[-1].replace('\\', '\\\\').replace('"', '\\"') 139 path.rpartition(b'/')[-1].replace(b'\\', b'\\\\').replace(b'"', b'\\"')
140 ) 140 )
141 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename 141 web.res.headers[b'Content-Disposition'] = (
142 b'inline; filename="%s"' % filename
143 )
142 web.res.setbodybytes(text) 144 web.res.setbodybytes(text)
143 return web.res.sendresponse() 145 return web.res.sendresponse()
144 146
145 147
146 def _filerevision(web, fctx): 148 def _filerevision(web, fctx):
152 if stringutil.binary(text): 154 if stringutil.binary(text):
153 mt = pycompat.sysbytes( 155 mt = pycompat.sysbytes(
154 mimetypes.guess_type(pycompat.fsdecode(f))[0] 156 mimetypes.guess_type(pycompat.fsdecode(f))[0]
155 or r'application/octet-stream' 157 or r'application/octet-stream'
156 ) 158 )
157 text = '(binary:%s)' % mt 159 text = b'(binary:%s)' % mt
158 160
159 def lines(context): 161 def lines(context):
160 for lineno, t in enumerate(text.splitlines(True)): 162 for lineno, t in enumerate(text.splitlines(True)):
161 yield { 163 yield {
162 "line": t, 164 b"line": t,
163 "lineid": "l%d" % (lineno + 1), 165 b"lineid": b"l%d" % (lineno + 1),
164 "linenumber": "% 6d" % (lineno + 1), 166 b"linenumber": b"% 6d" % (lineno + 1),
165 "parity": next(parity), 167 b"parity": next(parity),
166 } 168 }
167 169
168 return web.sendtemplate( 170 return web.sendtemplate(
169 'filerevision', 171 b'filerevision',
170 file=f, 172 file=f,
171 path=webutil.up(f), 173 path=webutil.up(f),
172 text=templateutil.mappinggenerator(lines), 174 text=templateutil.mappinggenerator(lines),
173 symrev=webutil.symrevorshortnode(web.req, fctx), 175 symrev=webutil.symrevorshortnode(web.req, fctx),
174 rename=webutil.renamelink(fctx), 176 rename=webutil.renamelink(fctx),
176 ishead=int(ishead), 178 ishead=int(ishead),
177 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) 179 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
178 ) 180 )
179 181
180 182
181 @webcommand('file') 183 @webcommand(b'file')
182 def file(web): 184 def file(web):
183 """ 185 """
184 /file/{revision}[/{path}] 186 /file/{revision}[/{path}]
185 ------------------------- 187 -------------------------
186 188
196 the ``filerevision`` template. 198 the ``filerevision`` template.
197 199
198 If ``path`` is not defined, information about the root directory will 200 If ``path`` is not defined, information about the root directory will
199 be rendered. 201 be rendered.
200 """ 202 """
201 if web.req.qsparams.get('style') == 'raw': 203 if web.req.qsparams.get(b'style') == b'raw':
202 return rawfile(web) 204 return rawfile(web)
203 205
204 path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', '')) 206 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
205 if not path: 207 if not path:
206 return manifest(web) 208 return manifest(web)
207 try: 209 try:
208 return _filerevision(web, webutil.filectx(web.repo, web.req)) 210 return _filerevision(web, webutil.filectx(web.repo, web.req))
209 except error.LookupError as inst: 211 except error.LookupError as inst:
212 except ErrorResponse: 214 except ErrorResponse:
213 raise inst 215 raise inst
214 216
215 217
216 def _search(web): 218 def _search(web):
217 MODE_REVISION = 'rev' 219 MODE_REVISION = b'rev'
218 MODE_KEYWORD = 'keyword' 220 MODE_KEYWORD = b'keyword'
219 MODE_REVSET = 'revset' 221 MODE_REVSET = b'revset'
220 222
221 def revsearch(ctx): 223 def revsearch(ctx):
222 yield ctx 224 yield ctx
223 225
224 def keywordsearch(query): 226 def keywordsearch(query):
240 miss = 0 242 miss = 0
241 for q in qw: 243 for q in qw:
242 if not ( 244 if not (
243 q in lower(ctx.user()) 245 q in lower(ctx.user())
244 or q in lower(ctx.description()) 246 or q in lower(ctx.description())
245 or q in lower(" ".join(ctx.files())) 247 or q in lower(b" ".join(ctx.files()))
246 ): 248 ):
247 miss = 1 249 miss = 1
248 break 250 break
249 if miss: 251 if miss:
250 continue 252 continue
254 def revsetsearch(revs): 256 def revsetsearch(revs):
255 for r in revs: 257 for r in revs:
256 yield web.repo[r] 258 yield web.repo[r]
257 259
258 searchfuncs = { 260 searchfuncs = {
259 MODE_REVISION: (revsearch, 'exact revision search'), 261 MODE_REVISION: (revsearch, b'exact revision search'),
260 MODE_KEYWORD: (keywordsearch, 'literal keyword search'), 262 MODE_KEYWORD: (keywordsearch, b'literal keyword search'),
261 MODE_REVSET: (revsetsearch, 'revset expression search'), 263 MODE_REVSET: (revsetsearch, b'revset expression search'),
262 } 264 }
263 265
264 def getsearchmode(query): 266 def getsearchmode(query):
265 try: 267 try:
266 ctx = scmutil.revsymbol(web.repo, query) 268 ctx = scmutil.revsymbol(web.repo, query)
269 # decide if it's a revset expression or keywords 271 # decide if it's a revset expression or keywords
270 pass 272 pass
271 else: 273 else:
272 return MODE_REVISION, ctx 274 return MODE_REVISION, ctx
273 275
274 revdef = 'reverse(%s)' % query 276 revdef = b'reverse(%s)' % query
275 try: 277 try:
276 tree = revsetlang.parse(revdef) 278 tree = revsetlang.parse(revdef)
277 except error.ParseError: 279 except error.ParseError:
278 # can't parse to a revset tree 280 # can't parse to a revset tree
279 return MODE_KEYWORD, query 281 return MODE_KEYWORD, query
281 if revsetlang.depth(tree) <= 2: 283 if revsetlang.depth(tree) <= 2:
282 # no revset syntax used 284 # no revset syntax used
283 return MODE_KEYWORD, query 285 return MODE_KEYWORD, query
284 286
285 if any( 287 if any(
286 (token, (value or '')[:3]) == ('string', 're:') 288 (token, (value or b'')[:3]) == (b'string', b're:')
287 for token, value, pos in revsetlang.tokenize(revdef) 289 for token, value, pos in revsetlang.tokenize(revdef)
288 ): 290 ):
289 return MODE_KEYWORD, query 291 return MODE_KEYWORD, query
290 292
291 funcsused = revsetlang.funcsused(tree) 293 funcsused = revsetlang.funcsused(tree)
314 count = 0 316 count = 0
315 317
316 for ctx in searchfunc[0](funcarg): 318 for ctx in searchfunc[0](funcarg):
317 count += 1 319 count += 1
318 n = scmutil.binnode(ctx) 320 n = scmutil.binnode(ctx)
319 showtags = webutil.showtag(web.repo, 'changelogtag', n) 321 showtags = webutil.showtag(web.repo, b'changelogtag', n)
320 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles) 322 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles)
321 323
322 lm = webutil.commonentry(web.repo, ctx) 324 lm = webutil.commonentry(web.repo, ctx)
323 lm.update( 325 lm.update(
324 { 326 {
325 'parity': next(parity), 327 b'parity': next(parity),
326 'changelogtag': showtags, 328 b'changelogtag': showtags,
327 'files': files, 329 b'files': files,
328 } 330 }
329 ) 331 )
330 yield lm 332 yield lm
331 333
332 if count >= revcount: 334 if count >= revcount:
333 break 335 break
334 336
335 query = web.req.qsparams['rev'] 337 query = web.req.qsparams[b'rev']
336 revcount = web.maxchanges 338 revcount = web.maxchanges
337 if 'revcount' in web.req.qsparams: 339 if b'revcount' in web.req.qsparams:
338 try: 340 try:
339 revcount = int(web.req.qsparams.get('revcount', revcount)) 341 revcount = int(web.req.qsparams.get(b'revcount', revcount))
340 revcount = max(revcount, 1) 342 revcount = max(revcount, 1)
341 web.tmpl.defaults['sessionvars']['revcount'] = revcount 343 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
342 except ValueError: 344 except ValueError:
343 pass 345 pass
344 346
345 lessvars = copy.copy(web.tmpl.defaults['sessionvars']) 347 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
346 lessvars['revcount'] = max(revcount // 2, 1) 348 lessvars[b'revcount'] = max(revcount // 2, 1)
347 lessvars['rev'] = query 349 lessvars[b'rev'] = query
348 morevars = copy.copy(web.tmpl.defaults['sessionvars']) 350 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
349 morevars['revcount'] = revcount * 2 351 morevars[b'revcount'] = revcount * 2
350 morevars['rev'] = query 352 morevars[b'rev'] = query
351 353
352 mode, funcarg = getsearchmode(query) 354 mode, funcarg = getsearchmode(query)
353 355
354 if 'forcekw' in web.req.qsparams: 356 if b'forcekw' in web.req.qsparams:
355 showforcekw = '' 357 showforcekw = b''
356 showunforcekw = searchfuncs[mode][1] 358 showunforcekw = searchfuncs[mode][1]
357 mode = MODE_KEYWORD 359 mode = MODE_KEYWORD
358 funcarg = query 360 funcarg = query
359 else: 361 else:
360 if mode != MODE_KEYWORD: 362 if mode != MODE_KEYWORD:
361 showforcekw = searchfuncs[MODE_KEYWORD][1] 363 showforcekw = searchfuncs[MODE_KEYWORD][1]
362 else: 364 else:
363 showforcekw = '' 365 showforcekw = b''
364 showunforcekw = '' 366 showunforcekw = b''
365 367
366 searchfunc = searchfuncs[mode] 368 searchfunc = searchfuncs[mode]
367 369
368 tip = web.repo['tip'] 370 tip = web.repo[b'tip']
369 parity = paritygen(web.stripecount) 371 parity = paritygen(web.stripecount)
370 372
371 return web.sendtemplate( 373 return web.sendtemplate(
372 'search', 374 b'search',
373 query=query, 375 query=query,
374 node=tip.hex(), 376 node=tip.hex(),
375 symrev='tip', 377 symrev=b'tip',
376 entries=templateutil.mappinggenerator(changelist, name='searchentry'), 378 entries=templateutil.mappinggenerator(changelist, name=b'searchentry'),
377 archives=web.archivelist('tip'), 379 archives=web.archivelist(b'tip'),
378 morevars=morevars, 380 morevars=morevars,
379 lessvars=lessvars, 381 lessvars=lessvars,
380 modedesc=searchfunc[1], 382 modedesc=searchfunc[1],
381 showforcekw=showforcekw, 383 showforcekw=showforcekw,
382 showunforcekw=showunforcekw, 384 showunforcekw=showunforcekw,
383 ) 385 )
384 386
385 387
386 @webcommand('changelog') 388 @webcommand(b'changelog')
387 def changelog(web, shortlog=False): 389 def changelog(web, shortlog=False):
388 """ 390 """
389 /changelog[/{revision}] 391 /changelog[/{revision}]
390 ----------------------- 392 -----------------------
391 393
407 changesets to render. 409 changesets to render.
408 410
409 For non-searches, the ``changelog`` template will be rendered. 411 For non-searches, the ``changelog`` template will be rendered.
410 """ 412 """
411 413
412 query = '' 414 query = b''
413 if 'node' in web.req.qsparams: 415 if b'node' in web.req.qsparams:
414 ctx = webutil.changectx(web.repo, web.req) 416 ctx = webutil.changectx(web.repo, web.req)
415 symrev = webutil.symrevorshortnode(web.req, ctx) 417 symrev = webutil.symrevorshortnode(web.req, ctx)
416 elif 'rev' in web.req.qsparams: 418 elif b'rev' in web.req.qsparams:
417 return _search(web) 419 return _search(web)
418 else: 420 else:
419 ctx = web.repo['tip'] 421 ctx = web.repo[b'tip']
420 symrev = 'tip' 422 symrev = b'tip'
421 423
422 def changelist(maxcount): 424 def changelist(maxcount):
423 revs = [] 425 revs = []
424 if pos != -1: 426 if pos != -1:
425 revs = web.repo.changelog.revs(pos, 0) 427 revs = web.repo.changelog.revs(pos, 0)
430 if shortlog: 432 if shortlog:
431 revcount = web.maxshortchanges 433 revcount = web.maxshortchanges
432 else: 434 else:
433 revcount = web.maxchanges 435 revcount = web.maxchanges
434 436
435 if 'revcount' in web.req.qsparams: 437 if b'revcount' in web.req.qsparams:
436 try: 438 try:
437 revcount = int(web.req.qsparams.get('revcount', revcount)) 439 revcount = int(web.req.qsparams.get(b'revcount', revcount))
438 revcount = max(revcount, 1) 440 revcount = max(revcount, 1)
439 web.tmpl.defaults['sessionvars']['revcount'] = revcount 441 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
440 except ValueError: 442 except ValueError:
441 pass 443 pass
442 444
443 lessvars = copy.copy(web.tmpl.defaults['sessionvars']) 445 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
444 lessvars['revcount'] = max(revcount // 2, 1) 446 lessvars[b'revcount'] = max(revcount // 2, 1)
445 morevars = copy.copy(web.tmpl.defaults['sessionvars']) 447 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
446 morevars['revcount'] = revcount * 2 448 morevars[b'revcount'] = revcount * 2
447 449
448 count = len(web.repo) 450 count = len(web.repo)
449 pos = ctx.rev() 451 pos = ctx.rev()
450 parity = paritygen(web.stripecount) 452 parity = paritygen(web.stripecount)
451 453
458 entries = entries[:-1] 460 entries = entries[:-1]
459 else: 461 else:
460 nextentry = [] 462 nextentry = []
461 463
462 return web.sendtemplate( 464 return web.sendtemplate(
463 'shortlog' if shortlog else 'changelog', 465 b'shortlog' if shortlog else b'changelog',
464 changenav=changenav, 466 changenav=changenav,
465 node=ctx.hex(), 467 node=ctx.hex(),
466 rev=pos, 468 rev=pos,
467 symrev=symrev, 469 symrev=symrev,
468 changesets=count, 470 changesets=count,
469 entries=templateutil.mappinglist(entries), 471 entries=templateutil.mappinglist(entries),
470 latestentry=templateutil.mappinglist(latestentry), 472 latestentry=templateutil.mappinglist(latestentry),
471 nextentry=templateutil.mappinglist(nextentry), 473 nextentry=templateutil.mappinglist(nextentry),
472 archives=web.archivelist('tip'), 474 archives=web.archivelist(b'tip'),
473 revcount=revcount, 475 revcount=revcount,
474 morevars=morevars, 476 morevars=morevars,
475 lessvars=lessvars, 477 lessvars=lessvars,
476 query=query, 478 query=query,
477 ) 479 )
478 480
479 481
480 @webcommand('shortlog') 482 @webcommand(b'shortlog')
481 def shortlog(web): 483 def shortlog(web):
482 """ 484 """
483 /shortlog 485 /shortlog
484 --------- 486 ---------
485 487
490 ``changelog`` template. 492 ``changelog`` template.
491 """ 493 """
492 return changelog(web, shortlog=True) 494 return changelog(web, shortlog=True)
493 495
494 496
495 @webcommand('changeset') 497 @webcommand(b'changeset')
496 def changeset(web): 498 def changeset(web):
497 """ 499 """
498 /changeset[/{revision}] 500 /changeset[/{revision}]
499 ----------------------- 501 -----------------------
500 502
508 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many 510 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
509 templates related to diffs may all be used to produce the output. 511 templates related to diffs may all be used to produce the output.
510 """ 512 """
511 ctx = webutil.changectx(web.repo, web.req) 513 ctx = webutil.changectx(web.repo, web.req)
512 514
513 return web.sendtemplate('changeset', **webutil.changesetentry(web, ctx)) 515 return web.sendtemplate(b'changeset', **webutil.changesetentry(web, ctx))
514 516
515 517
516 rev = webcommand('rev')(changeset) 518 rev = webcommand(b'rev')(changeset)
517 519
518 520
519 def decodepath(path): 521 def decodepath(path):
520 """Hook for mapping a path in the repository to a path in the 522 """Hook for mapping a path in the repository to a path in the
521 working copy. 523 working copy.
523 Extensions (e.g., largefiles) can override this to remap files in 525 Extensions (e.g., largefiles) can override this to remap files in
524 the virtual file system presented by the manifest command below.""" 526 the virtual file system presented by the manifest command below."""
525 return path 527 return path
526 528
527 529
528 @webcommand('manifest') 530 @webcommand(b'manifest')
529 def manifest(web): 531 def manifest(web):
530 """ 532 """
531 /manifest[/{revision}[/{path}]] 533 /manifest[/{revision}[/{path}]]
532 ------------------------------- 534 -------------------------------
533 535
540 is recommended to use the ``file`` handler instead, as it can handle both 542 is recommended to use the ``file`` handler instead, as it can handle both
541 directories and files. 543 directories and files.
542 544
543 The ``manifest`` template will be rendered for this handler. 545 The ``manifest`` template will be rendered for this handler.
544 """ 546 """
545 if 'node' in web.req.qsparams: 547 if b'node' in web.req.qsparams:
546 ctx = webutil.changectx(web.repo, web.req) 548 ctx = webutil.changectx(web.repo, web.req)
547 symrev = webutil.symrevorshortnode(web.req, ctx) 549 symrev = webutil.symrevorshortnode(web.req, ctx)
548 else: 550 else:
549 ctx = web.repo['tip'] 551 ctx = web.repo[b'tip']
550 symrev = 'tip' 552 symrev = b'tip'
551 path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', '')) 553 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
552 mf = ctx.manifest() 554 mf = ctx.manifest()
553 node = scmutil.binnode(ctx) 555 node = scmutil.binnode(ctx)
554 556
555 files = {} 557 files = {}
556 dirs = {} 558 dirs = {}
557 parity = paritygen(web.stripecount) 559 parity = paritygen(web.stripecount)
558 560
559 if path and path[-1:] != "/": 561 if path and path[-1:] != b"/":
560 path += "/" 562 path += b"/"
561 l = len(path) 563 l = len(path)
562 abspath = "/" + path 564 abspath = b"/" + path
563 565
564 for full, n in mf.iteritems(): 566 for full, n in mf.iteritems():
565 # the virtual path (working copy path) used for the full 567 # the virtual path (working copy path) used for the full
566 # (repository) path 568 # (repository) path
567 f = decodepath(full) 569 f = decodepath(full)
568 570
569 if f[:l] != path: 571 if f[:l] != path:
570 continue 572 continue
571 remain = f[l:] 573 remain = f[l:]
572 elements = remain.split('/') 574 elements = remain.split(b'/')
573 if len(elements) == 1: 575 if len(elements) == 1:
574 files[remain] = full 576 files[remain] = full
575 else: 577 else:
576 h = dirs # need to retain ref to dirs (root) 578 h = dirs # need to retain ref to dirs (root)
577 for elem in elements[0:-1]: 579 for elem in elements[0:-1]:
581 if len(h) > 1: 583 if len(h) > 1:
582 break 584 break
583 h[None] = None # denotes files present 585 h[None] = None # denotes files present
584 586
585 if mf and not files and not dirs: 587 if mf and not files and not dirs:
586 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) 588 raise ErrorResponse(HTTP_NOT_FOUND, b'path not found: ' + path)
587 589
588 def filelist(context): 590 def filelist(context):
589 for f in sorted(files): 591 for f in sorted(files):
590 full = files[f] 592 full = files[f]
591 593
592 fctx = ctx.filectx(full) 594 fctx = ctx.filectx(full)
593 yield { 595 yield {
594 "file": full, 596 b"file": full,
595 "parity": next(parity), 597 b"parity": next(parity),
596 "basename": f, 598 b"basename": f,
597 "date": fctx.date(), 599 b"date": fctx.date(),
598 "size": fctx.size(), 600 b"size": fctx.size(),
599 "permissions": mf.flags(full), 601 b"permissions": mf.flags(full),
600 } 602 }
601 603
602 def dirlist(context): 604 def dirlist(context):
603 for d in sorted(dirs): 605 for d in sorted(dirs):
604 606
608 k, v = next(iter(h.items())) 610 k, v = next(iter(h.items()))
609 if v: 611 if v:
610 emptydirs.append(k) 612 emptydirs.append(k)
611 h = v 613 h = v
612 614
613 path = "%s%s" % (abspath, d) 615 path = b"%s%s" % (abspath, d)
614 yield { 616 yield {
615 "parity": next(parity), 617 b"parity": next(parity),
616 "path": path, 618 b"path": path,
617 "emptydirs": "/".join(emptydirs), 619 b"emptydirs": b"/".join(emptydirs),
618 "basename": d, 620 b"basename": d,
619 } 621 }
620 622
621 return web.sendtemplate( 623 return web.sendtemplate(
622 'manifest', 624 b'manifest',
623 symrev=symrev, 625 symrev=symrev,
624 path=abspath, 626 path=abspath,
625 up=webutil.up(abspath), 627 up=webutil.up(abspath),
626 upparity=next(parity), 628 upparity=next(parity),
627 fentries=templateutil.mappinggenerator(filelist), 629 fentries=templateutil.mappinggenerator(filelist),
629 archives=web.archivelist(hex(node)), 631 archives=web.archivelist(hex(node)),
630 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) 632 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
631 ) 633 )
632 634
633 635
634 @webcommand('tags') 636 @webcommand(b'tags')
635 def tags(web): 637 def tags(web):
636 """ 638 """
637 /tags 639 /tags
638 ----- 640 -----
639 641
647 parity = paritygen(web.stripecount) 649 parity = paritygen(web.stripecount)
648 650
649 def entries(context, notip, latestonly): 651 def entries(context, notip, latestonly):
650 t = i 652 t = i
651 if notip: 653 if notip:
652 t = [(k, n) for k, n in i if k != "tip"] 654 t = [(k, n) for k, n in i if k != b"tip"]
653 if latestonly: 655 if latestonly:
654 t = t[:1] 656 t = t[:1]
655 for k, n in t: 657 for k, n in t:
656 yield { 658 yield {
657 "parity": next(parity), 659 b"parity": next(parity),
658 "tag": k, 660 b"tag": k,
659 "date": web.repo[n].date(), 661 b"date": web.repo[n].date(),
660 "node": hex(n), 662 b"node": hex(n),
661 } 663 }
662 664
663 return web.sendtemplate( 665 return web.sendtemplate(
664 'tags', 666 b'tags',
665 node=hex(web.repo.changelog.tip()), 667 node=hex(web.repo.changelog.tip()),
666 entries=templateutil.mappinggenerator(entries, args=(False, False)), 668 entries=templateutil.mappinggenerator(entries, args=(False, False)),
667 entriesnotip=templateutil.mappinggenerator(entries, args=(True, False)), 669 entriesnotip=templateutil.mappinggenerator(entries, args=(True, False)),
668 latestentry=templateutil.mappinggenerator(entries, args=(True, True)), 670 latestentry=templateutil.mappinggenerator(entries, args=(True, True)),
669 ) 671 )
670 672
671 673
672 @webcommand('bookmarks') 674 @webcommand(b'bookmarks')
673 def bookmarks(web): 675 def bookmarks(web):
674 """ 676 """
675 /bookmarks 677 /bookmarks
676 ---------- 678 ----------
677 679
690 t = i 692 t = i
691 if latestonly: 693 if latestonly:
692 t = i[:1] 694 t = i[:1]
693 for k, n in t: 695 for k, n in t:
694 yield { 696 yield {
695 "parity": next(parity), 697 b"parity": next(parity),
696 "bookmark": k, 698 b"bookmark": k,
697 "date": web.repo[n].date(), 699 b"date": web.repo[n].date(),
698 "node": hex(n), 700 b"node": hex(n),
699 } 701 }
700 702
701 if i: 703 if i:
702 latestrev = i[0][1] 704 latestrev = i[0][1]
703 else: 705 else:
704 latestrev = -1 706 latestrev = -1
705 lastdate = web.repo[latestrev].date() 707 lastdate = web.repo[latestrev].date()
706 708
707 return web.sendtemplate( 709 return web.sendtemplate(
708 'bookmarks', 710 b'bookmarks',
709 node=hex(web.repo.changelog.tip()), 711 node=hex(web.repo.changelog.tip()),
710 lastchange=templateutil.mappinglist([{'date': lastdate}]), 712 lastchange=templateutil.mappinglist([{b'date': lastdate}]),
711 entries=templateutil.mappinggenerator(entries, args=(False,)), 713 entries=templateutil.mappinggenerator(entries, args=(False,)),
712 latestentry=templateutil.mappinggenerator(entries, args=(True,)), 714 latestentry=templateutil.mappinggenerator(entries, args=(True,)),
713 ) 715 )
714 716
715 717
716 @webcommand('branches') 718 @webcommand(b'branches')
717 def branches(web): 719 def branches(web):
718 """ 720 """
719 /branches 721 /branches
720 --------- 722 ---------
721 723
729 """ 731 """
730 entries = webutil.branchentries(web.repo, web.stripecount) 732 entries = webutil.branchentries(web.repo, web.stripecount)
731 latestentry = webutil.branchentries(web.repo, web.stripecount, 1) 733 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
732 734
733 return web.sendtemplate( 735 return web.sendtemplate(
734 'branches', 736 b'branches',
735 node=hex(web.repo.changelog.tip()), 737 node=hex(web.repo.changelog.tip()),
736 entries=entries, 738 entries=entries,
737 latestentry=latestentry, 739 latestentry=latestentry,
738 ) 740 )
739 741
740 742
741 @webcommand('summary') 743 @webcommand(b'summary')
742 def summary(web): 744 def summary(web):
743 """ 745 """
744 /summary 746 /summary
745 -------- 747 --------
746 748
755 757
756 def tagentries(context): 758 def tagentries(context):
757 parity = paritygen(web.stripecount) 759 parity = paritygen(web.stripecount)
758 count = 0 760 count = 0
759 for k, n in i: 761 for k, n in i:
760 if k == "tip": # skip tip 762 if k == b"tip": # skip tip
761 continue 763 continue
762 764
763 count += 1 765 count += 1
764 if count > 10: # limit to 10 tags 766 if count > 10: # limit to 10 tags
765 break 767 break
766 768
767 yield { 769 yield {
768 'parity': next(parity), 770 b'parity': next(parity),
769 'tag': k, 771 b'tag': k,
770 'node': hex(n), 772 b'node': hex(n),
771 'date': web.repo[n].date(), 773 b'date': web.repo[n].date(),
772 } 774 }
773 775
774 def bookmarks(context): 776 def bookmarks(context):
775 parity = paritygen(web.stripecount) 777 parity = paritygen(web.stripecount)
776 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] 778 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
777 sortkey = lambda b: (web.repo[b[1]].rev(), b[0]) 779 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
778 marks = sorted(marks, key=sortkey, reverse=True) 780 marks = sorted(marks, key=sortkey, reverse=True)
779 for k, n in marks[:10]: # limit to 10 bookmarks 781 for k, n in marks[:10]: # limit to 10 bookmarks
780 yield { 782 yield {
781 'parity': next(parity), 783 b'parity': next(parity),
782 'bookmark': k, 784 b'bookmark': k,
783 'date': web.repo[n].date(), 785 b'date': web.repo[n].date(),
784 'node': hex(n), 786 b'node': hex(n),
785 } 787 }
786 788
787 def changelist(context): 789 def changelist(context):
788 parity = paritygen(web.stripecount, offset=start - end) 790 parity = paritygen(web.stripecount, offset=start - end)
789 l = [] # build a list in forward order for efficiency 791 l = [] # build a list in forward order for efficiency
791 if start < end: 793 if start < end:
792 revs = web.repo.changelog.revs(start, end - 1) 794 revs = web.repo.changelog.revs(start, end - 1)
793 for i in revs: 795 for i in revs:
794 ctx = web.repo[i] 796 ctx = web.repo[i]
795 lm = webutil.commonentry(web.repo, ctx) 797 lm = webutil.commonentry(web.repo, ctx)
796 lm['parity'] = next(parity) 798 lm[b'parity'] = next(parity)
797 l.append(lm) 799 l.append(lm)
798 800
799 for entry in reversed(l): 801 for entry in reversed(l):
800 yield entry 802 yield entry
801 803
802 tip = web.repo['tip'] 804 tip = web.repo[b'tip']
803 count = len(web.repo) 805 count = len(web.repo)
804 start = max(0, count - web.maxchanges) 806 start = max(0, count - web.maxchanges)
805 end = min(count, start + web.maxchanges) 807 end = min(count, start + web.maxchanges)
806 808
807 desc = web.config("web", "description") 809 desc = web.config(b"web", b"description")
808 if not desc: 810 if not desc:
809 desc = 'unknown' 811 desc = b'unknown'
810 labels = web.configlist('web', 'labels') 812 labels = web.configlist(b'web', b'labels')
811 813
812 return web.sendtemplate( 814 return web.sendtemplate(
813 'summary', 815 b'summary',
814 desc=desc, 816 desc=desc,
815 owner=get_contact(web.config) or 'unknown', 817 owner=get_contact(web.config) or b'unknown',
816 lastchange=tip.date(), 818 lastchange=tip.date(),
817 tags=templateutil.mappinggenerator(tagentries, name='tagentry'), 819 tags=templateutil.mappinggenerator(tagentries, name=b'tagentry'),
818 bookmarks=templateutil.mappinggenerator(bookmarks), 820 bookmarks=templateutil.mappinggenerator(bookmarks),
819 branches=webutil.branchentries(web.repo, web.stripecount, 10), 821 branches=webutil.branchentries(web.repo, web.stripecount, 10),
820 shortlog=templateutil.mappinggenerator( 822 shortlog=templateutil.mappinggenerator(
821 changelist, name='shortlogentry' 823 changelist, name=b'shortlogentry'
822 ), 824 ),
823 node=tip.hex(), 825 node=tip.hex(),
824 symrev='tip', 826 symrev=b'tip',
825 archives=web.archivelist('tip'), 827 archives=web.archivelist(b'tip'),
826 labels=templateutil.hybridlist(labels, name='label'), 828 labels=templateutil.hybridlist(labels, name=b'label'),
827 ) 829 )
828 830
829 831
830 @webcommand('filediff') 832 @webcommand(b'filediff')
831 def filediff(web): 833 def filediff(web):
832 """ 834 """
833 /diff/{revision}/{path} 835 /diff/{revision}/{path}
834 ----------------------- 836 -----------------------
835 837
843 fctx, ctx = None, None 845 fctx, ctx = None, None
844 try: 846 try:
845 fctx = webutil.filectx(web.repo, web.req) 847 fctx = webutil.filectx(web.repo, web.req)
846 except LookupError: 848 except LookupError:
847 ctx = webutil.changectx(web.repo, web.req) 849 ctx = webutil.changectx(web.repo, web.req)
848 path = webutil.cleanpath(web.repo, web.req.qsparams['file']) 850 path = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
849 if path not in ctx.files(): 851 if path not in ctx.files():
850 raise 852 raise
851 853
852 if fctx is not None: 854 if fctx is not None:
853 path = fctx.path() 855 path = fctx.path()
854 ctx = fctx.changectx() 856 ctx = fctx.changectx()
855 basectx = ctx.p1() 857 basectx = ctx.p1()
856 858
857 style = web.config('web', 'style') 859 style = web.config(b'web', b'style')
858 if 'style' in web.req.qsparams: 860 if b'style' in web.req.qsparams:
859 style = web.req.qsparams['style'] 861 style = web.req.qsparams[b'style']
860 862
861 diffs = webutil.diffs(web, ctx, basectx, [path], style) 863 diffs = webutil.diffs(web, ctx, basectx, [path], style)
862 if fctx is not None: 864 if fctx is not None:
863 rename = webutil.renamelink(fctx) 865 rename = webutil.renamelink(fctx)
864 ctx = fctx 866 ctx = fctx
865 else: 867 else:
866 rename = templateutil.mappinglist([]) 868 rename = templateutil.mappinglist([])
867 ctx = ctx 869 ctx = ctx
868 870
869 return web.sendtemplate( 871 return web.sendtemplate(
870 'filediff', 872 b'filediff',
871 file=path, 873 file=path,
872 symrev=webutil.symrevorshortnode(web.req, ctx), 874 symrev=webutil.symrevorshortnode(web.req, ctx),
873 rename=rename, 875 rename=rename,
874 diff=diffs, 876 diff=diffs,
875 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) 877 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
876 ) 878 )
877 879
878 880
879 diff = webcommand('diff')(filediff) 881 diff = webcommand(b'diff')(filediff)
880 882
881 883
882 @webcommand('comparison') 884 @webcommand(b'comparison')
883 def comparison(web): 885 def comparison(web):
884 """ 886 """
885 /comparison/{revision}/{path} 887 /comparison/{revision}/{path}
886 ----------------------------- 888 -----------------------------
887 889
895 context in the diff. 897 context in the diff.
896 898
897 The ``filecomparison`` template is rendered. 899 The ``filecomparison`` template is rendered.
898 """ 900 """
899 ctx = webutil.changectx(web.repo, web.req) 901 ctx = webutil.changectx(web.repo, web.req)
900 if 'file' not in web.req.qsparams: 902 if b'file' not in web.req.qsparams:
901 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') 903 raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
902 path = webutil.cleanpath(web.repo, web.req.qsparams['file']) 904 path = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
903 905
904 parsecontext = lambda v: v == 'full' and -1 or int(v) 906 parsecontext = lambda v: v == b'full' and -1 or int(v)
905 if 'context' in web.req.qsparams: 907 if b'context' in web.req.qsparams:
906 context = parsecontext(web.req.qsparams['context']) 908 context = parsecontext(web.req.qsparams[b'context'])
907 else: 909 else:
908 context = parsecontext(web.config('web', 'comparisoncontext')) 910 context = parsecontext(web.config(b'web', b'comparisoncontext'))
909 911
910 def filelines(f): 912 def filelines(f):
911 if f.isbinary(): 913 if f.isbinary():
912 mt = pycompat.sysbytes( 914 mt = pycompat.sysbytes(
913 mimetypes.guess_type(pycompat.fsdecode(f.path()))[0] 915 mimetypes.guess_type(pycompat.fsdecode(f.path()))[0]
914 or r'application/octet-stream' 916 or r'application/octet-stream'
915 ) 917 )
916 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))] 918 return [_(b'(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
917 return f.data().splitlines() 919 return f.data().splitlines()
918 920
919 fctx = None 921 fctx = None
920 parent = ctx.p1() 922 parent = ctx.p1()
921 leftrev = parent.rev() 923 leftrev = parent.rev()
942 else: 944 else:
943 rename = templateutil.mappinglist([]) 945 rename = templateutil.mappinglist([])
944 ctx = ctx 946 ctx = ctx
945 947
946 return web.sendtemplate( 948 return web.sendtemplate(
947 'filecomparison', 949 b'filecomparison',
948 file=path, 950 file=path,
949 symrev=webutil.symrevorshortnode(web.req, ctx), 951 symrev=webutil.symrevorshortnode(web.req, ctx),
950 rename=rename, 952 rename=rename,
951 leftrev=leftrev, 953 leftrev=leftrev,
952 leftnode=hex(leftnode), 954 leftnode=hex(leftnode),
955 comparison=comparison, 957 comparison=comparison,
956 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) 958 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
957 ) 959 )
958 960
959 961
960 @webcommand('annotate') 962 @webcommand(b'annotate')
961 def annotate(web): 963 def annotate(web):
962 """ 964 """
963 /annotate/{revision}/{path} 965 /annotate/{revision}/{path}
964 --------------------------- 966 ---------------------------
965 967
989 rev = f.rev() 991 rev = f.rev()
990 if rev not in parentscache: 992 if rev not in parentscache:
991 parentscache[rev] = [] 993 parentscache[rev] = []
992 for p in f.parents(): 994 for p in f.parents():
993 entry = { 995 entry = {
994 'node': p.hex(), 996 b'node': p.hex(),
995 'rev': p.rev(), 997 b'rev': p.rev(),
996 } 998 }
997 parentscache[rev].append(entry) 999 parentscache[rev].append(entry)
998 1000
999 for p in parentscache[rev]: 1001 for p in parentscache[rev]:
1000 yield p 1002 yield p
1007 ) 1009 )
1008 lines = [ 1010 lines = [
1009 dagop.annotateline( 1011 dagop.annotateline(
1010 fctx=fctx.filectx(fctx.filerev()), 1012 fctx=fctx.filectx(fctx.filerev()),
1011 lineno=1, 1013 lineno=1,
1012 text='(binary:%s)' % mt, 1014 text=b'(binary:%s)' % mt,
1013 ) 1015 )
1014 ] 1016 ]
1015 else: 1017 else:
1016 lines = webutil.annotate(web.req, fctx, web.repo.ui) 1018 lines = webutil.annotate(web.req, fctx, web.repo.ui)
1017 1019
1025 blockparity = next(blockparitygen) 1027 blockparity = next(blockparitygen)
1026 else: 1028 else:
1027 blockhead = None 1029 blockhead = None
1028 previousrev = rev 1030 previousrev = rev
1029 yield { 1031 yield {
1030 "parity": next(parity), 1032 b"parity": next(parity),
1031 "node": f.hex(), 1033 b"node": f.hex(),
1032 "rev": rev, 1034 b"rev": rev,
1033 "author": f.user(), 1035 b"author": f.user(),
1034 "parents": templateutil.mappinggenerator(parents, args=(f,)), 1036 b"parents": templateutil.mappinggenerator(parents, args=(f,)),
1035 "desc": f.description(), 1037 b"desc": f.description(),
1036 "extra": f.extra(), 1038 b"extra": f.extra(),
1037 "file": f.path(), 1039 b"file": f.path(),
1038 "blockhead": blockhead, 1040 b"blockhead": blockhead,
1039 "blockparity": blockparity, 1041 b"blockparity": blockparity,
1040 "targetline": aline.lineno, 1042 b"targetline": aline.lineno,
1041 "line": aline.text, 1043 b"line": aline.text,
1042 "lineno": lineno + 1, 1044 b"lineno": lineno + 1,
1043 "lineid": "l%d" % (lineno + 1), 1045 b"lineid": b"l%d" % (lineno + 1),
1044 "linenumber": "% 6d" % (lineno + 1), 1046 b"linenumber": b"% 6d" % (lineno + 1),
1045 "revdate": f.date(), 1047 b"revdate": f.date(),
1046 } 1048 }
1047 1049
1048 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, 'annotate') 1050 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, b'annotate')
1049 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults} 1051 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
1050 1052
1051 return web.sendtemplate( 1053 return web.sendtemplate(
1052 'fileannotate', 1054 b'fileannotate',
1053 file=f, 1055 file=f,
1054 annotate=templateutil.mappinggenerator(annotate), 1056 annotate=templateutil.mappinggenerator(annotate),
1055 path=webutil.up(f), 1057 path=webutil.up(f),
1056 symrev=webutil.symrevorshortnode(web.req, fctx), 1058 symrev=webutil.symrevorshortnode(web.req, fctx),
1057 rename=webutil.renamelink(fctx), 1059 rename=webutil.renamelink(fctx),
1060 diffopts=templateutil.hybriddict(diffopts), 1062 diffopts=templateutil.hybriddict(diffopts),
1061 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) 1063 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
1062 ) 1064 )
1063 1065
1064 1066
1065 @webcommand('filelog') 1067 @webcommand(b'filelog')
1066 def filelog(web): 1068 def filelog(web):
1067 """ 1069 """
1068 /filelog/{revision}/{path} 1070 /filelog/{revision}/{path}
1069 -------------------------- 1071 --------------------------
1070 1072
1079 try: 1081 try:
1080 fctx = webutil.filectx(web.repo, web.req) 1082 fctx = webutil.filectx(web.repo, web.req)
1081 f = fctx.path() 1083 f = fctx.path()
1082 fl = fctx.filelog() 1084 fl = fctx.filelog()
1083 except error.LookupError: 1085 except error.LookupError:
1084 f = webutil.cleanpath(web.repo, web.req.qsparams['file']) 1086 f = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
1085 fl = web.repo.file(f) 1087 fl = web.repo.file(f)
1086 numrevs = len(fl) 1088 numrevs = len(fl)
1087 if not numrevs: # file doesn't exist at all 1089 if not numrevs: # file doesn't exist at all
1088 raise 1090 raise
1089 rev = webutil.changectx(web.repo, web.req).rev() 1091 rev = webutil.changectx(web.repo, web.req).rev()
1094 while fl.linkrev(frev) > rev: 1096 while fl.linkrev(frev) > rev:
1095 frev -= 1 1097 frev -= 1
1096 fctx = web.repo.filectx(f, fl.linkrev(frev)) 1098 fctx = web.repo.filectx(f, fl.linkrev(frev))
1097 1099
1098 revcount = web.maxshortchanges 1100 revcount = web.maxshortchanges
1099 if 'revcount' in web.req.qsparams: 1101 if b'revcount' in web.req.qsparams:
1100 try: 1102 try:
1101 revcount = int(web.req.qsparams.get('revcount', revcount)) 1103 revcount = int(web.req.qsparams.get(b'revcount', revcount))
1102 revcount = max(revcount, 1) 1104 revcount = max(revcount, 1)
1103 web.tmpl.defaults['sessionvars']['revcount'] = revcount 1105 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
1104 except ValueError: 1106 except ValueError:
1105 pass 1107 pass
1106 1108
1107 lrange = webutil.linerange(web.req) 1109 lrange = webutil.linerange(web.req)
1108 1110
1109 lessvars = copy.copy(web.tmpl.defaults['sessionvars']) 1111 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1110 lessvars['revcount'] = max(revcount // 2, 1) 1112 lessvars[b'revcount'] = max(revcount // 2, 1)
1111 morevars = copy.copy(web.tmpl.defaults['sessionvars']) 1113 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1112 morevars['revcount'] = revcount * 2 1114 morevars[b'revcount'] = revcount * 2
1113 1115
1114 patch = 'patch' in web.req.qsparams 1116 patch = b'patch' in web.req.qsparams
1115 if patch: 1117 if patch:
1116 lessvars['patch'] = morevars['patch'] = web.req.qsparams['patch'] 1118 lessvars[b'patch'] = morevars[b'patch'] = web.req.qsparams[b'patch']
1117 descend = 'descend' in web.req.qsparams 1119 descend = b'descend' in web.req.qsparams
1118 if descend: 1120 if descend:
1119 lessvars['descend'] = morevars['descend'] = web.req.qsparams['descend'] 1121 lessvars[b'descend'] = morevars[b'descend'] = web.req.qsparams[
1122 b'descend'
1123 ]
1120 1124
1121 count = fctx.filerev() + 1 1125 count = fctx.filerev() + 1
1122 start = max(0, count - revcount) # first rev on this page 1126 start = max(0, count - revcount) # first rev on this page
1123 end = min(count, start + revcount) # last rev on this page 1127 end = min(count, start + revcount) # last rev on this page
1124 parity = paritygen(web.stripecount, offset=start - end) 1128 parity = paritygen(web.stripecount, offset=start - end)
1130 for filerev in filelog.revs(start, end - 1) 1134 for filerev in filelog.revs(start, end - 1)
1131 if filelog.linkrev(filerev) in repo 1135 if filelog.linkrev(filerev) in repo
1132 ] 1136 ]
1133 entries = [] 1137 entries = []
1134 1138
1135 diffstyle = web.config('web', 'style') 1139 diffstyle = web.config(b'web', b'style')
1136 if 'style' in web.req.qsparams: 1140 if b'style' in web.req.qsparams:
1137 diffstyle = web.req.qsparams['style'] 1141 diffstyle = web.req.qsparams[b'style']
1138 1142
1139 def diff(fctx, linerange=None): 1143 def diff(fctx, linerange=None):
1140 ctx = fctx.changectx() 1144 ctx = fctx.changectx()
1141 basectx = ctx.p1() 1145 basectx = ctx.p1()
1142 path = fctx.path() 1146 path = fctx.path()
1145 ctx, 1149 ctx,
1146 basectx, 1150 basectx,
1147 [path], 1151 [path],
1148 diffstyle, 1152 diffstyle,
1149 linerange=linerange, 1153 linerange=linerange,
1150 lineidprefix='%s-' % ctx.hex()[:12], 1154 lineidprefix=b'%s-' % ctx.hex()[:12],
1151 ) 1155 )
1152 1156
1153 linerange = None 1157 linerange = None
1154 if lrange is not None: 1158 if lrange is not None:
1155 linerange = webutil.formatlinerange(*lrange) 1159 linerange = webutil.formatlinerange(*lrange)
1167 # follow renames accross filtered (not in range) revisions 1171 # follow renames accross filtered (not in range) revisions
1168 path = c.path() 1172 path = c.path()
1169 lm = webutil.commonentry(repo, c) 1173 lm = webutil.commonentry(repo, c)
1170 lm.update( 1174 lm.update(
1171 { 1175 {
1172 'parity': next(parity), 1176 b'parity': next(parity),
1173 'filerev': c.rev(), 1177 b'filerev': c.rev(),
1174 'file': path, 1178 b'file': path,
1175 'diff': diffs, 1179 b'diff': diffs,
1176 'linerange': webutil.formatlinerange(*lr), 1180 b'linerange': webutil.formatlinerange(*lr),
1177 'rename': templateutil.mappinglist([]), 1181 b'rename': templateutil.mappinglist([]),
1178 } 1182 }
1179 ) 1183 )
1180 entries.append(lm) 1184 entries.append(lm)
1181 if i == revcount: 1185 if i == revcount:
1182 break 1186 break
1183 lessvars['linerange'] = webutil.formatlinerange(*lrange) 1187 lessvars[b'linerange'] = webutil.formatlinerange(*lrange)
1184 morevars['linerange'] = lessvars['linerange'] 1188 morevars[b'linerange'] = lessvars[b'linerange']
1185 else: 1189 else:
1186 for i in revs: 1190 for i in revs:
1187 iterfctx = fctx.filectx(i) 1191 iterfctx = fctx.filectx(i)
1188 diffs = None 1192 diffs = None
1189 if patch: 1193 if patch:
1190 diffs = diff(iterfctx) 1194 diffs = diff(iterfctx)
1191 lm = webutil.commonentry(repo, iterfctx) 1195 lm = webutil.commonentry(repo, iterfctx)
1192 lm.update( 1196 lm.update(
1193 { 1197 {
1194 'parity': next(parity), 1198 b'parity': next(parity),
1195 'filerev': i, 1199 b'filerev': i,
1196 'file': f, 1200 b'file': f,
1197 'diff': diffs, 1201 b'diff': diffs,
1198 'rename': webutil.renamelink(iterfctx), 1202 b'rename': webutil.renamelink(iterfctx),
1199 } 1203 }
1200 ) 1204 )
1201 entries.append(lm) 1205 entries.append(lm)
1202 entries.reverse() 1206 entries.reverse()
1203 revnav = webutil.filerevnav(web.repo, fctx.path()) 1207 revnav = webutil.filerevnav(web.repo, fctx.path())
1204 nav = revnav.gen(end - 1, revcount, count) 1208 nav = revnav.gen(end - 1, revcount, count)
1205 1209
1206 latestentry = entries[:1] 1210 latestentry = entries[:1]
1207 1211
1208 return web.sendtemplate( 1212 return web.sendtemplate(
1209 'filelog', 1213 b'filelog',
1210 file=f, 1214 file=f,
1211 nav=nav, 1215 nav=nav,
1212 symrev=webutil.symrevorshortnode(web.req, fctx), 1216 symrev=webutil.symrevorshortnode(web.req, fctx),
1213 entries=templateutil.mappinglist(entries), 1217 entries=templateutil.mappinglist(entries),
1214 descend=descend, 1218 descend=descend,
1220 lessvars=lessvars, 1224 lessvars=lessvars,
1221 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) 1225 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
1222 ) 1226 )
1223 1227
1224 1228
1225 @webcommand('archive') 1229 @webcommand(b'archive')
1226 def archive(web): 1230 def archive(web):
1227 """ 1231 """
1228 /archive/{revision}.{format}[/{path}] 1232 /archive/{revision}.{format}[/{path}]
1229 ------------------------------------- 1233 -------------------------------------
1230 1234
1241 directory will be included in the archive. 1245 directory will be included in the archive.
1242 1246
1243 No template is used for this handler. Raw, binary content is generated. 1247 No template is used for this handler. Raw, binary content is generated.
1244 """ 1248 """
1245 1249
1246 type_ = web.req.qsparams.get('type') 1250 type_ = web.req.qsparams.get(b'type')
1247 allowed = web.configlist("web", "allow-archive") 1251 allowed = web.configlist(b"web", b"allow-archive")
1248 key = web.req.qsparams['node'] 1252 key = web.req.qsparams[b'node']
1249 1253
1250 if type_ not in webutil.archivespecs: 1254 if type_ not in webutil.archivespecs:
1251 msg = 'Unsupported archive type: %s' % stringutil.pprint(type_) 1255 msg = b'Unsupported archive type: %s' % stringutil.pprint(type_)
1252 raise ErrorResponse(HTTP_NOT_FOUND, msg) 1256 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1253 1257
1254 if not ((type_ in allowed or web.configbool("web", "allow" + type_))): 1258 if not ((type_ in allowed or web.configbool(b"web", b"allow" + type_))):
1255 msg = 'Archive type not allowed: %s' % type_ 1259 msg = b'Archive type not allowed: %s' % type_
1256 raise ErrorResponse(HTTP_FORBIDDEN, msg) 1260 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1257 1261
1258 reponame = re.sub(br"\W+", "-", os.path.basename(web.reponame)) 1262 reponame = re.sub(br"\W+", b"-", os.path.basename(web.reponame))
1259 cnode = web.repo.lookup(key) 1263 cnode = web.repo.lookup(key)
1260 arch_version = key 1264 arch_version = key
1261 if cnode == key or key == 'tip': 1265 if cnode == key or key == b'tip':
1262 arch_version = short(cnode) 1266 arch_version = short(cnode)
1263 name = "%s-%s" % (reponame, arch_version) 1267 name = b"%s-%s" % (reponame, arch_version)
1264 1268
1265 ctx = webutil.changectx(web.repo, web.req) 1269 ctx = webutil.changectx(web.repo, web.req)
1266 pats = [] 1270 pats = []
1267 match = scmutil.match(ctx, []) 1271 match = scmutil.match(ctx, [])
1268 file = web.req.qsparams.get('file') 1272 file = web.req.qsparams.get(b'file')
1269 if file: 1273 if file:
1270 pats = ['path:' + file] 1274 pats = [b'path:' + file]
1271 match = scmutil.match(ctx, pats, default='path') 1275 match = scmutil.match(ctx, pats, default=b'path')
1272 if pats: 1276 if pats:
1273 files = [f for f in ctx.manifest().keys() if match(f)] 1277 files = [f for f in ctx.manifest().keys() if match(f)]
1274 if not files: 1278 if not files:
1275 raise ErrorResponse( 1279 raise ErrorResponse(
1276 HTTP_NOT_FOUND, 'file(s) not found: %s' % file 1280 HTTP_NOT_FOUND, b'file(s) not found: %s' % file
1277 ) 1281 )
1278 1282
1279 mimetype, artype, extension, encoding = webutil.archivespecs[type_] 1283 mimetype, artype, extension, encoding = webutil.archivespecs[type_]
1280 1284
1281 web.res.headers['Content-Type'] = mimetype 1285 web.res.headers[b'Content-Type'] = mimetype
1282 web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % ( 1286 web.res.headers[b'Content-Disposition'] = b'attachment; filename=%s%s' % (
1283 name, 1287 name,
1284 extension, 1288 extension,
1285 ) 1289 )
1286 1290
1287 if encoding: 1291 if encoding:
1288 web.res.headers['Content-Encoding'] = encoding 1292 web.res.headers[b'Content-Encoding'] = encoding
1289 1293
1290 web.res.setbodywillwrite() 1294 web.res.setbodywillwrite()
1291 if list(web.res.sendresponse()): 1295 if list(web.res.sendresponse()):
1292 raise error.ProgrammingError( 1296 raise error.ProgrammingError(
1293 'sendresponse() should not emit data ' 'if writing later' 1297 b'sendresponse() should not emit data ' b'if writing later'
1294 ) 1298 )
1295 1299
1296 bodyfh = web.res.getbodyfile() 1300 bodyfh = web.res.getbodyfile()
1297 1301
1298 archival.archive( 1302 archival.archive(
1300 bodyfh, 1304 bodyfh,
1301 cnode, 1305 cnode,
1302 artype, 1306 artype,
1303 prefix=name, 1307 prefix=name,
1304 match=match, 1308 match=match,
1305 subrepos=web.configbool("web", "archivesubrepos"), 1309 subrepos=web.configbool(b"web", b"archivesubrepos"),
1306 ) 1310 )
1307 1311
1308 return [] 1312 return []
1309 1313
1310 1314
1311 @webcommand('static') 1315 @webcommand(b'static')
1312 def static(web): 1316 def static(web):
1313 fname = web.req.qsparams['file'] 1317 fname = web.req.qsparams[b'file']
1314 # a repo owner may set web.static in .hg/hgrc to get any file 1318 # a repo owner may set web.static in .hg/hgrc to get any file
1315 # readable by the user running the CGI script 1319 # readable by the user running the CGI script
1316 static = web.config("web", "static", untrusted=False) 1320 static = web.config(b"web", b"static", untrusted=False)
1317 if not static: 1321 if not static:
1318 tp = web.templatepath or templater.templatepaths() 1322 tp = web.templatepath or templater.templatepaths()
1319 if isinstance(tp, str): 1323 if isinstance(tp, str):
1320 tp = [tp] 1324 tp = [tp]
1321 static = [os.path.join(p, 'static') for p in tp] 1325 static = [os.path.join(p, b'static') for p in tp]
1322 1326
1323 staticfile(static, fname, web.res) 1327 staticfile(static, fname, web.res)
1324 return web.res.sendresponse() 1328 return web.res.sendresponse()
1325 1329
1326 1330
1327 @webcommand('graph') 1331 @webcommand(b'graph')
1328 def graph(web): 1332 def graph(web):
1329 """ 1333 """
1330 /graph[/{revision}] 1334 /graph[/{revision}]
1331 ------------------- 1335 -------------------
1332 1336
1346 JavaScript. By default it has the same value as ``revision``. 1350 JavaScript. By default it has the same value as ``revision``.
1347 1351
1348 This handler will render the ``graph`` template. 1352 This handler will render the ``graph`` template.
1349 """ 1353 """
1350 1354
1351 if 'node' in web.req.qsparams: 1355 if b'node' in web.req.qsparams:
1352 ctx = webutil.changectx(web.repo, web.req) 1356 ctx = webutil.changectx(web.repo, web.req)
1353 symrev = webutil.symrevorshortnode(web.req, ctx) 1357 symrev = webutil.symrevorshortnode(web.req, ctx)
1354 else: 1358 else:
1355 ctx = web.repo['tip'] 1359 ctx = web.repo[b'tip']
1356 symrev = 'tip' 1360 symrev = b'tip'
1357 rev = ctx.rev() 1361 rev = ctx.rev()
1358 1362
1359 bg_height = 39 1363 bg_height = 39
1360 revcount = web.maxshortchanges 1364 revcount = web.maxshortchanges
1361 if 'revcount' in web.req.qsparams: 1365 if b'revcount' in web.req.qsparams:
1362 try: 1366 try:
1363 revcount = int(web.req.qsparams.get('revcount', revcount)) 1367 revcount = int(web.req.qsparams.get(b'revcount', revcount))
1364 revcount = max(revcount, 1) 1368 revcount = max(revcount, 1)
1365 web.tmpl.defaults['sessionvars']['revcount'] = revcount 1369 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
1366 except ValueError: 1370 except ValueError:
1367 pass 1371 pass
1368 1372
1369 lessvars = copy.copy(web.tmpl.defaults['sessionvars']) 1373 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1370 lessvars['revcount'] = max(revcount // 2, 1) 1374 lessvars[b'revcount'] = max(revcount // 2, 1)
1371 morevars = copy.copy(web.tmpl.defaults['sessionvars']) 1375 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1372 morevars['revcount'] = revcount * 2 1376 morevars[b'revcount'] = revcount * 2
1373 1377
1374 graphtop = web.req.qsparams.get('graphtop', ctx.hex()) 1378 graphtop = web.req.qsparams.get(b'graphtop', ctx.hex())
1375 graphvars = copy.copy(web.tmpl.defaults['sessionvars']) 1379 graphvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1376 graphvars['graphtop'] = graphtop 1380 graphvars[b'graphtop'] = graphtop
1377 1381
1378 count = len(web.repo) 1382 count = len(web.repo)
1379 pos = rev 1383 pos = rev
1380 1384
1381 uprev = min(max(0, count - 1), rev + revcount) 1385 uprev = min(max(0, count - 1), rev + revcount)
1424 return tree 1428 return tree
1425 1429
1426 def jsdata(context): 1430 def jsdata(context):
1427 for (id, type, ctx, vtx, edges) in fulltree(): 1431 for (id, type, ctx, vtx, edges) in fulltree():
1428 yield { 1432 yield {
1429 'node': pycompat.bytestr(ctx), 1433 b'node': pycompat.bytestr(ctx),
1430 'graphnode': webutil.getgraphnode(web.repo, ctx), 1434 b'graphnode': webutil.getgraphnode(web.repo, ctx),
1431 'vertex': vtx, 1435 b'vertex': vtx,
1432 'edges': edges, 1436 b'edges': edges,
1433 } 1437 }
1434 1438
1435 def nodes(context): 1439 def nodes(context):
1436 parity = paritygen(web.stripecount) 1440 parity = paritygen(web.stripecount)
1437 for row, (id, type, ctx, vtx, edges) in enumerate(tree): 1441 for row, (id, type, ctx, vtx, edges) in enumerate(tree):
1438 entry = webutil.commonentry(web.repo, ctx) 1442 entry = webutil.commonentry(web.repo, ctx)
1439 edgedata = [ 1443 edgedata = [
1440 { 1444 {
1441 'col': edge[0], 1445 b'col': edge[0],
1442 'nextcol': edge[1], 1446 b'nextcol': edge[1],
1443 'color': (edge[2] - 1) % 6 + 1, 1447 b'color': (edge[2] - 1) % 6 + 1,
1444 'width': edge[3], 1448 b'width': edge[3],
1445 'bcolor': edge[4], 1449 b'bcolor': edge[4],
1446 } 1450 }
1447 for edge in edges 1451 for edge in edges
1448 ] 1452 ]
1449 1453
1450 entry.update( 1454 entry.update(
1451 { 1455 {
1452 'col': vtx[0], 1456 b'col': vtx[0],
1453 'color': (vtx[1] - 1) % 6 + 1, 1457 b'color': (vtx[1] - 1) % 6 + 1,
1454 'parity': next(parity), 1458 b'parity': next(parity),
1455 'edges': templateutil.mappinglist(edgedata), 1459 b'edges': templateutil.mappinglist(edgedata),
1456 'row': row, 1460 b'row': row,
1457 'nextrow': row + 1, 1461 b'nextrow': row + 1,
1458 } 1462 }
1459 ) 1463 )
1460 1464
1461 yield entry 1465 yield entry
1462 1466
1463 rows = len(tree) 1467 rows = len(tree)
1464 1468
1465 return web.sendtemplate( 1469 return web.sendtemplate(
1466 'graph', 1470 b'graph',
1467 rev=rev, 1471 rev=rev,
1468 symrev=symrev, 1472 symrev=symrev,
1469 revcount=revcount, 1473 revcount=revcount,
1470 uprev=uprev, 1474 uprev=uprev,
1471 lessvars=lessvars, 1475 lessvars=lessvars,
1477 changesets=count, 1481 changesets=count,
1478 nextentry=templateutil.mappinglist(nextentry), 1482 nextentry=templateutil.mappinglist(nextentry),
1479 jsdata=templateutil.mappinggenerator(jsdata), 1483 jsdata=templateutil.mappinggenerator(jsdata),
1480 nodes=templateutil.mappinggenerator(nodes), 1484 nodes=templateutil.mappinggenerator(nodes),
1481 node=ctx.hex(), 1485 node=ctx.hex(),
1482 archives=web.archivelist('tip'), 1486 archives=web.archivelist(b'tip'),
1483 changenav=changenav, 1487 changenav=changenav,
1484 ) 1488 )
1485 1489
1486 1490
1487 def _getdoc(e): 1491 def _getdoc(e):
1488 doc = e[0].__doc__ 1492 doc = e[0].__doc__
1489 if doc: 1493 if doc:
1490 doc = _(doc).partition('\n')[0] 1494 doc = _(doc).partition(b'\n')[0]
1491 else: 1495 else:
1492 doc = _('(no help text available)') 1496 doc = _(b'(no help text available)')
1493 return doc 1497 return doc
1494 1498
1495 1499
1496 @webcommand('help') 1500 @webcommand(b'help')
1497 def help(web): 1501 def help(web):
1498 """ 1502 """
1499 /help[/{topic}] 1503 /help[/{topic}]
1500 --------------- 1504 ---------------
1501 1505
1508 The ``help`` template will be rendered when requesting help for a topic. 1512 The ``help`` template will be rendered when requesting help for a topic.
1509 ``helptopics`` will be rendered for the index of help topics. 1513 ``helptopics`` will be rendered for the index of help topics.
1510 """ 1514 """
1511 from .. import commands, help as helpmod # avoid cycle 1515 from .. import commands, help as helpmod # avoid cycle
1512 1516
1513 topicname = web.req.qsparams.get('node') 1517 topicname = web.req.qsparams.get(b'node')
1514 if not topicname: 1518 if not topicname:
1515 1519
1516 def topics(context): 1520 def topics(context):
1517 for h in helpmod.helptable: 1521 for h in helpmod.helptable:
1518 entries, summary, _doc = h[0:3] 1522 entries, summary, _doc = h[0:3]
1519 yield {'topic': entries[0], 'summary': summary} 1523 yield {b'topic': entries[0], b'summary': summary}
1520 1524
1521 early, other = [], [] 1525 early, other = [], []
1522 primary = lambda s: s.partition('|')[0] 1526 primary = lambda s: s.partition(b'|')[0]
1523 for c, e in commands.table.iteritems(): 1527 for c, e in commands.table.iteritems():
1524 doc = _getdoc(e) 1528 doc = _getdoc(e)
1525 if 'DEPRECATED' in doc or c.startswith('debug'): 1529 if b'DEPRECATED' in doc or c.startswith(b'debug'):
1526 continue 1530 continue
1527 cmd = primary(c) 1531 cmd = primary(c)
1528 if getattr(e[0], 'helpbasic', False): 1532 if getattr(e[0], 'helpbasic', False):
1529 early.append((cmd, doc)) 1533 early.append((cmd, doc))
1530 else: 1534 else:
1533 early.sort() 1537 early.sort()
1534 other.sort() 1538 other.sort()
1535 1539
1536 def earlycommands(context): 1540 def earlycommands(context):
1537 for c, doc in early: 1541 for c, doc in early:
1538 yield {'topic': c, 'summary': doc} 1542 yield {b'topic': c, b'summary': doc}
1539 1543
1540 def othercommands(context): 1544 def othercommands(context):
1541 for c, doc in other: 1545 for c, doc in other:
1542 yield {'topic': c, 'summary': doc} 1546 yield {b'topic': c, b'summary': doc}
1543 1547
1544 return web.sendtemplate( 1548 return web.sendtemplate(
1545 'helptopics', 1549 b'helptopics',
1546 topics=templateutil.mappinggenerator(topics), 1550 topics=templateutil.mappinggenerator(topics),
1547 earlycommands=templateutil.mappinggenerator(earlycommands), 1551 earlycommands=templateutil.mappinggenerator(earlycommands),
1548 othercommands=templateutil.mappinggenerator(othercommands), 1552 othercommands=templateutil.mappinggenerator(othercommands),
1549 title='Index', 1553 title=b'Index',
1550 ) 1554 )
1551 1555
1552 # Render an index of sub-topics. 1556 # Render an index of sub-topics.
1553 if topicname in helpmod.subtopics: 1557 if topicname in helpmod.subtopics:
1554 topics = [] 1558 topics = []
1555 for entries, summary, _doc in helpmod.subtopics[topicname]: 1559 for entries, summary, _doc in helpmod.subtopics[topicname]:
1556 topics.append( 1560 topics.append(
1557 { 1561 {
1558 'topic': '%s.%s' % (topicname, entries[0]), 1562 b'topic': b'%s.%s' % (topicname, entries[0]),
1559 'basename': entries[0], 1563 b'basename': entries[0],
1560 'summary': summary, 1564 b'summary': summary,
1561 } 1565 }
1562 ) 1566 )
1563 1567
1564 return web.sendtemplate( 1568 return web.sendtemplate(
1565 'helptopics', 1569 b'helptopics',
1566 topics=templateutil.mappinglist(topics), 1570 topics=templateutil.mappinglist(topics),
1567 title=topicname, 1571 title=topicname,
1568 subindex=True, 1572 subindex=True,
1569 ) 1573 )
1570 1574
1571 u = webutil.wsgiui.load() 1575 u = webutil.wsgiui.load()
1572 u.verbose = True 1576 u.verbose = True
1573 1577
1574 # Render a page from a sub-topic. 1578 # Render a page from a sub-topic.
1575 if '.' in topicname: 1579 if b'.' in topicname:
1576 # TODO implement support for rendering sections, like 1580 # TODO implement support for rendering sections, like
1577 # `hg help` works. 1581 # `hg help` works.
1578 topic, subtopic = topicname.split('.', 1) 1582 topic, subtopic = topicname.split(b'.', 1)
1579 if topic not in helpmod.subtopics: 1583 if topic not in helpmod.subtopics:
1580 raise ErrorResponse(HTTP_NOT_FOUND) 1584 raise ErrorResponse(HTTP_NOT_FOUND)
1581 else: 1585 else:
1582 topic = topicname 1586 topic = topicname
1583 subtopic = None 1587 subtopic = None
1585 try: 1589 try:
1586 doc = helpmod.help_(u, commands, topic, subtopic=subtopic) 1590 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1587 except error.Abort: 1591 except error.Abort:
1588 raise ErrorResponse(HTTP_NOT_FOUND) 1592 raise ErrorResponse(HTTP_NOT_FOUND)
1589 1593
1590 return web.sendtemplate('help', topic=topicname, doc=doc) 1594 return web.sendtemplate(b'help', topic=topicname, doc=doc)
1591 1595
1592 1596
1593 # tell hggettext to extract docstrings from these functions: 1597 # tell hggettext to extract docstrings from these functions:
1594 i18nfunctions = commands.values() 1598 i18nfunctions = commands.values()