##// END OF EJS Templates
hgweb: add HTML elements to control whitespace settings for annotate...
Gregory Szorc -
r34392:6797f1fb default
parent child Browse files
Show More
@@ -1,1394 +1,1398 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import cgi
11 11 import copy
12 12 import mimetypes
13 13 import os
14 14 import re
15 15
16 16 from ..i18n import _
17 17 from ..node import hex, short
18 18
19 19 from .common import (
20 20 ErrorResponse,
21 21 HTTP_FORBIDDEN,
22 22 HTTP_NOT_FOUND,
23 23 HTTP_OK,
24 24 get_contact,
25 25 paritygen,
26 26 staticfile,
27 27 )
28 28
29 29 from .. import (
30 30 archival,
31 31 dagop,
32 32 encoding,
33 33 error,
34 34 graphmod,
35 35 revset,
36 36 revsetlang,
37 37 scmutil,
38 38 smartset,
39 39 templatefilters,
40 40 templater,
41 41 util,
42 42 )
43 43
44 44 from . import (
45 45 webutil,
46 46 )
47 47
48 48 __all__ = []
49 49 commands = {}
50 50
51 51 class webcommand(object):
52 52 """Decorator used to register a web command handler.
53 53
54 54 The decorator takes as its positional arguments the name/path the
55 55 command should be accessible under.
56 56
57 57 Usage:
58 58
59 59 @webcommand('mycommand')
60 60 def mycommand(web, req, tmpl):
61 61 pass
62 62 """
63 63
64 64 def __init__(self, name):
65 65 self.name = name
66 66
67 67 def __call__(self, func):
68 68 __all__.append(self.name)
69 69 commands[self.name] = func
70 70 return func
71 71
72 72 @webcommand('log')
73 73 def log(web, req, tmpl):
74 74 """
75 75 /log[/{revision}[/{path}]]
76 76 --------------------------
77 77
78 78 Show repository or file history.
79 79
80 80 For URLs of the form ``/log/{revision}``, a list of changesets starting at
81 81 the specified changeset identifier is shown. If ``{revision}`` is not
82 82 defined, the default is ``tip``. This form is equivalent to the
83 83 ``changelog`` handler.
84 84
85 85 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
86 86 file will be shown. This form is equivalent to the ``filelog`` handler.
87 87 """
88 88
89 89 if 'file' in req.form and req.form['file'][0]:
90 90 return filelog(web, req, tmpl)
91 91 else:
92 92 return changelog(web, req, tmpl)
93 93
94 94 @webcommand('rawfile')
95 95 def rawfile(web, req, tmpl):
96 96 guessmime = web.configbool('web', 'guessmime', False)
97 97
98 98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
99 99 if not path:
100 100 content = manifest(web, req, tmpl)
101 101 req.respond(HTTP_OK, web.ctype)
102 102 return content
103 103
104 104 try:
105 105 fctx = webutil.filectx(web.repo, req)
106 106 except error.LookupError as inst:
107 107 try:
108 108 content = manifest(web, req, tmpl)
109 109 req.respond(HTTP_OK, web.ctype)
110 110 return content
111 111 except ErrorResponse:
112 112 raise inst
113 113
114 114 path = fctx.path()
115 115 text = fctx.data()
116 116 mt = 'application/binary'
117 117 if guessmime:
118 118 mt = mimetypes.guess_type(path)[0]
119 119 if mt is None:
120 120 if util.binary(text):
121 121 mt = 'application/binary'
122 122 else:
123 123 mt = 'text/plain'
124 124 if mt.startswith('text/'):
125 125 mt += '; charset="%s"' % encoding.encoding
126 126
127 127 req.respond(HTTP_OK, mt, path, body=text)
128 128 return []
129 129
130 130 def _filerevision(web, req, tmpl, fctx):
131 131 f = fctx.path()
132 132 text = fctx.data()
133 133 parity = paritygen(web.stripecount)
134 134 ishead = fctx.filerev() in fctx.filelog().headrevs()
135 135
136 136 if util.binary(text):
137 137 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
138 138 text = '(binary:%s)' % mt
139 139
140 140 def lines():
141 141 for lineno, t in enumerate(text.splitlines(True)):
142 142 yield {"line": t,
143 143 "lineid": "l%d" % (lineno + 1),
144 144 "linenumber": "% 6d" % (lineno + 1),
145 145 "parity": next(parity)}
146 146
147 147 return tmpl("filerevision",
148 148 file=f,
149 149 path=webutil.up(f),
150 150 text=lines(),
151 151 symrev=webutil.symrevorshortnode(req, fctx),
152 152 rename=webutil.renamelink(fctx),
153 153 permissions=fctx.manifest().flags(f),
154 154 ishead=int(ishead),
155 155 **webutil.commonentry(web.repo, fctx))
156 156
157 157 @webcommand('file')
158 158 def file(web, req, tmpl):
159 159 """
160 160 /file/{revision}[/{path}]
161 161 -------------------------
162 162
163 163 Show information about a directory or file in the repository.
164 164
165 165 Info about the ``path`` given as a URL parameter will be rendered.
166 166
167 167 If ``path`` is a directory, information about the entries in that
168 168 directory will be rendered. This form is equivalent to the ``manifest``
169 169 handler.
170 170
171 171 If ``path`` is a file, information about that file will be shown via
172 172 the ``filerevision`` template.
173 173
174 174 If ``path`` is not defined, information about the root directory will
175 175 be rendered.
176 176 """
177 177 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
178 178 if not path:
179 179 return manifest(web, req, tmpl)
180 180 try:
181 181 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
182 182 except error.LookupError as inst:
183 183 try:
184 184 return manifest(web, req, tmpl)
185 185 except ErrorResponse:
186 186 raise inst
187 187
188 188 def _search(web, req, tmpl):
189 189 MODE_REVISION = 'rev'
190 190 MODE_KEYWORD = 'keyword'
191 191 MODE_REVSET = 'revset'
192 192
193 193 def revsearch(ctx):
194 194 yield ctx
195 195
196 196 def keywordsearch(query):
197 197 lower = encoding.lower
198 198 qw = lower(query).split()
199 199
200 200 def revgen():
201 201 cl = web.repo.changelog
202 202 for i in xrange(len(web.repo) - 1, 0, -100):
203 203 l = []
204 204 for j in cl.revs(max(0, i - 99), i):
205 205 ctx = web.repo[j]
206 206 l.append(ctx)
207 207 l.reverse()
208 208 for e in l:
209 209 yield e
210 210
211 211 for ctx in revgen():
212 212 miss = 0
213 213 for q in qw:
214 214 if not (q in lower(ctx.user()) or
215 215 q in lower(ctx.description()) or
216 216 q in lower(" ".join(ctx.files()))):
217 217 miss = 1
218 218 break
219 219 if miss:
220 220 continue
221 221
222 222 yield ctx
223 223
224 224 def revsetsearch(revs):
225 225 for r in revs:
226 226 yield web.repo[r]
227 227
228 228 searchfuncs = {
229 229 MODE_REVISION: (revsearch, 'exact revision search'),
230 230 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
231 231 MODE_REVSET: (revsetsearch, 'revset expression search'),
232 232 }
233 233
234 234 def getsearchmode(query):
235 235 try:
236 236 ctx = web.repo[query]
237 237 except (error.RepoError, error.LookupError):
238 238 # query is not an exact revision pointer, need to
239 239 # decide if it's a revset expression or keywords
240 240 pass
241 241 else:
242 242 return MODE_REVISION, ctx
243 243
244 244 revdef = 'reverse(%s)' % query
245 245 try:
246 246 tree = revsetlang.parse(revdef)
247 247 except error.ParseError:
248 248 # can't parse to a revset tree
249 249 return MODE_KEYWORD, query
250 250
251 251 if revsetlang.depth(tree) <= 2:
252 252 # no revset syntax used
253 253 return MODE_KEYWORD, query
254 254
255 255 if any((token, (value or '')[:3]) == ('string', 're:')
256 256 for token, value, pos in revsetlang.tokenize(revdef)):
257 257 return MODE_KEYWORD, query
258 258
259 259 funcsused = revsetlang.funcsused(tree)
260 260 if not funcsused.issubset(revset.safesymbols):
261 261 return MODE_KEYWORD, query
262 262
263 263 mfunc = revset.match(web.repo.ui, revdef, repo=web.repo)
264 264 try:
265 265 revs = mfunc(web.repo)
266 266 return MODE_REVSET, revs
267 267 # ParseError: wrongly placed tokens, wrongs arguments, etc
268 268 # RepoLookupError: no such revision, e.g. in 'revision:'
269 269 # Abort: bookmark/tag not exists
270 270 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
271 271 except (error.ParseError, error.RepoLookupError, error.Abort,
272 272 LookupError):
273 273 return MODE_KEYWORD, query
274 274
275 275 def changelist(**map):
276 276 count = 0
277 277
278 278 for ctx in searchfunc[0](funcarg):
279 279 count += 1
280 280 n = ctx.node()
281 281 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
282 282 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
283 283
284 284 yield tmpl('searchentry',
285 285 parity=next(parity),
286 286 changelogtag=showtags,
287 287 files=files,
288 288 **webutil.commonentry(web.repo, ctx))
289 289
290 290 if count >= revcount:
291 291 break
292 292
293 293 query = req.form['rev'][0]
294 294 revcount = web.maxchanges
295 295 if 'revcount' in req.form:
296 296 try:
297 297 revcount = int(req.form.get('revcount', [revcount])[0])
298 298 revcount = max(revcount, 1)
299 299 tmpl.defaults['sessionvars']['revcount'] = revcount
300 300 except ValueError:
301 301 pass
302 302
303 303 lessvars = copy.copy(tmpl.defaults['sessionvars'])
304 304 lessvars['revcount'] = max(revcount / 2, 1)
305 305 lessvars['rev'] = query
306 306 morevars = copy.copy(tmpl.defaults['sessionvars'])
307 307 morevars['revcount'] = revcount * 2
308 308 morevars['rev'] = query
309 309
310 310 mode, funcarg = getsearchmode(query)
311 311
312 312 if 'forcekw' in req.form:
313 313 showforcekw = ''
314 314 showunforcekw = searchfuncs[mode][1]
315 315 mode = MODE_KEYWORD
316 316 funcarg = query
317 317 else:
318 318 if mode != MODE_KEYWORD:
319 319 showforcekw = searchfuncs[MODE_KEYWORD][1]
320 320 else:
321 321 showforcekw = ''
322 322 showunforcekw = ''
323 323
324 324 searchfunc = searchfuncs[mode]
325 325
326 326 tip = web.repo['tip']
327 327 parity = paritygen(web.stripecount)
328 328
329 329 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
330 330 entries=changelist, archives=web.archivelist("tip"),
331 331 morevars=morevars, lessvars=lessvars,
332 332 modedesc=searchfunc[1],
333 333 showforcekw=showforcekw, showunforcekw=showunforcekw)
334 334
335 335 @webcommand('changelog')
336 336 def changelog(web, req, tmpl, shortlog=False):
337 337 """
338 338 /changelog[/{revision}]
339 339 -----------------------
340 340
341 341 Show information about multiple changesets.
342 342
343 343 If the optional ``revision`` URL argument is absent, information about
344 344 all changesets starting at ``tip`` will be rendered. If the ``revision``
345 345 argument is present, changesets will be shown starting from the specified
346 346 revision.
347 347
348 348 If ``revision`` is absent, the ``rev`` query string argument may be
349 349 defined. This will perform a search for changesets.
350 350
351 351 The argument for ``rev`` can be a single revision, a revision set,
352 352 or a literal keyword to search for in changeset data (equivalent to
353 353 :hg:`log -k`).
354 354
355 355 The ``revcount`` query string argument defines the maximum numbers of
356 356 changesets to render.
357 357
358 358 For non-searches, the ``changelog`` template will be rendered.
359 359 """
360 360
361 361 query = ''
362 362 if 'node' in req.form:
363 363 ctx = webutil.changectx(web.repo, req)
364 364 symrev = webutil.symrevorshortnode(req, ctx)
365 365 elif 'rev' in req.form:
366 366 return _search(web, req, tmpl)
367 367 else:
368 368 ctx = web.repo['tip']
369 369 symrev = 'tip'
370 370
371 371 def changelist():
372 372 revs = []
373 373 if pos != -1:
374 374 revs = web.repo.changelog.revs(pos, 0)
375 375 curcount = 0
376 376 for rev in revs:
377 377 curcount += 1
378 378 if curcount > revcount + 1:
379 379 break
380 380
381 381 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
382 382 entry['parity'] = next(parity)
383 383 yield entry
384 384
385 385 if shortlog:
386 386 revcount = web.maxshortchanges
387 387 else:
388 388 revcount = web.maxchanges
389 389
390 390 if 'revcount' in req.form:
391 391 try:
392 392 revcount = int(req.form.get('revcount', [revcount])[0])
393 393 revcount = max(revcount, 1)
394 394 tmpl.defaults['sessionvars']['revcount'] = revcount
395 395 except ValueError:
396 396 pass
397 397
398 398 lessvars = copy.copy(tmpl.defaults['sessionvars'])
399 399 lessvars['revcount'] = max(revcount / 2, 1)
400 400 morevars = copy.copy(tmpl.defaults['sessionvars'])
401 401 morevars['revcount'] = revcount * 2
402 402
403 403 count = len(web.repo)
404 404 pos = ctx.rev()
405 405 parity = paritygen(web.stripecount)
406 406
407 407 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
408 408
409 409 entries = list(changelist())
410 410 latestentry = entries[:1]
411 411 if len(entries) > revcount:
412 412 nextentry = entries[-1:]
413 413 entries = entries[:-1]
414 414 else:
415 415 nextentry = []
416 416
417 417 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
418 418 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
419 419 entries=entries,
420 420 latestentry=latestentry, nextentry=nextentry,
421 421 archives=web.archivelist("tip"), revcount=revcount,
422 422 morevars=morevars, lessvars=lessvars, query=query)
423 423
424 424 @webcommand('shortlog')
425 425 def shortlog(web, req, tmpl):
426 426 """
427 427 /shortlog
428 428 ---------
429 429
430 430 Show basic information about a set of changesets.
431 431
432 432 This accepts the same parameters as the ``changelog`` handler. The only
433 433 difference is the ``shortlog`` template will be rendered instead of the
434 434 ``changelog`` template.
435 435 """
436 436 return changelog(web, req, tmpl, shortlog=True)
437 437
438 438 @webcommand('changeset')
439 439 def changeset(web, req, tmpl):
440 440 """
441 441 /changeset[/{revision}]
442 442 -----------------------
443 443
444 444 Show information about a single changeset.
445 445
446 446 A URL path argument is the changeset identifier to show. See ``hg help
447 447 revisions`` for possible values. If not defined, the ``tip`` changeset
448 448 will be shown.
449 449
450 450 The ``changeset`` template is rendered. Contents of the ``changesettag``,
451 451 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
452 452 templates related to diffs may all be used to produce the output.
453 453 """
454 454 ctx = webutil.changectx(web.repo, req)
455 455
456 456 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
457 457
458 458 rev = webcommand('rev')(changeset)
459 459
460 460 def decodepath(path):
461 461 """Hook for mapping a path in the repository to a path in the
462 462 working copy.
463 463
464 464 Extensions (e.g., largefiles) can override this to remap files in
465 465 the virtual file system presented by the manifest command below."""
466 466 return path
467 467
468 468 @webcommand('manifest')
469 469 def manifest(web, req, tmpl):
470 470 """
471 471 /manifest[/{revision}[/{path}]]
472 472 -------------------------------
473 473
474 474 Show information about a directory.
475 475
476 476 If the URL path arguments are omitted, information about the root
477 477 directory for the ``tip`` changeset will be shown.
478 478
479 479 Because this handler can only show information for directories, it
480 480 is recommended to use the ``file`` handler instead, as it can handle both
481 481 directories and files.
482 482
483 483 The ``manifest`` template will be rendered for this handler.
484 484 """
485 485 if 'node' in req.form:
486 486 ctx = webutil.changectx(web.repo, req)
487 487 symrev = webutil.symrevorshortnode(req, ctx)
488 488 else:
489 489 ctx = web.repo['tip']
490 490 symrev = 'tip'
491 491 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
492 492 mf = ctx.manifest()
493 493 node = ctx.node()
494 494
495 495 files = {}
496 496 dirs = {}
497 497 parity = paritygen(web.stripecount)
498 498
499 499 if path and path[-1] != "/":
500 500 path += "/"
501 501 l = len(path)
502 502 abspath = "/" + path
503 503
504 504 for full, n in mf.iteritems():
505 505 # the virtual path (working copy path) used for the full
506 506 # (repository) path
507 507 f = decodepath(full)
508 508
509 509 if f[:l] != path:
510 510 continue
511 511 remain = f[l:]
512 512 elements = remain.split('/')
513 513 if len(elements) == 1:
514 514 files[remain] = full
515 515 else:
516 516 h = dirs # need to retain ref to dirs (root)
517 517 for elem in elements[0:-1]:
518 518 if elem not in h:
519 519 h[elem] = {}
520 520 h = h[elem]
521 521 if len(h) > 1:
522 522 break
523 523 h[None] = None # denotes files present
524 524
525 525 if mf and not files and not dirs:
526 526 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
527 527
528 528 def filelist(**map):
529 529 for f in sorted(files):
530 530 full = files[f]
531 531
532 532 fctx = ctx.filectx(full)
533 533 yield {"file": full,
534 534 "parity": next(parity),
535 535 "basename": f,
536 536 "date": fctx.date(),
537 537 "size": fctx.size(),
538 538 "permissions": mf.flags(full)}
539 539
540 540 def dirlist(**map):
541 541 for d in sorted(dirs):
542 542
543 543 emptydirs = []
544 544 h = dirs[d]
545 545 while isinstance(h, dict) and len(h) == 1:
546 546 k, v = h.items()[0]
547 547 if v:
548 548 emptydirs.append(k)
549 549 h = v
550 550
551 551 path = "%s%s" % (abspath, d)
552 552 yield {"parity": next(parity),
553 553 "path": path,
554 554 "emptydirs": "/".join(emptydirs),
555 555 "basename": d}
556 556
557 557 return tmpl("manifest",
558 558 symrev=symrev,
559 559 path=abspath,
560 560 up=webutil.up(abspath),
561 561 upparity=next(parity),
562 562 fentries=filelist,
563 563 dentries=dirlist,
564 564 archives=web.archivelist(hex(node)),
565 565 **webutil.commonentry(web.repo, ctx))
566 566
567 567 @webcommand('tags')
568 568 def tags(web, req, tmpl):
569 569 """
570 570 /tags
571 571 -----
572 572
573 573 Show information about tags.
574 574
575 575 No arguments are accepted.
576 576
577 577 The ``tags`` template is rendered.
578 578 """
579 579 i = list(reversed(web.repo.tagslist()))
580 580 parity = paritygen(web.stripecount)
581 581
582 582 def entries(notip, latestonly, **map):
583 583 t = i
584 584 if notip:
585 585 t = [(k, n) for k, n in i if k != "tip"]
586 586 if latestonly:
587 587 t = t[:1]
588 588 for k, n in t:
589 589 yield {"parity": next(parity),
590 590 "tag": k,
591 591 "date": web.repo[n].date(),
592 592 "node": hex(n)}
593 593
594 594 return tmpl("tags",
595 595 node=hex(web.repo.changelog.tip()),
596 596 entries=lambda **x: entries(False, False, **x),
597 597 entriesnotip=lambda **x: entries(True, False, **x),
598 598 latestentry=lambda **x: entries(True, True, **x))
599 599
600 600 @webcommand('bookmarks')
601 601 def bookmarks(web, req, tmpl):
602 602 """
603 603 /bookmarks
604 604 ----------
605 605
606 606 Show information about bookmarks.
607 607
608 608 No arguments are accepted.
609 609
610 610 The ``bookmarks`` template is rendered.
611 611 """
612 612 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
613 613 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
614 614 i = sorted(i, key=sortkey, reverse=True)
615 615 parity = paritygen(web.stripecount)
616 616
617 617 def entries(latestonly, **map):
618 618 t = i
619 619 if latestonly:
620 620 t = i[:1]
621 621 for k, n in t:
622 622 yield {"parity": next(parity),
623 623 "bookmark": k,
624 624 "date": web.repo[n].date(),
625 625 "node": hex(n)}
626 626
627 627 if i:
628 628 latestrev = i[0][1]
629 629 else:
630 630 latestrev = -1
631 631
632 632 return tmpl("bookmarks",
633 633 node=hex(web.repo.changelog.tip()),
634 634 lastchange=[{"date": web.repo[latestrev].date()}],
635 635 entries=lambda **x: entries(latestonly=False, **x),
636 636 latestentry=lambda **x: entries(latestonly=True, **x))
637 637
638 638 @webcommand('branches')
639 639 def branches(web, req, tmpl):
640 640 """
641 641 /branches
642 642 ---------
643 643
644 644 Show information about branches.
645 645
646 646 All known branches are contained in the output, even closed branches.
647 647
648 648 No arguments are accepted.
649 649
650 650 The ``branches`` template is rendered.
651 651 """
652 652 entries = webutil.branchentries(web.repo, web.stripecount)
653 653 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
654 654 return tmpl('branches', node=hex(web.repo.changelog.tip()),
655 655 entries=entries, latestentry=latestentry)
656 656
657 657 @webcommand('summary')
658 658 def summary(web, req, tmpl):
659 659 """
660 660 /summary
661 661 --------
662 662
663 663 Show a summary of repository state.
664 664
665 665 Information about the latest changesets, bookmarks, tags, and branches
666 666 is captured by this handler.
667 667
668 668 The ``summary`` template is rendered.
669 669 """
670 670 i = reversed(web.repo.tagslist())
671 671
672 672 def tagentries(**map):
673 673 parity = paritygen(web.stripecount)
674 674 count = 0
675 675 for k, n in i:
676 676 if k == "tip": # skip tip
677 677 continue
678 678
679 679 count += 1
680 680 if count > 10: # limit to 10 tags
681 681 break
682 682
683 683 yield tmpl("tagentry",
684 684 parity=next(parity),
685 685 tag=k,
686 686 node=hex(n),
687 687 date=web.repo[n].date())
688 688
689 689 def bookmarks(**map):
690 690 parity = paritygen(web.stripecount)
691 691 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
692 692 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
693 693 marks = sorted(marks, key=sortkey, reverse=True)
694 694 for k, n in marks[:10]: # limit to 10 bookmarks
695 695 yield {'parity': next(parity),
696 696 'bookmark': k,
697 697 'date': web.repo[n].date(),
698 698 'node': hex(n)}
699 699
700 700 def changelist(**map):
701 701 parity = paritygen(web.stripecount, offset=start - end)
702 702 l = [] # build a list in forward order for efficiency
703 703 revs = []
704 704 if start < end:
705 705 revs = web.repo.changelog.revs(start, end - 1)
706 706 for i in revs:
707 707 ctx = web.repo[i]
708 708
709 709 l.append(tmpl(
710 710 'shortlogentry',
711 711 parity=next(parity),
712 712 **webutil.commonentry(web.repo, ctx)))
713 713
714 714 for entry in reversed(l):
715 715 yield entry
716 716
717 717 tip = web.repo['tip']
718 718 count = len(web.repo)
719 719 start = max(0, count - web.maxchanges)
720 720 end = min(count, start + web.maxchanges)
721 721
722 722 desc = web.config("web", "description")
723 723 if not desc:
724 724 desc = 'unknown'
725 725 return tmpl("summary",
726 726 desc=desc,
727 727 owner=get_contact(web.config) or "unknown",
728 728 lastchange=tip.date(),
729 729 tags=tagentries,
730 730 bookmarks=bookmarks,
731 731 branches=webutil.branchentries(web.repo, web.stripecount, 10),
732 732 shortlog=changelist,
733 733 node=tip.hex(),
734 734 symrev='tip',
735 735 archives=web.archivelist("tip"),
736 736 labels=web.configlist('web', 'labels'))
737 737
738 738 @webcommand('filediff')
739 739 def filediff(web, req, tmpl):
740 740 """
741 741 /diff/{revision}/{path}
742 742 -----------------------
743 743
744 744 Show how a file changed in a particular commit.
745 745
746 746 The ``filediff`` template is rendered.
747 747
748 748 This handler is registered under both the ``/diff`` and ``/filediff``
749 749 paths. ``/diff`` is used in modern code.
750 750 """
751 751 fctx, ctx = None, None
752 752 try:
753 753 fctx = webutil.filectx(web.repo, req)
754 754 except LookupError:
755 755 ctx = webutil.changectx(web.repo, req)
756 756 path = webutil.cleanpath(web.repo, req.form['file'][0])
757 757 if path not in ctx.files():
758 758 raise
759 759
760 760 if fctx is not None:
761 761 path = fctx.path()
762 762 ctx = fctx.changectx()
763 763 basectx = ctx.p1()
764 764
765 765 style = web.config('web', 'style')
766 766 if 'style' in req.form:
767 767 style = req.form['style'][0]
768 768
769 769 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
770 770 if fctx is not None:
771 771 rename = webutil.renamelink(fctx)
772 772 ctx = fctx
773 773 else:
774 774 rename = []
775 775 ctx = ctx
776 776 return tmpl("filediff",
777 777 file=path,
778 778 symrev=webutil.symrevorshortnode(req, ctx),
779 779 rename=rename,
780 780 diff=diffs,
781 781 **webutil.commonentry(web.repo, ctx))
782 782
783 783 diff = webcommand('diff')(filediff)
784 784
785 785 @webcommand('comparison')
786 786 def comparison(web, req, tmpl):
787 787 """
788 788 /comparison/{revision}/{path}
789 789 -----------------------------
790 790
791 791 Show a comparison between the old and new versions of a file from changes
792 792 made on a particular revision.
793 793
794 794 This is similar to the ``diff`` handler. However, this form features
795 795 a split or side-by-side diff rather than a unified diff.
796 796
797 797 The ``context`` query string argument can be used to control the lines of
798 798 context in the diff.
799 799
800 800 The ``filecomparison`` template is rendered.
801 801 """
802 802 ctx = webutil.changectx(web.repo, req)
803 803 if 'file' not in req.form:
804 804 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
805 805 path = webutil.cleanpath(web.repo, req.form['file'][0])
806 806
807 807 parsecontext = lambda v: v == 'full' and -1 or int(v)
808 808 if 'context' in req.form:
809 809 context = parsecontext(req.form['context'][0])
810 810 else:
811 811 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
812 812
813 813 def filelines(f):
814 814 if f.isbinary():
815 815 mt = mimetypes.guess_type(f.path())[0]
816 816 if not mt:
817 817 mt = 'application/octet-stream'
818 818 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
819 819 return f.data().splitlines()
820 820
821 821 fctx = None
822 822 parent = ctx.p1()
823 823 leftrev = parent.rev()
824 824 leftnode = parent.node()
825 825 rightrev = ctx.rev()
826 826 rightnode = ctx.node()
827 827 if path in ctx:
828 828 fctx = ctx[path]
829 829 rightlines = filelines(fctx)
830 830 if path not in parent:
831 831 leftlines = ()
832 832 else:
833 833 pfctx = parent[path]
834 834 leftlines = filelines(pfctx)
835 835 else:
836 836 rightlines = ()
837 837 pfctx = ctx.parents()[0][path]
838 838 leftlines = filelines(pfctx)
839 839
840 840 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
841 841 if fctx is not None:
842 842 rename = webutil.renamelink(fctx)
843 843 ctx = fctx
844 844 else:
845 845 rename = []
846 846 ctx = ctx
847 847 return tmpl('filecomparison',
848 848 file=path,
849 849 symrev=webutil.symrevorshortnode(req, ctx),
850 850 rename=rename,
851 851 leftrev=leftrev,
852 852 leftnode=hex(leftnode),
853 853 rightrev=rightrev,
854 854 rightnode=hex(rightnode),
855 855 comparison=comparison,
856 856 **webutil.commonentry(web.repo, ctx))
857 857
858 858 @webcommand('annotate')
859 859 def annotate(web, req, tmpl):
860 860 """
861 861 /annotate/{revision}/{path}
862 862 ---------------------------
863 863
864 864 Show changeset information for each line in a file.
865 865
866 866 The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
867 867 ``ignoreblanklines`` query string arguments have the same meaning as
868 868 their ``[annotate]`` config equivalents. A value of ``0`` sets the
869 869 whitespace option to false. All other values are true. If not defined,
870 870 the server default settings are used.
871 871
872 872 The ``fileannotate`` template is rendered.
873 873 """
874 874 fctx = webutil.filectx(web.repo, req)
875 875 f = fctx.path()
876 876 parity = paritygen(web.stripecount)
877 877 ishead = fctx.filerev() in fctx.filelog().headrevs()
878 878
879 879 # parents() is called once per line and several lines likely belong to
880 880 # same revision. So it is worth caching.
881 881 # TODO there are still redundant operations within basefilectx.parents()
882 882 # and from the fctx.annotate() call itself that could be cached.
883 883 parentscache = {}
884 884 def parents(f):
885 885 rev = f.rev()
886 886 if rev not in parentscache:
887 887 parentscache[rev] = []
888 888 for p in f.parents():
889 889 entry = {
890 890 'node': p.hex(),
891 891 'rev': p.rev(),
892 892 }
893 893 parentscache[rev].append(entry)
894 894
895 895 for p in parentscache[rev]:
896 896 yield p
897 897
898 898 def annotate(**map):
899 899 if fctx.isbinary():
900 900 mt = (mimetypes.guess_type(fctx.path())[0]
901 901 or 'application/octet-stream')
902 902 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
903 903 else:
904 904 lines = webutil.annotate(req, fctx, web.repo.ui)
905 905
906 906 previousrev = None
907 907 blockparitygen = paritygen(1)
908 908 for lineno, ((f, targetline), l) in enumerate(lines):
909 909 rev = f.rev()
910 910 if rev != previousrev:
911 911 blockhead = True
912 912 blockparity = next(blockparitygen)
913 913 else:
914 914 blockhead = None
915 915 previousrev = rev
916 916 yield {"parity": next(parity),
917 917 "node": f.hex(),
918 918 "rev": rev,
919 919 "author": f.user(),
920 920 "parents": parents(f),
921 921 "desc": f.description(),
922 922 "extra": f.extra(),
923 923 "file": f.path(),
924 924 "blockhead": blockhead,
925 925 "blockparity": blockparity,
926 926 "targetline": targetline,
927 927 "line": l,
928 928 "lineno": lineno + 1,
929 929 "lineid": "l%d" % (lineno + 1),
930 930 "linenumber": "% 6d" % (lineno + 1),
931 931 "revdate": f.date()}
932 932
933 diffopts = webutil.difffeatureopts(req, web.repo.ui, 'annotate')
934 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
935
933 936 return tmpl("fileannotate",
934 937 file=f,
935 938 annotate=annotate,
936 939 path=webutil.up(f),
937 940 symrev=webutil.symrevorshortnode(req, fctx),
938 941 rename=webutil.renamelink(fctx),
939 942 permissions=fctx.manifest().flags(f),
940 943 ishead=int(ishead),
944 diffopts=diffopts,
941 945 **webutil.commonentry(web.repo, fctx))
942 946
943 947 @webcommand('filelog')
944 948 def filelog(web, req, tmpl):
945 949 """
946 950 /filelog/{revision}/{path}
947 951 --------------------------
948 952
949 953 Show information about the history of a file in the repository.
950 954
951 955 The ``revcount`` query string argument can be defined to control the
952 956 maximum number of entries to show.
953 957
954 958 The ``filelog`` template will be rendered.
955 959 """
956 960
957 961 try:
958 962 fctx = webutil.filectx(web.repo, req)
959 963 f = fctx.path()
960 964 fl = fctx.filelog()
961 965 except error.LookupError:
962 966 f = webutil.cleanpath(web.repo, req.form['file'][0])
963 967 fl = web.repo.file(f)
964 968 numrevs = len(fl)
965 969 if not numrevs: # file doesn't exist at all
966 970 raise
967 971 rev = webutil.changectx(web.repo, req).rev()
968 972 first = fl.linkrev(0)
969 973 if rev < first: # current rev is from before file existed
970 974 raise
971 975 frev = numrevs - 1
972 976 while fl.linkrev(frev) > rev:
973 977 frev -= 1
974 978 fctx = web.repo.filectx(f, fl.linkrev(frev))
975 979
976 980 revcount = web.maxshortchanges
977 981 if 'revcount' in req.form:
978 982 try:
979 983 revcount = int(req.form.get('revcount', [revcount])[0])
980 984 revcount = max(revcount, 1)
981 985 tmpl.defaults['sessionvars']['revcount'] = revcount
982 986 except ValueError:
983 987 pass
984 988
985 989 lrange = webutil.linerange(req)
986 990
987 991 lessvars = copy.copy(tmpl.defaults['sessionvars'])
988 992 lessvars['revcount'] = max(revcount / 2, 1)
989 993 morevars = copy.copy(tmpl.defaults['sessionvars'])
990 994 morevars['revcount'] = revcount * 2
991 995
992 996 patch = 'patch' in req.form
993 997 if patch:
994 998 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
995 999 descend = 'descend' in req.form
996 1000 if descend:
997 1001 lessvars['descend'] = morevars['descend'] = req.form['descend'][0]
998 1002
999 1003 count = fctx.filerev() + 1
1000 1004 start = max(0, count - revcount) # first rev on this page
1001 1005 end = min(count, start + revcount) # last rev on this page
1002 1006 parity = paritygen(web.stripecount, offset=start - end)
1003 1007
1004 1008 repo = web.repo
1005 1009 revs = fctx.filelog().revs(start, end - 1)
1006 1010 entries = []
1007 1011
1008 1012 diffstyle = web.config('web', 'style')
1009 1013 if 'style' in req.form:
1010 1014 diffstyle = req.form['style'][0]
1011 1015
1012 1016 def diff(fctx, linerange=None):
1013 1017 ctx = fctx.changectx()
1014 1018 basectx = ctx.p1()
1015 1019 path = fctx.path()
1016 1020 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle,
1017 1021 linerange=linerange,
1018 1022 lineidprefix='%s-' % ctx.hex()[:12])
1019 1023
1020 1024 linerange = None
1021 1025 if lrange is not None:
1022 1026 linerange = webutil.formatlinerange(*lrange)
1023 1027 # deactivate numeric nav links when linerange is specified as this
1024 1028 # would required a dedicated "revnav" class
1025 1029 nav = None
1026 1030 if descend:
1027 1031 it = dagop.blockdescendants(fctx, *lrange)
1028 1032 else:
1029 1033 it = dagop.blockancestors(fctx, *lrange)
1030 1034 for i, (c, lr) in enumerate(it, 1):
1031 1035 diffs = None
1032 1036 if patch:
1033 1037 diffs = diff(c, linerange=lr)
1034 1038 # follow renames accross filtered (not in range) revisions
1035 1039 path = c.path()
1036 1040 entries.append(dict(
1037 1041 parity=next(parity),
1038 1042 filerev=c.rev(),
1039 1043 file=path,
1040 1044 diff=diffs,
1041 1045 linerange=webutil.formatlinerange(*lr),
1042 1046 **webutil.commonentry(repo, c)))
1043 1047 if i == revcount:
1044 1048 break
1045 1049 lessvars['linerange'] = webutil.formatlinerange(*lrange)
1046 1050 morevars['linerange'] = lessvars['linerange']
1047 1051 else:
1048 1052 for i in revs:
1049 1053 iterfctx = fctx.filectx(i)
1050 1054 diffs = None
1051 1055 if patch:
1052 1056 diffs = diff(iterfctx)
1053 1057 entries.append(dict(
1054 1058 parity=next(parity),
1055 1059 filerev=i,
1056 1060 file=f,
1057 1061 diff=diffs,
1058 1062 rename=webutil.renamelink(iterfctx),
1059 1063 **webutil.commonentry(repo, iterfctx)))
1060 1064 entries.reverse()
1061 1065 revnav = webutil.filerevnav(web.repo, fctx.path())
1062 1066 nav = revnav.gen(end - 1, revcount, count)
1063 1067
1064 1068 latestentry = entries[:1]
1065 1069
1066 1070 return tmpl("filelog",
1067 1071 file=f,
1068 1072 nav=nav,
1069 1073 symrev=webutil.symrevorshortnode(req, fctx),
1070 1074 entries=entries,
1071 1075 descend=descend,
1072 1076 patch=patch,
1073 1077 latestentry=latestentry,
1074 1078 linerange=linerange,
1075 1079 revcount=revcount,
1076 1080 morevars=morevars,
1077 1081 lessvars=lessvars,
1078 1082 **webutil.commonentry(web.repo, fctx))
1079 1083
1080 1084 @webcommand('archive')
1081 1085 def archive(web, req, tmpl):
1082 1086 """
1083 1087 /archive/{revision}.{format}[/{path}]
1084 1088 -------------------------------------
1085 1089
1086 1090 Obtain an archive of repository content.
1087 1091
1088 1092 The content and type of the archive is defined by a URL path parameter.
1089 1093 ``format`` is the file extension of the archive type to be generated. e.g.
1090 1094 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1091 1095 server configuration.
1092 1096
1093 1097 The optional ``path`` URL parameter controls content to include in the
1094 1098 archive. If omitted, every file in the specified revision is present in the
1095 1099 archive. If included, only the specified file or contents of the specified
1096 1100 directory will be included in the archive.
1097 1101
1098 1102 No template is used for this handler. Raw, binary content is generated.
1099 1103 """
1100 1104
1101 1105 type_ = req.form.get('type', [None])[0]
1102 1106 allowed = web.configlist("web", "allow_archive")
1103 1107 key = req.form['node'][0]
1104 1108
1105 1109 if type_ not in web.archivespecs:
1106 1110 msg = 'Unsupported archive type: %s' % type_
1107 1111 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1108 1112
1109 1113 if not ((type_ in allowed or
1110 1114 web.configbool("web", "allow" + type_, False))):
1111 1115 msg = 'Archive type not allowed: %s' % type_
1112 1116 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1113 1117
1114 1118 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1115 1119 cnode = web.repo.lookup(key)
1116 1120 arch_version = key
1117 1121 if cnode == key or key == 'tip':
1118 1122 arch_version = short(cnode)
1119 1123 name = "%s-%s" % (reponame, arch_version)
1120 1124
1121 1125 ctx = webutil.changectx(web.repo, req)
1122 1126 pats = []
1123 1127 match = scmutil.match(ctx, [])
1124 1128 file = req.form.get('file', None)
1125 1129 if file:
1126 1130 pats = ['path:' + file[0]]
1127 1131 match = scmutil.match(ctx, pats, default='path')
1128 1132 if pats:
1129 1133 files = [f for f in ctx.manifest().keys() if match(f)]
1130 1134 if not files:
1131 1135 raise ErrorResponse(HTTP_NOT_FOUND,
1132 1136 'file(s) not found: %s' % file[0])
1133 1137
1134 1138 mimetype, artype, extension, encoding = web.archivespecs[type_]
1135 1139 headers = [
1136 1140 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1137 1141 ]
1138 1142 if encoding:
1139 1143 headers.append(('Content-Encoding', encoding))
1140 1144 req.headers.extend(headers)
1141 1145 req.respond(HTTP_OK, mimetype)
1142 1146
1143 1147 archival.archive(web.repo, req, cnode, artype, prefix=name,
1144 1148 matchfn=match,
1145 1149 subrepos=web.configbool("web", "archivesubrepos"))
1146 1150 return []
1147 1151
1148 1152
1149 1153 @webcommand('static')
1150 1154 def static(web, req, tmpl):
1151 1155 fname = req.form['file'][0]
1152 1156 # a repo owner may set web.static in .hg/hgrc to get any file
1153 1157 # readable by the user running the CGI script
1154 1158 static = web.config("web", "static", None, untrusted=False)
1155 1159 if not static:
1156 1160 tp = web.templatepath or templater.templatepaths()
1157 1161 if isinstance(tp, str):
1158 1162 tp = [tp]
1159 1163 static = [os.path.join(p, 'static') for p in tp]
1160 1164 staticfile(static, fname, req)
1161 1165 return []
1162 1166
1163 1167 @webcommand('graph')
1164 1168 def graph(web, req, tmpl):
1165 1169 """
1166 1170 /graph[/{revision}]
1167 1171 -------------------
1168 1172
1169 1173 Show information about the graphical topology of the repository.
1170 1174
1171 1175 Information rendered by this handler can be used to create visual
1172 1176 representations of repository topology.
1173 1177
1174 1178 The ``revision`` URL parameter controls the starting changeset.
1175 1179
1176 1180 The ``revcount`` query string argument can define the number of changesets
1177 1181 to show information for.
1178 1182
1179 1183 This handler will render the ``graph`` template.
1180 1184 """
1181 1185
1182 1186 if 'node' in req.form:
1183 1187 ctx = webutil.changectx(web.repo, req)
1184 1188 symrev = webutil.symrevorshortnode(req, ctx)
1185 1189 else:
1186 1190 ctx = web.repo['tip']
1187 1191 symrev = 'tip'
1188 1192 rev = ctx.rev()
1189 1193
1190 1194 bg_height = 39
1191 1195 revcount = web.maxshortchanges
1192 1196 if 'revcount' in req.form:
1193 1197 try:
1194 1198 revcount = int(req.form.get('revcount', [revcount])[0])
1195 1199 revcount = max(revcount, 1)
1196 1200 tmpl.defaults['sessionvars']['revcount'] = revcount
1197 1201 except ValueError:
1198 1202 pass
1199 1203
1200 1204 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1201 1205 lessvars['revcount'] = max(revcount / 2, 1)
1202 1206 morevars = copy.copy(tmpl.defaults['sessionvars'])
1203 1207 morevars['revcount'] = revcount * 2
1204 1208
1205 1209 count = len(web.repo)
1206 1210 pos = rev
1207 1211
1208 1212 uprev = min(max(0, count - 1), rev + revcount)
1209 1213 downrev = max(0, rev - revcount)
1210 1214 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1211 1215
1212 1216 tree = []
1213 1217 if pos != -1:
1214 1218 allrevs = web.repo.changelog.revs(pos, 0)
1215 1219 revs = []
1216 1220 for i in allrevs:
1217 1221 revs.append(i)
1218 1222 if len(revs) >= revcount:
1219 1223 break
1220 1224
1221 1225 # We have to feed a baseset to dagwalker as it is expecting smartset
1222 1226 # object. This does not have a big impact on hgweb performance itself
1223 1227 # since hgweb graphing code is not itself lazy yet.
1224 1228 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1225 1229 # As we said one line above... not lazy.
1226 1230 tree = list(graphmod.colored(dag, web.repo))
1227 1231
1228 1232 def getcolumns(tree):
1229 1233 cols = 0
1230 1234 for (id, type, ctx, vtx, edges) in tree:
1231 1235 if type != graphmod.CHANGESET:
1232 1236 continue
1233 1237 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1234 1238 max([edge[1] for edge in edges] or [0]))
1235 1239 return cols
1236 1240
1237 1241 def graphdata(usetuples, encodestr):
1238 1242 data = []
1239 1243
1240 1244 row = 0
1241 1245 for (id, type, ctx, vtx, edges) in tree:
1242 1246 if type != graphmod.CHANGESET:
1243 1247 continue
1244 1248 node = str(ctx)
1245 1249 age = encodestr(templatefilters.age(ctx.date()))
1246 1250 desc = templatefilters.firstline(encodestr(ctx.description()))
1247 1251 desc = cgi.escape(templatefilters.nonempty(desc))
1248 1252 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1249 1253 branch = cgi.escape(encodestr(ctx.branch()))
1250 1254 try:
1251 1255 branchnode = web.repo.branchtip(branch)
1252 1256 except error.RepoLookupError:
1253 1257 branchnode = None
1254 1258 branch = branch, branchnode == ctx.node()
1255 1259
1256 1260 if usetuples:
1257 1261 data.append((node, vtx, edges, desc, user, age, branch,
1258 1262 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1259 1263 [cgi.escape(encodestr(x))
1260 1264 for x in ctx.bookmarks()]))
1261 1265 else:
1262 1266 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1263 1267 'color': (edge[2] - 1) % 6 + 1,
1264 1268 'width': edge[3], 'bcolor': edge[4]}
1265 1269 for edge in edges]
1266 1270
1267 1271 data.append(
1268 1272 {'node': node,
1269 1273 'col': vtx[0],
1270 1274 'color': (vtx[1] - 1) % 6 + 1,
1271 1275 'edges': edgedata,
1272 1276 'row': row,
1273 1277 'nextrow': row + 1,
1274 1278 'desc': desc,
1275 1279 'user': user,
1276 1280 'age': age,
1277 1281 'bookmarks': webutil.nodebookmarksdict(
1278 1282 web.repo, ctx.node()),
1279 1283 'branches': webutil.nodebranchdict(web.repo, ctx),
1280 1284 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1281 1285 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1282 1286
1283 1287 row += 1
1284 1288
1285 1289 return data
1286 1290
1287 1291 cols = getcolumns(tree)
1288 1292 rows = len(tree)
1289 1293 canvasheight = (rows + 1) * bg_height - 27
1290 1294
1291 1295 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1292 1296 uprev=uprev,
1293 1297 lessvars=lessvars, morevars=morevars, downrev=downrev,
1294 1298 cols=cols, rows=rows,
1295 1299 canvaswidth=(cols + 1) * bg_height,
1296 1300 truecanvasheight=rows * bg_height,
1297 1301 canvasheight=canvasheight, bg_height=bg_height,
1298 1302 # {jsdata} will be passed to |json, so it must be in utf-8
1299 1303 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1300 1304 nodes=lambda **x: graphdata(False, str),
1301 1305 node=ctx.hex(), changenav=changenav)
1302 1306
1303 1307 def _getdoc(e):
1304 1308 doc = e[0].__doc__
1305 1309 if doc:
1306 1310 doc = _(doc).partition('\n')[0]
1307 1311 else:
1308 1312 doc = _('(no help text available)')
1309 1313 return doc
1310 1314
1311 1315 @webcommand('help')
1312 1316 def help(web, req, tmpl):
1313 1317 """
1314 1318 /help[/{topic}]
1315 1319 ---------------
1316 1320
1317 1321 Render help documentation.
1318 1322
1319 1323 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1320 1324 is defined, that help topic will be rendered. If not, an index of
1321 1325 available help topics will be rendered.
1322 1326
1323 1327 The ``help`` template will be rendered when requesting help for a topic.
1324 1328 ``helptopics`` will be rendered for the index of help topics.
1325 1329 """
1326 1330 from .. import commands, help as helpmod # avoid cycle
1327 1331
1328 1332 topicname = req.form.get('node', [None])[0]
1329 1333 if not topicname:
1330 1334 def topics(**map):
1331 1335 for entries, summary, _doc in helpmod.helptable:
1332 1336 yield {'topic': entries[0], 'summary': summary}
1333 1337
1334 1338 early, other = [], []
1335 1339 primary = lambda s: s.partition('|')[0]
1336 1340 for c, e in commands.table.iteritems():
1337 1341 doc = _getdoc(e)
1338 1342 if 'DEPRECATED' in doc or c.startswith('debug'):
1339 1343 continue
1340 1344 cmd = primary(c)
1341 1345 if cmd.startswith('^'):
1342 1346 early.append((cmd[1:], doc))
1343 1347 else:
1344 1348 other.append((cmd, doc))
1345 1349
1346 1350 early.sort()
1347 1351 other.sort()
1348 1352
1349 1353 def earlycommands(**map):
1350 1354 for c, doc in early:
1351 1355 yield {'topic': c, 'summary': doc}
1352 1356
1353 1357 def othercommands(**map):
1354 1358 for c, doc in other:
1355 1359 yield {'topic': c, 'summary': doc}
1356 1360
1357 1361 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1358 1362 othercommands=othercommands, title='Index')
1359 1363
1360 1364 # Render an index of sub-topics.
1361 1365 if topicname in helpmod.subtopics:
1362 1366 topics = []
1363 1367 for entries, summary, _doc in helpmod.subtopics[topicname]:
1364 1368 topics.append({
1365 1369 'topic': '%s.%s' % (topicname, entries[0]),
1366 1370 'basename': entries[0],
1367 1371 'summary': summary,
1368 1372 })
1369 1373
1370 1374 return tmpl('helptopics', topics=topics, title=topicname,
1371 1375 subindex=True)
1372 1376
1373 1377 u = webutil.wsgiui.load()
1374 1378 u.verbose = True
1375 1379
1376 1380 # Render a page from a sub-topic.
1377 1381 if '.' in topicname:
1378 1382 # TODO implement support for rendering sections, like
1379 1383 # `hg help` works.
1380 1384 topic, subtopic = topicname.split('.', 1)
1381 1385 if topic not in helpmod.subtopics:
1382 1386 raise ErrorResponse(HTTP_NOT_FOUND)
1383 1387 else:
1384 1388 topic = topicname
1385 1389 subtopic = None
1386 1390
1387 1391 try:
1388 1392 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1389 1393 except error.UnknownCommand:
1390 1394 raise ErrorResponse(HTTP_NOT_FOUND)
1391 1395 return tmpl('help', topic=topicname, doc=doc)
1392 1396
1393 1397 # tell hggettext to extract docstrings from these functions:
1394 1398 i18nfunctions = commands.values()
@@ -1,78 +1,85 b''
1 1 {header}
2 2 <title>{repo|escape}: {file|escape}@{node|short} (annotated)</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/>
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/>
7 7 </head>
8 8 <body>
9 9
10 10 <div class="page_header">
11 11 <a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a>
12 12 <a href="/">Mercurial</a> {pathdef%breadcrumb} / annotate
13 13 </div>
14 14
15 15 <div class="page_nav">
16 16 <div>
17 17 <a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a> |
18 18 <a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a> |
19 19 <a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a> |
20 20 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a> |
21 21 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
22 22 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
23 23 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
24 24 <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a> |
25 25 <a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a> |
26 26 <a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
27 27 <a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
28 28 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
29 29 annotate |
30 30 <a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
31 31 <a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
32 32 <a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a> |
33 33 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
34 34 </div>
35 35 {searchform}
36 36 </div>
37 37
38 38 <div class="title">{file|escape}</div>
39 39
40 40 <div class="title_text">
41 41 <table cellspacing="0">
42 42 <tr>
43 43 <td>author</td>
44 44 <td>{author|obfuscate}</td>
45 45 </tr>
46 46 <tr>
47 47 <td></td>
48 48 <td class="date age">{date|rfc822date}</td>
49 49 </tr>
50 50 {branch%filerevbranch}
51 51 <tr>
52 52 <td>changeset {rev}</td>
53 53 <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
54 54 </tr>
55 55 {parent%fileannotateparent}
56 56 {child%fileannotatechild}
57 57 <tr>
58 58 <td>permissions</td>
59 59 <td style="font-family:monospace">{permissions|permissions}</td>
60 60 </tr>
61 61 </table>
62 62 </div>
63 63
64 64 <div class="page_path description">{desc|strip|escape|websub|nonempty}</div>
65
66 {diffoptsform}
67
68 <script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
69 renderDiffOptsForm();
70 </script>
71
65 72 <div class="page_body">
66 73 <table>
67 74 <tbody class="sourcelines"
68 75 data-logurl="{url|urlescape}log/{symrev}/{file|urlescape}"
69 76 data-selectabletag="TR"
70 77 data-ishead="{ishead}">
71 78 {annotate%annotateline}
72 79 </tbody>
73 80 </table>
74 81 </div>
75 82
76 83 <script type="text/javascript" src="{staticurl|urlescape}followlines.js"></script>
77 84
78 85 {footer}
@@ -1,336 +1,352 b''
1 1 default = 'summary'
2 2 mimetype = 'text/html; charset={encoding}'
3 3 header = header.tmpl
4 4 footer = footer.tmpl
5 5 search = search.tmpl
6 6 changelog = changelog.tmpl
7 7 summary = summary.tmpl
8 8 error = error.tmpl
9 9 notfound = notfound.tmpl
10 10
11 11 help = help.tmpl
12 12 helptopics = helptopics.tmpl
13 13
14 14 helpentry = '
15 15 <tr><td>
16 16 <a href="{url|urlescape}help/{topic|escape}{sessionvars%urlparameter}">
17 17 {if(basename, '{basename|escape}', '{topic|escape}')}
18 18 </a>
19 19 </td><td>
20 20 {summary|escape}
21 21 </td></tr>'
22 22
23 23 naventry = '<a href="{url|urlescape}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
24 24 navshortentry = '<a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
25 25 navgraphentry = '<a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
26 26 filenaventry = '<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
27 27 filedifflink = '<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a> '
28 28 filenodelink = '
29 29 <tr class="parity{parity}">
30 30 <td><a class="list" href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a></td>
31 31 <td></td>
32 32 <td class="link">
33 33 <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
34 34 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
35 35 <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
36 36 <a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
37 37 <a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
38 38 </td>
39 39 </tr>'
40 40 filenolink = '
41 41 <tr class="parity{parity}">
42 42 <td><a class="list" href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a></td>
43 43 <td></td>
44 44 <td class="link">
45 45 file |
46 46 annotate |
47 47 <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
48 48 <a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
49 49 <a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
50 50 </td>
51 51 </tr>'
52 52
53 53 nav = '{before%naventry} {after%naventry}'
54 54 navshort = '{before%navshortentry}{after%navshortentry}'
55 55 navgraph = '{before%navgraphentry}{after%navgraphentry}'
56 56 filenav = '{before%filenaventry}{after%filenaventry}'
57 57
58 58 fileellipses = '...'
59 59 changelogentry = changelogentry.tmpl
60 60 searchentry = changelogentry.tmpl
61 61 changeset = changeset.tmpl
62 62 manifest = manifest.tmpl
63 63 direntry = '
64 64 <tr class="parity{parity}">
65 65 <td style="font-family:monospace">drwxr-xr-x</td>
66 66 <td style="font-family:monospace"></td>
67 67 <td style="font-family:monospace"></td>
68 68 <td>
69 69 <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>
70 70 <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">{emptydirs|escape}</a>
71 71 </td>
72 72 <td class="link">
73 73 <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a>
74 74 </td>
75 75 </tr>'
76 76 fileentry = '
77 77 <tr class="parity{parity}">
78 78 <td style="font-family:monospace">{permissions|permissions}</td>
79 79 <td style="font-family:monospace" align=right>{date|isodate}</td>
80 80 <td style="font-family:monospace" align=right>{size}</td>
81 81 <td class="list">
82 82 <a class="list" href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>
83 83 </td>
84 84 <td class="link">
85 85 <a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
86 86 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
87 87 <a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
88 88 </td>
89 89 </tr>'
90 90 filerevision = filerevision.tmpl
91 91 fileannotate = fileannotate.tmpl
92 92 filediff = filediff.tmpl
93 93 filecomparison = filecomparison.tmpl
94 94 filelog = filelog.tmpl
95 95 fileline = '
96 96 <a href="#{lineid}"></a><span id="{lineid}">{strip(line|escape, '\r\n')}</span>'
97 97 annotateline = '
98 98 <tr id="{lineid}" style="font-family:monospace" class="parity{parity}{ifeq(node, originalnode, ' thisrev')}">
99 99 <td class="annotate linenr parity{blockparity}" style="text-align: right;">
100 100 {if(blockhead,
101 101 '<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}">
102 102 {rev}
103 103 </a>')}
104 104 <div class="annotate-info">
105 105 <div>
106 106 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}">
107 107 {node|short}</a>
108 108 {desc|escape|firstline}
109 109 </div>
110 110 <div><em>{author|obfuscate}</em></div>
111 111 <div>parents: {parents%annotateparent}</div>
112 112 <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a>
113 113 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
114 114 </div>
115 115 </td>
116 116 <td class="followlines-btn-parent"><pre><a class="linenr" href="#{lineid}">{linenumber}</a></pre></td>
117 117 <td><pre>{line|escape}</pre></td>
118 118 </tr>'
119 119 annotateparent = '
120 120 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rev}</a>'
121 121 difflineplus = '
122 122 <a href="#{lineid}"></a><span id="{lineid}" class="difflineplus">{strip(line|escape, '\r\n')}</span>'
123 123 difflineminus = '
124 124 <a href="#{lineid}"></a><span id="{lineid}" class="difflineminus">{strip(line|escape, '\r\n')}</span>'
125 125 difflineat = '
126 126 <a href="#{lineid}"></a><span id="{lineid}" class="difflineat">{strip(line|escape, '\r\n')}</span>'
127 127 diffline = '
128 128 <a href="#{lineid}"></a><span id="{lineid}">{strip(line|escape, '\r\n')}</span>'
129 129
130 130 comparisonblock ='
131 131 <tbody class="block">
132 132 {lines}
133 133 </tbody>'
134 134 comparisonline = '
135 135 <tr id="{lineid}" style="font-family:monospace">
136 136 <td class="{type}"><pre><a class="linenr" href="#{lineid}">{leftlinenumber}</a> {leftline|escape}</pre></td>
137 137 <td class="{type}"><pre><a class="linenr" href="#{lineid}">{rightlinenumber}</a> {rightline|escape}</pre></td>
138 138 </tr>'
139 139
140 140 changesetlink = '<a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>'
141 141 changesetbranch = '<tr><td>branch</td><td>{name|escape}</td></tr>'
142 142 changesetparent = '
143 143 <tr>
144 144 <td>parent {rev}</td>
145 145 <td style="font-family:monospace">
146 146 {changesetlink}
147 147 </td>
148 148 </tr>'
149 149 changesetparentdiff = '
150 150 <tr>
151 151 <td>parent {rev}</td>
152 152 <td style="font-family:monospace">
153 153 {changesetlink} {ifeq(node, basenode, '(current diff)', '({difffrom})')}
154 154 </td>
155 155 </tr>'
156 156 difffrom = '<a href="{url|urlescape}rev/{node|short}:{originalnode|short}{sessionvars%urlparameter}">diff</a>'
157 157 filerevbranch = '<tr><td>branch</td><td>{name|escape}</td></tr>'
158 158 filerevparent = '
159 159 <tr>
160 160 <td>parent {rev}</td>
161 161 <td style="font-family:monospace">
162 162 <a class="list" href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
163 163 {rename%filerename}{node|short}
164 164 </a>
165 165 </td>
166 166 </tr>'
167 167 filerename = '{file|escape}@'
168 168 filelogrename = '| <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">base</a>'
169 169 fileannotateparent = '
170 170 <tr>
171 171 <td>parent {rev}</td>
172 172 <td style="font-family:monospace">
173 173 <a class="list" href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
174 174 {rename%filerename}{node|short}
175 175 </a>
176 176 </td>
177 177 </tr>'
178 178 changesetchild = '
179 179 <tr>
180 180 <td>child {rev}</td>
181 181 <td style="font-family:monospace">
182 182 <a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
183 183 </td>
184 184 </tr>'
185 185 filerevchild = '
186 186 <tr>
187 187 <td>child {rev}</td>
188 188 <td style="font-family:monospace">
189 189 <a class="list" href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td>
190 190 </tr>'
191 191 fileannotatechild = '
192 192 <tr>
193 193 <td>child {rev}</td>
194 194 <td style="font-family:monospace">
195 195 <a class="list" href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td>
196 196 </tr>'
197 197 tags = tags.tmpl
198 198 tagentry = '
199 199 <tr class="parity{parity}">
200 200 <td class="age"><i class="age">{date|rfc822date}</i></td>
201 201 <td><a class="list" href="{url|urlescape}rev/{tag|revescape}{sessionvars%urlparameter}"><b>{tag|escape}</b></a></td>
202 202 <td class="link">
203 203 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
204 204 <a href="{url|urlescape}log/{node|short}{sessionvars%urlparameter}">changelog</a> |
205 205 <a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
206 206 </td>
207 207 </tr>'
208 208 bookmarks = bookmarks.tmpl
209 209 bookmarkentry = '
210 210 <tr class="parity{parity}">
211 211 <td class="age"><i class="age">{date|rfc822date}</i></td>
212 212 <td><a class="list" href="{url|urlescape}rev/{bookmark|revescape}{sessionvars%urlparameter}"><b>{bookmark|escape}</b></a></td>
213 213 <td class="link">
214 214 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
215 215 <a href="{url|urlescape}log/{node|short}{sessionvars%urlparameter}">changelog</a> |
216 216 <a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
217 217 </td>
218 218 </tr>'
219 219 branches = branches.tmpl
220 220 branchentry = '
221 221 <tr class="parity{parity}">
222 222 <td class="age"><i class="age">{date|rfc822date}</i></td>
223 223 <td class="{status}"><a class="list" href="{url|urlescape}shortlog/{branch|revescape}{sessionvars%urlparameter}"><b>{branch|escape}</b></a></td>
224 224 <td class="link">
225 225 <a href="{url|urlescape}changeset/{node|short}{sessionvars%urlparameter}">changeset</a> |
226 226 <a href="{url|urlescape}log/{node|short}{sessionvars%urlparameter}">changelog</a> |
227 227 <a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
228 228 </td>
229 229 </tr>'
230 230 diffblock = '<div class="diffblock"><pre class="sourcelines">{lines}</pre></div>'
231 231 filediffparent = '
232 232 <tr>
233 233 <td>parent {rev}</td>
234 234 <td style="font-family:monospace">
235 235 <a class="list" href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
236 236 {node|short}
237 237 </a>
238 238 </td>
239 239 </tr>'
240 240 filecompparent = '
241 241 <tr>
242 242 <td>parent {rev}</td>
243 243 <td style="font-family:monospace">
244 244 <a class="list" href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
245 245 {node|short}
246 246 </a>
247 247 </td>
248 248 </tr>'
249 249 filediffchild = '
250 250 <tr>
251 251 <td>child {rev}</td>
252 252 <td style="font-family:monospace">
253 253 <a class="list" href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a>
254 254 </td>
255 255 </tr>'
256 256 filecompchild = '
257 257 <tr>
258 258 <td>child {rev}</td>
259 259 <td style="font-family:monospace">
260 260 <a class="list" href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a>
261 261 </td>
262 262 </tr>'
263 263 shortlog = shortlog.tmpl
264 264 graph = graph.tmpl
265 265 tagtag = '<span class="tagtag" title="{name|escape}">{name|escape}</span> '
266 266 branchtag = '<span class="branchtag" title="{name|escape}">{name|escape}</span> '
267 267 inbranchtag = '<span class="inbranchtag" title="{name|escape}">{name|escape}</span> '
268 268 bookmarktag = '<span class="bookmarktag" title="{name|escape}">{name|escape}</span> '
269 269 shortlogentry = '
270 270 <tr class="parity{parity}">
271 271 <td class="age"><i class="age">{date|rfc822date}</i></td>
272 272 <td><i>{author|person}</i></td>
273 273 <td>
274 274 <a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
275 275 <b>{desc|strip|firstline|escape|nonempty}</b>
276 276 <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
277 277 </a>
278 278 </td>
279 279 <td class="link" nowrap>
280 280 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
281 281 <a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
282 282 </td>
283 283 </tr>'
284 284 filelogentry = '
285 285 <tr class="parity{if(patch, '1', '{parity}')}">
286 286 <td class="age"><i class="age">{date|rfc822date}</i></td>
287 287 <td><i>{author|person}</i></td>
288 288 <td>
289 289 <a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
290 290 <b>{desc|strip|firstline|escape|nonempty}</b>
291 291 <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
292 292 </a>
293 293 </td>
294 294 <td class="link">
295 295 <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
296 296 <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
297 297 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
298 298 {rename%filelogrename}
299 299 </td>
300 300 </tr>
301 301 {if(patch, '<tr><td colspan="4">{diff}</td></tr>')}'
302 302 archiveentry = ' | <a href="{url|urlescape}archive/{symrev}{extension}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a> '
303 303 indexentry = '
304 304 <tr class="parity{parity}">
305 305 <td>
306 306 <a class="list" href="{url|urlescape}{sessionvars%urlparameter}">
307 307 <b>{name|escape}</b>
308 308 </a>
309 309 </td>
310 310 <td>{description}</td>
311 311 <td>{contact|obfuscate}</td>
312 312 <td class="age">{lastchange|rfc822date}</td>
313 313 <td class="indexlinks">{archives%indexarchiveentry}</td>
314 314 <td>{if(isdirectory, '',
315 315 '<div class="rss_logo">
316 316 <a href="{url|urlescape}rss-log">RSS</a> <a href="{url|urlescape}atom-log">Atom</a>
317 317 </div>'
318 318 )}
319 319 </td>
320 320 </tr>\n'
321 321 indexarchiveentry = ' <a href="{url|urlescape}archive/{node|short}{extension}">{type|escape}</a> '
322 322 index = index.tmpl
323 323 urlparameter = '{separator}{name}={value|urlescape}'
324 324 hiddenformentry = '<input type="hidden" name="{name}" value="{value|escape}" />'
325 325 breadcrumb = '&gt; <a href="{url|urlescape}">{name|escape}</a> '
326 326
327 327 searchform = '
328 328 <div class="search">
329 329 <form id="searchform" action="{url|urlescape}log">
330 330 {sessionvars%hiddenformentry}
331 331 <input name="rev" type="text" value="{query|escape}" size="40" />
332 332 <div id="hint">{searchhint}</div>
333 333 </form>
334 334 </div>'
335 335 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
336 336 number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
337
338 diffoptsform = '
339 <form id="diffopts-form"
340 data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
341 data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
342 data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
343 data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
344 <span>Ignore whitespace changes - </span>
345 <span>Everywhere:</span>
346 <input id="ignorews-checkbox" type="checkbox" />
347 <span>Within whitespace:</span>
348 <input id="ignorewsamount-checkbox" type="checkbox" />
349 <span>At end of lines:</span>
350 <input id="ignorewseol-checkbox" type="checkbox" />
351 </form>
352 </div>'
@@ -1,89 +1,95 b''
1 1 {header}
2 2 <title>{repo|escape}: {file|escape} annotate</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="container">
7 7 <div class="menu">
8 8 <div class="logo">
9 9 <a href="{logourl}">
10 10 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
11 11 </div>
12 12 <ul>
13 13 <li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
14 14 <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
15 15 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
16 16 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
17 17 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
18 18 </ul>
19 19
20 20 <ul>
21 21 <li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
22 22 <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
23 23 </ul>
24 24 <ul>
25 25 <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
26 26 <li><a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a></li>
27 27 <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
28 28 <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
29 29 <li class="active">annotate</li>
30 30 <li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
31 31 <li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
32 32 </ul>
33 33 <ul>
34 34 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
35 35 </ul>
36 36 </div>
37 37
38 38 <div class="main">
39 39 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
40 40 <h3>
41 41 annotate {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
42 42 {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
43 43 </h3>
44 44
45 45 {searchform}
46 46
47 47 <div class="description">{desc|strip|escape|websub|nonempty}</div>
48 48
49 49 <table id="changesetEntry">
50 50 <tr>
51 51 <th class="author">author</th>
52 52 <td class="author">{author|obfuscate}</td>
53 53 </tr>
54 54 <tr>
55 55 <th class="date">date</th>
56 56 <td class="date age">{date|rfc822date}</td>
57 57 </tr>
58 58 <tr>
59 59 <th class="author">parents</th>
60 60 <td class="author">{parent%filerevparent}</td>
61 61 </tr>
62 62 <tr>
63 63 <th class="author">children</th>
64 64 <td class="author">{child%filerevchild}</td>
65 65 </tr>
66 66 </table>
67 67
68 {diffoptsform}
69
70 <script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
71 renderDiffOptsForm();
72 </script>
73
68 74 <div class="overflow">
69 75 <table class="bigtable">
70 76 <thead>
71 77 <tr>
72 78 <th class="annotate">rev</th>
73 79 <th class="line">&nbsp;&nbsp;line source</th>
74 80 </tr>
75 81 </thead>
76 82 <tbody class="stripes2 sourcelines"
77 83 data-logurl="{url|urlescape}log/{symrev}/{file|urlescape}"
78 84 data-selectabletag="TR"
79 85 data-ishead="{ishead}">
80 86 {annotate%annotateline}
81 87 </tbody>
82 88 </table>
83 89 </div>
84 90 </div>
85 91 </div>
86 92
87 93 <script type="text/javascript" src="{staticurl|urlescape}followlines.js"></script>
88 94
89 95 {footer}
@@ -1,253 +1,268 b''
1 1 default = 'shortlog'
2 2
3 3 mimetype = 'text/html; charset={encoding}'
4 4 header = header.tmpl
5 5 footer = footer.tmpl
6 6 search = search.tmpl
7 7
8 8 changelog = shortlog.tmpl
9 9 shortlog = shortlog.tmpl
10 10 shortlogentry = shortlogentry.tmpl
11 11 graph = graph.tmpl
12 12 help = help.tmpl
13 13 helptopics = helptopics.tmpl
14 14
15 15 helpentry = '
16 16 <tr><td>
17 17 <a href="{url|urlescape}help/{topic|escape}{sessionvars%urlparameter}">
18 18 {if(basename, '{basename|escape}', '{topic|escape}')}
19 19 </a>
20 20 </td><td>
21 21 {summary|escape}
22 22 </td></tr>'
23 23
24 24 naventry = '<a href="{url|urlescape}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
25 25 navshortentry = '<a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
26 26 navgraphentry = '<a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
27 27 filenaventry = '<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
28 28 filedifflink = '<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a> '
29 29 filenodelink = '<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a> '
30 30 filenolink = '{file|escape} '
31 31 fileellipses = '...'
32 32 diffstatlink = diffstat.tmpl
33 33 diffstatnolink = diffstat.tmpl
34 34 changelogentry = shortlogentry.tmpl
35 35 searchentry = shortlogentry.tmpl
36 36 changeset = changeset.tmpl
37 37 manifest = manifest.tmpl
38 38
39 39 nav = '{before%naventry} {after%naventry}'
40 40 navshort = '{before%navshortentry}{after%navshortentry}'
41 41 navgraph = '{before%navgraphentry}{after%navgraphentry}'
42 42 filenav = '{before%filenaventry}{after%filenaventry}'
43 43
44 44 direntry = '
45 45 <tr class="fileline">
46 46 <td class="name">
47 47 <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">
48 48 <img src="{staticurl|urlescape}coal-folder.png" alt="dir."/> {basename|escape}/
49 49 </a>
50 50 <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
51 51 {emptydirs|escape}
52 52 </a>
53 53 </td>
54 54 <td class="size"></td>
55 55 <td class="permissions">drwxr-xr-x</td>
56 56 </tr>'
57 57
58 58 fileentry = '
59 59 <tr class="fileline">
60 60 <td class="filename">
61 61 <a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">
62 62 <img src="{staticurl|urlescape}coal-file.png" alt="file"/> {basename|escape}
63 63 </a>
64 64 </td>
65 65 <td class="size">{size}</td>
66 66 <td class="permissions">{permissions|permissions}</td>
67 67 </tr>'
68 68
69 69 filerevision = filerevision.tmpl
70 70 fileannotate = fileannotate.tmpl
71 71 filediff = filediff.tmpl
72 72 filecomparison = filecomparison.tmpl
73 73 filelog = filelog.tmpl
74 74 fileline = '
75 75 <span id="{lineid}">{strip(line|escape, '\r\n')}</span><a href="#{lineid}"></a>'
76 76 filelogentry = filelogentry.tmpl
77 77
78 78 annotateline = '
79 79 <tr id="{lineid}"{ifeq(node, originalnode, ' class="thisrev"')}>
80 80 <td class="annotate parity{blockparity}">
81 81 {if(blockhead,
82 82 '<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}">
83 83 {rev}
84 84 </a>')}
85 85 <div class="annotate-info">
86 86 <div>
87 87 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}">
88 88 {node|short}</a>
89 89 {desc|escape|firstline}
90 90 </div>
91 91 <div><em>{author|obfuscate}</em></div>
92 92 <div>parents: {parents%annotateparent}</div>
93 93 <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a>
94 94 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
95 95 </div>
96 96 </td>
97 97 <td class="source followlines-btn-parent"><a href="#{lineid}">{linenumber}</a> {line|escape}</td>
98 98 </tr>'
99 99 annotateparent = '
100 100 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rev}</a>'
101 101 diffblock = '<div class="bottomline inc-lineno"><pre class="sourcelines wrap">{lines}</pre></div>'
102 102 difflineplus = '
103 103 <span id="{lineid}" class="plusline">{strip(line|escape, '\r\n')}</span><a href="#{lineid}"></a>'
104 104 difflineminus = '
105 105 <span id="{lineid}" class="minusline">{strip(line|escape, '\r\n')}</span><a href="#{lineid}"></a>'
106 106 difflineat = '
107 107 <span id="{lineid}" class="atline">{strip(line|escape, '\r\n')}</span><a href="#{lineid}"></a>'
108 108 diffline = '
109 109 <span id="{lineid}">{strip(line|escape, '\r\n')}</span><a href="#{lineid}"></a>'
110 110
111 111 comparisonblock ='
112 112 <tbody class="block">
113 113 {lines}
114 114 </tbody>'
115 115 comparisonline = '
116 116 <tr id="{lineid}">
117 117 <td class="source {type}"><a href="#{lineid}">{leftlinenumber}</a> {leftline|escape}</td>
118 118 <td class="source {type}"><a href="#{lineid}">{rightlinenumber}</a> {rightline|escape}</td>
119 119 </tr>'
120 120
121 121 changesetparent = '<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a> '
122 122
123 123 changesetparentdiff = '
124 124 {changesetparent}
125 125 {ifeq(node, basenode, '(current diff)', '({difffrom})')}'
126 126
127 127 difffrom = '<a href="{url|urlescape}rev/{node|short}:{originalnode|short}{sessionvars%urlparameter}">diff</a>'
128 128
129 129 filerevparent = '<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a> '
130 130 filerevchild = '<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a> '
131 131
132 132 filerename = '{file|escape}@'
133 133 filelogrename = '
134 134 <span class="base">
135 135 base
136 136 <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
137 137 {file|escape}@{node|short}
138 138 </a>
139 139 </span>'
140 140 fileannotateparent = '
141 141 <tr>
142 142 <td class="metatag">parent:</td>
143 143 <td>
144 144 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
145 145 {rename%filerename}{node|short}
146 146 </a>
147 147 </td>
148 148 </tr>'
149 149 changesetchild = ' <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>'
150 150 fileannotatechild = '
151 151 <tr>
152 152 <td class="metatag">child:</td>
153 153 <td>
154 154 <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
155 155 {node|short}
156 156 </a>
157 157 </td>
158 158 </tr>'
159 159 tags = tags.tmpl
160 160 tagentry = '
161 161 <tr class="tagEntry">
162 162 <td>
163 163 <a href="{url|urlescape}rev/{tag|revescape}{sessionvars%urlparameter}">
164 164 {tag|escape}
165 165 </a>
166 166 </td>
167 167 <td class="node">
168 168 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
169 169 {node|short}
170 170 </a>
171 171 </td>
172 172 </tr>'
173 173 bookmarks = bookmarks.tmpl
174 174 bookmarkentry = '
175 175 <tr class="tagEntry">
176 176 <td>
177 177 <a href="{url|urlescape}rev/{bookmark|revescape}{sessionvars%urlparameter}">
178 178 {bookmark|escape}
179 179 </a>
180 180 </td>
181 181 <td class="node">
182 182 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
183 183 {node|short}
184 184 </a>
185 185 </td>
186 186 </tr>'
187 187 branches = branches.tmpl
188 188 branchentry = '
189 189 <tr class="tagEntry">
190 190 <td>
191 191 <a href="{url|urlescape}shortlog/{branch|revescape}{sessionvars%urlparameter}" class="{status}">
192 192 {branch|escape}
193 193 </a>
194 194 </td>
195 195 <td class="node">
196 196 <a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}" class="{status}">
197 197 {node|short}
198 198 </a>
199 199 </td>
200 200 </tr>'
201 201 changelogtag = '<span class="tag">{name|escape}</span> '
202 202 changesettag = '<span class="tag">{tag|escape}</span> '
203 203 changesetbookmark = '<span class="tag">{bookmark|escape}</span> '
204 204 changelogbranchhead = '<span class="branchhead">{name|escape}</span> '
205 205 changelogbranchname = '<span class="branchname">{name|escape}</span> '
206 206
207 207 filediffparent = '
208 208 <tr>
209 209 <th class="parent">parent {rev}:</th>
210 210 <td class="parent"><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
211 211 </tr>'
212 212 filediffchild = '
213 213 <tr>
214 214 <th class="child">child {rev}:</th>
215 215 <td class="child"><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
216 216 </td>
217 217 </tr>'
218 218
219 219 indexentry = '
220 220 <tr>
221 221 <td><a href="{url|urlescape}{sessionvars%urlparameter}">{name|escape}</a></td>
222 222 <td>{description}</td>
223 223 <td>{contact|obfuscate}</td>
224 224 <td class="age">{lastchange|rfc822date}</td>
225 225 <td class="indexlinks">{archives%indexarchiveentry}</td>
226 226 <td>
227 227 {if(isdirectory, '',
228 228 '<a href="{url|urlescape}atom-log" title="subscribe to repository atom feed">
229 229 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="subscribe to repository atom feed">
230 230 </a>'
231 231 )}
232 232 </td>
233 233 </tr>\n'
234 234 indexarchiveentry = '<a href="{url|urlescape}archive/{node|short}{extension|urlescape}">&nbsp;&darr;{type|escape}</a>'
235 235 index = index.tmpl
236 236 archiveentry = '
237 237 <li>
238 238 <a href="{url|urlescape}archive/{symrev}{extension|urlescape}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a>
239 239 </li>'
240 240 notfound = notfound.tmpl
241 241 error = error.tmpl
242 242 urlparameter = '{separator}{name}={value|urlescape}'
243 243 hiddenformentry = '<input type="hidden" name="{name}" value="{value|escape}" />'
244 244 breadcrumb = '&gt; <a href="{url|urlescape}">{name|escape}</a> '
245 245
246 246 searchform = '
247 247 <form class="search" action="{url|urlescape}log">
248 248 {sessionvars%hiddenformentry}
249 249 <p><input name="rev" id="search1" type="text" size="30" value="{query|escape}" /></p>
250 250 <div id="hint">{searchhint}</div>
251 251 </form>'
252 252 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
253 253 number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
254
255 diffoptsform = '
256 <form id="diffopts-form"
257 data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
258 data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
259 data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
260 data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
261 <span>Ignore whitespace changes - </span>
262 <span>Everywhere:</span>
263 <input id="ignorews-checkbox" type="checkbox" />
264 <span>Within whitespace:</span>
265 <input id="ignorewsamount-checkbox" type="checkbox" />
266 <span>At end of lines:</span>
267 <input id="ignorewseol-checkbox" type="checkbox" />
268 </form>'
@@ -1,439 +1,489 b''
1 1 // mercurial.js - JavaScript utility functions
2 2 //
3 3 // Rendering of branch DAGs on the client side
4 4 // Display of elapsed time
5 5 // Show or hide diffstat
6 6 //
7 7 // Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl>
8 8 // Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de>
9 9 //
10 10 // derived from code written by Scott James Remnant <scott@ubuntu.com>
11 11 // Copyright 2005 Canonical Ltd.
12 12 //
13 13 // This software may be used and distributed according to the terms
14 14 // of the GNU General Public License, incorporated herein by reference.
15 15
16 16 var colors = [
17 17 [ 1.0, 0.0, 0.0 ],
18 18 [ 1.0, 1.0, 0.0 ],
19 19 [ 0.0, 1.0, 0.0 ],
20 20 [ 0.0, 1.0, 1.0 ],
21 21 [ 0.0, 0.0, 1.0 ],
22 22 [ 1.0, 0.0, 1.0 ]
23 23 ];
24 24
25 25 function Graph() {
26 26
27 27 this.canvas = document.getElementById('graph');
28 28 if (window.G_vmlCanvasManager) this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);
29 29 this.ctx = this.canvas.getContext('2d');
30 30 this.ctx.strokeStyle = 'rgb(0, 0, 0)';
31 31 this.ctx.fillStyle = 'rgb(0, 0, 0)';
32 32 this.cur = [0, 0];
33 33 this.line_width = 3;
34 34 this.bg = [0, 4];
35 35 this.cell = [2, 0];
36 36 this.columns = 0;
37 37 this.revlink = '';
38 38
39 39 this.reset = function() {
40 40 this.bg = [0, 4];
41 41 this.cell = [2, 0];
42 42 this.columns = 0;
43 43 document.getElementById('nodebgs').innerHTML = '';
44 44 document.getElementById('graphnodes').innerHTML = '';
45 45 }
46 46
47 47 this.scale = function(height) {
48 48 this.bg_height = height;
49 49 this.box_size = Math.floor(this.bg_height / 1.2);
50 50 this.cell_height = this.box_size;
51 51 }
52 52
53 53 this.setColor = function(color, bg, fg) {
54 54
55 55 // Set the colour.
56 56 //
57 57 // If color is a string, expect an hexadecimal RGB
58 58 // value and apply it unchanged. If color is a number,
59 59 // pick a distinct colour based on an internal wheel;
60 60 // the bg parameter provides the value that should be
61 61 // assigned to the 'zero' colours and the fg parameter
62 62 // provides the multiplier that should be applied to
63 63 // the foreground colours.
64 64 var s;
65 65 if(typeof color == "string") {
66 66 s = "#" + color;
67 67 } else { //typeof color == "number"
68 68 color %= colors.length;
69 69 var red = (colors[color][0] * fg) || bg;
70 70 var green = (colors[color][1] * fg) || bg;
71 71 var blue = (colors[color][2] * fg) || bg;
72 72 red = Math.round(red * 255);
73 73 green = Math.round(green * 255);
74 74 blue = Math.round(blue * 255);
75 75 s = 'rgb(' + red + ', ' + green + ', ' + blue + ')';
76 76 }
77 77 this.ctx.strokeStyle = s;
78 78 this.ctx.fillStyle = s;
79 79 return s;
80 80
81 81 }
82 82
83 83 this.edge = function(x0, y0, x1, y1, color, width) {
84 84
85 85 this.setColor(color, 0.0, 0.65);
86 86 if(width >= 0)
87 87 this.ctx.lineWidth = width;
88 88 this.ctx.beginPath();
89 89 this.ctx.moveTo(x0, y0);
90 90 this.ctx.lineTo(x1, y1);
91 91 this.ctx.stroke();
92 92
93 93 }
94 94
95 95 this.render = function(data) {
96 96
97 97 var backgrounds = '';
98 98 var nodedata = '';
99 99
100 100 for (var i in data) {
101 101
102 102 var parity = i % 2;
103 103 this.cell[1] += this.bg_height;
104 104 this.bg[1] += this.bg_height;
105 105
106 106 var cur = data[i];
107 107 var node = cur[1];
108 108 var edges = cur[2];
109 109 var fold = false;
110 110
111 111 var prevWidth = this.ctx.lineWidth;
112 112 for (var j in edges) {
113 113
114 114 line = edges[j];
115 115 start = line[0];
116 116 end = line[1];
117 117 color = line[2];
118 118 var width = line[3];
119 119 if(width < 0)
120 120 width = prevWidth;
121 121 var branchcolor = line[4];
122 122 if(branchcolor)
123 123 color = branchcolor;
124 124
125 125 if (end > this.columns || start > this.columns) {
126 126 this.columns += 1;
127 127 }
128 128
129 129 if (start == this.columns && start > end) {
130 130 var fold = true;
131 131 }
132 132
133 133 x0 = this.cell[0] + this.box_size * start + this.box_size / 2;
134 134 y0 = this.bg[1] - this.bg_height / 2;
135 135 x1 = this.cell[0] + this.box_size * end + this.box_size / 2;
136 136 y1 = this.bg[1] + this.bg_height / 2;
137 137
138 138 this.edge(x0, y0, x1, y1, color, width);
139 139
140 140 }
141 141 this.ctx.lineWidth = prevWidth;
142 142
143 143 // Draw the revision node in the right column
144 144
145 145 column = node[0]
146 146 color = node[1]
147 147
148 148 radius = this.box_size / 8;
149 149 x = this.cell[0] + this.box_size * column + this.box_size / 2;
150 150 y = this.bg[1] - this.bg_height / 2;
151 151 var add = this.vertex(x, y, color, parity, cur);
152 152 backgrounds += add[0];
153 153 nodedata += add[1];
154 154
155 155 if (fold) this.columns -= 1;
156 156
157 157 }
158 158
159 159 document.getElementById('nodebgs').innerHTML += backgrounds;
160 160 document.getElementById('graphnodes').innerHTML += nodedata;
161 161
162 162 }
163 163
164 164 }
165 165
166 166
167 167 function process_dates(parentSelector){
168 168
169 169 // derived from code from mercurial/templatefilter.py
170 170
171 171 var scales = {
172 172 'year': 365 * 24 * 60 * 60,
173 173 'month': 30 * 24 * 60 * 60,
174 174 'week': 7 * 24 * 60 * 60,
175 175 'day': 24 * 60 * 60,
176 176 'hour': 60 * 60,
177 177 'minute': 60,
178 178 'second': 1
179 179 };
180 180
181 181 function format(count, string){
182 182 var ret = count + ' ' + string;
183 183 if (count > 1){
184 184 ret = ret + 's';
185 185 }
186 186 return ret;
187 187 }
188 188
189 189 function shortdate(date){
190 190 var ret = date.getFullYear() + '-';
191 191 // getMonth() gives a 0-11 result
192 192 var month = date.getMonth() + 1;
193 193 if (month <= 9){
194 194 ret += '0' + month;
195 195 } else {
196 196 ret += month;
197 197 }
198 198 ret += '-';
199 199 var day = date.getDate();
200 200 if (day <= 9){
201 201 ret += '0' + day;
202 202 } else {
203 203 ret += day;
204 204 }
205 205 return ret;
206 206 }
207 207
208 208 function age(datestr){
209 209 var now = new Date();
210 210 var once = new Date(datestr);
211 211 if (isNaN(once.getTime())){
212 212 // parsing error
213 213 return datestr;
214 214 }
215 215
216 216 var delta = Math.floor((now.getTime() - once.getTime()) / 1000);
217 217
218 218 var future = false;
219 219 if (delta < 0){
220 220 future = true;
221 221 delta = -delta;
222 222 if (delta > (30 * scales.year)){
223 223 return "in the distant future";
224 224 }
225 225 }
226 226
227 227 if (delta > (2 * scales.year)){
228 228 return shortdate(once);
229 229 }
230 230
231 231 for (unit in scales){
232 232 var s = scales[unit];
233 233 var n = Math.floor(delta / s);
234 234 if ((n >= 2) || (s == 1)){
235 235 if (future){
236 236 return format(n, unit) + ' from now';
237 237 } else {
238 238 return format(n, unit) + ' ago';
239 239 }
240 240 }
241 241 }
242 242 }
243 243
244 244 var nodes = document.querySelectorAll((parentSelector || '') + ' .age');
245 245 var dateclass = new RegExp('\\bdate\\b');
246 246 for (var i=0; i<nodes.length; ++i){
247 247 var node = nodes[i];
248 248 var classes = node.className;
249 249 var agevalue = age(node.textContent);
250 250 if (dateclass.test(classes)){
251 251 // We want both: date + (age)
252 252 node.textContent += ' ('+agevalue+')';
253 253 } else {
254 254 node.title = node.textContent;
255 255 node.textContent = agevalue;
256 256 }
257 257 }
258 258 }
259 259
260 260 function toggleDiffstat() {
261 261 var curdetails = document.getElementById('diffstatdetails').style.display;
262 262 var curexpand = curdetails == 'none' ? 'inline' : 'none';
263 263 document.getElementById('diffstatdetails').style.display = curexpand;
264 264 document.getElementById('diffstatexpand').style.display = curdetails;
265 265 }
266 266
267 267 function toggleLinewrap() {
268 268 function getLinewrap() {
269 269 var nodes = document.getElementsByClassName('sourcelines');
270 270 // if there are no such nodes, error is thrown here
271 271 return nodes[0].classList.contains('wrap');
272 272 }
273 273
274 274 function setLinewrap(enable) {
275 275 var nodes = document.getElementsByClassName('sourcelines');
276 276 for (var i = 0; i < nodes.length; i++) {
277 277 if (enable) {
278 278 nodes[i].classList.add('wrap');
279 279 } else {
280 280 nodes[i].classList.remove('wrap');
281 281 }
282 282 }
283 283
284 284 var links = document.getElementsByClassName('linewraplink');
285 285 for (var i = 0; i < links.length; i++) {
286 286 links[i].innerHTML = enable ? 'on' : 'off';
287 287 }
288 288 }
289 289
290 290 setLinewrap(!getLinewrap());
291 291 }
292 292
293 293 function format(str, replacements) {
294 294 return str.replace(/%(\w+)%/g, function(match, p1) {
295 295 return String(replacements[p1]);
296 296 });
297 297 }
298 298
299 299 function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) {
300 300 xfr = new XMLHttpRequest();
301 301 xfr.onreadystatechange = function() {
302 302 if (xfr.readyState === 4) {
303 303 try {
304 304 if (xfr.status === 200) {
305 305 onsuccess(xfr.responseText);
306 306 } else {
307 307 throw 'server error';
308 308 }
309 309 } catch (e) {
310 310 onerror(e);
311 311 } finally {
312 312 oncomplete();
313 313 }
314 314 }
315 315 };
316 316
317 317 xfr.open(method, url);
318 318 xfr.overrideMimeType("text/xhtml; charset=" + document.characterSet.toLowerCase());
319 319 xfr.send();
320 320 onstart();
321 321 return xfr;
322 322 }
323 323
324 324 function removeByClassName(className) {
325 325 var nodes = document.getElementsByClassName(className);
326 326 while (nodes.length) {
327 327 nodes[0].parentNode.removeChild(nodes[0]);
328 328 }
329 329 }
330 330
331 331 function docFromHTML(html) {
332 332 var doc = document.implementation.createHTMLDocument('');
333 333 doc.documentElement.innerHTML = html;
334 334 return doc;
335 335 }
336 336
337 337 function appendFormatHTML(element, formatStr, replacements) {
338 338 element.insertAdjacentHTML('beforeend', format(formatStr, replacements));
339 339 }
340 340
341 341 function ajaxScrollInit(urlFormat,
342 342 nextPageVar,
343 343 nextPageVarGet,
344 344 containerSelector,
345 345 messageFormat,
346 346 mode) {
347 347 updateInitiated = false;
348 348 container = document.querySelector(containerSelector);
349 349
350 350 function scrollHandler() {
351 351 if (updateInitiated) {
352 352 return;
353 353 }
354 354
355 355 var scrollHeight = document.documentElement.scrollHeight;
356 356 var clientHeight = document.documentElement.clientHeight;
357 357 var scrollTop = document.body.scrollTop
358 358 || document.documentElement.scrollTop;
359 359
360 360 if (scrollHeight - (scrollTop + clientHeight) < 50) {
361 361 updateInitiated = true;
362 362 removeByClassName('scroll-loading-error');
363 363 container.lastElementChild.classList.add('scroll-separator');
364 364
365 365 if (!nextPageVar) {
366 366 var message = {
367 367 'class': 'scroll-loading-info',
368 368 text: 'No more entries'
369 369 };
370 370 appendFormatHTML(container, messageFormat, message);
371 371 return;
372 372 }
373 373
374 374 makeRequest(
375 375 format(urlFormat, {next: nextPageVar}),
376 376 'GET',
377 377 function onstart() {
378 378 var message = {
379 379 'class': 'scroll-loading',
380 380 text: 'Loading...'
381 381 };
382 382 appendFormatHTML(container, messageFormat, message);
383 383 },
384 384 function onsuccess(htmlText) {
385 385 if (mode == 'graph') {
386 386 var sizes = htmlText.match(/^\s*<canvas id="graph" width="(\d+)" height="(\d+)"><\/canvas>$/m);
387 387 var addWidth = sizes[1];
388 388 var addHeight = sizes[2];
389 389 addWidth = parseInt(addWidth);
390 390 addHeight = parseInt(addHeight);
391 391 graph.canvas.width = addWidth;
392 392 graph.canvas.height = addHeight;
393 393
394 394 var dataStr = htmlText.match(/^\s*var data = (.*);$/m)[1];
395 395 var data = JSON.parse(dataStr);
396 396 if (data.length < nextPageVar) {
397 397 nextPageVar = undefined;
398 398 }
399 399 graph.reset();
400 400 graph.render(data);
401 401 } else {
402 402 var doc = docFromHTML(htmlText);
403 403 var nodes = doc.querySelector(containerSelector).children;
404 404 var curClass = 'c' + Date.now();
405 405 while (nodes.length) {
406 406 var node = nodes[0];
407 407 node = document.adoptNode(node);
408 408 node.classList.add(curClass);
409 409 container.appendChild(node);
410 410 }
411 411 process_dates('.' + curClass);
412 412 }
413 413
414 414 nextPageVar = nextPageVarGet(htmlText, nextPageVar);
415 415 },
416 416 function onerror(errorText) {
417 417 var message = {
418 418 'class': 'scroll-loading-error',
419 419 text: 'Error: ' + errorText
420 420 };
421 421 appendFormatHTML(container, messageFormat, message);
422 422 },
423 423 function oncomplete() {
424 424 removeByClassName('scroll-loading');
425 425 updateInitiated = false;
426 426 scrollHandler();
427 427 }
428 428 );
429 429 }
430 430 }
431 431
432 432 window.addEventListener('scroll', scrollHandler);
433 433 window.addEventListener('resize', scrollHandler);
434 434 scrollHandler();
435 435 }
436 436
437 function renderDiffOptsForm() {
438 // We use URLSearchParams for query string manipulation. Old browsers don't
439 // support this API.
440 if (!("URLSearchParams" in window)) {
441 return;
442 }
443
444 var form = document.getElementById("diffopts-form");
445
446 var KEYS = [
447 "ignorews",
448 "ignorewsamount",
449 "ignorewseol",
450 "ignoreblanklines",
451 ];
452
453 var urlParams = new URLSearchParams(window.location.search);
454
455 function updateAndRefresh(e) {
456 var checkbox = e.target;
457 var name = checkbox.id.substr(0, checkbox.id.indexOf("-"));
458 urlParams.set(name, checkbox.checked ? "1" : "0");
459 window.location.search = urlParams.toString();
460 }
461
462 var allChecked = form.getAttribute("data-ignorews") == "1";
463
464 for (var i = 0; i < KEYS.length; i++) {
465 var key = KEYS[i];
466
467 var checkbox = document.getElementById(key + "-checkbox");
468 if (!checkbox) {
469 continue;
470 }
471
472 currentValue = form.getAttribute("data-" + key);
473 checkbox.checked = currentValue != "0";
474
475 // ignorews implies ignorewsamount and ignorewseol.
476 if (allChecked && (key == "ignorewsamount" || key == "ignorewseol")) {
477 checkbox.checked = true;
478 checkbox.disabled = true;
479 }
480
481 checkbox.addEventListener("change", updateAndRefresh, false);
482 }
483
484 form.style.display = 'block';
485 }
486
437 487 document.addEventListener('DOMContentLoaded', function() {
438 488 process_dates();
439 489 }, false);
@@ -1,384 +1,390 b''
1 1 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
2 2 a { color:#0000cc; }
3 3 a:hover, a:visited, a:active { color:#880000; }
4 4 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
5 5 div.page_header a:visited { color:#0000cc; }
6 6 div.page_header a:hover { color:#880000; }
7 7 div.page_nav {
8 8 padding:8px;
9 9 display: flex;
10 10 justify-content: space-between;
11 11 align-items: center;
12 12 }
13 13 div.page_nav a:visited { color:#0000cc; }
14 14 div.extra_nav {
15 15 padding: 8px;
16 16 }
17 17 div.extra_nav a:visited {
18 18 color: #0000cc;
19 19 }
20 20 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
21 21 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
22 22 div.page_footer_text { float:left; color:#555555; font-style:italic; }
23 23 div.page_body { padding:8px; }
24 24 div.title, a.title {
25 25 display:block; padding:6px 8px;
26 26 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
27 27 }
28 28 a.title:hover { background-color: #d9d8d1; }
29 29 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
30 30 div.log_body { padding:8px 8px 8px 150px; }
31 31 .age { white-space:nowrap; }
32 32 span.age { position:relative; float:left; width:142px; font-style:italic; }
33 33 div.log_link {
34 34 padding:0px 8px;
35 35 font-size:10px; font-family:sans-serif; font-style:normal;
36 36 position:relative; float:left; width:136px;
37 37 }
38 38 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
39 39 a.list { text-decoration:none; color:#000000; }
40 40 a.list:hover { text-decoration:underline; color:#880000; }
41 41 table { padding:8px 4px; }
42 42 th { padding:2px 5px; font-size:12px; text-align:left; }
43 43 .parity0 { background-color:#ffffff; }
44 44 tr.dark, .parity1, pre.sourcelines.stripes > :nth-child(4n+4) { background-color:#f6f6f0; }
45 45 tr.light:hover, .parity0:hover, tr.dark:hover, .parity1:hover,
46 46 pre.sourcelines.stripes > :nth-child(4n+2):hover,
47 47 pre.sourcelines.stripes > :nth-child(4n+4):hover,
48 48 pre.sourcelines.stripes > :nth-child(4n+1):hover + :nth-child(4n+2),
49 49 pre.sourcelines.stripes > :nth-child(4n+3):hover + :nth-child(4n+4) { background-color:#edece6; }
50 50 td { padding:2px 5px; font-size:12px; vertical-align:top; }
51 51 td.closed { background-color: #99f; }
52 52 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
53 53 td.indexlinks { white-space: nowrap; }
54 54 td.indexlinks a {
55 55 padding: 2px 5px; line-height: 10px;
56 56 border: 1px solid;
57 57 color: #ffffff; background-color: #7777bb;
58 58 border-color: #aaaadd #333366 #333366 #aaaadd;
59 59 font-weight: bold; text-align: center; text-decoration: none;
60 60 font-size: 10px;
61 61 }
62 62 td.indexlinks a:hover { background-color: #6666aa; }
63 63 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
64 64 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
65 65 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
66 66
67 67 .search {
68 68 margin-right: 8px;
69 69 }
70 70
71 71 div#hint {
72 72 position: absolute;
73 73 display: none;
74 74 width: 250px;
75 75 padding: 5px;
76 76 background: #ffc;
77 77 border: 1px solid yellow;
78 78 border-radius: 5px;
79 79 }
80 80
81 81 #searchform:hover div#hint { display: block; }
82 82
83 83 tr.thisrev a { color:#999999; text-decoration: none; }
84 84 tr.thisrev pre { color:#009900; }
85 85 td.annotate {
86 86 white-space: nowrap;
87 87 }
88 88 div.annotate-info {
89 89 z-index: 5;
90 90 display: none;
91 91 position: absolute;
92 92 background-color: #FFFFFF;
93 93 border: 1px solid #d9d8d1;
94 94 text-align: left;
95 95 color: #000000;
96 96 padding: 5px;
97 97 }
98 98 div.annotate-info a { color: #0000FF; text-decoration: underline; }
99 99 td.annotate:hover div.annotate-info { display: inline; }
100
101 #diffopts-form {
102 padding-left: 8px;
103 display: none;
104 }
105
100 106 .linenr { color:#999999; text-decoration:none }
101 107 div.rss_logo { float: right; white-space: nowrap; }
102 108 div.rss_logo a {
103 109 padding:3px 6px; line-height:10px;
104 110 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
105 111 color:#ffffff; background-color:#ff6600;
106 112 font-weight:bold; font-family:sans-serif; font-size:10px;
107 113 text-align:center; text-decoration:none;
108 114 }
109 115 div.rss_logo a:hover { background-color:#ee5500; }
110 116 pre { margin: 0; }
111 117 span.logtags span {
112 118 padding: 0px 4px;
113 119 font-size: 10px;
114 120 font-weight: normal;
115 121 border: 1px solid;
116 122 background-color: #ffaaff;
117 123 border-color: #ffccff #ff00ee #ff00ee #ffccff;
118 124 }
119 125 span.logtags span.tagtag {
120 126 background-color: #ffffaa;
121 127 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
122 128 }
123 129 span.logtags span.branchtag {
124 130 background-color: #aaffaa;
125 131 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
126 132 }
127 133 span.logtags span.inbranchtag {
128 134 background-color: #d5dde6;
129 135 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
130 136 }
131 137 span.logtags span.bookmarktag {
132 138 background-color: #afdffa;
133 139 border-color: #ccecff #46ace6 #46ace6 #ccecff;
134 140 }
135 141 span.difflineplus { color:#008800; }
136 142 span.difflineminus { color:#cc0000; }
137 143 span.difflineat { color:#990099; }
138 144 div.diffblocks { counter-reset: lineno; }
139 145 div.diffblock { counter-increment: lineno; }
140 146 pre.sourcelines { position: relative; counter-reset: lineno; }
141 147 pre.sourcelines > span {
142 148 display: inline-block;
143 149 box-sizing: border-box;
144 150 width: 100%;
145 151 padding: 0 0 0 5em;
146 152 counter-increment: lineno;
147 153 vertical-align: top;
148 154 }
149 155 pre.sourcelines > span:before {
150 156 -moz-user-select: -moz-none;
151 157 -khtml-user-select: none;
152 158 -webkit-user-select: none;
153 159 -ms-user-select: none;
154 160 user-select: none;
155 161 display: inline-block;
156 162 margin-left: -6em;
157 163 width: 4em;
158 164 color: #999;
159 165 text-align: right;
160 166 content: counters(lineno,".");
161 167 float: left;
162 168 }
163 169 pre.sourcelines > a {
164 170 display: inline-block;
165 171 position: absolute;
166 172 left: 0px;
167 173 width: 4em;
168 174 height: 1em;
169 175 }
170 176 tr:target td,
171 177 pre.sourcelines > span:target,
172 178 pre.sourcelines.stripes > span:target {
173 179 background-color: #bfdfff;
174 180 }
175 181
176 182 .description {
177 183 font-family: monospace;
178 184 white-space: pre;
179 185 }
180 186
181 187 /* Followlines */
182 188 tbody.sourcelines > tr.followlines-selected,
183 189 pre.sourcelines > span.followlines-selected {
184 190 background-color: #99C7E9 !important;
185 191 }
186 192
187 193 div#followlines {
188 194 background-color: #B7B7B7;
189 195 border: 1px solid #CCC;
190 196 border-radius: 5px;
191 197 padding: 4px;
192 198 position: fixed;
193 199 }
194 200
195 201 div.followlines-cancel {
196 202 text-align: right;
197 203 }
198 204
199 205 div.followlines-cancel > button {
200 206 line-height: 80%;
201 207 padding: 0;
202 208 border: 0;
203 209 border-radius: 2px;
204 210 background-color: inherit;
205 211 font-weight: bold;
206 212 }
207 213
208 214 div.followlines-cancel > button:hover {
209 215 color: #FFFFFF;
210 216 background-color: #CF1F1F;
211 217 }
212 218
213 219 div.followlines-link {
214 220 margin: 2px;
215 221 margin-top: 4px;
216 222 font-family: sans-serif;
217 223 }
218 224
219 225 .btn-followlines {
220 226 display: none;
221 227 cursor: pointer;
222 228 box-sizing: content-box;
223 229 font-size: 11px;
224 230 width: 13px;
225 231 height: 13px;
226 232 border-radius: 3px;
227 233 margin: 0px;
228 234 margin-top: -2px;
229 235 padding: 0px;
230 236 background-color: #E5FDE5;
231 237 border: 1px solid #9BC19B;
232 238 font-family: monospace;
233 239 text-align: center;
234 240 line-height: 5px;
235 241 }
236 242
237 243 tr .btn-followlines {
238 244 position: absolute;
239 245 }
240 246
241 247 span .btn-followlines {
242 248 float: left;
243 249 }
244 250
245 251 span.followlines-select .btn-followlines {
246 252 margin-left: -1.6em;
247 253 }
248 254
249 255 .btn-followlines:hover {
250 256 transform: scale(1.1, 1.1);
251 257 }
252 258
253 259 .btn-followlines .followlines-plus {
254 260 color: green;
255 261 }
256 262
257 263 .btn-followlines .followlines-minus {
258 264 color: red;
259 265 }
260 266
261 267 .btn-followlines-end {
262 268 background-color: #ffdcdc;
263 269 }
264 270
265 271 .sourcelines tr:hover .btn-followlines,
266 272 .sourcelines span.followlines-select:hover > .btn-followlines {
267 273 display: inline;
268 274 }
269 275
270 276 .btn-followlines-hidden,
271 277 .sourcelines tr:hover .btn-followlines-hidden {
272 278 display: none;
273 279 }
274 280
275 281 /* Graph */
276 282 div#wrapper {
277 283 position: relative;
278 284 margin: 0;
279 285 padding: 0;
280 286 margin-top: 3px;
281 287 }
282 288
283 289 canvas {
284 290 position: absolute;
285 291 z-index: 5;
286 292 top: -0.9em;
287 293 margin: 0;
288 294 }
289 295
290 296 ul#nodebgs {
291 297 list-style: none inside none;
292 298 padding: 0;
293 299 margin: 0;
294 300 top: -0.7em;
295 301 }
296 302
297 303 ul#graphnodes li, ul#nodebgs li {
298 304 height: 39px;
299 305 }
300 306
301 307 ul#graphnodes {
302 308 position: absolute;
303 309 z-index: 10;
304 310 top: -0.8em;
305 311 list-style: none inside none;
306 312 padding: 0;
307 313 }
308 314
309 315 ul#graphnodes li .info {
310 316 display: block;
311 317 font-size: 100%;
312 318 position: relative;
313 319 top: -3px;
314 320 font-style: italic;
315 321 }
316 322
317 323 /* Comparison */
318 324 .legend {
319 325 padding: 1.5% 0 1.5% 0;
320 326 }
321 327
322 328 .legendinfo {
323 329 border: 1px solid #d9d8d1;
324 330 font-size: 80%;
325 331 text-align: center;
326 332 padding: 0.5%;
327 333 }
328 334
329 335 .equal {
330 336 background-color: #ffffff;
331 337 }
332 338
333 339 .delete {
334 340 background-color: #faa;
335 341 color: #333;
336 342 }
337 343
338 344 .insert {
339 345 background-color: #ffa;
340 346 }
341 347
342 348 .replace {
343 349 background-color: #e8e8e8;
344 350 }
345 351
346 352 .comparison {
347 353 overflow-x: auto;
348 354 }
349 355
350 356 .header th {
351 357 text-align: center;
352 358 }
353 359
354 360 .block {
355 361 border-top: 1px solid #d9d8d1;
356 362 }
357 363
358 364 .scroll-loading {
359 365 -webkit-animation: change_color 1s linear 0s infinite alternate;
360 366 -moz-animation: change_color 1s linear 0s infinite alternate;
361 367 -o-animation: change_color 1s linear 0s infinite alternate;
362 368 animation: change_color 1s linear 0s infinite alternate;
363 369 }
364 370
365 371 @-webkit-keyframes change_color {
366 372 from { background-color: #A0CEFF; } to { }
367 373 }
368 374 @-moz-keyframes change_color {
369 375 from { background-color: #A0CEFF; } to { }
370 376 }
371 377 @-o-keyframes change_color {
372 378 from { background-color: #A0CEFF; } to { }
373 379 }
374 380 @keyframes change_color {
375 381 from { background-color: #A0CEFF; } to { }
376 382 }
377 383
378 384 .scroll-loading-error {
379 385 background-color: #FFCCCC !important;
380 386 }
381 387
382 388 #doc {
383 389 margin: 0 8px;
384 390 }
@@ -1,526 +1,533 b''
1 1 body {
2 2 margin: 0;
3 3 padding: 0;
4 4 background: white;
5 5 color: black;
6 6 font-family: sans-serif;
7 7 }
8 8
9 9 .container {
10 10 padding-left: 115px;
11 11 }
12 12
13 13 .main {
14 14 position: relative;
15 15 background: white;
16 16 padding: 2em 2em 2em 0;
17 17 }
18 18
19 19 #.main {
20 20 width: 98%;
21 21 }
22 22
23 23 .overflow {
24 24 width: 100%;
25 25 overflow: auto;
26 26 }
27 27
28 28 .menu {
29 29 width: 90px;
30 30 margin: 0;
31 31 font-size: 80%;
32 32 text-align: left;
33 33 position: absolute;
34 34 top: 20px;
35 35 left: 20px;
36 36 right: auto;
37 37 }
38 38
39 39 .menu ul {
40 40 list-style: none;
41 41 padding: 0;
42 42 margin: 10px 0 0 0;
43 43 border-left: 2px solid #999;
44 44 }
45 45
46 46 .menu li {
47 47 margin-bottom: 3px;
48 48 padding: 2px 4px;
49 49 background: white;
50 50 color: black;
51 51 font-weight: normal;
52 52 }
53 53
54 54 .menu li.active {
55 55 font-weight: bold;
56 56 }
57 57
58 58 .menu img {
59 59 width: 75px;
60 60 height: 90px;
61 61 border: 0;
62 62 }
63 63
64 64 div.atom-logo {
65 65 margin-top: 10px;
66 66 }
67 67
68 68 .atom-logo img{
69 69 width: 14px;
70 70 height: 14px;
71 71 border: 0;
72 72 }
73 73
74 74 .menu a { color: black; display: block; }
75 75
76 76 .search {
77 77 position: absolute;
78 78 top: .7em;
79 79 right: 2em;
80 80 }
81 81
82 82 form.search div#hint {
83 83 display: none;
84 84 position: absolute;
85 85 top: 40px;
86 86 right: 0px;
87 87 width: 190px;
88 88 padding: 5px;
89 89 background: #ffc;
90 90 font-size: 70%;
91 91 border: 1px solid yellow;
92 92 border-radius: 5px;
93 93 }
94 94
95 95 form.search:hover div#hint { display: block; }
96 96
97 97 a { text-decoration:none; }
98 98 .age { white-space:nowrap; }
99 99 .date { white-space:nowrap; }
100 100 .indexlinks { white-space:nowrap; }
101 101 .parity0,
102 102 .stripes4 > :nth-child(4n+1),
103 103 .stripes2 > :nth-child(2n+1) { background-color: #f0f0f0; }
104 104 .parity1,
105 105 .stripes4 > :nth-child(4n+3),
106 106 .stripes2 > :nth-child(2n+2) { background-color: white; }
107 107 .plusline { color: green; }
108 108 .minusline { color: #dc143c; } /* crimson */
109 109 .atline { color: purple; }
110 110
111 111 .diffstat-table {
112 112 margin-top: 1em;
113 113 }
114 114 .diffstat-file {
115 115 white-space: nowrap;
116 116 font-size: 90%;
117 117 }
118 118 .diffstat-total {
119 119 white-space: nowrap;
120 120 font-size: 90%;
121 121 }
122 122 .diffstat-graph {
123 123 width: 100%;
124 124 }
125 125 .diffstat-add {
126 126 background-color: green;
127 127 float: left;
128 128 }
129 129 .diffstat-remove {
130 130 background-color: red;
131 131 float: left;
132 132 }
133 133
134 134 .navigate {
135 135 text-align: right;
136 136 font-size: 60%;
137 137 margin: 1em 0;
138 138 }
139 139
140 140 .tag {
141 141 color: #999;
142 142 font-size: 70%;
143 143 font-weight: normal;
144 144 margin-left: .5em;
145 145 vertical-align: baseline;
146 146 }
147 147
148 148 .branchhead {
149 149 color: #000;
150 150 font-size: 80%;
151 151 font-weight: normal;
152 152 margin-left: .5em;
153 153 vertical-align: baseline;
154 154 }
155 155
156 156 ul#graphnodes .branchhead {
157 157 font-size: 75%;
158 158 }
159 159
160 160 .branchname {
161 161 color: #000;
162 162 font-size: 60%;
163 163 font-weight: normal;
164 164 margin-left: .5em;
165 165 vertical-align: baseline;
166 166 }
167 167
168 168 h3 .branchname {
169 169 font-size: 80%;
170 170 }
171 171
172 172 /* Common */
173 173 pre { margin: 0; }
174 174
175 175 h2 { font-size: 120%; border-bottom: 1px solid #999; }
176 176 h2 a { color: #000; }
177 177 h3 {
178 178 margin-top: +.7em;
179 179 font-size: 100%;
180 180 }
181 181
182 182 /* log and tags tables */
183 183 .bigtable {
184 184 border-bottom: 1px solid #999;
185 185 border-collapse: collapse;
186 186 font-size: 90%;
187 187 width: 100%;
188 188 font-weight: normal;
189 189 text-align: left;
190 190 }
191 191
192 192 .bigtable td {
193 193 vertical-align: top;
194 194 }
195 195
196 196 .bigtable th {
197 197 padding: 1px 4px;
198 198 border-bottom: 1px solid #999;
199 199 }
200 200 .bigtable tr { border: none; }
201 201 .bigtable .age { width: 7em; }
202 202 .bigtable .author { width: 15em; }
203 203 .bigtable .description { }
204 204 .bigtable .description .base { font-size: 70%; float: right; line-height: 1.66; }
205 205 .bigtable .node { width: 5em; font-family: monospace;}
206 206 .bigtable .permissions { width: 8em; text-align: left;}
207 207 .bigtable .size { width: 5em; text-align: right; }
208 208 .bigtable .annotate { text-align: right; }
209 209 .bigtable td.annotate { font-size: smaller; }
210 210 .bigtable td.source { font-size: inherit; }
211 211 tr.thisrev a { color:#999999; text-decoration: none; }
212 212 tr.thisrev td.source { color:#009900; }
213 213 td.annotate {
214 214 white-space: nowrap;
215 215 }
216 216 div.annotate-info {
217 217 z-index: 5;
218 218 display: none;
219 219 position: absolute;
220 220 background-color: #FFFFFF;
221 221 border: 1px solid #999;
222 222 text-align: left;
223 223 color: #000000;
224 224 padding: 5px;
225 225 }
226 226 div.annotate-info a { color: #0000FF; }
227 227 td.annotate:hover div.annotate-info { display: inline; }
228 228
229 #diffopts-form {
230 font-size: smaller;
231 color: #424242;
232 padding-bottom: 10px;
233 display: none;
234 }
235
229 236 .source, .sourcefirst {
230 237 font-family: monospace;
231 238 white-space: pre;
232 239 padding: 1px 4px;
233 240 font-size: 90%;
234 241 }
235 242 .sourcefirst { border-bottom: 1px solid #999; font-weight: bold; }
236 243 .source a { color: #999; font-size: smaller; font-family: monospace;}
237 244 .bottomline { border-bottom: 1px solid #999; }
238 245
239 246 .sourcelines {
240 247 font-size: 90%;
241 248 position: relative;
242 249 counter-reset: lineno;
243 250 }
244 251
245 252 .wrap > span {
246 253 white-space: pre-wrap;
247 254 }
248 255
249 256 .linewraptoggle {
250 257 float: right;
251 258 }
252 259
253 260 .diffblocks { counter-reset: lineno; }
254 261 .diffblocks > div { counter-increment: lineno; }
255 262
256 263 .sourcelines > span {
257 264 display: inline-block;
258 265 box-sizing: border-box;
259 266 width: 100%;
260 267 padding: 1px 0px 1px 5em;
261 268 counter-increment: lineno;
262 269 }
263 270
264 271 .sourcelines > span:before {
265 272 -moz-user-select: -moz-none;
266 273 -khtml-user-select: none;
267 274 -webkit-user-select: none;
268 275 -ms-user-select: none;
269 276 user-select: none;
270 277 display: inline-block;
271 278 margin-left: -6em;
272 279 width: 4em;
273 280 font-size: smaller;
274 281 color: #999;
275 282 text-align: right;
276 283 content: counters(lineno, ".");
277 284 float: left;
278 285 }
279 286
280 287 .sourcelines > span:target, tr:target td {
281 288 background-color: #bfdfff;
282 289 }
283 290
284 291 /* Followlines */
285 292 tbody.sourcelines > tr.followlines-selected,
286 293 pre.sourcelines > span.followlines-selected {
287 294 background-color: #99C7E9;
288 295 }
289 296
290 297 div#followlines {
291 298 background-color: #B7B7B7;
292 299 border: 1px solid #CCC;
293 300 border-radius: 5px;
294 301 padding: 4px;
295 302 position: fixed;
296 303 }
297 304
298 305 div.followlines-cancel {
299 306 text-align: right;
300 307 }
301 308
302 309 div.followlines-cancel > button {
303 310 line-height: 80%;
304 311 padding: 0;
305 312 border: 0;
306 313 border-radius: 2px;
307 314 background-color: inherit;
308 315 font-weight: bold;
309 316 }
310 317
311 318 div.followlines-cancel > button:hover {
312 319 color: #FFFFFF;
313 320 background-color: #CF1F1F;
314 321 }
315 322
316 323 div.followlines-link {
317 324 margin: 2px;
318 325 margin-top: 4px;
319 326 font-family: sans-serif;
320 327 }
321 328
322 329 .btn-followlines {
323 330 display: none;
324 331 cursor: pointer;
325 332 box-sizing: content-box;
326 333 font-size: 12px;
327 334 width: 13px;
328 335 height: 13px;
329 336 border-radius: 3px;
330 337 margin: 0px;
331 338 margin-top: -2px;
332 339 padding: 0px;
333 340 background-color: #E5FDE5;
334 341 border: 1px solid #9BC19B;
335 342 font-family: monospace;
336 343 text-align: center;
337 344 line-height: 5px;
338 345 }
339 346
340 347 tr .btn-followlines {
341 348 position: absolute;
342 349 }
343 350
344 351 span .btn-followlines {
345 352 float: left;
346 353 }
347 354
348 355 span.followlines-select .btn-followlines {
349 356 margin-left: -1.5em;
350 357 }
351 358
352 359 .btn-followlines:hover {
353 360 transform: scale(1.2, 1.2);
354 361 }
355 362
356 363 .btn-followlines .followlines-plus {
357 364 color: green;
358 365 }
359 366
360 367 .btn-followlines .followlines-minus {
361 368 color: red;
362 369 }
363 370
364 371 .btn-followlines-end {
365 372 background-color: #ffdcdc;
366 373 }
367 374
368 375 .sourcelines tr:hover .btn-followlines,
369 376 .sourcelines span.followlines-select:hover > .btn-followlines {
370 377 display: inline;
371 378 }
372 379
373 380 .btn-followlines-hidden,
374 381 .sourcelines tr:hover .btn-followlines-hidden {
375 382 display: none;
376 383 }
377 384
378 385 .sourcelines > a {
379 386 display: inline-block;
380 387 position: absolute;
381 388 left: 0px;
382 389 width: 4em;
383 390 height: 1em;
384 391 }
385 392
386 393 .fileline { font-family: monospace; }
387 394 .fileline img { border: 0; }
388 395
389 396 .tagEntry .closed { color: #99f; }
390 397
391 398 /* Changeset entry */
392 399 #changesetEntry {
393 400 border-collapse: collapse;
394 401 font-size: 90%;
395 402 width: 100%;
396 403 margin-bottom: 1em;
397 404 }
398 405
399 406 #changesetEntry th {
400 407 padding: 1px 4px;
401 408 width: 4em;
402 409 text-align: right;
403 410 font-weight: normal;
404 411 color: #999;
405 412 margin-right: .5em;
406 413 vertical-align: top;
407 414 }
408 415
409 416 div.description {
410 417 border-left: 2px solid #999;
411 418 margin: 1em 0 1em 0;
412 419 padding: .3em;
413 420 white-space: pre;
414 421 font-family: monospace;
415 422 }
416 423
417 424 /* Graph */
418 425 div#wrapper {
419 426 position: relative;
420 427 border-top: 1px solid black;
421 428 border-bottom: 1px solid black;
422 429 margin: 0;
423 430 padding: 0;
424 431 }
425 432
426 433 canvas {
427 434 position: absolute;
428 435 z-index: 5;
429 436 top: -0.7em;
430 437 margin: 0;
431 438 }
432 439
433 440 ul#graphnodes {
434 441 position: absolute;
435 442 z-index: 10;
436 443 top: -1.0em;
437 444 list-style: none inside none;
438 445 padding: 0;
439 446 }
440 447
441 448 ul#nodebgs {
442 449 list-style: none inside none;
443 450 padding: 0;
444 451 margin: 0;
445 452 top: -0.7em;
446 453 }
447 454
448 455 ul#graphnodes li, ul#nodebgs li {
449 456 height: 39px;
450 457 }
451 458
452 459 ul#graphnodes li .info {
453 460 display: block;
454 461 font-size: 70%;
455 462 position: relative;
456 463 top: -3px;
457 464 }
458 465
459 466 /* Comparison */
460 467 .legend {
461 468 padding: 1.5% 0 1.5% 0;
462 469 }
463 470
464 471 .legendinfo {
465 472 border: 1px solid #999;
466 473 font-size: 80%;
467 474 text-align: center;
468 475 padding: 0.5%;
469 476 }
470 477
471 478 .equal {
472 479 background-color: #ffffff;
473 480 }
474 481
475 482 .delete {
476 483 background-color: #faa;
477 484 color: #333;
478 485 }
479 486
480 487 .insert {
481 488 background-color: #ffa;
482 489 }
483 490
484 491 .replace {
485 492 background-color: #e8e8e8;
486 493 }
487 494
488 495 .header {
489 496 text-align: center;
490 497 }
491 498
492 499 .block {
493 500 border-top: 1px solid #999;
494 501 }
495 502
496 503 .breadcrumb {
497 504 color: gray;
498 505 }
499 506
500 507 .breadcrumb a {
501 508 color: blue;
502 509 }
503 510
504 511 .scroll-loading {
505 512 -webkit-animation: change_color 1s linear 0s infinite alternate;
506 513 -moz-animation: change_color 1s linear 0s infinite alternate;
507 514 -o-animation: change_color 1s linear 0s infinite alternate;
508 515 animation: change_color 1s linear 0s infinite alternate;
509 516 }
510 517
511 518 @-webkit-keyframes change_color {
512 519 from { background-color: #A0CEFF; } to { }
513 520 }
514 521 @-moz-keyframes change_color {
515 522 from { background-color: #A0CEFF; } to { }
516 523 }
517 524 @-o-keyframes change_color {
518 525 from { background-color: #A0CEFF; } to { }
519 526 }
520 527 @keyframes change_color {
521 528 from { background-color: #A0CEFF; } to { }
522 529 }
523 530
524 531 .scroll-loading-error {
525 532 background-color: #FFCCCC !important;
526 533 }
@@ -1,874 +1,880 b''
1 1 #require serve
2 2
3 3 Some tests for hgweb. Tests static files, plain files and different 404's.
4 4
5 5 $ hg init test
6 6 $ cd test
7 7 $ mkdir da
8 8 $ echo foo > da/foo
9 9 $ echo foo > foo
10 10 $ hg ci -Ambase
11 11 adding da/foo
12 12 adding foo
13 13 $ hg bookmark -r0 '@'
14 14 $ hg bookmark -r0 'a b c'
15 15 $ hg bookmark -r0 'd/e/f'
16 16 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
17 17 $ cat hg.pid >> $DAEMON_PIDS
18 18
19 19 manifest
20 20
21 21 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw')
22 22 200 Script output follows
23 23
24 24
25 25 drwxr-xr-x da
26 26 -rw-r--r-- 4 foo
27 27
28 28
29 29 $ (get-with-headers.py localhost:$HGPORT 'file/tip/da?style=raw')
30 30 200 Script output follows
31 31
32 32
33 33 -rw-r--r-- 4 foo
34 34
35 35
36 36
37 37 plain file
38 38
39 39 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?style=raw'
40 40 200 Script output follows
41 41
42 42 foo
43 43
44 44 should give a 404 - static file that does not exist
45 45
46 46 $ get-with-headers.py localhost:$HGPORT 'static/bogus'
47 47 404 Not Found
48 48
49 49 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
50 50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
51 51 <head>
52 52 <link rel="icon" href="/static/hgicon.png" type="image/png" />
53 53 <meta name="robots" content="index, nofollow" />
54 54 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
55 55 <script type="text/javascript" src="/static/mercurial.js"></script>
56 56
57 57 <title>test: error</title>
58 58 </head>
59 59 <body>
60 60
61 61 <div class="container">
62 62 <div class="menu">
63 63 <div class="logo">
64 64 <a href="https://mercurial-scm.org/">
65 65 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
66 66 </div>
67 67 <ul>
68 68 <li><a href="/shortlog">log</a></li>
69 69 <li><a href="/graph">graph</a></li>
70 70 <li><a href="/tags">tags</a></li>
71 71 <li><a href="/bookmarks">bookmarks</a></li>
72 72 <li><a href="/branches">branches</a></li>
73 73 </ul>
74 74 <ul>
75 75 <li><a href="/help">help</a></li>
76 76 </ul>
77 77 </div>
78 78
79 79 <div class="main">
80 80
81 81 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
82 82 <h3>error</h3>
83 83
84 84
85 85 <form class="search" action="/log">
86 86
87 87 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
88 88 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
89 89 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
90 90 </form>
91 91
92 92 <div class="description">
93 93 <p>
94 94 An error occurred while processing your request:
95 95 </p>
96 96 <p>
97 97 Not Found
98 98 </p>
99 99 </div>
100 100 </div>
101 101 </div>
102 102
103 103
104 104
105 105 </body>
106 106 </html>
107 107
108 108 [1]
109 109
110 110 should give a 404 - bad revision
111 111
112 112 $ get-with-headers.py localhost:$HGPORT 'file/spam/foo?style=raw'
113 113 404 Not Found
114 114
115 115
116 116 error: revision not found: spam
117 117 [1]
118 118
119 119 should give a 400 - bad command
120 120
121 121 $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
122 122 400* (glob)
123 123
124 124
125 125 error: no such method: spam
126 126 [1]
127 127
128 128 $ get-with-headers.py --headeronly localhost:$HGPORT '?cmd=spam'
129 129 400 no such method: spam
130 130 [1]
131 131
132 132 should give a 400 - bad command as a part of url path (issue4071)
133 133
134 134 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam'
135 135 400 no such method: spam
136 136 [1]
137 137
138 138 $ get-with-headers.py --headeronly localhost:$HGPORT 'raw-spam'
139 139 400 no such method: spam
140 140 [1]
141 141
142 142 $ get-with-headers.py --headeronly localhost:$HGPORT 'spam/tip/foo'
143 143 400 no such method: spam
144 144 [1]
145 145
146 146 should give a 404 - file does not exist
147 147
148 148 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork?style=raw'
149 149 404 Not Found
150 150
151 151
152 152 error: bork@2ef0ac749a14: not found in manifest
153 153 [1]
154 154 $ get-with-headers.py localhost:$HGPORT 'file/tip/bork'
155 155 404 Not Found
156 156
157 157 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
158 158 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
159 159 <head>
160 160 <link rel="icon" href="/static/hgicon.png" type="image/png" />
161 161 <meta name="robots" content="index, nofollow" />
162 162 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
163 163 <script type="text/javascript" src="/static/mercurial.js"></script>
164 164
165 165 <title>test: error</title>
166 166 </head>
167 167 <body>
168 168
169 169 <div class="container">
170 170 <div class="menu">
171 171 <div class="logo">
172 172 <a href="https://mercurial-scm.org/">
173 173 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
174 174 </div>
175 175 <ul>
176 176 <li><a href="/shortlog">log</a></li>
177 177 <li><a href="/graph">graph</a></li>
178 178 <li><a href="/tags">tags</a></li>
179 179 <li><a href="/bookmarks">bookmarks</a></li>
180 180 <li><a href="/branches">branches</a></li>
181 181 </ul>
182 182 <ul>
183 183 <li><a href="/help">help</a></li>
184 184 </ul>
185 185 </div>
186 186
187 187 <div class="main">
188 188
189 189 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
190 190 <h3>error</h3>
191 191
192 192
193 193 <form class="search" action="/log">
194 194
195 195 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
196 196 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
197 197 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
198 198 </form>
199 199
200 200 <div class="description">
201 201 <p>
202 202 An error occurred while processing your request:
203 203 </p>
204 204 <p>
205 205 bork@2ef0ac749a14: not found in manifest
206 206 </p>
207 207 </div>
208 208 </div>
209 209 </div>
210 210
211 211
212 212
213 213 </body>
214 214 </html>
215 215
216 216 [1]
217 217 $ get-with-headers.py localhost:$HGPORT 'diff/tip/bork?style=raw'
218 218 404 Not Found
219 219
220 220
221 221 error: bork@2ef0ac749a14: not found in manifest
222 222 [1]
223 223
224 224 try bad style
225 225
226 226 $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=foobar')
227 227 200 Script output follows
228 228
229 229 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
230 230 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
231 231 <head>
232 232 <link rel="icon" href="/static/hgicon.png" type="image/png" />
233 233 <meta name="robots" content="index, nofollow" />
234 234 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
235 235 <script type="text/javascript" src="/static/mercurial.js"></script>
236 236
237 237 <title>test: 2ef0ac749a14 /</title>
238 238 </head>
239 239 <body>
240 240
241 241 <div class="container">
242 242 <div class="menu">
243 243 <div class="logo">
244 244 <a href="https://mercurial-scm.org/">
245 245 <img src="/static/hglogo.png" alt="mercurial" /></a>
246 246 </div>
247 247 <ul>
248 248 <li><a href="/shortlog/tip">log</a></li>
249 249 <li><a href="/graph/tip">graph</a></li>
250 250 <li><a href="/tags">tags</a></li>
251 251 <li><a href="/bookmarks">bookmarks</a></li>
252 252 <li><a href="/branches">branches</a></li>
253 253 </ul>
254 254 <ul>
255 255 <li><a href="/rev/tip">changeset</a></li>
256 256 <li class="active">browse</li>
257 257 </ul>
258 258 <ul>
259 259
260 260 </ul>
261 261 <ul>
262 262 <li><a href="/help">help</a></li>
263 263 </ul>
264 264 </div>
265 265
266 266 <div class="main">
267 267 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
268 268 <h3>
269 269 directory / @ 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
270 270 <span class="tag">tip</span> <span class="tag">@</span> <span class="tag">a b c</span> <span class="tag">d/e/f</span>
271 271 </h3>
272 272
273 273
274 274 <form class="search" action="/log">
275 275
276 276 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
277 277 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
278 278 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
279 279 </form>
280 280
281 281 <table class="bigtable">
282 282 <thead>
283 283 <tr>
284 284 <th class="name">name</th>
285 285 <th class="size">size</th>
286 286 <th class="permissions">permissions</th>
287 287 </tr>
288 288 </thead>
289 289 <tbody class="stripes2">
290 290 <tr class="fileline">
291 291 <td class="name"><a href="/file/tip/">[up]</a></td>
292 292 <td class="size"></td>
293 293 <td class="permissions">drwxr-xr-x</td>
294 294 </tr>
295 295
296 296 <tr class="fileline">
297 297 <td class="name">
298 298 <a href="/file/tip/da">
299 299 <img src="/static/coal-folder.png" alt="dir."/> da/
300 300 </a>
301 301 <a href="/file/tip/da/">
302 302
303 303 </a>
304 304 </td>
305 305 <td class="size"></td>
306 306 <td class="permissions">drwxr-xr-x</td>
307 307 </tr>
308 308
309 309 <tr class="fileline">
310 310 <td class="filename">
311 311 <a href="/file/tip/foo">
312 312 <img src="/static/coal-file.png" alt="file"/> foo
313 313 </a>
314 314 </td>
315 315 <td class="size">4</td>
316 316 <td class="permissions">-rw-r--r--</td>
317 317 </tr>
318 318 </tbody>
319 319 </table>
320 320 </div>
321 321 </div>
322 322
323 323
324 324 </body>
325 325 </html>
326 326
327 327
328 328 stop and restart
329 329
330 330 $ killdaemons.py
331 331 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
332 332 $ cat hg.pid >> $DAEMON_PIDS
333 333
334 334 Test the access/error files are opened in append mode
335 335
336 336 $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'"
337 337 14 log lines written
338 338
339 339 static file
340 340
341 341 $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
342 342 200 Script output follows
343 content-length: 9007
343 content-length: 9066
344 344 content-type: text/css
345 345
346 346 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
347 347 a { color:#0000cc; }
348 348 a:hover, a:visited, a:active { color:#880000; }
349 349 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
350 350 div.page_header a:visited { color:#0000cc; }
351 351 div.page_header a:hover { color:#880000; }
352 352 div.page_nav {
353 353 padding:8px;
354 354 display: flex;
355 355 justify-content: space-between;
356 356 align-items: center;
357 357 }
358 358 div.page_nav a:visited { color:#0000cc; }
359 359 div.extra_nav {
360 360 padding: 8px;
361 361 }
362 362 div.extra_nav a:visited {
363 363 color: #0000cc;
364 364 }
365 365 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
366 366 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
367 367 div.page_footer_text { float:left; color:#555555; font-style:italic; }
368 368 div.page_body { padding:8px; }
369 369 div.title, a.title {
370 370 display:block; padding:6px 8px;
371 371 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
372 372 }
373 373 a.title:hover { background-color: #d9d8d1; }
374 374 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
375 375 div.log_body { padding:8px 8px 8px 150px; }
376 376 .age { white-space:nowrap; }
377 377 span.age { position:relative; float:left; width:142px; font-style:italic; }
378 378 div.log_link {
379 379 padding:0px 8px;
380 380 font-size:10px; font-family:sans-serif; font-style:normal;
381 381 position:relative; float:left; width:136px;
382 382 }
383 383 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
384 384 a.list { text-decoration:none; color:#000000; }
385 385 a.list:hover { text-decoration:underline; color:#880000; }
386 386 table { padding:8px 4px; }
387 387 th { padding:2px 5px; font-size:12px; text-align:left; }
388 388 .parity0 { background-color:#ffffff; }
389 389 tr.dark, .parity1, pre.sourcelines.stripes > :nth-child(4n+4) { background-color:#f6f6f0; }
390 390 tr.light:hover, .parity0:hover, tr.dark:hover, .parity1:hover,
391 391 pre.sourcelines.stripes > :nth-child(4n+2):hover,
392 392 pre.sourcelines.stripes > :nth-child(4n+4):hover,
393 393 pre.sourcelines.stripes > :nth-child(4n+1):hover + :nth-child(4n+2),
394 394 pre.sourcelines.stripes > :nth-child(4n+3):hover + :nth-child(4n+4) { background-color:#edece6; }
395 395 td { padding:2px 5px; font-size:12px; vertical-align:top; }
396 396 td.closed { background-color: #99f; }
397 397 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
398 398 td.indexlinks { white-space: nowrap; }
399 399 td.indexlinks a {
400 400 padding: 2px 5px; line-height: 10px;
401 401 border: 1px solid;
402 402 color: #ffffff; background-color: #7777bb;
403 403 border-color: #aaaadd #333366 #333366 #aaaadd;
404 404 font-weight: bold; text-align: center; text-decoration: none;
405 405 font-size: 10px;
406 406 }
407 407 td.indexlinks a:hover { background-color: #6666aa; }
408 408 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
409 409 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
410 410 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
411 411
412 412 .search {
413 413 margin-right: 8px;
414 414 }
415 415
416 416 div#hint {
417 417 position: absolute;
418 418 display: none;
419 419 width: 250px;
420 420 padding: 5px;
421 421 background: #ffc;
422 422 border: 1px solid yellow;
423 423 border-radius: 5px;
424 424 }
425 425
426 426 #searchform:hover div#hint { display: block; }
427 427
428 428 tr.thisrev a { color:#999999; text-decoration: none; }
429 429 tr.thisrev pre { color:#009900; }
430 430 td.annotate {
431 431 white-space: nowrap;
432 432 }
433 433 div.annotate-info {
434 434 z-index: 5;
435 435 display: none;
436 436 position: absolute;
437 437 background-color: #FFFFFF;
438 438 border: 1px solid #d9d8d1;
439 439 text-align: left;
440 440 color: #000000;
441 441 padding: 5px;
442 442 }
443 443 div.annotate-info a { color: #0000FF; text-decoration: underline; }
444 444 td.annotate:hover div.annotate-info { display: inline; }
445
446 #diffopts-form {
447 padding-left: 8px;
448 display: none;
449 }
450
445 451 .linenr { color:#999999; text-decoration:none }
446 452 div.rss_logo { float: right; white-space: nowrap; }
447 453 div.rss_logo a {
448 454 padding:3px 6px; line-height:10px;
449 455 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
450 456 color:#ffffff; background-color:#ff6600;
451 457 font-weight:bold; font-family:sans-serif; font-size:10px;
452 458 text-align:center; text-decoration:none;
453 459 }
454 460 div.rss_logo a:hover { background-color:#ee5500; }
455 461 pre { margin: 0; }
456 462 span.logtags span {
457 463 padding: 0px 4px;
458 464 font-size: 10px;
459 465 font-weight: normal;
460 466 border: 1px solid;
461 467 background-color: #ffaaff;
462 468 border-color: #ffccff #ff00ee #ff00ee #ffccff;
463 469 }
464 470 span.logtags span.tagtag {
465 471 background-color: #ffffaa;
466 472 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
467 473 }
468 474 span.logtags span.branchtag {
469 475 background-color: #aaffaa;
470 476 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
471 477 }
472 478 span.logtags span.inbranchtag {
473 479 background-color: #d5dde6;
474 480 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
475 481 }
476 482 span.logtags span.bookmarktag {
477 483 background-color: #afdffa;
478 484 border-color: #ccecff #46ace6 #46ace6 #ccecff;
479 485 }
480 486 span.difflineplus { color:#008800; }
481 487 span.difflineminus { color:#cc0000; }
482 488 span.difflineat { color:#990099; }
483 489 div.diffblocks { counter-reset: lineno; }
484 490 div.diffblock { counter-increment: lineno; }
485 491 pre.sourcelines { position: relative; counter-reset: lineno; }
486 492 pre.sourcelines > span {
487 493 display: inline-block;
488 494 box-sizing: border-box;
489 495 width: 100%;
490 496 padding: 0 0 0 5em;
491 497 counter-increment: lineno;
492 498 vertical-align: top;
493 499 }
494 500 pre.sourcelines > span:before {
495 501 -moz-user-select: -moz-none;
496 502 -khtml-user-select: none;
497 503 -webkit-user-select: none;
498 504 -ms-user-select: none;
499 505 user-select: none;
500 506 display: inline-block;
501 507 margin-left: -6em;
502 508 width: 4em;
503 509 color: #999;
504 510 text-align: right;
505 511 content: counters(lineno,".");
506 512 float: left;
507 513 }
508 514 pre.sourcelines > a {
509 515 display: inline-block;
510 516 position: absolute;
511 517 left: 0px;
512 518 width: 4em;
513 519 height: 1em;
514 520 }
515 521 tr:target td,
516 522 pre.sourcelines > span:target,
517 523 pre.sourcelines.stripes > span:target {
518 524 background-color: #bfdfff;
519 525 }
520 526
521 527 .description {
522 528 font-family: monospace;
523 529 white-space: pre;
524 530 }
525 531
526 532 /* Followlines */
527 533 tbody.sourcelines > tr.followlines-selected,
528 534 pre.sourcelines > span.followlines-selected {
529 535 background-color: #99C7E9 !important;
530 536 }
531 537
532 538 div#followlines {
533 539 background-color: #B7B7B7;
534 540 border: 1px solid #CCC;
535 541 border-radius: 5px;
536 542 padding: 4px;
537 543 position: fixed;
538 544 }
539 545
540 546 div.followlines-cancel {
541 547 text-align: right;
542 548 }
543 549
544 550 div.followlines-cancel > button {
545 551 line-height: 80%;
546 552 padding: 0;
547 553 border: 0;
548 554 border-radius: 2px;
549 555 background-color: inherit;
550 556 font-weight: bold;
551 557 }
552 558
553 559 div.followlines-cancel > button:hover {
554 560 color: #FFFFFF;
555 561 background-color: #CF1F1F;
556 562 }
557 563
558 564 div.followlines-link {
559 565 margin: 2px;
560 566 margin-top: 4px;
561 567 font-family: sans-serif;
562 568 }
563 569
564 570 .btn-followlines {
565 571 display: none;
566 572 cursor: pointer;
567 573 box-sizing: content-box;
568 574 font-size: 11px;
569 575 width: 13px;
570 576 height: 13px;
571 577 border-radius: 3px;
572 578 margin: 0px;
573 579 margin-top: -2px;
574 580 padding: 0px;
575 581 background-color: #E5FDE5;
576 582 border: 1px solid #9BC19B;
577 583 font-family: monospace;
578 584 text-align: center;
579 585 line-height: 5px;
580 586 }
581 587
582 588 tr .btn-followlines {
583 589 position: absolute;
584 590 }
585 591
586 592 span .btn-followlines {
587 593 float: left;
588 594 }
589 595
590 596 span.followlines-select .btn-followlines {
591 597 margin-left: -1.6em;
592 598 }
593 599
594 600 .btn-followlines:hover {
595 601 transform: scale(1.1, 1.1);
596 602 }
597 603
598 604 .btn-followlines .followlines-plus {
599 605 color: green;
600 606 }
601 607
602 608 .btn-followlines .followlines-minus {
603 609 color: red;
604 610 }
605 611
606 612 .btn-followlines-end {
607 613 background-color: #ffdcdc;
608 614 }
609 615
610 616 .sourcelines tr:hover .btn-followlines,
611 617 .sourcelines span.followlines-select:hover > .btn-followlines {
612 618 display: inline;
613 619 }
614 620
615 621 .btn-followlines-hidden,
616 622 .sourcelines tr:hover .btn-followlines-hidden {
617 623 display: none;
618 624 }
619 625
620 626 /* Graph */
621 627 div#wrapper {
622 628 position: relative;
623 629 margin: 0;
624 630 padding: 0;
625 631 margin-top: 3px;
626 632 }
627 633
628 634 canvas {
629 635 position: absolute;
630 636 z-index: 5;
631 637 top: -0.9em;
632 638 margin: 0;
633 639 }
634 640
635 641 ul#nodebgs {
636 642 list-style: none inside none;
637 643 padding: 0;
638 644 margin: 0;
639 645 top: -0.7em;
640 646 }
641 647
642 648 ul#graphnodes li, ul#nodebgs li {
643 649 height: 39px;
644 650 }
645 651
646 652 ul#graphnodes {
647 653 position: absolute;
648 654 z-index: 10;
649 655 top: -0.8em;
650 656 list-style: none inside none;
651 657 padding: 0;
652 658 }
653 659
654 660 ul#graphnodes li .info {
655 661 display: block;
656 662 font-size: 100%;
657 663 position: relative;
658 664 top: -3px;
659 665 font-style: italic;
660 666 }
661 667
662 668 /* Comparison */
663 669 .legend {
664 670 padding: 1.5% 0 1.5% 0;
665 671 }
666 672
667 673 .legendinfo {
668 674 border: 1px solid #d9d8d1;
669 675 font-size: 80%;
670 676 text-align: center;
671 677 padding: 0.5%;
672 678 }
673 679
674 680 .equal {
675 681 background-color: #ffffff;
676 682 }
677 683
678 684 .delete {
679 685 background-color: #faa;
680 686 color: #333;
681 687 }
682 688
683 689 .insert {
684 690 background-color: #ffa;
685 691 }
686 692
687 693 .replace {
688 694 background-color: #e8e8e8;
689 695 }
690 696
691 697 .comparison {
692 698 overflow-x: auto;
693 699 }
694 700
695 701 .header th {
696 702 text-align: center;
697 703 }
698 704
699 705 .block {
700 706 border-top: 1px solid #d9d8d1;
701 707 }
702 708
703 709 .scroll-loading {
704 710 -webkit-animation: change_color 1s linear 0s infinite alternate;
705 711 -moz-animation: change_color 1s linear 0s infinite alternate;
706 712 -o-animation: change_color 1s linear 0s infinite alternate;
707 713 animation: change_color 1s linear 0s infinite alternate;
708 714 }
709 715
710 716 @-webkit-keyframes change_color {
711 717 from { background-color: #A0CEFF; } to { }
712 718 }
713 719 @-moz-keyframes change_color {
714 720 from { background-color: #A0CEFF; } to { }
715 721 }
716 722 @-o-keyframes change_color {
717 723 from { background-color: #A0CEFF; } to { }
718 724 }
719 725 @keyframes change_color {
720 726 from { background-color: #A0CEFF; } to { }
721 727 }
722 728
723 729 .scroll-loading-error {
724 730 background-color: #FFCCCC !important;
725 731 }
726 732
727 733 #doc {
728 734 margin: 0 8px;
729 735 }
730 736 304 Not Modified
731 737
732 738
733 739 phase changes are refreshed (issue4061)
734 740
735 741 $ echo bar >> foo
736 742 $ hg ci -msecret --secret
737 743 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
738 744 200 Script output follows
739 745
740 746
741 747 # HG changelog
742 748 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
743 749
744 750 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
745 751 revision: 0
746 752 user: test
747 753 date: Thu, 01 Jan 1970 00:00:00 +0000
748 754 summary: base
749 755 branch: default
750 756 tag: tip
751 757 bookmark: @
752 758 bookmark: a b c
753 759 bookmark: d/e/f
754 760
755 761
756 762 $ hg phase --draft tip
757 763 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
758 764 200 Script output follows
759 765
760 766
761 767 # HG changelog
762 768 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
763 769
764 770 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
765 771 revision: 1
766 772 user: test
767 773 date: Thu, 01 Jan 1970 00:00:00 +0000
768 774 summary: secret
769 775 branch: default
770 776 tag: tip
771 777
772 778 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
773 779 revision: 0
774 780 user: test
775 781 date: Thu, 01 Jan 1970 00:00:00 +0000
776 782 summary: base
777 783 bookmark: @
778 784 bookmark: a b c
779 785 bookmark: d/e/f
780 786
781 787
782 788
783 789 access bookmarks
784 790
785 791 $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:'
786 792 200 Script output follows
787 793 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
788 794
789 795 $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:'
790 796 200 Script output follows
791 797 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
792 798
793 799 $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:'
794 800 200 Script output follows
795 801 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
796 802
797 803 $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:'
798 804 200 Script output follows
799 805 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
800 806
801 807 no style can be loaded from directories other than the specified paths
802 808
803 809 $ mkdir -p x/templates/fallback
804 810 $ cat <<EOF > x/templates/fallback/map
805 811 > default = 'shortlog'
806 812 > shortlog = 'fall back to default\n'
807 813 > mimetype = 'text/plain'
808 814 > EOF
809 815 $ cat <<EOF > x/map
810 816 > default = 'shortlog'
811 817 > shortlog = 'access to outside of templates directory\n'
812 818 > mimetype = 'text/plain'
813 819 > EOF
814 820
815 821 $ killdaemons.py
816 822 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \
817 823 > --config web.style=fallback --config web.templates=x/templates
818 824 $ cat hg.pid >> $DAEMON_PIDS
819 825
820 826 $ get-with-headers.py localhost:$HGPORT "?style=`pwd`/x"
821 827 200 Script output follows
822 828
823 829 fall back to default
824 830
825 831 $ get-with-headers.py localhost:$HGPORT '?style=..'
826 832 200 Script output follows
827 833
828 834 fall back to default
829 835
830 836 $ get-with-headers.py localhost:$HGPORT '?style=./..'
831 837 200 Script output follows
832 838
833 839 fall back to default
834 840
835 841 $ get-with-headers.py localhost:$HGPORT '?style=.../.../'
836 842 200 Script output follows
837 843
838 844 fall back to default
839 845
840 846 errors
841 847
842 848 $ cat errors.log
843 849
844 850 Uncaught exceptions result in a logged error and canned HTTP response
845 851
846 852 $ killdaemons.py
847 853 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
848 854 $ cat hg.pid >> $DAEMON_PIDS
849 855
850 856 $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
851 857 500 Internal Server Error
852 858 transfer-encoding: chunked
853 859
854 860 Internal Server Error (no-eol)
855 861 [1]
856 862
857 863 $ killdaemons.py
858 864 $ head -1 errors.log
859 865 .* Exception happened during processing request '/raiseerror': (re)
860 866
861 867 Uncaught exception after partial content sent
862 868
863 869 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
864 870 $ cat hg.pid >> $DAEMON_PIDS
865 871 $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
866 872 200 Script output follows
867 873 transfer-encoding: chunked
868 874 content-type: text/plain
869 875
870 876 partial content
871 877 Internal Server Error (no-eol)
872 878
873 879 $ killdaemons.py
874 880 $ cd ..
@@ -1,998 +1,1017 b''
1 1 #require pygments serve
2 2
3 3 $ cat <<EOF >> $HGRCPATH
4 4 > [extensions]
5 5 > highlight =
6 6 > [web]
7 7 > pygments_style = friendly
8 8 > highlightfiles = **.py and size('<100KB')
9 9 > EOF
10 10 $ hg init test
11 11 $ cd test
12 12
13 13 $ filterhtml () {
14 14 > sed -e "s/class=\"k\"/class=\"kn\"/g" \
15 15 > -e "s/class=\"mf\"/class=\"mi\"/g" \
16 16 > -e "s/class=\"vm\"/class=\"n\"/g" \
17 17 > -e "s/class=\"\([cs]\)[h12]\"/class=\"\1\"/g"
18 18 > }
19 19
20 20 create random Python file to exercise Pygments
21 21
22 22 $ cat <<EOF > primes.py
23 23 > """Fun with generators. Corresponding Haskell implementation:
24 24 >
25 25 > primes = 2 : sieve [3, 5..]
26 26 > where sieve (p:ns) = p : sieve [n | n <- ns, mod n p /= 0]
27 27 > """
28 28 >
29 29 > from itertools import dropwhile, ifilter, islice, count, chain
30 30 >
31 31 > def primes():
32 32 > """Generate all primes."""
33 33 > def sieve(ns):
34 34 > p = ns.next()
35 35 > # It is important to yield *here* in order to stop the
36 36 > # infinite recursion.
37 37 > yield p
38 38 > ns = ifilter(lambda n: n % p != 0, ns)
39 39 > for n in sieve(ns):
40 40 > yield n
41 41 >
42 42 > odds = ifilter(lambda i: i % 2 == 1, count())
43 43 > return chain([2], sieve(dropwhile(lambda n: n < 3, odds)))
44 44 >
45 45 > if __name__ == "__main__":
46 46 > import sys
47 47 > try:
48 48 > n = int(sys.argv[1])
49 49 > except (ValueError, IndexError):
50 50 > n = 10
51 51 > p = primes()
52 52 > print("The first %d primes: %s" % (n, list(islice(p, n))))
53 53 > EOF
54 54 $ echo >> primes.py # to test html markup with an empty line just before EOF
55 55 $ hg ci -Ama
56 56 adding primes.py
57 57
58 58 hg serve
59 59
60 60 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid -A access.log -E errors.log
61 61 $ cat hg.pid >> $DAEMON_PIDS
62 62
63 63 hgweb filerevision, html
64 64
65 65 $ (get-with-headers.py localhost:$HGPORT 'file/tip/primes.py') | filterhtml
66 66 200 Script output follows
67 67
68 68 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
69 69 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
70 70 <head>
71 71 <link rel="icon" href="/static/hgicon.png" type="image/png" />
72 72 <meta name="robots" content="index, nofollow" />
73 73 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
74 74 <script type="text/javascript" src="/static/mercurial.js"></script>
75 75
76 76 <link rel="stylesheet" href="/highlightcss" type="text/css" />
77 77 <title>test: f4fca47b67e6 primes.py</title>
78 78 </head>
79 79 <body>
80 80
81 81 <div class="container">
82 82 <div class="menu">
83 83 <div class="logo">
84 84 <a href="https://mercurial-scm.org/">
85 85 <img src="/static/hglogo.png" alt="mercurial" /></a>
86 86 </div>
87 87 <ul>
88 88 <li><a href="/shortlog/tip">log</a></li>
89 89 <li><a href="/graph/tip">graph</a></li>
90 90 <li><a href="/tags">tags</a></li>
91 91 <li><a href="/bookmarks">bookmarks</a></li>
92 92 <li><a href="/branches">branches</a></li>
93 93 </ul>
94 94 <ul>
95 95 <li><a href="/rev/tip">changeset</a></li>
96 96 <li><a href="/file/tip/">browse</a></li>
97 97 </ul>
98 98 <ul>
99 99 <li class="active">file</li>
100 100 <li><a href="/file/tip/primes.py">latest</a></li>
101 101 <li><a href="/diff/tip/primes.py">diff</a></li>
102 102 <li><a href="/comparison/tip/primes.py">comparison</a></li>
103 103 <li><a href="/annotate/tip/primes.py">annotate</a></li>
104 104 <li><a href="/log/tip/primes.py">file log</a></li>
105 105 <li><a href="/raw-file/tip/primes.py">raw</a></li>
106 106 </ul>
107 107 <ul>
108 108 <li><a href="/help">help</a></li>
109 109 </ul>
110 110 </div>
111 111
112 112 <div class="main">
113 113 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
114 114 <h3>
115 115 view primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
116 116 <span class="tag">tip</span>
117 117 </h3>
118 118
119 119
120 120 <form class="search" action="/log">
121 121
122 122 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
123 123 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
124 124 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
125 125 </form>
126 126
127 127 <div class="description">a</div>
128 128
129 129 <table id="changesetEntry">
130 130 <tr>
131 131 <th class="author">author</th>
132 132 <td class="author">&#116;&#101;&#115;&#116;</td>
133 133 </tr>
134 134 <tr>
135 135 <th class="date">date</th>
136 136 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
137 137 </tr>
138 138 <tr>
139 139 <th class="author">parents</th>
140 140 <td class="author"></td>
141 141 </tr>
142 142 <tr>
143 143 <th class="author">children</th>
144 144 <td class="author"></td>
145 145 </tr>
146 146 </table>
147 147
148 148 <div class="overflow">
149 149 <div class="sourcefirst linewraptoggle">line wrap: <a class="linewraplink" href="javascript:toggleLinewrap()">on</a></div>
150 150 <div class="sourcefirst"> line source</div>
151 151 <pre class="sourcelines stripes4 wrap bottomline"
152 152 data-logurl="/log/tip/primes.py"
153 153 data-selectabletag="SPAN"
154 154 data-ishead="1">
155 155
156 156 <span id="l1"><span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></span><a href="#l1"></a>
157 157 <span id="l2"></span><a href="#l2"></a>
158 158 <span id="l3"><span class="sd">primes = 2 : sieve [3, 5..]</span></span><a href="#l3"></a>
159 159 <span id="l4"><span class="sd"> where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></span><a href="#l4"></a>
160 160 <span id="l5"><span class="sd">&quot;&quot;&quot;</span></span><a href="#l5"></a>
161 161 <span id="l6"></span><a href="#l6"></a>
162 162 <span id="l7"><span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></span><a href="#l7"></a>
163 163 <span id="l8"></span><a href="#l8"></a>
164 164 <span id="l9"><span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></span><a href="#l9"></a>
165 165 <span id="l10"> <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></span><a href="#l10"></a>
166 166 <span id="l11"> <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></span><a href="#l11"></a>
167 167 <span id="l12"> <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></span><a href="#l12"></a>
168 168 <span id="l13"> <span class="c"># It is important to yield *here* in order to stop the</span></span><a href="#l13"></a>
169 169 <span id="l14"> <span class="c"># infinite recursion.</span></span><a href="#l14"></a>
170 170 <span id="l15"> <span class="kn">yield</span> <span class="n">p</span></span><a href="#l15"></a>
171 171 <span id="l16"> <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></span><a href="#l16"></a>
172 172 <span id="l17"> <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></span><a href="#l17"></a>
173 173 <span id="l18"> <span class="kn">yield</span> <span class="n">n</span></span><a href="#l18"></a>
174 174 <span id="l19"></span><a href="#l19"></a>
175 175 <span id="l20"> <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></span><a href="#l20"></a>
176 176 <span id="l21"> <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></span><a href="#l21"></a>
177 177 <span id="l22"></span><a href="#l22"></a>
178 178 <span id="l23"><span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span></span><a href="#l23"></a>
179 179 <span id="l24"> <span class="kn">import</span> <span class="nn">sys</span></span><a href="#l24"></a>
180 180 <span id="l25"> <span class="kn">try</span><span class="p">:</span></span><a href="#l25"></a>
181 181 <span id="l26"> <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></span><a href="#l26"></a>
182 182 <span id="l27"> <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></span><a href="#l27"></a>
183 183 <span id="l28"> <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></span><a href="#l28"></a>
184 184 <span id="l29"> <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></span><a href="#l29"></a>
185 185 <span id="l30"> <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></span><a href="#l30"></a>
186 186 <span id="l31"></span><a href="#l31"></a>
187 187 </pre>
188 188 </div>
189 189
190 190 <script type="text/javascript" src="/static/followlines.js"></script>
191 191
192 192 </div>
193 193 </div>
194 194
195 195
196 196
197 197 </body>
198 198 </html>
199 199
200 200
201 201 hgweb fileannotate, html
202 202
203 203 $ (get-with-headers.py localhost:$HGPORT 'annotate/tip/primes.py') | filterhtml
204 204 200 Script output follows
205 205
206 206 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
207 207 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
208 208 <head>
209 209 <link rel="icon" href="/static/hgicon.png" type="image/png" />
210 210 <meta name="robots" content="index, nofollow" />
211 211 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
212 212 <script type="text/javascript" src="/static/mercurial.js"></script>
213 213
214 214 <link rel="stylesheet" href="/highlightcss" type="text/css" />
215 215 <title>test: primes.py annotate</title>
216 216 </head>
217 217 <body>
218 218
219 219 <div class="container">
220 220 <div class="menu">
221 221 <div class="logo">
222 222 <a href="https://mercurial-scm.org/">
223 223 <img src="/static/hglogo.png" alt="mercurial" /></a>
224 224 </div>
225 225 <ul>
226 226 <li><a href="/shortlog/tip">log</a></li>
227 227 <li><a href="/graph/tip">graph</a></li>
228 228 <li><a href="/tags">tags</a></li>
229 229 <li><a href="/bookmarks">bookmarks</a></li>
230 230 <li><a href="/branches">branches</a></li>
231 231 </ul>
232 232
233 233 <ul>
234 234 <li><a href="/rev/tip">changeset</a></li>
235 235 <li><a href="/file/tip/">browse</a></li>
236 236 </ul>
237 237 <ul>
238 238 <li><a href="/file/tip/primes.py">file</a></li>
239 239 <li><a href="/file/tip/primes.py">latest</a></li>
240 240 <li><a href="/diff/tip/primes.py">diff</a></li>
241 241 <li><a href="/comparison/tip/primes.py">comparison</a></li>
242 242 <li class="active">annotate</li>
243 243 <li><a href="/log/tip/primes.py">file log</a></li>
244 244 <li><a href="/raw-file/tip/primes.py">raw</a></li>
245 245 </ul>
246 246 <ul>
247 247 <li><a href="/help">help</a></li>
248 248 </ul>
249 249 </div>
250 250
251 251 <div class="main">
252 252 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
253 253 <h3>
254 254 annotate primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
255 255 <span class="tag">tip</span>
256 256 </h3>
257 257
258 258
259 259 <form class="search" action="/log">
260 260
261 261 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
262 262 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
263 263 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
264 264 </form>
265 265
266 266 <div class="description">a</div>
267 267
268 268 <table id="changesetEntry">
269 269 <tr>
270 270 <th class="author">author</th>
271 271 <td class="author">&#116;&#101;&#115;&#116;</td>
272 272 </tr>
273 273 <tr>
274 274 <th class="date">date</th>
275 275 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
276 276 </tr>
277 277 <tr>
278 278 <th class="author">parents</th>
279 279 <td class="author"></td>
280 280 </tr>
281 281 <tr>
282 282 <th class="author">children</th>
283 283 <td class="author"></td>
284 284 </tr>
285 285 </table>
286 286
287
288 <form id="diffopts-form"
289 data-ignorews="0"
290 data-ignorewsamount="0"
291 data-ignorewseol="0"
292 data-ignoreblanklines="0">
293 <span>Ignore whitespace changes - </span>
294 <span>Everywhere:</span>
295 <input id="ignorews-checkbox" type="checkbox" />
296 <span>Within whitespace:</span>
297 <input id="ignorewsamount-checkbox" type="checkbox" />
298 <span>At end of lines:</span>
299 <input id="ignorewseol-checkbox" type="checkbox" />
300 </form>
301
302 <script type="text/javascript">
303 renderDiffOptsForm();
304 </script>
305
287 306 <div class="overflow">
288 307 <table class="bigtable">
289 308 <thead>
290 309 <tr>
291 310 <th class="annotate">rev</th>
292 311 <th class="line">&nbsp;&nbsp;line source</th>
293 312 </tr>
294 313 </thead>
295 314 <tbody class="stripes2 sourcelines"
296 315 data-logurl="/log/tip/primes.py"
297 316 data-selectabletag="TR"
298 317 data-ishead="1">
299 318
300 319 <tr id="l1" class="thisrev">
301 320 <td class="annotate parity0">
302 321 <a href="/annotate/f4fca47b67e6/primes.py#l1">
303 322 0
304 323 </a>
305 324 <div class="annotate-info">
306 325 <div>
307 326 <a href="/annotate/f4fca47b67e6/primes.py#l1">
308 327 f4fca47b67e6</a>
309 328 a
310 329 </div>
311 330 <div><em>&#116;&#101;&#115;&#116;</em></div>
312 331 <div>parents: </div>
313 332 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
314 333 <a href="/rev/f4fca47b67e6">changeset</a>
315 334 </div>
316 335 </td>
317 336 <td class="source followlines-btn-parent"><a href="#l1"> 1</a> <span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></td>
318 337 </tr>
319 338 <tr id="l2" class="thisrev">
320 339 <td class="annotate parity0">
321 340
322 341 <div class="annotate-info">
323 342 <div>
324 343 <a href="/annotate/f4fca47b67e6/primes.py#l2">
325 344 f4fca47b67e6</a>
326 345 a
327 346 </div>
328 347 <div><em>&#116;&#101;&#115;&#116;</em></div>
329 348 <div>parents: </div>
330 349 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
331 350 <a href="/rev/f4fca47b67e6">changeset</a>
332 351 </div>
333 352 </td>
334 353 <td class="source followlines-btn-parent"><a href="#l2"> 2</a> </td>
335 354 </tr>
336 355 <tr id="l3" class="thisrev">
337 356 <td class="annotate parity0">
338 357
339 358 <div class="annotate-info">
340 359 <div>
341 360 <a href="/annotate/f4fca47b67e6/primes.py#l3">
342 361 f4fca47b67e6</a>
343 362 a
344 363 </div>
345 364 <div><em>&#116;&#101;&#115;&#116;</em></div>
346 365 <div>parents: </div>
347 366 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
348 367 <a href="/rev/f4fca47b67e6">changeset</a>
349 368 </div>
350 369 </td>
351 370 <td class="source followlines-btn-parent"><a href="#l3"> 3</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></td>
352 371 </tr>
353 372 <tr id="l4" class="thisrev">
354 373 <td class="annotate parity0">
355 374
356 375 <div class="annotate-info">
357 376 <div>
358 377 <a href="/annotate/f4fca47b67e6/primes.py#l4">
359 378 f4fca47b67e6</a>
360 379 a
361 380 </div>
362 381 <div><em>&#116;&#101;&#115;&#116;</em></div>
363 382 <div>parents: </div>
364 383 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
365 384 <a href="/rev/f4fca47b67e6">changeset</a>
366 385 </div>
367 386 </td>
368 387 <td class="source followlines-btn-parent"><a href="#l4"> 4</a> <span class="sd"> where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
369 388 </tr>
370 389 <tr id="l5" class="thisrev">
371 390 <td class="annotate parity0">
372 391
373 392 <div class="annotate-info">
374 393 <div>
375 394 <a href="/annotate/f4fca47b67e6/primes.py#l5">
376 395 f4fca47b67e6</a>
377 396 a
378 397 </div>
379 398 <div><em>&#116;&#101;&#115;&#116;</em></div>
380 399 <div>parents: </div>
381 400 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
382 401 <a href="/rev/f4fca47b67e6">changeset</a>
383 402 </div>
384 403 </td>
385 404 <td class="source followlines-btn-parent"><a href="#l5"> 5</a> <span class="sd">&quot;&quot;&quot;</span></td>
386 405 </tr>
387 406 <tr id="l6" class="thisrev">
388 407 <td class="annotate parity0">
389 408
390 409 <div class="annotate-info">
391 410 <div>
392 411 <a href="/annotate/f4fca47b67e6/primes.py#l6">
393 412 f4fca47b67e6</a>
394 413 a
395 414 </div>
396 415 <div><em>&#116;&#101;&#115;&#116;</em></div>
397 416 <div>parents: </div>
398 417 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
399 418 <a href="/rev/f4fca47b67e6">changeset</a>
400 419 </div>
401 420 </td>
402 421 <td class="source followlines-btn-parent"><a href="#l6"> 6</a> </td>
403 422 </tr>
404 423 <tr id="l7" class="thisrev">
405 424 <td class="annotate parity0">
406 425
407 426 <div class="annotate-info">
408 427 <div>
409 428 <a href="/annotate/f4fca47b67e6/primes.py#l7">
410 429 f4fca47b67e6</a>
411 430 a
412 431 </div>
413 432 <div><em>&#116;&#101;&#115;&#116;</em></div>
414 433 <div>parents: </div>
415 434 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
416 435 <a href="/rev/f4fca47b67e6">changeset</a>
417 436 </div>
418 437 </td>
419 438 <td class="source followlines-btn-parent"><a href="#l7"> 7</a> <span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></td>
420 439 </tr>
421 440 <tr id="l8" class="thisrev">
422 441 <td class="annotate parity0">
423 442
424 443 <div class="annotate-info">
425 444 <div>
426 445 <a href="/annotate/f4fca47b67e6/primes.py#l8">
427 446 f4fca47b67e6</a>
428 447 a
429 448 </div>
430 449 <div><em>&#116;&#101;&#115;&#116;</em></div>
431 450 <div>parents: </div>
432 451 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
433 452 <a href="/rev/f4fca47b67e6">changeset</a>
434 453 </div>
435 454 </td>
436 455 <td class="source followlines-btn-parent"><a href="#l8"> 8</a> </td>
437 456 </tr>
438 457 <tr id="l9" class="thisrev">
439 458 <td class="annotate parity0">
440 459
441 460 <div class="annotate-info">
442 461 <div>
443 462 <a href="/annotate/f4fca47b67e6/primes.py#l9">
444 463 f4fca47b67e6</a>
445 464 a
446 465 </div>
447 466 <div><em>&#116;&#101;&#115;&#116;</em></div>
448 467 <div>parents: </div>
449 468 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
450 469 <a href="/rev/f4fca47b67e6">changeset</a>
451 470 </div>
452 471 </td>
453 472 <td class="source followlines-btn-parent"><a href="#l9"> 9</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></td>
454 473 </tr>
455 474 <tr id="l10" class="thisrev">
456 475 <td class="annotate parity0">
457 476
458 477 <div class="annotate-info">
459 478 <div>
460 479 <a href="/annotate/f4fca47b67e6/primes.py#l10">
461 480 f4fca47b67e6</a>
462 481 a
463 482 </div>
464 483 <div><em>&#116;&#101;&#115;&#116;</em></div>
465 484 <div>parents: </div>
466 485 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
467 486 <a href="/rev/f4fca47b67e6">changeset</a>
468 487 </div>
469 488 </td>
470 489 <td class="source followlines-btn-parent"><a href="#l10"> 10</a> <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></td>
471 490 </tr>
472 491 <tr id="l11" class="thisrev">
473 492 <td class="annotate parity0">
474 493
475 494 <div class="annotate-info">
476 495 <div>
477 496 <a href="/annotate/f4fca47b67e6/primes.py#l11">
478 497 f4fca47b67e6</a>
479 498 a
480 499 </div>
481 500 <div><em>&#116;&#101;&#115;&#116;</em></div>
482 501 <div>parents: </div>
483 502 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
484 503 <a href="/rev/f4fca47b67e6">changeset</a>
485 504 </div>
486 505 </td>
487 506 <td class="source followlines-btn-parent"><a href="#l11"> 11</a> <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
488 507 </tr>
489 508 <tr id="l12" class="thisrev">
490 509 <td class="annotate parity0">
491 510
492 511 <div class="annotate-info">
493 512 <div>
494 513 <a href="/annotate/f4fca47b67e6/primes.py#l12">
495 514 f4fca47b67e6</a>
496 515 a
497 516 </div>
498 517 <div><em>&#116;&#101;&#115;&#116;</em></div>
499 518 <div>parents: </div>
500 519 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
501 520 <a href="/rev/f4fca47b67e6">changeset</a>
502 521 </div>
503 522 </td>
504 523 <td class="source followlines-btn-parent"><a href="#l12"> 12</a> <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></td>
505 524 </tr>
506 525 <tr id="l13" class="thisrev">
507 526 <td class="annotate parity0">
508 527
509 528 <div class="annotate-info">
510 529 <div>
511 530 <a href="/annotate/f4fca47b67e6/primes.py#l13">
512 531 f4fca47b67e6</a>
513 532 a
514 533 </div>
515 534 <div><em>&#116;&#101;&#115;&#116;</em></div>
516 535 <div>parents: </div>
517 536 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
518 537 <a href="/rev/f4fca47b67e6">changeset</a>
519 538 </div>
520 539 </td>
521 540 <td class="source followlines-btn-parent"><a href="#l13"> 13</a> <span class="c"># It is important to yield *here* in order to stop the</span></td>
522 541 </tr>
523 542 <tr id="l14" class="thisrev">
524 543 <td class="annotate parity0">
525 544
526 545 <div class="annotate-info">
527 546 <div>
528 547 <a href="/annotate/f4fca47b67e6/primes.py#l14">
529 548 f4fca47b67e6</a>
530 549 a
531 550 </div>
532 551 <div><em>&#116;&#101;&#115;&#116;</em></div>
533 552 <div>parents: </div>
534 553 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
535 554 <a href="/rev/f4fca47b67e6">changeset</a>
536 555 </div>
537 556 </td>
538 557 <td class="source followlines-btn-parent"><a href="#l14"> 14</a> <span class="c"># infinite recursion.</span></td>
539 558 </tr>
540 559 <tr id="l15" class="thisrev">
541 560 <td class="annotate parity0">
542 561
543 562 <div class="annotate-info">
544 563 <div>
545 564 <a href="/annotate/f4fca47b67e6/primes.py#l15">
546 565 f4fca47b67e6</a>
547 566 a
548 567 </div>
549 568 <div><em>&#116;&#101;&#115;&#116;</em></div>
550 569 <div>parents: </div>
551 570 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
552 571 <a href="/rev/f4fca47b67e6">changeset</a>
553 572 </div>
554 573 </td>
555 574 <td class="source followlines-btn-parent"><a href="#l15"> 15</a> <span class="kn">yield</span> <span class="n">p</span></td>
556 575 </tr>
557 576 <tr id="l16" class="thisrev">
558 577 <td class="annotate parity0">
559 578
560 579 <div class="annotate-info">
561 580 <div>
562 581 <a href="/annotate/f4fca47b67e6/primes.py#l16">
563 582 f4fca47b67e6</a>
564 583 a
565 584 </div>
566 585 <div><em>&#116;&#101;&#115;&#116;</em></div>
567 586 <div>parents: </div>
568 587 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
569 588 <a href="/rev/f4fca47b67e6">changeset</a>
570 589 </div>
571 590 </td>
572 591 <td class="source followlines-btn-parent"><a href="#l16"> 16</a> <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></td>
573 592 </tr>
574 593 <tr id="l17" class="thisrev">
575 594 <td class="annotate parity0">
576 595
577 596 <div class="annotate-info">
578 597 <div>
579 598 <a href="/annotate/f4fca47b67e6/primes.py#l17">
580 599 f4fca47b67e6</a>
581 600 a
582 601 </div>
583 602 <div><em>&#116;&#101;&#115;&#116;</em></div>
584 603 <div>parents: </div>
585 604 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
586 605 <a href="/rev/f4fca47b67e6">changeset</a>
587 606 </div>
588 607 </td>
589 608 <td class="source followlines-btn-parent"><a href="#l17"> 17</a> <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
590 609 </tr>
591 610 <tr id="l18" class="thisrev">
592 611 <td class="annotate parity0">
593 612
594 613 <div class="annotate-info">
595 614 <div>
596 615 <a href="/annotate/f4fca47b67e6/primes.py#l18">
597 616 f4fca47b67e6</a>
598 617 a
599 618 </div>
600 619 <div><em>&#116;&#101;&#115;&#116;</em></div>
601 620 <div>parents: </div>
602 621 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
603 622 <a href="/rev/f4fca47b67e6">changeset</a>
604 623 </div>
605 624 </td>
606 625 <td class="source followlines-btn-parent"><a href="#l18"> 18</a> <span class="kn">yield</span> <span class="n">n</span></td>
607 626 </tr>
608 627 <tr id="l19" class="thisrev">
609 628 <td class="annotate parity0">
610 629
611 630 <div class="annotate-info">
612 631 <div>
613 632 <a href="/annotate/f4fca47b67e6/primes.py#l19">
614 633 f4fca47b67e6</a>
615 634 a
616 635 </div>
617 636 <div><em>&#116;&#101;&#115;&#116;</em></div>
618 637 <div>parents: </div>
619 638 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
620 639 <a href="/rev/f4fca47b67e6">changeset</a>
621 640 </div>
622 641 </td>
623 642 <td class="source followlines-btn-parent"><a href="#l19"> 19</a> </td>
624 643 </tr>
625 644 <tr id="l20" class="thisrev">
626 645 <td class="annotate parity0">
627 646
628 647 <div class="annotate-info">
629 648 <div>
630 649 <a href="/annotate/f4fca47b67e6/primes.py#l20">
631 650 f4fca47b67e6</a>
632 651 a
633 652 </div>
634 653 <div><em>&#116;&#101;&#115;&#116;</em></div>
635 654 <div>parents: </div>
636 655 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
637 656 <a href="/rev/f4fca47b67e6">changeset</a>
638 657 </div>
639 658 </td>
640 659 <td class="source followlines-btn-parent"><a href="#l20"> 20</a> <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></td>
641 660 </tr>
642 661 <tr id="l21" class="thisrev">
643 662 <td class="annotate parity0">
644 663
645 664 <div class="annotate-info">
646 665 <div>
647 666 <a href="/annotate/f4fca47b67e6/primes.py#l21">
648 667 f4fca47b67e6</a>
649 668 a
650 669 </div>
651 670 <div><em>&#116;&#101;&#115;&#116;</em></div>
652 671 <div>parents: </div>
653 672 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
654 673 <a href="/rev/f4fca47b67e6">changeset</a>
655 674 </div>
656 675 </td>
657 676 <td class="source followlines-btn-parent"><a href="#l21"> 21</a> <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></td>
658 677 </tr>
659 678 <tr id="l22" class="thisrev">
660 679 <td class="annotate parity0">
661 680
662 681 <div class="annotate-info">
663 682 <div>
664 683 <a href="/annotate/f4fca47b67e6/primes.py#l22">
665 684 f4fca47b67e6</a>
666 685 a
667 686 </div>
668 687 <div><em>&#116;&#101;&#115;&#116;</em></div>
669 688 <div>parents: </div>
670 689 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
671 690 <a href="/rev/f4fca47b67e6">changeset</a>
672 691 </div>
673 692 </td>
674 693 <td class="source followlines-btn-parent"><a href="#l22"> 22</a> </td>
675 694 </tr>
676 695 <tr id="l23" class="thisrev">
677 696 <td class="annotate parity0">
678 697
679 698 <div class="annotate-info">
680 699 <div>
681 700 <a href="/annotate/f4fca47b67e6/primes.py#l23">
682 701 f4fca47b67e6</a>
683 702 a
684 703 </div>
685 704 <div><em>&#116;&#101;&#115;&#116;</em></div>
686 705 <div>parents: </div>
687 706 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
688 707 <a href="/rev/f4fca47b67e6">changeset</a>
689 708 </div>
690 709 </td>
691 710 <td class="source followlines-btn-parent"><a href="#l23"> 23</a> <span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span></td>
692 711 </tr>
693 712 <tr id="l24" class="thisrev">
694 713 <td class="annotate parity0">
695 714
696 715 <div class="annotate-info">
697 716 <div>
698 717 <a href="/annotate/f4fca47b67e6/primes.py#l24">
699 718 f4fca47b67e6</a>
700 719 a
701 720 </div>
702 721 <div><em>&#116;&#101;&#115;&#116;</em></div>
703 722 <div>parents: </div>
704 723 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
705 724 <a href="/rev/f4fca47b67e6">changeset</a>
706 725 </div>
707 726 </td>
708 727 <td class="source followlines-btn-parent"><a href="#l24"> 24</a> <span class="kn">import</span> <span class="nn">sys</span></td>
709 728 </tr>
710 729 <tr id="l25" class="thisrev">
711 730 <td class="annotate parity0">
712 731
713 732 <div class="annotate-info">
714 733 <div>
715 734 <a href="/annotate/f4fca47b67e6/primes.py#l25">
716 735 f4fca47b67e6</a>
717 736 a
718 737 </div>
719 738 <div><em>&#116;&#101;&#115;&#116;</em></div>
720 739 <div>parents: </div>
721 740 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
722 741 <a href="/rev/f4fca47b67e6">changeset</a>
723 742 </div>
724 743 </td>
725 744 <td class="source followlines-btn-parent"><a href="#l25"> 25</a> <span class="kn">try</span><span class="p">:</span></td>
726 745 </tr>
727 746 <tr id="l26" class="thisrev">
728 747 <td class="annotate parity0">
729 748
730 749 <div class="annotate-info">
731 750 <div>
732 751 <a href="/annotate/f4fca47b67e6/primes.py#l26">
733 752 f4fca47b67e6</a>
734 753 a
735 754 </div>
736 755 <div><em>&#116;&#101;&#115;&#116;</em></div>
737 756 <div>parents: </div>
738 757 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
739 758 <a href="/rev/f4fca47b67e6">changeset</a>
740 759 </div>
741 760 </td>
742 761 <td class="source followlines-btn-parent"><a href="#l26"> 26</a> <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></td>
743 762 </tr>
744 763 <tr id="l27" class="thisrev">
745 764 <td class="annotate parity0">
746 765
747 766 <div class="annotate-info">
748 767 <div>
749 768 <a href="/annotate/f4fca47b67e6/primes.py#l27">
750 769 f4fca47b67e6</a>
751 770 a
752 771 </div>
753 772 <div><em>&#116;&#101;&#115;&#116;</em></div>
754 773 <div>parents: </div>
755 774 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
756 775 <a href="/rev/f4fca47b67e6">changeset</a>
757 776 </div>
758 777 </td>
759 778 <td class="source followlines-btn-parent"><a href="#l27"> 27</a> <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></td>
760 779 </tr>
761 780 <tr id="l28" class="thisrev">
762 781 <td class="annotate parity0">
763 782
764 783 <div class="annotate-info">
765 784 <div>
766 785 <a href="/annotate/f4fca47b67e6/primes.py#l28">
767 786 f4fca47b67e6</a>
768 787 a
769 788 </div>
770 789 <div><em>&#116;&#101;&#115;&#116;</em></div>
771 790 <div>parents: </div>
772 791 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
773 792 <a href="/rev/f4fca47b67e6">changeset</a>
774 793 </div>
775 794 </td>
776 795 <td class="source followlines-btn-parent"><a href="#l28"> 28</a> <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
777 796 </tr>
778 797 <tr id="l29" class="thisrev">
779 798 <td class="annotate parity0">
780 799
781 800 <div class="annotate-info">
782 801 <div>
783 802 <a href="/annotate/f4fca47b67e6/primes.py#l29">
784 803 f4fca47b67e6</a>
785 804 a
786 805 </div>
787 806 <div><em>&#116;&#101;&#115;&#116;</em></div>
788 807 <div>parents: </div>
789 808 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
790 809 <a href="/rev/f4fca47b67e6">changeset</a>
791 810 </div>
792 811 </td>
793 812 <td class="source followlines-btn-parent"><a href="#l29"> 29</a> <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></td>
794 813 </tr>
795 814 <tr id="l30" class="thisrev">
796 815 <td class="annotate parity0">
797 816
798 817 <div class="annotate-info">
799 818 <div>
800 819 <a href="/annotate/f4fca47b67e6/primes.py#l30">
801 820 f4fca47b67e6</a>
802 821 a
803 822 </div>
804 823 <div><em>&#116;&#101;&#115;&#116;</em></div>
805 824 <div>parents: </div>
806 825 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
807 826 <a href="/rev/f4fca47b67e6">changeset</a>
808 827 </div>
809 828 </td>
810 829 <td class="source followlines-btn-parent"><a href="#l30"> 30</a> <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></td>
811 830 </tr>
812 831 <tr id="l31" class="thisrev">
813 832 <td class="annotate parity0">
814 833
815 834 <div class="annotate-info">
816 835 <div>
817 836 <a href="/annotate/f4fca47b67e6/primes.py#l31">
818 837 f4fca47b67e6</a>
819 838 a
820 839 </div>
821 840 <div><em>&#116;&#101;&#115;&#116;</em></div>
822 841 <div>parents: </div>
823 842 <a href="/diff/f4fca47b67e6/primes.py">diff</a>
824 843 <a href="/rev/f4fca47b67e6">changeset</a>
825 844 </div>
826 845 </td>
827 846 <td class="source followlines-btn-parent"><a href="#l31"> 31</a> </td>
828 847 </tr>
829 848 </tbody>
830 849 </table>
831 850 </div>
832 851 </div>
833 852 </div>
834 853
835 854 <script type="text/javascript" src="/static/followlines.js"></script>
836 855
837 856
838 857
839 858 </body>
840 859 </html>
841 860
842 861
843 862 hgweb fileannotate, raw
844 863
845 864 $ (get-with-headers.py localhost:$HGPORT 'annotate/tip/primes.py?style=raw') \
846 865 > | sed "s/test@//" > a
847 866 $ echo "200 Script output follows" > b
848 867 $ echo "" >> b
849 868 $ echo "" >> b
850 869 $ hg annotate "primes.py" >> b
851 870 $ echo "" >> b
852 871 $ echo "" >> b
853 872 $ echo "" >> b
854 873 $ echo "" >> b
855 874 $ cmp b a || diff -u b a
856 875
857 876 hgweb filerevision, raw
858 877
859 878 $ (get-with-headers.py localhost:$HGPORT 'file/tip/primes.py?style=raw') \
860 879 > > a
861 880 $ echo "200 Script output follows" > b
862 881 $ echo "" >> b
863 882 $ hg cat primes.py >> b
864 883 $ cmp b a || diff -u b a
865 884
866 885 hgweb highlightcss friendly
867 886
868 887 $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out
869 888 $ head -n 4 out
870 889 200 Script output follows
871 890
872 891 /* pygments_style = friendly */
873 892
874 893 $ rm out
875 894
876 895 errors encountered
877 896
878 897 $ cat errors.log
879 898 $ killdaemons.py
880 899
881 900 Change the pygments style
882 901
883 902 $ cat > .hg/hgrc <<EOF
884 903 > [web]
885 904 > pygments_style = fruity
886 905 > EOF
887 906
888 907 hg serve again
889 908
890 909 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid -A access.log -E errors.log
891 910 $ cat hg.pid >> $DAEMON_PIDS
892 911
893 912 hgweb highlightcss fruity
894 913
895 914 $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out
896 915 $ head -n 4 out
897 916 200 Script output follows
898 917
899 918 /* pygments_style = fruity */
900 919
901 920 $ rm out
902 921
903 922 errors encountered
904 923
905 924 $ cat errors.log
906 925 $ killdaemons.py
907 926
908 927 only highlight C source files
909 928
910 929 $ cat > .hg/hgrc <<EOF
911 930 > [web]
912 931 > highlightfiles = **.c
913 932 > EOF
914 933
915 934 hg serve again
916 935
917 936 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid -A access.log -E errors.log
918 937 $ cat hg.pid >> $DAEMON_PIDS
919 938
920 939 test that fileset in highlightfiles works and primes.py is not highlighted
921 940
922 941 $ get-with-headers.py localhost:$HGPORT 'file/tip/primes.py' | grep 'id="l11"'
923 942 <span id="l11"> def sieve(ns):</span><a href="#l11"></a>
924 943
925 944 errors encountered
926 945
927 946 $ cat errors.log
928 947 $ cd ..
929 948 $ hg init eucjp
930 949 $ cd eucjp
931 950 $ $PYTHON -c 'print("\265\376")' >> eucjp.txt # Japanese kanji "Kyo"
932 951 $ hg ci -Ama
933 952 adding eucjp.txt
934 953 $ hgserveget () {
935 954 > killdaemons.py
936 955 > echo % HGENCODING="$1" hg serve
937 956 > HGENCODING="$1" hg serve -p $HGPORT -d -n test --pid-file=hg.pid -E errors.log
938 957 > cat hg.pid >> $DAEMON_PIDS
939 958 >
940 959 > echo % hgweb filerevision, html
941 960 > get-with-headers.py localhost:$HGPORT "file/tip/$2" \
942 961 > | grep '<div class="parity0 source">'
943 962 > echo % errors encountered
944 963 > cat errors.log
945 964 > }
946 965 $ hgserveget euc-jp eucjp.txt
947 966 % HGENCODING=euc-jp hg serve
948 967 % hgweb filerevision, html
949 968 % errors encountered
950 969 $ hgserveget utf-8 eucjp.txt
951 970 % HGENCODING=utf-8 hg serve
952 971 % hgweb filerevision, html
953 972 % errors encountered
954 973 $ hgserveget us-ascii eucjp.txt
955 974 % HGENCODING=us-ascii hg serve
956 975 % hgweb filerevision, html
957 976 % errors encountered
958 977
959 978 We attempt to highlight unknown files by default
960 979
961 980 $ killdaemons.py
962 981
963 982 $ cat > .hg/hgrc << EOF
964 983 > [web]
965 984 > highlightfiles = **
966 985 > EOF
967 986
968 987 $ cat > unknownfile << EOF
969 988 > #!$PYTHON
970 989 > def foo():
971 990 > pass
972 991 > EOF
973 992
974 993 $ hg add unknownfile
975 994 $ hg commit -m unknown unknownfile
976 995
977 996 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid
978 997 $ cat hg.pid >> $DAEMON_PIDS
979 998
980 999 $ get-with-headers.py localhost:$HGPORT 'file/tip/unknownfile' | grep l2
981 1000 <span id="l2"><span class="k">def</span> <span class="nf">foo</span><span class="p">():</span></span><a href="#l2"></a>
982 1001
983 1002 We can prevent Pygments from falling back to a non filename-based
984 1003 detection mode
985 1004
986 1005 $ cat > .hg/hgrc << EOF
987 1006 > [web]
988 1007 > highlightfiles = **
989 1008 > highlightonlymatchfilename = true
990 1009 > EOF
991 1010
992 1011 $ killdaemons.py
993 1012 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid
994 1013 $ cat hg.pid >> $DAEMON_PIDS
995 1014 $ get-with-headers.py localhost:$HGPORT 'file/tip/unknownfile' | grep l2
996 1015 <span id="l2">def foo():</span><a href="#l2"></a>
997 1016
998 1017 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now