##// END OF EJS Templates
hgweb: convert _siblings to a factory function of mappinggenerator...
Yuya Nishihara -
r37718:495fbeae default
parent child Browse files
Show More
@@ -1,741 +1,735 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 def _ctxsgen(ctxs):
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 class _siblings(object):
201 def __init__(self, siblings=None, hiderev=None):
202 if siblings is None:
203 siblings = []
204 self.siblings = [s for s in siblings if s.node() != nullid]
205 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
206 self.siblings = []
207
208 def __iter__(self):
209 return _ctxsgen(self.siblings)
210
211 def __len__(self):
212 return len(self.siblings)
200 def _siblings(siblings=None, hiderev=None):
201 if siblings is None:
202 siblings = []
203 siblings = [s for s in siblings if s.node() != nullid]
204 if len(siblings) == 1 and siblings[0].rev() == hiderev:
205 siblings = []
206 return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
213 207
214 208 def difffeatureopts(req, ui, section):
215 209 diffopts = patch.difffeatureopts(ui, untrusted=True,
216 210 section=section, whitespace=True)
217 211
218 212 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
219 213 v = req.qsparams.get(k)
220 214 if v is not None:
221 215 v = stringutil.parsebool(v)
222 216 setattr(diffopts, k, v if v is not None else True)
223 217
224 218 return diffopts
225 219
226 220 def annotate(req, fctx, ui):
227 221 diffopts = difffeatureopts(req, ui, 'annotate')
228 222 return fctx.annotate(follow=True, diffopts=diffopts)
229 223
230 224 def parents(ctx, hide=None):
231 225 if isinstance(ctx, context.basefilectx):
232 226 introrev = ctx.introrev()
233 227 if ctx.changectx().rev() != introrev:
234 228 return _siblings([ctx.repo()[introrev]], hide)
235 229 return _siblings(ctx.parents(), hide)
236 230
237 231 def children(ctx, hide=None):
238 232 return _siblings(ctx.children(), hide)
239 233
240 234 def renamelink(fctx):
241 235 r = fctx.renamed()
242 236 if r:
243 237 return [{'file': r[0], 'node': hex(r[1])}]
244 238 return []
245 239
246 240 def nodetagsdict(repo, node):
247 241 return [{"name": i} for i in repo.nodetags(node)]
248 242
249 243 def nodebookmarksdict(repo, node):
250 244 return [{"name": i} for i in repo.nodebookmarks(node)]
251 245
252 246 def nodebranchdict(repo, ctx):
253 247 branches = []
254 248 branch = ctx.branch()
255 249 # If this is an empty repo, ctx.node() == nullid,
256 250 # ctx.branch() == 'default'.
257 251 try:
258 252 branchnode = repo.branchtip(branch)
259 253 except error.RepoLookupError:
260 254 branchnode = None
261 255 if branchnode == ctx.node():
262 256 branches.append({"name": branch})
263 257 return branches
264 258
265 259 def nodeinbranch(repo, ctx):
266 260 branches = []
267 261 branch = ctx.branch()
268 262 try:
269 263 branchnode = repo.branchtip(branch)
270 264 except error.RepoLookupError:
271 265 branchnode = None
272 266 if branch != 'default' and branchnode != ctx.node():
273 267 branches.append({"name": branch})
274 268 return branches
275 269
276 270 def nodebranchnodefault(ctx):
277 271 branches = []
278 272 branch = ctx.branch()
279 273 if branch != 'default':
280 274 branches.append({"name": branch})
281 275 return branches
282 276
283 277 def showtag(repo, tmpl, t1, node=nullid, **args):
284 278 args = pycompat.byteskwargs(args)
285 279 for t in repo.nodetags(node):
286 280 lm = args.copy()
287 281 lm['tag'] = t
288 282 yield tmpl.generate(t1, lm)
289 283
290 284 def showbookmark(repo, tmpl, t1, node=nullid, **args):
291 285 args = pycompat.byteskwargs(args)
292 286 for t in repo.nodebookmarks(node):
293 287 lm = args.copy()
294 288 lm['bookmark'] = t
295 289 yield tmpl.generate(t1, lm)
296 290
297 291 def branchentries(repo, stripecount, limit=0):
298 292 tips = []
299 293 heads = repo.heads()
300 294 parity = paritygen(stripecount)
301 295 sortkey = lambda item: (not item[1], item[0].rev())
302 296
303 297 def entries(**map):
304 298 count = 0
305 299 if not tips:
306 300 for tag, hs, tip, closed in repo.branchmap().iterbranches():
307 301 tips.append((repo[tip], closed))
308 302 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
309 303 if limit > 0 and count >= limit:
310 304 return
311 305 count += 1
312 306 if closed:
313 307 status = 'closed'
314 308 elif ctx.node() not in heads:
315 309 status = 'inactive'
316 310 else:
317 311 status = 'open'
318 312 yield {
319 313 'parity': next(parity),
320 314 'branch': ctx.branch(),
321 315 'status': status,
322 316 'node': ctx.hex(),
323 317 'date': ctx.date()
324 318 }
325 319
326 320 return entries
327 321
328 322 def cleanpath(repo, path):
329 323 path = path.lstrip('/')
330 324 return pathutil.canonpath(repo.root, '', path)
331 325
332 326 def changectx(repo, req):
333 327 changeid = "tip"
334 328 if 'node' in req.qsparams:
335 329 changeid = req.qsparams['node']
336 330 ipos = changeid.find(':')
337 331 if ipos != -1:
338 332 changeid = changeid[(ipos + 1):]
339 333
340 334 return scmutil.revsymbol(repo, changeid)
341 335
342 336 def basechangectx(repo, req):
343 337 if 'node' in req.qsparams:
344 338 changeid = req.qsparams['node']
345 339 ipos = changeid.find(':')
346 340 if ipos != -1:
347 341 changeid = changeid[:ipos]
348 342 return scmutil.revsymbol(repo, changeid)
349 343
350 344 return None
351 345
352 346 def filectx(repo, req):
353 347 if 'file' not in req.qsparams:
354 348 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
355 349 path = cleanpath(repo, req.qsparams['file'])
356 350 if 'node' in req.qsparams:
357 351 changeid = req.qsparams['node']
358 352 elif 'filenode' in req.qsparams:
359 353 changeid = req.qsparams['filenode']
360 354 else:
361 355 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
362 356 try:
363 357 fctx = scmutil.revsymbol(repo, changeid)[path]
364 358 except error.RepoError:
365 359 fctx = repo.filectx(path, fileid=changeid)
366 360
367 361 return fctx
368 362
369 363 def linerange(req):
370 364 linerange = req.qsparams.getall('linerange')
371 365 if not linerange:
372 366 return None
373 367 if len(linerange) > 1:
374 368 raise ErrorResponse(HTTP_BAD_REQUEST,
375 369 'redundant linerange parameter')
376 370 try:
377 371 fromline, toline = map(int, linerange[0].split(':', 1))
378 372 except ValueError:
379 373 raise ErrorResponse(HTTP_BAD_REQUEST,
380 374 'invalid linerange parameter')
381 375 try:
382 376 return util.processlinerange(fromline, toline)
383 377 except error.ParseError as exc:
384 378 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
385 379
386 380 def formatlinerange(fromline, toline):
387 381 return '%d:%d' % (fromline + 1, toline)
388 382
389 383 def succsandmarkers(context, mapping):
390 384 repo = context.resource(mapping, 'repo')
391 385 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
392 386 for item in itemmappings.tovalue(context, mapping):
393 387 item['successors'] = _siblings(repo[successor]
394 388 for successor in item['successors'])
395 389 yield item
396 390
397 391 # teach templater succsandmarkers is switched to (context, mapping) API
398 392 succsandmarkers._requires = {'repo', 'ctx'}
399 393
400 394 def whyunstable(context, mapping):
401 395 repo = context.resource(mapping, 'repo')
402 396 ctx = context.resource(mapping, 'ctx')
403 397
404 398 entries = obsutil.whyunstable(repo, ctx)
405 399 for entry in entries:
406 400 if entry.get('divergentnodes'):
407 401 entry['divergentnodes'] = _siblings(entry['divergentnodes'])
408 402 yield entry
409 403
410 404 whyunstable._requires = {'repo', 'ctx'}
411 405
412 406 def commonentry(repo, ctx):
413 407 node = ctx.node()
414 408 return {
415 409 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
416 410 # filectx, but I'm not pretty sure if that would always work because
417 411 # fctx.parents() != fctx.changectx.parents() for example.
418 412 'ctx': ctx,
419 413 'rev': ctx.rev(),
420 414 'node': hex(node),
421 415 'author': ctx.user(),
422 416 'desc': ctx.description(),
423 417 'date': ctx.date(),
424 418 'extra': ctx.extra(),
425 419 'phase': ctx.phasestr(),
426 420 'obsolete': ctx.obsolete(),
427 421 'succsandmarkers': succsandmarkers,
428 422 'instabilities': [{"instability": i} for i in ctx.instabilities()],
429 423 'whyunstable': whyunstable,
430 424 'branch': nodebranchnodefault(ctx),
431 425 'inbranch': nodeinbranch(repo, ctx),
432 426 'branches': nodebranchdict(repo, ctx),
433 427 'tags': nodetagsdict(repo, node),
434 428 'bookmarks': nodebookmarksdict(repo, node),
435 429 'parent': lambda **x: parents(ctx),
436 430 'child': lambda **x: children(ctx),
437 431 }
438 432
439 433 def changelistentry(web, ctx):
440 434 '''Obtain a dictionary to be used for entries in a changelist.
441 435
442 436 This function is called when producing items for the "entries" list passed
443 437 to the "shortlog" and "changelog" templates.
444 438 '''
445 439 repo = web.repo
446 440 rev = ctx.rev()
447 441 n = ctx.node()
448 442 showtags = showtag(repo, web.tmpl, 'changelogtag', n)
449 443 files = listfilediffs(web.tmpl, ctx.files(), n, web.maxfiles)
450 444
451 445 entry = commonentry(repo, ctx)
452 446 entry.update(
453 447 allparents=lambda **x: parents(ctx),
454 448 parent=lambda **x: parents(ctx, rev - 1),
455 449 child=lambda **x: children(ctx, rev + 1),
456 450 changelogtag=showtags,
457 451 files=files,
458 452 )
459 453 return entry
460 454
461 455 def symrevorshortnode(req, ctx):
462 456 if 'node' in req.qsparams:
463 457 return templatefilters.revescape(req.qsparams['node'])
464 458 else:
465 459 return short(ctx.node())
466 460
467 461 def changesetentry(web, ctx):
468 462 '''Obtain a dictionary to be used to render the "changeset" template.'''
469 463
470 464 showtags = showtag(web.repo, web.tmpl, 'changesettag', ctx.node())
471 465 showbookmarks = showbookmark(web.repo, web.tmpl, 'changesetbookmark',
472 466 ctx.node())
473 467 showbranch = nodebranchnodefault(ctx)
474 468
475 469 files = []
476 470 parity = paritygen(web.stripecount)
477 471 for blockno, f in enumerate(ctx.files()):
478 472 template = 'filenodelink' if f in ctx else 'filenolink'
479 473 files.append(web.tmpl.generate(template, {
480 474 'node': ctx.hex(),
481 475 'file': f,
482 476 'blockno': blockno + 1,
483 477 'parity': next(parity),
484 478 }))
485 479
486 480 basectx = basechangectx(web.repo, web.req)
487 481 if basectx is None:
488 482 basectx = ctx.p1()
489 483
490 484 style = web.config('web', 'style')
491 485 if 'style' in web.req.qsparams:
492 486 style = web.req.qsparams['style']
493 487
494 488 diff = diffs(web, ctx, basectx, None, style)
495 489
496 490 parity = paritygen(web.stripecount)
497 491 diffstatsgen = diffstatgen(ctx, basectx)
498 492 diffstats = diffstat(web.tmpl, ctx, diffstatsgen, parity)
499 493
500 494 return dict(
501 495 diff=diff,
502 496 symrev=symrevorshortnode(web.req, ctx),
503 497 basenode=basectx.hex(),
504 498 changesettag=showtags,
505 499 changesetbookmark=showbookmarks,
506 500 changesetbranch=showbranch,
507 501 files=files,
508 502 diffsummary=lambda **x: diffsummary(diffstatsgen),
509 503 diffstat=diffstats,
510 504 archives=web.archivelist(ctx.hex()),
511 505 **pycompat.strkwargs(commonentry(web.repo, ctx)))
512 506
513 507 def listfilediffs(tmpl, files, node, max):
514 508 for f in files[:max]:
515 509 yield tmpl.generate('filedifflink', {'node': hex(node), 'file': f})
516 510 if len(files) > max:
517 511 yield tmpl.generate('fileellipses', {})
518 512
519 513 def diffs(web, ctx, basectx, files, style, linerange=None,
520 514 lineidprefix=''):
521 515
522 516 def prettyprintlines(lines, blockno):
523 517 for lineno, l in enumerate(lines, 1):
524 518 difflineno = "%d.%d" % (blockno, lineno)
525 519 if l.startswith('+'):
526 520 ltype = "difflineplus"
527 521 elif l.startswith('-'):
528 522 ltype = "difflineminus"
529 523 elif l.startswith('@'):
530 524 ltype = "difflineat"
531 525 else:
532 526 ltype = "diffline"
533 527 yield web.tmpl.generate(ltype, {
534 528 'line': l,
535 529 'lineno': lineno,
536 530 'lineid': lineidprefix + "l%s" % difflineno,
537 531 'linenumber': "% 8s" % difflineno,
538 532 })
539 533
540 534 repo = web.repo
541 535 if files:
542 536 m = match.exact(repo.root, repo.getcwd(), files)
543 537 else:
544 538 m = match.always(repo.root, repo.getcwd())
545 539
546 540 diffopts = patch.diffopts(repo.ui, untrusted=True)
547 541 node1 = basectx.node()
548 542 node2 = ctx.node()
549 543 parity = paritygen(web.stripecount)
550 544
551 545 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
552 546 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
553 547 if style != 'raw':
554 548 header = header[1:]
555 549 lines = [h + '\n' for h in header]
556 550 for hunkrange, hunklines in hunks:
557 551 if linerange is not None and hunkrange is not None:
558 552 s1, l1, s2, l2 = hunkrange
559 553 if not mdiff.hunkinrange((s2, l2), linerange):
560 554 continue
561 555 lines.extend(hunklines)
562 556 if lines:
563 557 yield web.tmpl.generate('diffblock', {
564 558 'parity': next(parity),
565 559 'blockno': blockno,
566 560 'lines': prettyprintlines(lines, blockno),
567 561 })
568 562
569 563 def compare(tmpl, context, leftlines, rightlines):
570 564 '''Generator function that provides side-by-side comparison data.'''
571 565
572 566 def compline(type, leftlineno, leftline, rightlineno, rightline):
573 567 lineid = leftlineno and ("l%d" % leftlineno) or ''
574 568 lineid += rightlineno and ("r%d" % rightlineno) or ''
575 569 llno = '%d' % leftlineno if leftlineno else ''
576 570 rlno = '%d' % rightlineno if rightlineno else ''
577 571 return tmpl.generate('comparisonline', {
578 572 'type': type,
579 573 'lineid': lineid,
580 574 'leftlineno': leftlineno,
581 575 'leftlinenumber': "% 6s" % llno,
582 576 'leftline': leftline or '',
583 577 'rightlineno': rightlineno,
584 578 'rightlinenumber': "% 6s" % rlno,
585 579 'rightline': rightline or '',
586 580 })
587 581
588 582 def getblock(opcodes):
589 583 for type, llo, lhi, rlo, rhi in opcodes:
590 584 len1 = lhi - llo
591 585 len2 = rhi - rlo
592 586 count = min(len1, len2)
593 587 for i in xrange(count):
594 588 yield compline(type=type,
595 589 leftlineno=llo + i + 1,
596 590 leftline=leftlines[llo + i],
597 591 rightlineno=rlo + i + 1,
598 592 rightline=rightlines[rlo + i])
599 593 if len1 > len2:
600 594 for i in xrange(llo + count, lhi):
601 595 yield compline(type=type,
602 596 leftlineno=i + 1,
603 597 leftline=leftlines[i],
604 598 rightlineno=None,
605 599 rightline=None)
606 600 elif len2 > len1:
607 601 for i in xrange(rlo + count, rhi):
608 602 yield compline(type=type,
609 603 leftlineno=None,
610 604 leftline=None,
611 605 rightlineno=i + 1,
612 606 rightline=rightlines[i])
613 607
614 608 s = difflib.SequenceMatcher(None, leftlines, rightlines)
615 609 if context < 0:
616 610 yield tmpl.generate('comparisonblock',
617 611 {'lines': getblock(s.get_opcodes())})
618 612 else:
619 613 for oc in s.get_grouped_opcodes(n=context):
620 614 yield tmpl.generate('comparisonblock', {'lines': getblock(oc)})
621 615
622 616 def diffstatgen(ctx, basectx):
623 617 '''Generator function that provides the diffstat data.'''
624 618
625 619 stats = patch.diffstatdata(
626 620 util.iterlines(ctx.diff(basectx, noprefix=False)))
627 621 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
628 622 while True:
629 623 yield stats, maxname, maxtotal, addtotal, removetotal, binary
630 624
631 625 def diffsummary(statgen):
632 626 '''Return a short summary of the diff.'''
633 627
634 628 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
635 629 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
636 630 len(stats), addtotal, removetotal)
637 631
638 632 def diffstat(tmpl, ctx, statgen, parity):
639 633 '''Return a diffstat template for each file in the diff.'''
640 634
641 635 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
642 636 files = ctx.files()
643 637
644 638 def pct(i):
645 639 if maxtotal == 0:
646 640 return 0
647 641 return (float(i) / maxtotal) * 100
648 642
649 643 fileno = 0
650 644 for filename, adds, removes, isbinary in stats:
651 645 template = 'diffstatlink' if filename in files else 'diffstatnolink'
652 646 total = adds + removes
653 647 fileno += 1
654 648 yield tmpl.generate(template, {
655 649 'node': ctx.hex(),
656 650 'file': filename,
657 651 'fileno': fileno,
658 652 'total': total,
659 653 'addpct': pct(adds),
660 654 'removepct': pct(removes),
661 655 'parity': next(parity),
662 656 })
663 657
664 658 class sessionvars(templateutil.wrapped):
665 659 def __init__(self, vars, start='?'):
666 660 self._start = start
667 661 self._vars = vars
668 662
669 663 def __getitem__(self, key):
670 664 return self._vars[key]
671 665
672 666 def __setitem__(self, key, value):
673 667 self._vars[key] = value
674 668
675 669 def __copy__(self):
676 670 return sessionvars(copy.copy(self._vars), self._start)
677 671
678 672 def itermaps(self, context):
679 673 separator = self._start
680 674 for key, value in sorted(self._vars.iteritems()):
681 675 yield {'name': key,
682 676 'value': pycompat.bytestr(value),
683 677 'separator': separator,
684 678 }
685 679 separator = '&'
686 680
687 681 def join(self, context, mapping, sep):
688 682 # could be '{separator}{name}={value|urlescape}'
689 683 raise error.ParseError(_('not displayable without template'))
690 684
691 685 def show(self, context, mapping):
692 686 return self.join(context, '')
693 687
694 688 def tovalue(self, context, mapping):
695 689 return self._vars
696 690
697 691 class wsgiui(uimod.ui):
698 692 # default termwidth breaks under mod_wsgi
699 693 def termwidth(self):
700 694 return 80
701 695
702 696 def getwebsubs(repo):
703 697 websubtable = []
704 698 websubdefs = repo.ui.configitems('websub')
705 699 # we must maintain interhg backwards compatibility
706 700 websubdefs += repo.ui.configitems('interhg')
707 701 for key, pattern in websubdefs:
708 702 # grab the delimiter from the character after the "s"
709 703 unesc = pattern[1:2]
710 704 delim = re.escape(unesc)
711 705
712 706 # identify portions of the pattern, taking care to avoid escaped
713 707 # delimiters. the replace format and flags are optional, but
714 708 # delimiters are required.
715 709 match = re.match(
716 710 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
717 711 % (delim, delim, delim), pattern)
718 712 if not match:
719 713 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
720 714 % (key, pattern))
721 715 continue
722 716
723 717 # we need to unescape the delimiter for regexp and format
724 718 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
725 719 regexp = delim_re.sub(unesc, match.group(1))
726 720 format = delim_re.sub(unesc, match.group(2))
727 721
728 722 # the pattern allows for 6 regexp flags, so set them if necessary
729 723 flagin = match.group(3)
730 724 flags = 0
731 725 if flagin:
732 726 for flag in flagin.upper():
733 727 flags |= re.__dict__[flag]
734 728
735 729 try:
736 730 regexp = re.compile(regexp, flags)
737 731 websubtable.append((regexp, format))
738 732 except re.error:
739 733 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
740 734 % (key, regexp))
741 735 return websubtable
General Comments 0
You need to be logged in to leave comments. Login now