##// END OF EJS Templates
hgweb: plug followlines action in annotate view...
Denis Laxalde -
r32994:1c97df5e default
parent child Browse files
Show More
@@ -1,1383 +1,1385
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)
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 return tmpl("summary",
723 723 desc=web.config("web", "description", "unknown"),
724 724 owner=get_contact(web.config) or "unknown",
725 725 lastchange=tip.date(),
726 726 tags=tagentries,
727 727 bookmarks=bookmarks,
728 728 branches=webutil.branchentries(web.repo, web.stripecount, 10),
729 729 shortlog=changelist,
730 730 node=tip.hex(),
731 731 symrev='tip',
732 732 archives=web.archivelist("tip"),
733 733 labels=web.configlist('web', 'labels'))
734 734
735 735 @webcommand('filediff')
736 736 def filediff(web, req, tmpl):
737 737 """
738 738 /diff/{revision}/{path}
739 739 -----------------------
740 740
741 741 Show how a file changed in a particular commit.
742 742
743 743 The ``filediff`` template is rendered.
744 744
745 745 This handler is registered under both the ``/diff`` and ``/filediff``
746 746 paths. ``/diff`` is used in modern code.
747 747 """
748 748 fctx, ctx = None, None
749 749 try:
750 750 fctx = webutil.filectx(web.repo, req)
751 751 except LookupError:
752 752 ctx = webutil.changectx(web.repo, req)
753 753 path = webutil.cleanpath(web.repo, req.form['file'][0])
754 754 if path not in ctx.files():
755 755 raise
756 756
757 757 if fctx is not None:
758 758 path = fctx.path()
759 759 ctx = fctx.changectx()
760 760 basectx = ctx.p1()
761 761
762 762 style = web.config('web', 'style', 'paper')
763 763 if 'style' in req.form:
764 764 style = req.form['style'][0]
765 765
766 766 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
767 767 if fctx is not None:
768 768 rename = webutil.renamelink(fctx)
769 769 ctx = fctx
770 770 else:
771 771 rename = []
772 772 ctx = ctx
773 773 return tmpl("filediff",
774 774 file=path,
775 775 symrev=webutil.symrevorshortnode(req, ctx),
776 776 rename=rename,
777 777 diff=diffs,
778 778 **webutil.commonentry(web.repo, ctx))
779 779
780 780 diff = webcommand('diff')(filediff)
781 781
782 782 @webcommand('comparison')
783 783 def comparison(web, req, tmpl):
784 784 """
785 785 /comparison/{revision}/{path}
786 786 -----------------------------
787 787
788 788 Show a comparison between the old and new versions of a file from changes
789 789 made on a particular revision.
790 790
791 791 This is similar to the ``diff`` handler. However, this form features
792 792 a split or side-by-side diff rather than a unified diff.
793 793
794 794 The ``context`` query string argument can be used to control the lines of
795 795 context in the diff.
796 796
797 797 The ``filecomparison`` template is rendered.
798 798 """
799 799 ctx = webutil.changectx(web.repo, req)
800 800 if 'file' not in req.form:
801 801 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
802 802 path = webutil.cleanpath(web.repo, req.form['file'][0])
803 803
804 804 parsecontext = lambda v: v == 'full' and -1 or int(v)
805 805 if 'context' in req.form:
806 806 context = parsecontext(req.form['context'][0])
807 807 else:
808 808 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
809 809
810 810 def filelines(f):
811 811 if f.isbinary():
812 812 mt = mimetypes.guess_type(f.path())[0]
813 813 if not mt:
814 814 mt = 'application/octet-stream'
815 815 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
816 816 return f.data().splitlines()
817 817
818 818 fctx = None
819 819 parent = ctx.p1()
820 820 leftrev = parent.rev()
821 821 leftnode = parent.node()
822 822 rightrev = ctx.rev()
823 823 rightnode = ctx.node()
824 824 if path in ctx:
825 825 fctx = ctx[path]
826 826 rightlines = filelines(fctx)
827 827 if path not in parent:
828 828 leftlines = ()
829 829 else:
830 830 pfctx = parent[path]
831 831 leftlines = filelines(pfctx)
832 832 else:
833 833 rightlines = ()
834 834 pfctx = ctx.parents()[0][path]
835 835 leftlines = filelines(pfctx)
836 836
837 837 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
838 838 if fctx is not None:
839 839 rename = webutil.renamelink(fctx)
840 840 ctx = fctx
841 841 else:
842 842 rename = []
843 843 ctx = ctx
844 844 return tmpl('filecomparison',
845 845 file=path,
846 846 symrev=webutil.symrevorshortnode(req, ctx),
847 847 rename=rename,
848 848 leftrev=leftrev,
849 849 leftnode=hex(leftnode),
850 850 rightrev=rightrev,
851 851 rightnode=hex(rightnode),
852 852 comparison=comparison,
853 853 **webutil.commonentry(web.repo, ctx))
854 854
855 855 @webcommand('annotate')
856 856 def annotate(web, req, tmpl):
857 857 """
858 858 /annotate/{revision}/{path}
859 859 ---------------------------
860 860
861 861 Show changeset information for each line in a file.
862 862
863 863 The ``fileannotate`` template is rendered.
864 864 """
865 865 fctx = webutil.filectx(web.repo, req)
866 866 f = fctx.path()
867 867 parity = paritygen(web.stripecount)
868 ishead = fctx.filerev() in fctx.filelog().headrevs()
868 869
869 870 # parents() is called once per line and several lines likely belong to
870 871 # same revision. So it is worth caching.
871 872 # TODO there are still redundant operations within basefilectx.parents()
872 873 # and from the fctx.annotate() call itself that could be cached.
873 874 parentscache = {}
874 875 def parents(f):
875 876 rev = f.rev()
876 877 if rev not in parentscache:
877 878 parentscache[rev] = []
878 879 for p in f.parents():
879 880 entry = {
880 881 'node': p.hex(),
881 882 'rev': p.rev(),
882 883 }
883 884 parentscache[rev].append(entry)
884 885
885 886 for p in parentscache[rev]:
886 887 yield p
887 888
888 889 def annotate(**map):
889 890 if fctx.isbinary():
890 891 mt = (mimetypes.guess_type(fctx.path())[0]
891 892 or 'application/octet-stream')
892 893 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
893 894 else:
894 895 lines = webutil.annotate(fctx, web.repo.ui)
895 896
896 897 previousrev = None
897 898 blockparitygen = paritygen(1)
898 899 for lineno, ((f, targetline), l) in enumerate(lines):
899 900 rev = f.rev()
900 901 if rev != previousrev:
901 902 blockhead = True
902 903 blockparity = next(blockparitygen)
903 904 else:
904 905 blockhead = None
905 906 previousrev = rev
906 907 yield {"parity": next(parity),
907 908 "node": f.hex(),
908 909 "rev": rev,
909 910 "author": f.user(),
910 911 "parents": parents(f),
911 912 "desc": f.description(),
912 913 "extra": f.extra(),
913 914 "file": f.path(),
914 915 "blockhead": blockhead,
915 916 "blockparity": blockparity,
916 917 "targetline": targetline,
917 918 "line": l,
918 919 "lineno": lineno + 1,
919 920 "lineid": "l%d" % (lineno + 1),
920 921 "linenumber": "% 6d" % (lineno + 1),
921 922 "revdate": f.date()}
922 923
923 924 return tmpl("fileannotate",
924 925 file=f,
925 926 annotate=annotate,
926 927 path=webutil.up(f),
927 928 symrev=webutil.symrevorshortnode(req, fctx),
928 929 rename=webutil.renamelink(fctx),
929 930 permissions=fctx.manifest().flags(f),
931 ishead=int(ishead),
930 932 **webutil.commonentry(web.repo, fctx))
931 933
932 934 @webcommand('filelog')
933 935 def filelog(web, req, tmpl):
934 936 """
935 937 /filelog/{revision}/{path}
936 938 --------------------------
937 939
938 940 Show information about the history of a file in the repository.
939 941
940 942 The ``revcount`` query string argument can be defined to control the
941 943 maximum number of entries to show.
942 944
943 945 The ``filelog`` template will be rendered.
944 946 """
945 947
946 948 try:
947 949 fctx = webutil.filectx(web.repo, req)
948 950 f = fctx.path()
949 951 fl = fctx.filelog()
950 952 except error.LookupError:
951 953 f = webutil.cleanpath(web.repo, req.form['file'][0])
952 954 fl = web.repo.file(f)
953 955 numrevs = len(fl)
954 956 if not numrevs: # file doesn't exist at all
955 957 raise
956 958 rev = webutil.changectx(web.repo, req).rev()
957 959 first = fl.linkrev(0)
958 960 if rev < first: # current rev is from before file existed
959 961 raise
960 962 frev = numrevs - 1
961 963 while fl.linkrev(frev) > rev:
962 964 frev -= 1
963 965 fctx = web.repo.filectx(f, fl.linkrev(frev))
964 966
965 967 revcount = web.maxshortchanges
966 968 if 'revcount' in req.form:
967 969 try:
968 970 revcount = int(req.form.get('revcount', [revcount])[0])
969 971 revcount = max(revcount, 1)
970 972 tmpl.defaults['sessionvars']['revcount'] = revcount
971 973 except ValueError:
972 974 pass
973 975
974 976 lrange = webutil.linerange(req)
975 977
976 978 lessvars = copy.copy(tmpl.defaults['sessionvars'])
977 979 lessvars['revcount'] = max(revcount / 2, 1)
978 980 morevars = copy.copy(tmpl.defaults['sessionvars'])
979 981 morevars['revcount'] = revcount * 2
980 982
981 983 patch = 'patch' in req.form
982 984 if patch:
983 985 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
984 986 descend = 'descend' in req.form
985 987 if descend:
986 988 lessvars['descend'] = morevars['descend'] = req.form['descend'][0]
987 989
988 990 count = fctx.filerev() + 1
989 991 start = max(0, count - revcount) # first rev on this page
990 992 end = min(count, start + revcount) # last rev on this page
991 993 parity = paritygen(web.stripecount, offset=start - end)
992 994
993 995 repo = web.repo
994 996 revs = fctx.filelog().revs(start, end - 1)
995 997 entries = []
996 998
997 999 diffstyle = web.config('web', 'style', 'paper')
998 1000 if 'style' in req.form:
999 1001 diffstyle = req.form['style'][0]
1000 1002
1001 1003 def diff(fctx, linerange=None):
1002 1004 ctx = fctx.changectx()
1003 1005 basectx = ctx.p1()
1004 1006 path = fctx.path()
1005 1007 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle,
1006 1008 linerange=linerange,
1007 1009 lineidprefix='%s-' % ctx.hex()[:12])
1008 1010
1009 1011 linerange = None
1010 1012 if lrange is not None:
1011 1013 linerange = webutil.formatlinerange(*lrange)
1012 1014 # deactivate numeric nav links when linerange is specified as this
1013 1015 # would required a dedicated "revnav" class
1014 1016 nav = None
1015 1017 if descend:
1016 1018 it = dagop.blockdescendants(fctx, *lrange)
1017 1019 else:
1018 1020 it = dagop.blockancestors(fctx, *lrange)
1019 1021 for i, (c, lr) in enumerate(it, 1):
1020 1022 diffs = None
1021 1023 if patch:
1022 1024 diffs = diff(c, linerange=lr)
1023 1025 # follow renames accross filtered (not in range) revisions
1024 1026 path = c.path()
1025 1027 entries.append(dict(
1026 1028 parity=next(parity),
1027 1029 filerev=c.rev(),
1028 1030 file=path,
1029 1031 diff=diffs,
1030 1032 linerange=webutil.formatlinerange(*lr),
1031 1033 **webutil.commonentry(repo, c)))
1032 1034 if i == revcount:
1033 1035 break
1034 1036 lessvars['linerange'] = webutil.formatlinerange(*lrange)
1035 1037 morevars['linerange'] = lessvars['linerange']
1036 1038 else:
1037 1039 for i in revs:
1038 1040 iterfctx = fctx.filectx(i)
1039 1041 diffs = None
1040 1042 if patch:
1041 1043 diffs = diff(iterfctx)
1042 1044 entries.append(dict(
1043 1045 parity=next(parity),
1044 1046 filerev=i,
1045 1047 file=f,
1046 1048 diff=diffs,
1047 1049 rename=webutil.renamelink(iterfctx),
1048 1050 **webutil.commonentry(repo, iterfctx)))
1049 1051 entries.reverse()
1050 1052 revnav = webutil.filerevnav(web.repo, fctx.path())
1051 1053 nav = revnav.gen(end - 1, revcount, count)
1052 1054
1053 1055 latestentry = entries[:1]
1054 1056
1055 1057 return tmpl("filelog",
1056 1058 file=f,
1057 1059 nav=nav,
1058 1060 symrev=webutil.symrevorshortnode(req, fctx),
1059 1061 entries=entries,
1060 1062 descend=descend,
1061 1063 patch=patch,
1062 1064 latestentry=latestentry,
1063 1065 linerange=linerange,
1064 1066 revcount=revcount,
1065 1067 morevars=morevars,
1066 1068 lessvars=lessvars,
1067 1069 **webutil.commonentry(web.repo, fctx))
1068 1070
1069 1071 @webcommand('archive')
1070 1072 def archive(web, req, tmpl):
1071 1073 """
1072 1074 /archive/{revision}.{format}[/{path}]
1073 1075 -------------------------------------
1074 1076
1075 1077 Obtain an archive of repository content.
1076 1078
1077 1079 The content and type of the archive is defined by a URL path parameter.
1078 1080 ``format`` is the file extension of the archive type to be generated. e.g.
1079 1081 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1080 1082 server configuration.
1081 1083
1082 1084 The optional ``path`` URL parameter controls content to include in the
1083 1085 archive. If omitted, every file in the specified revision is present in the
1084 1086 archive. If included, only the specified file or contents of the specified
1085 1087 directory will be included in the archive.
1086 1088
1087 1089 No template is used for this handler. Raw, binary content is generated.
1088 1090 """
1089 1091
1090 1092 type_ = req.form.get('type', [None])[0]
1091 1093 allowed = web.configlist("web", "allow_archive")
1092 1094 key = req.form['node'][0]
1093 1095
1094 1096 if type_ not in web.archivespecs:
1095 1097 msg = 'Unsupported archive type: %s' % type_
1096 1098 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1097 1099
1098 1100 if not ((type_ in allowed or
1099 1101 web.configbool("web", "allow" + type_, False))):
1100 1102 msg = 'Archive type not allowed: %s' % type_
1101 1103 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1102 1104
1103 1105 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1104 1106 cnode = web.repo.lookup(key)
1105 1107 arch_version = key
1106 1108 if cnode == key or key == 'tip':
1107 1109 arch_version = short(cnode)
1108 1110 name = "%s-%s" % (reponame, arch_version)
1109 1111
1110 1112 ctx = webutil.changectx(web.repo, req)
1111 1113 pats = []
1112 1114 matchfn = scmutil.match(ctx, [])
1113 1115 file = req.form.get('file', None)
1114 1116 if file:
1115 1117 pats = ['path:' + file[0]]
1116 1118 matchfn = scmutil.match(ctx, pats, default='path')
1117 1119 if pats:
1118 1120 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1119 1121 if not files:
1120 1122 raise ErrorResponse(HTTP_NOT_FOUND,
1121 1123 'file(s) not found: %s' % file[0])
1122 1124
1123 1125 mimetype, artype, extension, encoding = web.archivespecs[type_]
1124 1126 headers = [
1125 1127 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1126 1128 ]
1127 1129 if encoding:
1128 1130 headers.append(('Content-Encoding', encoding))
1129 1131 req.headers.extend(headers)
1130 1132 req.respond(HTTP_OK, mimetype)
1131 1133
1132 1134 archival.archive(web.repo, req, cnode, artype, prefix=name,
1133 1135 matchfn=matchfn,
1134 1136 subrepos=web.configbool("web", "archivesubrepos"))
1135 1137 return []
1136 1138
1137 1139
1138 1140 @webcommand('static')
1139 1141 def static(web, req, tmpl):
1140 1142 fname = req.form['file'][0]
1141 1143 # a repo owner may set web.static in .hg/hgrc to get any file
1142 1144 # readable by the user running the CGI script
1143 1145 static = web.config("web", "static", None, untrusted=False)
1144 1146 if not static:
1145 1147 tp = web.templatepath or templater.templatepaths()
1146 1148 if isinstance(tp, str):
1147 1149 tp = [tp]
1148 1150 static = [os.path.join(p, 'static') for p in tp]
1149 1151 staticfile(static, fname, req)
1150 1152 return []
1151 1153
1152 1154 @webcommand('graph')
1153 1155 def graph(web, req, tmpl):
1154 1156 """
1155 1157 /graph[/{revision}]
1156 1158 -------------------
1157 1159
1158 1160 Show information about the graphical topology of the repository.
1159 1161
1160 1162 Information rendered by this handler can be used to create visual
1161 1163 representations of repository topology.
1162 1164
1163 1165 The ``revision`` URL parameter controls the starting changeset.
1164 1166
1165 1167 The ``revcount`` query string argument can define the number of changesets
1166 1168 to show information for.
1167 1169
1168 1170 This handler will render the ``graph`` template.
1169 1171 """
1170 1172
1171 1173 if 'node' in req.form:
1172 1174 ctx = webutil.changectx(web.repo, req)
1173 1175 symrev = webutil.symrevorshortnode(req, ctx)
1174 1176 else:
1175 1177 ctx = web.repo['tip']
1176 1178 symrev = 'tip'
1177 1179 rev = ctx.rev()
1178 1180
1179 1181 bg_height = 39
1180 1182 revcount = web.maxshortchanges
1181 1183 if 'revcount' in req.form:
1182 1184 try:
1183 1185 revcount = int(req.form.get('revcount', [revcount])[0])
1184 1186 revcount = max(revcount, 1)
1185 1187 tmpl.defaults['sessionvars']['revcount'] = revcount
1186 1188 except ValueError:
1187 1189 pass
1188 1190
1189 1191 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1190 1192 lessvars['revcount'] = max(revcount / 2, 1)
1191 1193 morevars = copy.copy(tmpl.defaults['sessionvars'])
1192 1194 morevars['revcount'] = revcount * 2
1193 1195
1194 1196 count = len(web.repo)
1195 1197 pos = rev
1196 1198
1197 1199 uprev = min(max(0, count - 1), rev + revcount)
1198 1200 downrev = max(0, rev - revcount)
1199 1201 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1200 1202
1201 1203 tree = []
1202 1204 if pos != -1:
1203 1205 allrevs = web.repo.changelog.revs(pos, 0)
1204 1206 revs = []
1205 1207 for i in allrevs:
1206 1208 revs.append(i)
1207 1209 if len(revs) >= revcount:
1208 1210 break
1209 1211
1210 1212 # We have to feed a baseset to dagwalker as it is expecting smartset
1211 1213 # object. This does not have a big impact on hgweb performance itself
1212 1214 # since hgweb graphing code is not itself lazy yet.
1213 1215 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1214 1216 # As we said one line above... not lazy.
1215 1217 tree = list(graphmod.colored(dag, web.repo))
1216 1218
1217 1219 def getcolumns(tree):
1218 1220 cols = 0
1219 1221 for (id, type, ctx, vtx, edges) in tree:
1220 1222 if type != graphmod.CHANGESET:
1221 1223 continue
1222 1224 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1223 1225 max([edge[1] for edge in edges] or [0]))
1224 1226 return cols
1225 1227
1226 1228 def graphdata(usetuples, encodestr):
1227 1229 data = []
1228 1230
1229 1231 row = 0
1230 1232 for (id, type, ctx, vtx, edges) in tree:
1231 1233 if type != graphmod.CHANGESET:
1232 1234 continue
1233 1235 node = str(ctx)
1234 1236 age = encodestr(templatefilters.age(ctx.date()))
1235 1237 desc = templatefilters.firstline(encodestr(ctx.description()))
1236 1238 desc = cgi.escape(templatefilters.nonempty(desc))
1237 1239 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1238 1240 branch = cgi.escape(encodestr(ctx.branch()))
1239 1241 try:
1240 1242 branchnode = web.repo.branchtip(branch)
1241 1243 except error.RepoLookupError:
1242 1244 branchnode = None
1243 1245 branch = branch, branchnode == ctx.node()
1244 1246
1245 1247 if usetuples:
1246 1248 data.append((node, vtx, edges, desc, user, age, branch,
1247 1249 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1248 1250 [cgi.escape(encodestr(x))
1249 1251 for x in ctx.bookmarks()]))
1250 1252 else:
1251 1253 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1252 1254 'color': (edge[2] - 1) % 6 + 1,
1253 1255 'width': edge[3], 'bcolor': edge[4]}
1254 1256 for edge in edges]
1255 1257
1256 1258 data.append(
1257 1259 {'node': node,
1258 1260 'col': vtx[0],
1259 1261 'color': (vtx[1] - 1) % 6 + 1,
1260 1262 'edges': edgedata,
1261 1263 'row': row,
1262 1264 'nextrow': row + 1,
1263 1265 'desc': desc,
1264 1266 'user': user,
1265 1267 'age': age,
1266 1268 'bookmarks': webutil.nodebookmarksdict(
1267 1269 web.repo, ctx.node()),
1268 1270 'branches': webutil.nodebranchdict(web.repo, ctx),
1269 1271 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1270 1272 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1271 1273
1272 1274 row += 1
1273 1275
1274 1276 return data
1275 1277
1276 1278 cols = getcolumns(tree)
1277 1279 rows = len(tree)
1278 1280 canvasheight = (rows + 1) * bg_height - 27
1279 1281
1280 1282 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1281 1283 uprev=uprev,
1282 1284 lessvars=lessvars, morevars=morevars, downrev=downrev,
1283 1285 cols=cols, rows=rows,
1284 1286 canvaswidth=(cols + 1) * bg_height,
1285 1287 truecanvasheight=rows * bg_height,
1286 1288 canvasheight=canvasheight, bg_height=bg_height,
1287 1289 # {jsdata} will be passed to |json, so it must be in utf-8
1288 1290 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1289 1291 nodes=lambda **x: graphdata(False, str),
1290 1292 node=ctx.hex(), changenav=changenav)
1291 1293
1292 1294 def _getdoc(e):
1293 1295 doc = e[0].__doc__
1294 1296 if doc:
1295 1297 doc = _(doc).partition('\n')[0]
1296 1298 else:
1297 1299 doc = _('(no help text available)')
1298 1300 return doc
1299 1301
1300 1302 @webcommand('help')
1301 1303 def help(web, req, tmpl):
1302 1304 """
1303 1305 /help[/{topic}]
1304 1306 ---------------
1305 1307
1306 1308 Render help documentation.
1307 1309
1308 1310 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1309 1311 is defined, that help topic will be rendered. If not, an index of
1310 1312 available help topics will be rendered.
1311 1313
1312 1314 The ``help`` template will be rendered when requesting help for a topic.
1313 1315 ``helptopics`` will be rendered for the index of help topics.
1314 1316 """
1315 1317 from .. import commands, help as helpmod # avoid cycle
1316 1318
1317 1319 topicname = req.form.get('node', [None])[0]
1318 1320 if not topicname:
1319 1321 def topics(**map):
1320 1322 for entries, summary, _doc in helpmod.helptable:
1321 1323 yield {'topic': entries[0], 'summary': summary}
1322 1324
1323 1325 early, other = [], []
1324 1326 primary = lambda s: s.partition('|')[0]
1325 1327 for c, e in commands.table.iteritems():
1326 1328 doc = _getdoc(e)
1327 1329 if 'DEPRECATED' in doc or c.startswith('debug'):
1328 1330 continue
1329 1331 cmd = primary(c)
1330 1332 if cmd.startswith('^'):
1331 1333 early.append((cmd[1:], doc))
1332 1334 else:
1333 1335 other.append((cmd, doc))
1334 1336
1335 1337 early.sort()
1336 1338 other.sort()
1337 1339
1338 1340 def earlycommands(**map):
1339 1341 for c, doc in early:
1340 1342 yield {'topic': c, 'summary': doc}
1341 1343
1342 1344 def othercommands(**map):
1343 1345 for c, doc in other:
1344 1346 yield {'topic': c, 'summary': doc}
1345 1347
1346 1348 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1347 1349 othercommands=othercommands, title='Index')
1348 1350
1349 1351 # Render an index of sub-topics.
1350 1352 if topicname in helpmod.subtopics:
1351 1353 topics = []
1352 1354 for entries, summary, _doc in helpmod.subtopics[topicname]:
1353 1355 topics.append({
1354 1356 'topic': '%s.%s' % (topicname, entries[0]),
1355 1357 'basename': entries[0],
1356 1358 'summary': summary,
1357 1359 })
1358 1360
1359 1361 return tmpl('helptopics', topics=topics, title=topicname,
1360 1362 subindex=True)
1361 1363
1362 1364 u = webutil.wsgiui.load()
1363 1365 u.verbose = True
1364 1366
1365 1367 # Render a page from a sub-topic.
1366 1368 if '.' in topicname:
1367 1369 # TODO implement support for rendering sections, like
1368 1370 # `hg help` works.
1369 1371 topic, subtopic = topicname.split('.', 1)
1370 1372 if topic not in helpmod.subtopics:
1371 1373 raise ErrorResponse(HTTP_NOT_FOUND)
1372 1374 else:
1373 1375 topic = topicname
1374 1376 subtopic = None
1375 1377
1376 1378 try:
1377 1379 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1378 1380 except error.UnknownCommand:
1379 1381 raise ErrorResponse(HTTP_NOT_FOUND)
1380 1382 return tmpl('help', topic=topicname, doc=doc)
1381 1383
1382 1384 # tell hggettext to extract docstrings from these functions:
1383 1385 i18nfunctions = commands.values()
@@ -1,75 +1,80
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">
65 65 {desc|strip|escape|websub|addbreaks|nonempty}
66 66 </div>
67 67 <div class="page_body">
68 68 <table>
69 <tbody>
69 <tbody class="sourcelines"
70 data-logurl="{url|urlescape}log/{symrev}/{file|urlescape}"
71 data-selectabletag="TR"
72 data-ishead="{ishead}">
70 73 {annotate%annotateline}
71 74 </tbody>
72 75 </table>
73 76 </div>
74 77
78 <script type="text/javascript" src="{staticurl|urlescape}followlines.js"></script>
79
75 80 {footer}
@@ -1,84 +1,89
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 68 <div class="overflow">
69 69 <table class="bigtable">
70 70 <thead>
71 71 <tr>
72 72 <th class="annotate">rev</th>
73 73 <th class="line">&nbsp;&nbsp;line source</th>
74 74 </tr>
75 75 </thead>
76 <tbody class="stripes2">
76 <tbody class="stripes2 sourcelines"
77 data-logurl="{url|urlescape}log/{symrev}/{file|urlescape}"
78 data-selectabletag="TR"
79 data-ishead="{ishead}">
77 80 {annotate%annotateline}
78 81 </tbody>
79 82 </table>
80 83 </div>
81 84 </div>
82 85 </div>
83 86
87 <script type="text/javascript" src="{staticurl|urlescape}followlines.js"></script>
88
84 89 {footer}
@@ -1,344 +1,346
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 display: none;
90 90 position: absolute;
91 91 background-color: #FFFFFF;
92 92 border: 1px solid #d9d8d1;
93 93 text-align: left;
94 94 color: #000000;
95 95 padding: 5px;
96 96 }
97 97 div.annotate-info a { color: #0000FF; text-decoration: underline; }
98 98 td.annotate:hover div.annotate-info { display: inline; }
99 99 .linenr { color:#999999; text-decoration:none }
100 100 div.rss_logo { float: right; white-space: nowrap; }
101 101 div.rss_logo a {
102 102 padding:3px 6px; line-height:10px;
103 103 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
104 104 color:#ffffff; background-color:#ff6600;
105 105 font-weight:bold; font-family:sans-serif; font-size:10px;
106 106 text-align:center; text-decoration:none;
107 107 }
108 108 div.rss_logo a:hover { background-color:#ee5500; }
109 109 pre { margin: 0; }
110 110 span.logtags span {
111 111 padding: 0px 4px;
112 112 font-size: 10px;
113 113 font-weight: normal;
114 114 border: 1px solid;
115 115 background-color: #ffaaff;
116 116 border-color: #ffccff #ff00ee #ff00ee #ffccff;
117 117 }
118 118 span.logtags span.tagtag {
119 119 background-color: #ffffaa;
120 120 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
121 121 }
122 122 span.logtags span.branchtag {
123 123 background-color: #aaffaa;
124 124 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
125 125 }
126 126 span.logtags span.inbranchtag {
127 127 background-color: #d5dde6;
128 128 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
129 129 }
130 130 span.logtags span.bookmarktag {
131 131 background-color: #afdffa;
132 132 border-color: #ccecff #46ace6 #46ace6 #ccecff;
133 133 }
134 134 span.difflineplus { color:#008800; }
135 135 span.difflineminus { color:#cc0000; }
136 136 span.difflineat { color:#990099; }
137 137 div.diffblocks { counter-reset: lineno; }
138 138 div.diffblock { counter-increment: lineno; }
139 139 pre.sourcelines { position: relative; counter-reset: lineno; }
140 140 pre.sourcelines > span {
141 141 display: inline-block;
142 142 box-sizing: border-box;
143 143 width: 100%;
144 144 padding: 0 0 0 5em;
145 145 counter-increment: lineno;
146 146 vertical-align: top;
147 147 }
148 148 pre.sourcelines > span:before {
149 149 -moz-user-select: -moz-none;
150 150 -khtml-user-select: none;
151 151 -webkit-user-select: none;
152 152 -ms-user-select: none;
153 153 user-select: none;
154 154 display: inline-block;
155 155 margin-left: -5em;
156 156 width: 4em;
157 157 color: #999;
158 158 text-align: right;
159 159 content: counters(lineno,".");
160 160 float: left;
161 161 }
162 162 pre.sourcelines > a {
163 163 display: inline-block;
164 164 position: absolute;
165 165 left: 0px;
166 166 width: 4em;
167 167 height: 1em;
168 168 }
169 169 tr:target td,
170 170 pre.sourcelines > span:target,
171 171 pre.sourcelines.stripes > span:target {
172 172 background-color: #bfdfff;
173 173 }
174 174
175 175 .description {
176 176 font-family: monospace;
177 177 }
178 178
179 179 /* Followlines */
180 div.page_body table tbody.sourcelines > tr.followlines-select:hover,
180 181 div.page_body pre.sourcelines > span.followlines-select:hover {
181 182 cursor: cell;
182 183 }
183 184
185 tbody.sourcelines > tr.followlines-selected,
184 186 pre.sourcelines > span.followlines-selected {
185 187 background-color: #99C7E9 !important;
186 188 }
187 189
188 190 div#followlines {
189 191 background-color: #B7B7B7;
190 192 border: 1px solid #CCC;
191 193 border-radius: 5px;
192 194 padding: 4px;
193 195 position: fixed;
194 196 }
195 197
196 198 div.followlines-cancel {
197 199 text-align: right;
198 200 }
199 201
200 202 div.followlines-cancel > button {
201 203 line-height: 80%;
202 204 padding: 0;
203 205 border: 0;
204 206 border-radius: 2px;
205 207 background-color: inherit;
206 208 font-weight: bold;
207 209 }
208 210
209 211 div.followlines-cancel > button:hover {
210 212 color: #FFFFFF;
211 213 background-color: #CF1F1F;
212 214 }
213 215
214 216 div.followlines-link {
215 217 margin: 2px;
216 218 margin-top: 4px;
217 219 font-family: sans-serif;
218 220 }
219 221
220 222 div#followlines-tooltip {
221 223 display: none;
222 224 position: fixed;
223 225 background-color: #ffc;
224 226 border: 1px solid #999;
225 227 padding: 2px;
226 228 }
227 229
228 230 .sourcelines:hover > div#followlines-tooltip {
229 231 display: inline;
230 232 }
231 233
232 234 .sourcelines:hover > div#followlines-tooltip.hidden {
233 235 display: none;
234 236 }
235 237 /* Graph */
236 238 div#wrapper {
237 239 position: relative;
238 240 margin: 0;
239 241 padding: 0;
240 242 margin-top: 3px;
241 243 }
242 244
243 245 canvas {
244 246 position: absolute;
245 247 z-index: 5;
246 248 top: -0.9em;
247 249 margin: 0;
248 250 }
249 251
250 252 ul#nodebgs {
251 253 list-style: none inside none;
252 254 padding: 0;
253 255 margin: 0;
254 256 top: -0.7em;
255 257 }
256 258
257 259 ul#graphnodes li, ul#nodebgs li {
258 260 height: 39px;
259 261 }
260 262
261 263 ul#graphnodes {
262 264 position: absolute;
263 265 z-index: 10;
264 266 top: -0.8em;
265 267 list-style: none inside none;
266 268 padding: 0;
267 269 }
268 270
269 271 ul#graphnodes li .info {
270 272 display: block;
271 273 font-size: 100%;
272 274 position: relative;
273 275 top: -3px;
274 276 font-style: italic;
275 277 }
276 278
277 279 /* Comparison */
278 280 .legend {
279 281 padding: 1.5% 0 1.5% 0;
280 282 }
281 283
282 284 .legendinfo {
283 285 border: 1px solid #d9d8d1;
284 286 font-size: 80%;
285 287 text-align: center;
286 288 padding: 0.5%;
287 289 }
288 290
289 291 .equal {
290 292 background-color: #ffffff;
291 293 }
292 294
293 295 .delete {
294 296 background-color: #faa;
295 297 color: #333;
296 298 }
297 299
298 300 .insert {
299 301 background-color: #ffa;
300 302 }
301 303
302 304 .replace {
303 305 background-color: #e8e8e8;
304 306 }
305 307
306 308 .comparison {
307 309 overflow-x: auto;
308 310 }
309 311
310 312 .header th {
311 313 text-align: center;
312 314 }
313 315
314 316 .block {
315 317 border-top: 1px solid #d9d8d1;
316 318 }
317 319
318 320 .scroll-loading {
319 321 -webkit-animation: change_color 1s linear 0s infinite alternate;
320 322 -moz-animation: change_color 1s linear 0s infinite alternate;
321 323 -o-animation: change_color 1s linear 0s infinite alternate;
322 324 animation: change_color 1s linear 0s infinite alternate;
323 325 }
324 326
325 327 @-webkit-keyframes change_color {
326 328 from { background-color: #A0CEFF; } to { }
327 329 }
328 330 @-moz-keyframes change_color {
329 331 from { background-color: #A0CEFF; } to { }
330 332 }
331 333 @-o-keyframes change_color {
332 334 from { background-color: #A0CEFF; } to { }
333 335 }
334 336 @keyframes change_color {
335 337 from { background-color: #A0CEFF; } to { }
336 338 }
337 339
338 340 .scroll-loading-error {
339 341 background-color: #FFCCCC !important;
340 342 }
341 343
342 344 #doc {
343 345 margin: 0 8px;
344 346 }
@@ -1,487 +1,489
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 display: none;
218 218 position: absolute;
219 219 background-color: #FFFFFF;
220 220 border: 1px solid #999;
221 221 text-align: left;
222 222 color: #000000;
223 223 padding: 5px;
224 224 }
225 225 div.annotate-info a { color: #0000FF; }
226 226 td.annotate:hover div.annotate-info { display: inline; }
227 227
228 228 .source, .sourcefirst {
229 229 font-family: monospace;
230 230 white-space: pre;
231 231 padding: 1px 4px;
232 232 font-size: 90%;
233 233 }
234 234 .sourcefirst { border-bottom: 1px solid #999; font-weight: bold; }
235 235 .source a { color: #999; font-size: smaller; font-family: monospace;}
236 236 .bottomline { border-bottom: 1px solid #999; }
237 237
238 238 .sourcelines {
239 239 font-size: 90%;
240 240 position: relative;
241 241 counter-reset: lineno;
242 242 }
243 243
244 244 .wrap > span {
245 245 white-space: pre-wrap;
246 246 }
247 247
248 248 .linewraptoggle {
249 249 float: right;
250 250 }
251 251
252 252 .diffblocks { counter-reset: lineno; }
253 253 .diffblocks > div { counter-increment: lineno; }
254 254
255 255 .sourcelines > span {
256 256 display: inline-block;
257 257 box-sizing: border-box;
258 258 width: 100%;
259 259 padding: 1px 0px 1px 5em;
260 260 counter-increment: lineno;
261 261 }
262 262
263 263 .sourcelines > span:before {
264 264 -moz-user-select: -moz-none;
265 265 -khtml-user-select: none;
266 266 -webkit-user-select: none;
267 267 -ms-user-select: none;
268 268 user-select: none;
269 269 display: inline-block;
270 270 margin-left: -5em;
271 271 width: 4em;
272 272 font-size: smaller;
273 273 color: #999;
274 274 text-align: right;
275 275 content: counters(lineno, ".");
276 276 float: left;
277 277 }
278 278
279 279 .sourcelines > span:target, tr:target td {
280 280 background-color: #bfdfff;
281 281 }
282 282
283 div.overflow table tbody.sourcelines > tr.followlines-select:hover,
283 284 div.overflow pre.sourcelines > span.followlines-select:hover {
284 285 cursor: cell;
285 286 }
286 287
288 tbody.sourcelines > tr.followlines-selected,
287 289 pre.sourcelines > span.followlines-selected {
288 290 background-color: #99C7E9;
289 291 }
290 292
291 293 div#followlines {
292 294 background-color: #B7B7B7;
293 295 border: 1px solid #CCC;
294 296 border-radius: 5px;
295 297 padding: 4px;
296 298 position: fixed;
297 299 }
298 300
299 301 div.followlines-cancel {
300 302 text-align: right;
301 303 }
302 304
303 305 div.followlines-cancel > button {
304 306 line-height: 80%;
305 307 padding: 0;
306 308 border: 0;
307 309 border-radius: 2px;
308 310 background-color: inherit;
309 311 font-weight: bold;
310 312 }
311 313
312 314 div.followlines-cancel > button:hover {
313 315 color: #FFFFFF;
314 316 background-color: #CF1F1F;
315 317 }
316 318
317 319 div.followlines-link {
318 320 margin: 2px;
319 321 margin-top: 4px;
320 322 font-family: sans-serif;
321 323 }
322 324
323 325 div#followlines-tooltip {
324 326 display: none;
325 327 position: fixed;
326 328 background-color: #ffc;
327 329 border: 1px solid #999;
328 330 padding: 2px;
329 331 }
330 332
331 333 .sourcelines:hover > div#followlines-tooltip {
332 334 display: inline;
333 335 }
334 336
335 337 .sourcelines:hover > div#followlines-tooltip.hidden {
336 338 display: none;
337 339 }
338 340
339 341 .sourcelines > a {
340 342 display: inline-block;
341 343 position: absolute;
342 344 left: 0px;
343 345 width: 4em;
344 346 height: 1em;
345 347 }
346 348
347 349 .fileline { font-family: monospace; }
348 350 .fileline img { border: 0; }
349 351
350 352 .tagEntry .closed { color: #99f; }
351 353
352 354 /* Changeset entry */
353 355 #changesetEntry {
354 356 border-collapse: collapse;
355 357 font-size: 90%;
356 358 width: 100%;
357 359 margin-bottom: 1em;
358 360 }
359 361
360 362 #changesetEntry th {
361 363 padding: 1px 4px;
362 364 width: 4em;
363 365 text-align: right;
364 366 font-weight: normal;
365 367 color: #999;
366 368 margin-right: .5em;
367 369 vertical-align: top;
368 370 }
369 371
370 372 div.description {
371 373 border-left: 2px solid #999;
372 374 margin: 1em 0 1em 0;
373 375 padding: .3em;
374 376 white-space: pre;
375 377 font-family: monospace;
376 378 }
377 379
378 380 /* Graph */
379 381 div#wrapper {
380 382 position: relative;
381 383 border-top: 1px solid black;
382 384 border-bottom: 1px solid black;
383 385 margin: 0;
384 386 padding: 0;
385 387 }
386 388
387 389 canvas {
388 390 position: absolute;
389 391 z-index: 5;
390 392 top: -0.7em;
391 393 margin: 0;
392 394 }
393 395
394 396 ul#graphnodes {
395 397 position: absolute;
396 398 z-index: 10;
397 399 top: -1.0em;
398 400 list-style: none inside none;
399 401 padding: 0;
400 402 }
401 403
402 404 ul#nodebgs {
403 405 list-style: none inside none;
404 406 padding: 0;
405 407 margin: 0;
406 408 top: -0.7em;
407 409 }
408 410
409 411 ul#graphnodes li, ul#nodebgs li {
410 412 height: 39px;
411 413 }
412 414
413 415 ul#graphnodes li .info {
414 416 display: block;
415 417 font-size: 70%;
416 418 position: relative;
417 419 top: -3px;
418 420 }
419 421
420 422 /* Comparison */
421 423 .legend {
422 424 padding: 1.5% 0 1.5% 0;
423 425 }
424 426
425 427 .legendinfo {
426 428 border: 1px solid #999;
427 429 font-size: 80%;
428 430 text-align: center;
429 431 padding: 0.5%;
430 432 }
431 433
432 434 .equal {
433 435 background-color: #ffffff;
434 436 }
435 437
436 438 .delete {
437 439 background-color: #faa;
438 440 color: #333;
439 441 }
440 442
441 443 .insert {
442 444 background-color: #ffa;
443 445 }
444 446
445 447 .replace {
446 448 background-color: #e8e8e8;
447 449 }
448 450
449 451 .header {
450 452 text-align: center;
451 453 }
452 454
453 455 .block {
454 456 border-top: 1px solid #999;
455 457 }
456 458
457 459 .breadcrumb {
458 460 color: gray;
459 461 }
460 462
461 463 .breadcrumb a {
462 464 color: blue;
463 465 }
464 466
465 467 .scroll-loading {
466 468 -webkit-animation: change_color 1s linear 0s infinite alternate;
467 469 -moz-animation: change_color 1s linear 0s infinite alternate;
468 470 -o-animation: change_color 1s linear 0s infinite alternate;
469 471 animation: change_color 1s linear 0s infinite alternate;
470 472 }
471 473
472 474 @-webkit-keyframes change_color {
473 475 from { background-color: #A0CEFF; } to { }
474 476 }
475 477 @-moz-keyframes change_color {
476 478 from { background-color: #A0CEFF; } to { }
477 479 }
478 480 @-o-keyframes change_color {
479 481 from { background-color: #A0CEFF; } to { }
480 482 }
481 483 @keyframes change_color {
482 484 from { background-color: #A0CEFF; } to { }
483 485 }
484 486
485 487 .scroll-loading-error {
486 488 background-color: #FFCCCC !important;
487 489 }
@@ -1,834 +1,836
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: 8349
343 content-length: 8463
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 display: none;
435 435 position: absolute;
436 436 background-color: #FFFFFF;
437 437 border: 1px solid #d9d8d1;
438 438 text-align: left;
439 439 color: #000000;
440 440 padding: 5px;
441 441 }
442 442 div.annotate-info a { color: #0000FF; text-decoration: underline; }
443 443 td.annotate:hover div.annotate-info { display: inline; }
444 444 .linenr { color:#999999; text-decoration:none }
445 445 div.rss_logo { float: right; white-space: nowrap; }
446 446 div.rss_logo a {
447 447 padding:3px 6px; line-height:10px;
448 448 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
449 449 color:#ffffff; background-color:#ff6600;
450 450 font-weight:bold; font-family:sans-serif; font-size:10px;
451 451 text-align:center; text-decoration:none;
452 452 }
453 453 div.rss_logo a:hover { background-color:#ee5500; }
454 454 pre { margin: 0; }
455 455 span.logtags span {
456 456 padding: 0px 4px;
457 457 font-size: 10px;
458 458 font-weight: normal;
459 459 border: 1px solid;
460 460 background-color: #ffaaff;
461 461 border-color: #ffccff #ff00ee #ff00ee #ffccff;
462 462 }
463 463 span.logtags span.tagtag {
464 464 background-color: #ffffaa;
465 465 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
466 466 }
467 467 span.logtags span.branchtag {
468 468 background-color: #aaffaa;
469 469 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
470 470 }
471 471 span.logtags span.inbranchtag {
472 472 background-color: #d5dde6;
473 473 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
474 474 }
475 475 span.logtags span.bookmarktag {
476 476 background-color: #afdffa;
477 477 border-color: #ccecff #46ace6 #46ace6 #ccecff;
478 478 }
479 479 span.difflineplus { color:#008800; }
480 480 span.difflineminus { color:#cc0000; }
481 481 span.difflineat { color:#990099; }
482 482 div.diffblocks { counter-reset: lineno; }
483 483 div.diffblock { counter-increment: lineno; }
484 484 pre.sourcelines { position: relative; counter-reset: lineno; }
485 485 pre.sourcelines > span {
486 486 display: inline-block;
487 487 box-sizing: border-box;
488 488 width: 100%;
489 489 padding: 0 0 0 5em;
490 490 counter-increment: lineno;
491 491 vertical-align: top;
492 492 }
493 493 pre.sourcelines > span:before {
494 494 -moz-user-select: -moz-none;
495 495 -khtml-user-select: none;
496 496 -webkit-user-select: none;
497 497 -ms-user-select: none;
498 498 user-select: none;
499 499 display: inline-block;
500 500 margin-left: -5em;
501 501 width: 4em;
502 502 color: #999;
503 503 text-align: right;
504 504 content: counters(lineno,".");
505 505 float: left;
506 506 }
507 507 pre.sourcelines > a {
508 508 display: inline-block;
509 509 position: absolute;
510 510 left: 0px;
511 511 width: 4em;
512 512 height: 1em;
513 513 }
514 514 tr:target td,
515 515 pre.sourcelines > span:target,
516 516 pre.sourcelines.stripes > span:target {
517 517 background-color: #bfdfff;
518 518 }
519 519
520 520 .description {
521 521 font-family: monospace;
522 522 }
523 523
524 524 /* Followlines */
525 div.page_body table tbody.sourcelines > tr.followlines-select:hover,
525 526 div.page_body pre.sourcelines > span.followlines-select:hover {
526 527 cursor: cell;
527 528 }
528 529
530 tbody.sourcelines > tr.followlines-selected,
529 531 pre.sourcelines > span.followlines-selected {
530 532 background-color: #99C7E9 !important;
531 533 }
532 534
533 535 div#followlines {
534 536 background-color: #B7B7B7;
535 537 border: 1px solid #CCC;
536 538 border-radius: 5px;
537 539 padding: 4px;
538 540 position: fixed;
539 541 }
540 542
541 543 div.followlines-cancel {
542 544 text-align: right;
543 545 }
544 546
545 547 div.followlines-cancel > button {
546 548 line-height: 80%;
547 549 padding: 0;
548 550 border: 0;
549 551 border-radius: 2px;
550 552 background-color: inherit;
551 553 font-weight: bold;
552 554 }
553 555
554 556 div.followlines-cancel > button:hover {
555 557 color: #FFFFFF;
556 558 background-color: #CF1F1F;
557 559 }
558 560
559 561 div.followlines-link {
560 562 margin: 2px;
561 563 margin-top: 4px;
562 564 font-family: sans-serif;
563 565 }
564 566
565 567 div#followlines-tooltip {
566 568 display: none;
567 569 position: fixed;
568 570 background-color: #ffc;
569 571 border: 1px solid #999;
570 572 padding: 2px;
571 573 }
572 574
573 575 .sourcelines:hover > div#followlines-tooltip {
574 576 display: inline;
575 577 }
576 578
577 579 .sourcelines:hover > div#followlines-tooltip.hidden {
578 580 display: none;
579 581 }
580 582 /* Graph */
581 583 div#wrapper {
582 584 position: relative;
583 585 margin: 0;
584 586 padding: 0;
585 587 margin-top: 3px;
586 588 }
587 589
588 590 canvas {
589 591 position: absolute;
590 592 z-index: 5;
591 593 top: -0.9em;
592 594 margin: 0;
593 595 }
594 596
595 597 ul#nodebgs {
596 598 list-style: none inside none;
597 599 padding: 0;
598 600 margin: 0;
599 601 top: -0.7em;
600 602 }
601 603
602 604 ul#graphnodes li, ul#nodebgs li {
603 605 height: 39px;
604 606 }
605 607
606 608 ul#graphnodes {
607 609 position: absolute;
608 610 z-index: 10;
609 611 top: -0.8em;
610 612 list-style: none inside none;
611 613 padding: 0;
612 614 }
613 615
614 616 ul#graphnodes li .info {
615 617 display: block;
616 618 font-size: 100%;
617 619 position: relative;
618 620 top: -3px;
619 621 font-style: italic;
620 622 }
621 623
622 624 /* Comparison */
623 625 .legend {
624 626 padding: 1.5% 0 1.5% 0;
625 627 }
626 628
627 629 .legendinfo {
628 630 border: 1px solid #d9d8d1;
629 631 font-size: 80%;
630 632 text-align: center;
631 633 padding: 0.5%;
632 634 }
633 635
634 636 .equal {
635 637 background-color: #ffffff;
636 638 }
637 639
638 640 .delete {
639 641 background-color: #faa;
640 642 color: #333;
641 643 }
642 644
643 645 .insert {
644 646 background-color: #ffa;
645 647 }
646 648
647 649 .replace {
648 650 background-color: #e8e8e8;
649 651 }
650 652
651 653 .comparison {
652 654 overflow-x: auto;
653 655 }
654 656
655 657 .header th {
656 658 text-align: center;
657 659 }
658 660
659 661 .block {
660 662 border-top: 1px solid #d9d8d1;
661 663 }
662 664
663 665 .scroll-loading {
664 666 -webkit-animation: change_color 1s linear 0s infinite alternate;
665 667 -moz-animation: change_color 1s linear 0s infinite alternate;
666 668 -o-animation: change_color 1s linear 0s infinite alternate;
667 669 animation: change_color 1s linear 0s infinite alternate;
668 670 }
669 671
670 672 @-webkit-keyframes change_color {
671 673 from { background-color: #A0CEFF; } to { }
672 674 }
673 675 @-moz-keyframes change_color {
674 676 from { background-color: #A0CEFF; } to { }
675 677 }
676 678 @-o-keyframes change_color {
677 679 from { background-color: #A0CEFF; } to { }
678 680 }
679 681 @keyframes change_color {
680 682 from { background-color: #A0CEFF; } to { }
681 683 }
682 684
683 685 .scroll-loading-error {
684 686 background-color: #FFCCCC !important;
685 687 }
686 688
687 689 #doc {
688 690 margin: 0 8px;
689 691 }
690 692 304 Not Modified
691 693
692 694
693 695 phase changes are refreshed (issue4061)
694 696
695 697 $ echo bar >> foo
696 698 $ hg ci -msecret --secret
697 699 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
698 700 200 Script output follows
699 701
700 702
701 703 # HG changelog
702 704 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
703 705
704 706 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
705 707 revision: 0
706 708 user: test
707 709 date: Thu, 01 Jan 1970 00:00:00 +0000
708 710 summary: base
709 711 branch: default
710 712 tag: tip
711 713 bookmark: @
712 714 bookmark: a b c
713 715 bookmark: d/e/f
714 716
715 717
716 718 $ hg phase --draft tip
717 719 $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
718 720 200 Script output follows
719 721
720 722
721 723 # HG changelog
722 724 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
723 725
724 726 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
725 727 revision: 1
726 728 user: test
727 729 date: Thu, 01 Jan 1970 00:00:00 +0000
728 730 summary: secret
729 731 branch: default
730 732 tag: tip
731 733
732 734 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
733 735 revision: 0
734 736 user: test
735 737 date: Thu, 01 Jan 1970 00:00:00 +0000
736 738 summary: base
737 739 bookmark: @
738 740 bookmark: a b c
739 741 bookmark: d/e/f
740 742
741 743
742 744
743 745 access bookmarks
744 746
745 747 $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:'
746 748 200 Script output follows
747 749 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
748 750
749 751 $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:'
750 752 200 Script output follows
751 753 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
752 754
753 755 $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:'
754 756 200 Script output follows
755 757 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
756 758
757 759 $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:'
758 760 200 Script output follows
759 761 changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
760 762
761 763 no style can be loaded from directories other than the specified paths
762 764
763 765 $ mkdir -p x/templates/fallback
764 766 $ cat <<EOF > x/templates/fallback/map
765 767 > default = 'shortlog'
766 768 > shortlog = 'fall back to default\n'
767 769 > mimetype = 'text/plain'
768 770 > EOF
769 771 $ cat <<EOF > x/map
770 772 > default = 'shortlog'
771 773 > shortlog = 'access to outside of templates directory\n'
772 774 > mimetype = 'text/plain'
773 775 > EOF
774 776
775 777 $ killdaemons.py
776 778 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \
777 779 > --config web.style=fallback --config web.templates=x/templates
778 780 $ cat hg.pid >> $DAEMON_PIDS
779 781
780 782 $ get-with-headers.py localhost:$HGPORT "?style=`pwd`/x"
781 783 200 Script output follows
782 784
783 785 fall back to default
784 786
785 787 $ get-with-headers.py localhost:$HGPORT '?style=..'
786 788 200 Script output follows
787 789
788 790 fall back to default
789 791
790 792 $ get-with-headers.py localhost:$HGPORT '?style=./..'
791 793 200 Script output follows
792 794
793 795 fall back to default
794 796
795 797 $ get-with-headers.py localhost:$HGPORT '?style=.../.../'
796 798 200 Script output follows
797 799
798 800 fall back to default
799 801
800 802 errors
801 803
802 804 $ cat errors.log
803 805
804 806 Uncaught exceptions result in a logged error and canned HTTP response
805 807
806 808 $ killdaemons.py
807 809 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
808 810 $ cat hg.pid >> $DAEMON_PIDS
809 811
810 812 $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
811 813 500 Internal Server Error
812 814 transfer-encoding: chunked
813 815
814 816 Internal Server Error (no-eol)
815 817 [1]
816 818
817 819 $ killdaemons.py
818 820 $ head -1 errors.log
819 821 .* Exception happened during processing request '/raiseerror': (re)
820 822
821 823 Uncaught exception after partial content sent
822 824
823 825 $ hg serve --config extensions.hgweberror=$TESTDIR/hgweberror.py -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
824 826 $ cat hg.pid >> $DAEMON_PIDS
825 827 $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
826 828 200 Script output follows
827 829 transfer-encoding: chunked
828 830 content-type: text/plain
829 831
830 832 partial content
831 833 Internal Server Error (no-eol)
832 834
833 835 $ killdaemons.py
834 836 $ cd ..
@@ -1,993 +1,998
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: 1af356141006 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/1af356141006">1af356141006</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="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/1af356141006">1af356141006</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 287 <div class="overflow">
288 288 <table class="bigtable">
289 289 <thead>
290 290 <tr>
291 291 <th class="annotate">rev</th>
292 292 <th class="line">&nbsp;&nbsp;line source</th>
293 293 </tr>
294 294 </thead>
295 <tbody class="stripes2">
295 <tbody class="stripes2 sourcelines"
296 data-logurl="/log/tip/primes.py"
297 data-selectabletag="TR"
298 data-ishead="1">
296 299
297 300 <tr id="l1" class="thisrev">
298 301 <td class="annotate parity0">
299 302 <a href="/annotate/1af356141006/primes.py#l1">
300 303 0
301 304 </a>
302 305 <div class="annotate-info">
303 306 <div>
304 307 <a href="/annotate/1af356141006/primes.py#l1">
305 308 1af356141006</a>
306 309 a
307 310 </div>
308 311 <div><em>&#116;&#101;&#115;&#116;</em></div>
309 312 <div>parents: </div>
310 313 <a href="/diff/1af356141006/primes.py">diff</a>
311 314 <a href="/rev/1af356141006">changeset</a>
312 315 </div>
313 316 </td>
314 317 <td class="source"><a href="#l1"> 1</a> <span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></td>
315 318 </tr>
316 319 <tr id="l2" class="thisrev">
317 320 <td class="annotate parity0">
318 321
319 322 <div class="annotate-info">
320 323 <div>
321 324 <a href="/annotate/1af356141006/primes.py#l2">
322 325 1af356141006</a>
323 326 a
324 327 </div>
325 328 <div><em>&#116;&#101;&#115;&#116;</em></div>
326 329 <div>parents: </div>
327 330 <a href="/diff/1af356141006/primes.py">diff</a>
328 331 <a href="/rev/1af356141006">changeset</a>
329 332 </div>
330 333 </td>
331 334 <td class="source"><a href="#l2"> 2</a> </td>
332 335 </tr>
333 336 <tr id="l3" class="thisrev">
334 337 <td class="annotate parity0">
335 338
336 339 <div class="annotate-info">
337 340 <div>
338 341 <a href="/annotate/1af356141006/primes.py#l3">
339 342 1af356141006</a>
340 343 a
341 344 </div>
342 345 <div><em>&#116;&#101;&#115;&#116;</em></div>
343 346 <div>parents: </div>
344 347 <a href="/diff/1af356141006/primes.py">diff</a>
345 348 <a href="/rev/1af356141006">changeset</a>
346 349 </div>
347 350 </td>
348 351 <td class="source"><a href="#l3"> 3</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></td>
349 352 </tr>
350 353 <tr id="l4" class="thisrev">
351 354 <td class="annotate parity0">
352 355
353 356 <div class="annotate-info">
354 357 <div>
355 358 <a href="/annotate/1af356141006/primes.py#l4">
356 359 1af356141006</a>
357 360 a
358 361 </div>
359 362 <div><em>&#116;&#101;&#115;&#116;</em></div>
360 363 <div>parents: </div>
361 364 <a href="/diff/1af356141006/primes.py">diff</a>
362 365 <a href="/rev/1af356141006">changeset</a>
363 366 </div>
364 367 </td>
365 368 <td class="source"><a href="#l4"> 4</a> <span class="sd"> where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
366 369 </tr>
367 370 <tr id="l5" class="thisrev">
368 371 <td class="annotate parity0">
369 372
370 373 <div class="annotate-info">
371 374 <div>
372 375 <a href="/annotate/1af356141006/primes.py#l5">
373 376 1af356141006</a>
374 377 a
375 378 </div>
376 379 <div><em>&#116;&#101;&#115;&#116;</em></div>
377 380 <div>parents: </div>
378 381 <a href="/diff/1af356141006/primes.py">diff</a>
379 382 <a href="/rev/1af356141006">changeset</a>
380 383 </div>
381 384 </td>
382 385 <td class="source"><a href="#l5"> 5</a> <span class="sd">&quot;&quot;&quot;</span></td>
383 386 </tr>
384 387 <tr id="l6" class="thisrev">
385 388 <td class="annotate parity0">
386 389
387 390 <div class="annotate-info">
388 391 <div>
389 392 <a href="/annotate/1af356141006/primes.py#l6">
390 393 1af356141006</a>
391 394 a
392 395 </div>
393 396 <div><em>&#116;&#101;&#115;&#116;</em></div>
394 397 <div>parents: </div>
395 398 <a href="/diff/1af356141006/primes.py">diff</a>
396 399 <a href="/rev/1af356141006">changeset</a>
397 400 </div>
398 401 </td>
399 402 <td class="source"><a href="#l6"> 6</a> </td>
400 403 </tr>
401 404 <tr id="l7" class="thisrev">
402 405 <td class="annotate parity0">
403 406
404 407 <div class="annotate-info">
405 408 <div>
406 409 <a href="/annotate/1af356141006/primes.py#l7">
407 410 1af356141006</a>
408 411 a
409 412 </div>
410 413 <div><em>&#116;&#101;&#115;&#116;</em></div>
411 414 <div>parents: </div>
412 415 <a href="/diff/1af356141006/primes.py">diff</a>
413 416 <a href="/rev/1af356141006">changeset</a>
414 417 </div>
415 418 </td>
416 419 <td class="source"><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>
417 420 </tr>
418 421 <tr id="l8" class="thisrev">
419 422 <td class="annotate parity0">
420 423
421 424 <div class="annotate-info">
422 425 <div>
423 426 <a href="/annotate/1af356141006/primes.py#l8">
424 427 1af356141006</a>
425 428 a
426 429 </div>
427 430 <div><em>&#116;&#101;&#115;&#116;</em></div>
428 431 <div>parents: </div>
429 432 <a href="/diff/1af356141006/primes.py">diff</a>
430 433 <a href="/rev/1af356141006">changeset</a>
431 434 </div>
432 435 </td>
433 436 <td class="source"><a href="#l8"> 8</a> </td>
434 437 </tr>
435 438 <tr id="l9" class="thisrev">
436 439 <td class="annotate parity0">
437 440
438 441 <div class="annotate-info">
439 442 <div>
440 443 <a href="/annotate/1af356141006/primes.py#l9">
441 444 1af356141006</a>
442 445 a
443 446 </div>
444 447 <div><em>&#116;&#101;&#115;&#116;</em></div>
445 448 <div>parents: </div>
446 449 <a href="/diff/1af356141006/primes.py">diff</a>
447 450 <a href="/rev/1af356141006">changeset</a>
448 451 </div>
449 452 </td>
450 453 <td class="source"><a href="#l9"> 9</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></td>
451 454 </tr>
452 455 <tr id="l10" class="thisrev">
453 456 <td class="annotate parity0">
454 457
455 458 <div class="annotate-info">
456 459 <div>
457 460 <a href="/annotate/1af356141006/primes.py#l10">
458 461 1af356141006</a>
459 462 a
460 463 </div>
461 464 <div><em>&#116;&#101;&#115;&#116;</em></div>
462 465 <div>parents: </div>
463 466 <a href="/diff/1af356141006/primes.py">diff</a>
464 467 <a href="/rev/1af356141006">changeset</a>
465 468 </div>
466 469 </td>
467 470 <td class="source"><a href="#l10"> 10</a> <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></td>
468 471 </tr>
469 472 <tr id="l11" class="thisrev">
470 473 <td class="annotate parity0">
471 474
472 475 <div class="annotate-info">
473 476 <div>
474 477 <a href="/annotate/1af356141006/primes.py#l11">
475 478 1af356141006</a>
476 479 a
477 480 </div>
478 481 <div><em>&#116;&#101;&#115;&#116;</em></div>
479 482 <div>parents: </div>
480 483 <a href="/diff/1af356141006/primes.py">diff</a>
481 484 <a href="/rev/1af356141006">changeset</a>
482 485 </div>
483 486 </td>
484 487 <td class="source"><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>
485 488 </tr>
486 489 <tr id="l12" class="thisrev">
487 490 <td class="annotate parity0">
488 491
489 492 <div class="annotate-info">
490 493 <div>
491 494 <a href="/annotate/1af356141006/primes.py#l12">
492 495 1af356141006</a>
493 496 a
494 497 </div>
495 498 <div><em>&#116;&#101;&#115;&#116;</em></div>
496 499 <div>parents: </div>
497 500 <a href="/diff/1af356141006/primes.py">diff</a>
498 501 <a href="/rev/1af356141006">changeset</a>
499 502 </div>
500 503 </td>
501 504 <td class="source"><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>
502 505 </tr>
503 506 <tr id="l13" class="thisrev">
504 507 <td class="annotate parity0">
505 508
506 509 <div class="annotate-info">
507 510 <div>
508 511 <a href="/annotate/1af356141006/primes.py#l13">
509 512 1af356141006</a>
510 513 a
511 514 </div>
512 515 <div><em>&#116;&#101;&#115;&#116;</em></div>
513 516 <div>parents: </div>
514 517 <a href="/diff/1af356141006/primes.py">diff</a>
515 518 <a href="/rev/1af356141006">changeset</a>
516 519 </div>
517 520 </td>
518 521 <td class="source"><a href="#l13"> 13</a> <span class="c"># It is important to yield *here* in order to stop the</span></td>
519 522 </tr>
520 523 <tr id="l14" class="thisrev">
521 524 <td class="annotate parity0">
522 525
523 526 <div class="annotate-info">
524 527 <div>
525 528 <a href="/annotate/1af356141006/primes.py#l14">
526 529 1af356141006</a>
527 530 a
528 531 </div>
529 532 <div><em>&#116;&#101;&#115;&#116;</em></div>
530 533 <div>parents: </div>
531 534 <a href="/diff/1af356141006/primes.py">diff</a>
532 535 <a href="/rev/1af356141006">changeset</a>
533 536 </div>
534 537 </td>
535 538 <td class="source"><a href="#l14"> 14</a> <span class="c"># infinite recursion.</span></td>
536 539 </tr>
537 540 <tr id="l15" class="thisrev">
538 541 <td class="annotate parity0">
539 542
540 543 <div class="annotate-info">
541 544 <div>
542 545 <a href="/annotate/1af356141006/primes.py#l15">
543 546 1af356141006</a>
544 547 a
545 548 </div>
546 549 <div><em>&#116;&#101;&#115;&#116;</em></div>
547 550 <div>parents: </div>
548 551 <a href="/diff/1af356141006/primes.py">diff</a>
549 552 <a href="/rev/1af356141006">changeset</a>
550 553 </div>
551 554 </td>
552 555 <td class="source"><a href="#l15"> 15</a> <span class="kn">yield</span> <span class="n">p</span></td>
553 556 </tr>
554 557 <tr id="l16" class="thisrev">
555 558 <td class="annotate parity0">
556 559
557 560 <div class="annotate-info">
558 561 <div>
559 562 <a href="/annotate/1af356141006/primes.py#l16">
560 563 1af356141006</a>
561 564 a
562 565 </div>
563 566 <div><em>&#116;&#101;&#115;&#116;</em></div>
564 567 <div>parents: </div>
565 568 <a href="/diff/1af356141006/primes.py">diff</a>
566 569 <a href="/rev/1af356141006">changeset</a>
567 570 </div>
568 571 </td>
569 572 <td class="source"><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>
570 573 </tr>
571 574 <tr id="l17" class="thisrev">
572 575 <td class="annotate parity0">
573 576
574 577 <div class="annotate-info">
575 578 <div>
576 579 <a href="/annotate/1af356141006/primes.py#l17">
577 580 1af356141006</a>
578 581 a
579 582 </div>
580 583 <div><em>&#116;&#101;&#115;&#116;</em></div>
581 584 <div>parents: </div>
582 585 <a href="/diff/1af356141006/primes.py">diff</a>
583 586 <a href="/rev/1af356141006">changeset</a>
584 587 </div>
585 588 </td>
586 589 <td class="source"><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>
587 590 </tr>
588 591 <tr id="l18" class="thisrev">
589 592 <td class="annotate parity0">
590 593
591 594 <div class="annotate-info">
592 595 <div>
593 596 <a href="/annotate/1af356141006/primes.py#l18">
594 597 1af356141006</a>
595 598 a
596 599 </div>
597 600 <div><em>&#116;&#101;&#115;&#116;</em></div>
598 601 <div>parents: </div>
599 602 <a href="/diff/1af356141006/primes.py">diff</a>
600 603 <a href="/rev/1af356141006">changeset</a>
601 604 </div>
602 605 </td>
603 606 <td class="source"><a href="#l18"> 18</a> <span class="kn">yield</span> <span class="n">n</span></td>
604 607 </tr>
605 608 <tr id="l19" class="thisrev">
606 609 <td class="annotate parity0">
607 610
608 611 <div class="annotate-info">
609 612 <div>
610 613 <a href="/annotate/1af356141006/primes.py#l19">
611 614 1af356141006</a>
612 615 a
613 616 </div>
614 617 <div><em>&#116;&#101;&#115;&#116;</em></div>
615 618 <div>parents: </div>
616 619 <a href="/diff/1af356141006/primes.py">diff</a>
617 620 <a href="/rev/1af356141006">changeset</a>
618 621 </div>
619 622 </td>
620 623 <td class="source"><a href="#l19"> 19</a> </td>
621 624 </tr>
622 625 <tr id="l20" class="thisrev">
623 626 <td class="annotate parity0">
624 627
625 628 <div class="annotate-info">
626 629 <div>
627 630 <a href="/annotate/1af356141006/primes.py#l20">
628 631 1af356141006</a>
629 632 a
630 633 </div>
631 634 <div><em>&#116;&#101;&#115;&#116;</em></div>
632 635 <div>parents: </div>
633 636 <a href="/diff/1af356141006/primes.py">diff</a>
634 637 <a href="/rev/1af356141006">changeset</a>
635 638 </div>
636 639 </td>
637 640 <td class="source"><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>
638 641 </tr>
639 642 <tr id="l21" class="thisrev">
640 643 <td class="annotate parity0">
641 644
642 645 <div class="annotate-info">
643 646 <div>
644 647 <a href="/annotate/1af356141006/primes.py#l21">
645 648 1af356141006</a>
646 649 a
647 650 </div>
648 651 <div><em>&#116;&#101;&#115;&#116;</em></div>
649 652 <div>parents: </div>
650 653 <a href="/diff/1af356141006/primes.py">diff</a>
651 654 <a href="/rev/1af356141006">changeset</a>
652 655 </div>
653 656 </td>
654 657 <td class="source"><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>
655 658 </tr>
656 659 <tr id="l22" class="thisrev">
657 660 <td class="annotate parity0">
658 661
659 662 <div class="annotate-info">
660 663 <div>
661 664 <a href="/annotate/1af356141006/primes.py#l22">
662 665 1af356141006</a>
663 666 a
664 667 </div>
665 668 <div><em>&#116;&#101;&#115;&#116;</em></div>
666 669 <div>parents: </div>
667 670 <a href="/diff/1af356141006/primes.py">diff</a>
668 671 <a href="/rev/1af356141006">changeset</a>
669 672 </div>
670 673 </td>
671 674 <td class="source"><a href="#l22"> 22</a> </td>
672 675 </tr>
673 676 <tr id="l23" class="thisrev">
674 677 <td class="annotate parity0">
675 678
676 679 <div class="annotate-info">
677 680 <div>
678 681 <a href="/annotate/1af356141006/primes.py#l23">
679 682 1af356141006</a>
680 683 a
681 684 </div>
682 685 <div><em>&#116;&#101;&#115;&#116;</em></div>
683 686 <div>parents: </div>
684 687 <a href="/diff/1af356141006/primes.py">diff</a>
685 688 <a href="/rev/1af356141006">changeset</a>
686 689 </div>
687 690 </td>
688 691 <td class="source"><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>
689 692 </tr>
690 693 <tr id="l24" class="thisrev">
691 694 <td class="annotate parity0">
692 695
693 696 <div class="annotate-info">
694 697 <div>
695 698 <a href="/annotate/1af356141006/primes.py#l24">
696 699 1af356141006</a>
697 700 a
698 701 </div>
699 702 <div><em>&#116;&#101;&#115;&#116;</em></div>
700 703 <div>parents: </div>
701 704 <a href="/diff/1af356141006/primes.py">diff</a>
702 705 <a href="/rev/1af356141006">changeset</a>
703 706 </div>
704 707 </td>
705 708 <td class="source"><a href="#l24"> 24</a> <span class="kn">import</span> <span class="nn">sys</span></td>
706 709 </tr>
707 710 <tr id="l25" class="thisrev">
708 711 <td class="annotate parity0">
709 712
710 713 <div class="annotate-info">
711 714 <div>
712 715 <a href="/annotate/1af356141006/primes.py#l25">
713 716 1af356141006</a>
714 717 a
715 718 </div>
716 719 <div><em>&#116;&#101;&#115;&#116;</em></div>
717 720 <div>parents: </div>
718 721 <a href="/diff/1af356141006/primes.py">diff</a>
719 722 <a href="/rev/1af356141006">changeset</a>
720 723 </div>
721 724 </td>
722 725 <td class="source"><a href="#l25"> 25</a> <span class="kn">try</span><span class="p">:</span></td>
723 726 </tr>
724 727 <tr id="l26" class="thisrev">
725 728 <td class="annotate parity0">
726 729
727 730 <div class="annotate-info">
728 731 <div>
729 732 <a href="/annotate/1af356141006/primes.py#l26">
730 733 1af356141006</a>
731 734 a
732 735 </div>
733 736 <div><em>&#116;&#101;&#115;&#116;</em></div>
734 737 <div>parents: </div>
735 738 <a href="/diff/1af356141006/primes.py">diff</a>
736 739 <a href="/rev/1af356141006">changeset</a>
737 740 </div>
738 741 </td>
739 742 <td class="source"><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>
740 743 </tr>
741 744 <tr id="l27" class="thisrev">
742 745 <td class="annotate parity0">
743 746
744 747 <div class="annotate-info">
745 748 <div>
746 749 <a href="/annotate/1af356141006/primes.py#l27">
747 750 1af356141006</a>
748 751 a
749 752 </div>
750 753 <div><em>&#116;&#101;&#115;&#116;</em></div>
751 754 <div>parents: </div>
752 755 <a href="/diff/1af356141006/primes.py">diff</a>
753 756 <a href="/rev/1af356141006">changeset</a>
754 757 </div>
755 758 </td>
756 759 <td class="source"><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>
757 760 </tr>
758 761 <tr id="l28" class="thisrev">
759 762 <td class="annotate parity0">
760 763
761 764 <div class="annotate-info">
762 765 <div>
763 766 <a href="/annotate/1af356141006/primes.py#l28">
764 767 1af356141006</a>
765 768 a
766 769 </div>
767 770 <div><em>&#116;&#101;&#115;&#116;</em></div>
768 771 <div>parents: </div>
769 772 <a href="/diff/1af356141006/primes.py">diff</a>
770 773 <a href="/rev/1af356141006">changeset</a>
771 774 </div>
772 775 </td>
773 776 <td class="source"><a href="#l28"> 28</a> <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
774 777 </tr>
775 778 <tr id="l29" class="thisrev">
776 779 <td class="annotate parity0">
777 780
778 781 <div class="annotate-info">
779 782 <div>
780 783 <a href="/annotate/1af356141006/primes.py#l29">
781 784 1af356141006</a>
782 785 a
783 786 </div>
784 787 <div><em>&#116;&#101;&#115;&#116;</em></div>
785 788 <div>parents: </div>
786 789 <a href="/diff/1af356141006/primes.py">diff</a>
787 790 <a href="/rev/1af356141006">changeset</a>
788 791 </div>
789 792 </td>
790 793 <td class="source"><a href="#l29"> 29</a> <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></td>
791 794 </tr>
792 795 <tr id="l30" class="thisrev">
793 796 <td class="annotate parity0">
794 797
795 798 <div class="annotate-info">
796 799 <div>
797 800 <a href="/annotate/1af356141006/primes.py#l30">
798 801 1af356141006</a>
799 802 a
800 803 </div>
801 804 <div><em>&#116;&#101;&#115;&#116;</em></div>
802 805 <div>parents: </div>
803 806 <a href="/diff/1af356141006/primes.py">diff</a>
804 807 <a href="/rev/1af356141006">changeset</a>
805 808 </div>
806 809 </td>
807 810 <td class="source"><a href="#l30"> 30</a> <span class="kn">print</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>
808 811 </tr>
809 812 <tr id="l31" class="thisrev">
810 813 <td class="annotate parity0">
811 814
812 815 <div class="annotate-info">
813 816 <div>
814 817 <a href="/annotate/1af356141006/primes.py#l31">
815 818 1af356141006</a>
816 819 a
817 820 </div>
818 821 <div><em>&#116;&#101;&#115;&#116;</em></div>
819 822 <div>parents: </div>
820 823 <a href="/diff/1af356141006/primes.py">diff</a>
821 824 <a href="/rev/1af356141006">changeset</a>
822 825 </div>
823 826 </td>
824 827 <td class="source"><a href="#l31"> 31</a> </td>
825 828 </tr>
826 829 </tbody>
827 830 </table>
828 831 </div>
829 832 </div>
830 833 </div>
831 834
835 <script type="text/javascript" src="/static/followlines.js"></script>
836
832 837
833 838
834 839 </body>
835 840 </html>
836 841
837 842
838 843 hgweb fileannotate, raw
839 844
840 845 $ (get-with-headers.py localhost:$HGPORT 'annotate/tip/primes.py?style=raw') \
841 846 > | sed "s/test@//" > a
842 847 $ echo "200 Script output follows" > b
843 848 $ echo "" >> b
844 849 $ echo "" >> b
845 850 $ hg annotate "primes.py" >> b
846 851 $ echo "" >> b
847 852 $ echo "" >> b
848 853 $ echo "" >> b
849 854 $ echo "" >> b
850 855 $ cmp b a || diff -u b a
851 856
852 857 hgweb filerevision, raw
853 858
854 859 $ (get-with-headers.py localhost:$HGPORT 'file/tip/primes.py?style=raw') \
855 860 > > a
856 861 $ echo "200 Script output follows" > b
857 862 $ echo "" >> b
858 863 $ hg cat primes.py >> b
859 864 $ cmp b a || diff -u b a
860 865
861 866 hgweb highlightcss friendly
862 867
863 868 $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out
864 869 $ head -n 4 out
865 870 200 Script output follows
866 871
867 872 /* pygments_style = friendly */
868 873
869 874 $ rm out
870 875
871 876 errors encountered
872 877
873 878 $ cat errors.log
874 879 $ killdaemons.py
875 880
876 881 Change the pygments style
877 882
878 883 $ cat > .hg/hgrc <<EOF
879 884 > [web]
880 885 > pygments_style = fruity
881 886 > EOF
882 887
883 888 hg serve again
884 889
885 890 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid -A access.log -E errors.log
886 891 $ cat hg.pid >> $DAEMON_PIDS
887 892
888 893 hgweb highlightcss fruity
889 894
890 895 $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out
891 896 $ head -n 4 out
892 897 200 Script output follows
893 898
894 899 /* pygments_style = fruity */
895 900
896 901 $ rm out
897 902
898 903 errors encountered
899 904
900 905 $ cat errors.log
901 906 $ killdaemons.py
902 907
903 908 only highlight C source files
904 909
905 910 $ cat > .hg/hgrc <<EOF
906 911 > [web]
907 912 > highlightfiles = **.c
908 913 > EOF
909 914
910 915 hg serve again
911 916
912 917 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid -A access.log -E errors.log
913 918 $ cat hg.pid >> $DAEMON_PIDS
914 919
915 920 test that fileset in highlightfiles works and primes.py is not highlighted
916 921
917 922 $ get-with-headers.py localhost:$HGPORT 'file/tip/primes.py' | grep 'id="l11"'
918 923 <span id="l11"> def sieve(ns):</span><a href="#l11"></a>
919 924
920 925 errors encountered
921 926
922 927 $ cat errors.log
923 928 $ cd ..
924 929 $ hg init eucjp
925 930 $ cd eucjp
926 931 $ $PYTHON -c 'print("\265\376")' >> eucjp.txt # Japanese kanji "Kyo"
927 932 $ hg ci -Ama
928 933 adding eucjp.txt
929 934 $ hgserveget () {
930 935 > killdaemons.py
931 936 > echo % HGENCODING="$1" hg serve
932 937 > HGENCODING="$1" hg serve -p $HGPORT -d -n test --pid-file=hg.pid -E errors.log
933 938 > cat hg.pid >> $DAEMON_PIDS
934 939 >
935 940 > echo % hgweb filerevision, html
936 941 > get-with-headers.py localhost:$HGPORT "file/tip/$2" \
937 942 > | grep '<div class="parity0 source">'
938 943 > echo % errors encountered
939 944 > cat errors.log
940 945 > }
941 946 $ hgserveget euc-jp eucjp.txt
942 947 % HGENCODING=euc-jp hg serve
943 948 % hgweb filerevision, html
944 949 % errors encountered
945 950 $ hgserveget utf-8 eucjp.txt
946 951 % HGENCODING=utf-8 hg serve
947 952 % hgweb filerevision, html
948 953 % errors encountered
949 954 $ hgserveget us-ascii eucjp.txt
950 955 % HGENCODING=us-ascii hg serve
951 956 % hgweb filerevision, html
952 957 % errors encountered
953 958
954 959 We attempt to highlight unknown files by default
955 960
956 961 $ killdaemons.py
957 962
958 963 $ cat > .hg/hgrc << EOF
959 964 > [web]
960 965 > highlightfiles = **
961 966 > EOF
962 967
963 968 $ cat > unknownfile << EOF
964 969 > #!$PYTHON
965 970 > def foo():
966 971 > pass
967 972 > EOF
968 973
969 974 $ hg add unknownfile
970 975 $ hg commit -m unknown unknownfile
971 976
972 977 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid
973 978 $ cat hg.pid >> $DAEMON_PIDS
974 979
975 980 $ get-with-headers.py localhost:$HGPORT 'file/tip/unknownfile' | grep l2
976 981 <span id="l2"><span class="k">def</span> <span class="nf">foo</span><span class="p">():</span></span><a href="#l2"></a>
977 982
978 983 We can prevent Pygments from falling back to a non filename-based
979 984 detection mode
980 985
981 986 $ cat > .hg/hgrc << EOF
982 987 > [web]
983 988 > highlightfiles = **
984 989 > highlightonlymatchfilename = true
985 990 > EOF
986 991
987 992 $ killdaemons.py
988 993 $ hg serve -p $HGPORT -d -n test --pid-file=hg.pid
989 994 $ cat hg.pid >> $DAEMON_PIDS
990 995 $ get-with-headers.py localhost:$HGPORT 'file/tip/unknownfile' | grep l2
991 996 <span id="l2">def foo():</span><a href="#l2"></a>
992 997
993 998 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now