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