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