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