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