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