##// END OF EJS Templates
hgweb: handle a "linerange" request parameter in filelog command...
Denis Laxalde -
r31665:5e6d4451 default
parent child Browse files
Show More
@@ -1,1343 +1,1372 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import cgi
11 11 import copy
12 12 import mimetypes
13 13 import os
14 14 import re
15 15
16 16 from ..i18n import _
17 17 from ..node import hex, short
18 18
19 19 from .common import (
20 20 ErrorResponse,
21 21 HTTP_FORBIDDEN,
22 22 HTTP_NOT_FOUND,
23 23 HTTP_OK,
24 24 get_contact,
25 25 paritygen,
26 26 staticfile,
27 27 )
28 28
29 29 from .. import (
30 30 archival,
31 context,
31 32 encoding,
32 33 error,
33 34 graphmod,
34 35 revset,
35 36 revsetlang,
36 37 scmutil,
37 38 smartset,
38 39 templatefilters,
39 40 templater,
40 41 util,
41 42 )
42 43
43 44 from . import (
44 45 webutil,
45 46 )
46 47
47 48 __all__ = []
48 49 commands = {}
49 50
50 51 class webcommand(object):
51 52 """Decorator used to register a web command handler.
52 53
53 54 The decorator takes as its positional arguments the name/path the
54 55 command should be accessible under.
55 56
56 57 Usage:
57 58
58 59 @webcommand('mycommand')
59 60 def mycommand(web, req, tmpl):
60 61 pass
61 62 """
62 63
63 64 def __init__(self, name):
64 65 self.name = name
65 66
66 67 def __call__(self, func):
67 68 __all__.append(self.name)
68 69 commands[self.name] = func
69 70 return func
70 71
71 72 @webcommand('log')
72 73 def log(web, req, tmpl):
73 74 """
74 75 /log[/{revision}[/{path}]]
75 76 --------------------------
76 77
77 78 Show repository or file history.
78 79
79 80 For URLs of the form ``/log/{revision}``, a list of changesets starting at
80 81 the specified changeset identifier is shown. If ``{revision}`` is not
81 82 defined, the default is ``tip``. This form is equivalent to the
82 83 ``changelog`` handler.
83 84
84 85 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
85 86 file will be shown. This form is equivalent to the ``filelog`` handler.
86 87 """
87 88
88 89 if 'file' in req.form and req.form['file'][0]:
89 90 return filelog(web, req, tmpl)
90 91 else:
91 92 return changelog(web, req, tmpl)
92 93
93 94 @webcommand('rawfile')
94 95 def rawfile(web, req, tmpl):
95 96 guessmime = web.configbool('web', 'guessmime', False)
96 97
97 98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
98 99 if not path:
99 100 content = manifest(web, req, tmpl)
100 101 req.respond(HTTP_OK, web.ctype)
101 102 return content
102 103
103 104 try:
104 105 fctx = webutil.filectx(web.repo, req)
105 106 except error.LookupError as inst:
106 107 try:
107 108 content = manifest(web, req, tmpl)
108 109 req.respond(HTTP_OK, web.ctype)
109 110 return content
110 111 except ErrorResponse:
111 112 raise inst
112 113
113 114 path = fctx.path()
114 115 text = fctx.data()
115 116 mt = 'application/binary'
116 117 if guessmime:
117 118 mt = mimetypes.guess_type(path)[0]
118 119 if mt is None:
119 120 if util.binary(text):
120 121 mt = 'application/binary'
121 122 else:
122 123 mt = 'text/plain'
123 124 if mt.startswith('text/'):
124 125 mt += '; charset="%s"' % encoding.encoding
125 126
126 127 req.respond(HTTP_OK, mt, path, body=text)
127 128 return []
128 129
129 130 def _filerevision(web, req, tmpl, fctx):
130 131 f = fctx.path()
131 132 text = fctx.data()
132 133 parity = paritygen(web.stripecount)
133 134
134 135 if util.binary(text):
135 136 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
136 137 text = '(binary:%s)' % mt
137 138
138 139 def lines():
139 140 for lineno, t in enumerate(text.splitlines(True)):
140 141 yield {"line": t,
141 142 "lineid": "l%d" % (lineno + 1),
142 143 "linenumber": "% 6d" % (lineno + 1),
143 144 "parity": next(parity)}
144 145
145 146 return tmpl("filerevision",
146 147 file=f,
147 148 path=webutil.up(f),
148 149 text=lines(),
149 150 symrev=webutil.symrevorshortnode(req, fctx),
150 151 rename=webutil.renamelink(fctx),
151 152 permissions=fctx.manifest().flags(f),
152 153 **webutil.commonentry(web.repo, fctx))
153 154
154 155 @webcommand('file')
155 156 def file(web, req, tmpl):
156 157 """
157 158 /file/{revision}[/{path}]
158 159 -------------------------
159 160
160 161 Show information about a directory or file in the repository.
161 162
162 163 Info about the ``path`` given as a URL parameter will be rendered.
163 164
164 165 If ``path`` is a directory, information about the entries in that
165 166 directory will be rendered. This form is equivalent to the ``manifest``
166 167 handler.
167 168
168 169 If ``path`` is a file, information about that file will be shown via
169 170 the ``filerevision`` template.
170 171
171 172 If ``path`` is not defined, information about the root directory will
172 173 be rendered.
173 174 """
174 175 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
175 176 if not path:
176 177 return manifest(web, req, tmpl)
177 178 try:
178 179 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
179 180 except error.LookupError as inst:
180 181 try:
181 182 return manifest(web, req, tmpl)
182 183 except ErrorResponse:
183 184 raise inst
184 185
185 186 def _search(web, req, tmpl):
186 187 MODE_REVISION = 'rev'
187 188 MODE_KEYWORD = 'keyword'
188 189 MODE_REVSET = 'revset'
189 190
190 191 def revsearch(ctx):
191 192 yield ctx
192 193
193 194 def keywordsearch(query):
194 195 lower = encoding.lower
195 196 qw = lower(query).split()
196 197
197 198 def revgen():
198 199 cl = web.repo.changelog
199 200 for i in xrange(len(web.repo) - 1, 0, -100):
200 201 l = []
201 202 for j in cl.revs(max(0, i - 99), i):
202 203 ctx = web.repo[j]
203 204 l.append(ctx)
204 205 l.reverse()
205 206 for e in l:
206 207 yield e
207 208
208 209 for ctx in revgen():
209 210 miss = 0
210 211 for q in qw:
211 212 if not (q in lower(ctx.user()) or
212 213 q in lower(ctx.description()) or
213 214 q in lower(" ".join(ctx.files()))):
214 215 miss = 1
215 216 break
216 217 if miss:
217 218 continue
218 219
219 220 yield ctx
220 221
221 222 def revsetsearch(revs):
222 223 for r in revs:
223 224 yield web.repo[r]
224 225
225 226 searchfuncs = {
226 227 MODE_REVISION: (revsearch, 'exact revision search'),
227 228 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
228 229 MODE_REVSET: (revsetsearch, 'revset expression search'),
229 230 }
230 231
231 232 def getsearchmode(query):
232 233 try:
233 234 ctx = web.repo[query]
234 235 except (error.RepoError, error.LookupError):
235 236 # query is not an exact revision pointer, need to
236 237 # decide if it's a revset expression or keywords
237 238 pass
238 239 else:
239 240 return MODE_REVISION, ctx
240 241
241 242 revdef = 'reverse(%s)' % query
242 243 try:
243 244 tree = revsetlang.parse(revdef)
244 245 except error.ParseError:
245 246 # can't parse to a revset tree
246 247 return MODE_KEYWORD, query
247 248
248 249 if revsetlang.depth(tree) <= 2:
249 250 # no revset syntax used
250 251 return MODE_KEYWORD, query
251 252
252 253 if any((token, (value or '')[:3]) == ('string', 're:')
253 254 for token, value, pos in revsetlang.tokenize(revdef)):
254 255 return MODE_KEYWORD, query
255 256
256 257 funcsused = revsetlang.funcsused(tree)
257 258 if not funcsused.issubset(revset.safesymbols):
258 259 return MODE_KEYWORD, query
259 260
260 261 mfunc = revset.match(web.repo.ui, revdef)
261 262 try:
262 263 revs = mfunc(web.repo)
263 264 return MODE_REVSET, revs
264 265 # ParseError: wrongly placed tokens, wrongs arguments, etc
265 266 # RepoLookupError: no such revision, e.g. in 'revision:'
266 267 # Abort: bookmark/tag not exists
267 268 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
268 269 except (error.ParseError, error.RepoLookupError, error.Abort,
269 270 LookupError):
270 271 return MODE_KEYWORD, query
271 272
272 273 def changelist(**map):
273 274 count = 0
274 275
275 276 for ctx in searchfunc[0](funcarg):
276 277 count += 1
277 278 n = ctx.node()
278 279 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
279 280 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
280 281
281 282 yield tmpl('searchentry',
282 283 parity=next(parity),
283 284 changelogtag=showtags,
284 285 files=files,
285 286 **webutil.commonentry(web.repo, ctx))
286 287
287 288 if count >= revcount:
288 289 break
289 290
290 291 query = req.form['rev'][0]
291 292 revcount = web.maxchanges
292 293 if 'revcount' in req.form:
293 294 try:
294 295 revcount = int(req.form.get('revcount', [revcount])[0])
295 296 revcount = max(revcount, 1)
296 297 tmpl.defaults['sessionvars']['revcount'] = revcount
297 298 except ValueError:
298 299 pass
299 300
300 301 lessvars = copy.copy(tmpl.defaults['sessionvars'])
301 302 lessvars['revcount'] = max(revcount / 2, 1)
302 303 lessvars['rev'] = query
303 304 morevars = copy.copy(tmpl.defaults['sessionvars'])
304 305 morevars['revcount'] = revcount * 2
305 306 morevars['rev'] = query
306 307
307 308 mode, funcarg = getsearchmode(query)
308 309
309 310 if 'forcekw' in req.form:
310 311 showforcekw = ''
311 312 showunforcekw = searchfuncs[mode][1]
312 313 mode = MODE_KEYWORD
313 314 funcarg = query
314 315 else:
315 316 if mode != MODE_KEYWORD:
316 317 showforcekw = searchfuncs[MODE_KEYWORD][1]
317 318 else:
318 319 showforcekw = ''
319 320 showunforcekw = ''
320 321
321 322 searchfunc = searchfuncs[mode]
322 323
323 324 tip = web.repo['tip']
324 325 parity = paritygen(web.stripecount)
325 326
326 327 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
327 328 entries=changelist, archives=web.archivelist("tip"),
328 329 morevars=morevars, lessvars=lessvars,
329 330 modedesc=searchfunc[1],
330 331 showforcekw=showforcekw, showunforcekw=showunforcekw)
331 332
332 333 @webcommand('changelog')
333 334 def changelog(web, req, tmpl, shortlog=False):
334 335 """
335 336 /changelog[/{revision}]
336 337 -----------------------
337 338
338 339 Show information about multiple changesets.
339 340
340 341 If the optional ``revision`` URL argument is absent, information about
341 342 all changesets starting at ``tip`` will be rendered. If the ``revision``
342 343 argument is present, changesets will be shown starting from the specified
343 344 revision.
344 345
345 346 If ``revision`` is absent, the ``rev`` query string argument may be
346 347 defined. This will perform a search for changesets.
347 348
348 349 The argument for ``rev`` can be a single revision, a revision set,
349 350 or a literal keyword to search for in changeset data (equivalent to
350 351 :hg:`log -k`).
351 352
352 353 The ``revcount`` query string argument defines the maximum numbers of
353 354 changesets to render.
354 355
355 356 For non-searches, the ``changelog`` template will be rendered.
356 357 """
357 358
358 359 query = ''
359 360 if 'node' in req.form:
360 361 ctx = webutil.changectx(web.repo, req)
361 362 symrev = webutil.symrevorshortnode(req, ctx)
362 363 elif 'rev' in req.form:
363 364 return _search(web, req, tmpl)
364 365 else:
365 366 ctx = web.repo['tip']
366 367 symrev = 'tip'
367 368
368 369 def changelist():
369 370 revs = []
370 371 if pos != -1:
371 372 revs = web.repo.changelog.revs(pos, 0)
372 373 curcount = 0
373 374 for rev in revs:
374 375 curcount += 1
375 376 if curcount > revcount + 1:
376 377 break
377 378
378 379 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
379 380 entry['parity'] = next(parity)
380 381 yield entry
381 382
382 383 if shortlog:
383 384 revcount = web.maxshortchanges
384 385 else:
385 386 revcount = web.maxchanges
386 387
387 388 if 'revcount' in req.form:
388 389 try:
389 390 revcount = int(req.form.get('revcount', [revcount])[0])
390 391 revcount = max(revcount, 1)
391 392 tmpl.defaults['sessionvars']['revcount'] = revcount
392 393 except ValueError:
393 394 pass
394 395
395 396 lessvars = copy.copy(tmpl.defaults['sessionvars'])
396 397 lessvars['revcount'] = max(revcount / 2, 1)
397 398 morevars = copy.copy(tmpl.defaults['sessionvars'])
398 399 morevars['revcount'] = revcount * 2
399 400
400 401 count = len(web.repo)
401 402 pos = ctx.rev()
402 403 parity = paritygen(web.stripecount)
403 404
404 405 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
405 406
406 407 entries = list(changelist())
407 408 latestentry = entries[:1]
408 409 if len(entries) > revcount:
409 410 nextentry = entries[-1:]
410 411 entries = entries[:-1]
411 412 else:
412 413 nextentry = []
413 414
414 415 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
415 416 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
416 417 entries=entries,
417 418 latestentry=latestentry, nextentry=nextentry,
418 419 archives=web.archivelist("tip"), revcount=revcount,
419 420 morevars=morevars, lessvars=lessvars, query=query)
420 421
421 422 @webcommand('shortlog')
422 423 def shortlog(web, req, tmpl):
423 424 """
424 425 /shortlog
425 426 ---------
426 427
427 428 Show basic information about a set of changesets.
428 429
429 430 This accepts the same parameters as the ``changelog`` handler. The only
430 431 difference is the ``shortlog`` template will be rendered instead of the
431 432 ``changelog`` template.
432 433 """
433 434 return changelog(web, req, tmpl, shortlog=True)
434 435
435 436 @webcommand('changeset')
436 437 def changeset(web, req, tmpl):
437 438 """
438 439 /changeset[/{revision}]
439 440 -----------------------
440 441
441 442 Show information about a single changeset.
442 443
443 444 A URL path argument is the changeset identifier to show. See ``hg help
444 445 revisions`` for possible values. If not defined, the ``tip`` changeset
445 446 will be shown.
446 447
447 448 The ``changeset`` template is rendered. Contents of the ``changesettag``,
448 449 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
449 450 templates related to diffs may all be used to produce the output.
450 451 """
451 452 ctx = webutil.changectx(web.repo, req)
452 453
453 454 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
454 455
455 456 rev = webcommand('rev')(changeset)
456 457
457 458 def decodepath(path):
458 459 """Hook for mapping a path in the repository to a path in the
459 460 working copy.
460 461
461 462 Extensions (e.g., largefiles) can override this to remap files in
462 463 the virtual file system presented by the manifest command below."""
463 464 return path
464 465
465 466 @webcommand('manifest')
466 467 def manifest(web, req, tmpl):
467 468 """
468 469 /manifest[/{revision}[/{path}]]
469 470 -------------------------------
470 471
471 472 Show information about a directory.
472 473
473 474 If the URL path arguments are omitted, information about the root
474 475 directory for the ``tip`` changeset will be shown.
475 476
476 477 Because this handler can only show information for directories, it
477 478 is recommended to use the ``file`` handler instead, as it can handle both
478 479 directories and files.
479 480
480 481 The ``manifest`` template will be rendered for this handler.
481 482 """
482 483 if 'node' in req.form:
483 484 ctx = webutil.changectx(web.repo, req)
484 485 symrev = webutil.symrevorshortnode(req, ctx)
485 486 else:
486 487 ctx = web.repo['tip']
487 488 symrev = 'tip'
488 489 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
489 490 mf = ctx.manifest()
490 491 node = ctx.node()
491 492
492 493 files = {}
493 494 dirs = {}
494 495 parity = paritygen(web.stripecount)
495 496
496 497 if path and path[-1] != "/":
497 498 path += "/"
498 499 l = len(path)
499 500 abspath = "/" + path
500 501
501 502 for full, n in mf.iteritems():
502 503 # the virtual path (working copy path) used for the full
503 504 # (repository) path
504 505 f = decodepath(full)
505 506
506 507 if f[:l] != path:
507 508 continue
508 509 remain = f[l:]
509 510 elements = remain.split('/')
510 511 if len(elements) == 1:
511 512 files[remain] = full
512 513 else:
513 514 h = dirs # need to retain ref to dirs (root)
514 515 for elem in elements[0:-1]:
515 516 if elem not in h:
516 517 h[elem] = {}
517 518 h = h[elem]
518 519 if len(h) > 1:
519 520 break
520 521 h[None] = None # denotes files present
521 522
522 523 if mf and not files and not dirs:
523 524 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
524 525
525 526 def filelist(**map):
526 527 for f in sorted(files):
527 528 full = files[f]
528 529
529 530 fctx = ctx.filectx(full)
530 531 yield {"file": full,
531 532 "parity": next(parity),
532 533 "basename": f,
533 534 "date": fctx.date(),
534 535 "size": fctx.size(),
535 536 "permissions": mf.flags(full)}
536 537
537 538 def dirlist(**map):
538 539 for d in sorted(dirs):
539 540
540 541 emptydirs = []
541 542 h = dirs[d]
542 543 while isinstance(h, dict) and len(h) == 1:
543 544 k, v = h.items()[0]
544 545 if v:
545 546 emptydirs.append(k)
546 547 h = v
547 548
548 549 path = "%s%s" % (abspath, d)
549 550 yield {"parity": next(parity),
550 551 "path": path,
551 552 "emptydirs": "/".join(emptydirs),
552 553 "basename": d}
553 554
554 555 return tmpl("manifest",
555 556 symrev=symrev,
556 557 path=abspath,
557 558 up=webutil.up(abspath),
558 559 upparity=next(parity),
559 560 fentries=filelist,
560 561 dentries=dirlist,
561 562 archives=web.archivelist(hex(node)),
562 563 **webutil.commonentry(web.repo, ctx))
563 564
564 565 @webcommand('tags')
565 566 def tags(web, req, tmpl):
566 567 """
567 568 /tags
568 569 -----
569 570
570 571 Show information about tags.
571 572
572 573 No arguments are accepted.
573 574
574 575 The ``tags`` template is rendered.
575 576 """
576 577 i = list(reversed(web.repo.tagslist()))
577 578 parity = paritygen(web.stripecount)
578 579
579 580 def entries(notip, latestonly, **map):
580 581 t = i
581 582 if notip:
582 583 t = [(k, n) for k, n in i if k != "tip"]
583 584 if latestonly:
584 585 t = t[:1]
585 586 for k, n in t:
586 587 yield {"parity": next(parity),
587 588 "tag": k,
588 589 "date": web.repo[n].date(),
589 590 "node": hex(n)}
590 591
591 592 return tmpl("tags",
592 593 node=hex(web.repo.changelog.tip()),
593 594 entries=lambda **x: entries(False, False, **x),
594 595 entriesnotip=lambda **x: entries(True, False, **x),
595 596 latestentry=lambda **x: entries(True, True, **x))
596 597
597 598 @webcommand('bookmarks')
598 599 def bookmarks(web, req, tmpl):
599 600 """
600 601 /bookmarks
601 602 ----------
602 603
603 604 Show information about bookmarks.
604 605
605 606 No arguments are accepted.
606 607
607 608 The ``bookmarks`` template is rendered.
608 609 """
609 610 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
610 611 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
611 612 i = sorted(i, key=sortkey, reverse=True)
612 613 parity = paritygen(web.stripecount)
613 614
614 615 def entries(latestonly, **map):
615 616 t = i
616 617 if latestonly:
617 618 t = i[:1]
618 619 for k, n in t:
619 620 yield {"parity": next(parity),
620 621 "bookmark": k,
621 622 "date": web.repo[n].date(),
622 623 "node": hex(n)}
623 624
624 625 if i:
625 626 latestrev = i[0][1]
626 627 else:
627 628 latestrev = -1
628 629
629 630 return tmpl("bookmarks",
630 631 node=hex(web.repo.changelog.tip()),
631 632 lastchange=[{"date": web.repo[latestrev].date()}],
632 633 entries=lambda **x: entries(latestonly=False, **x),
633 634 latestentry=lambda **x: entries(latestonly=True, **x))
634 635
635 636 @webcommand('branches')
636 637 def branches(web, req, tmpl):
637 638 """
638 639 /branches
639 640 ---------
640 641
641 642 Show information about branches.
642 643
643 644 All known branches are contained in the output, even closed branches.
644 645
645 646 No arguments are accepted.
646 647
647 648 The ``branches`` template is rendered.
648 649 """
649 650 entries = webutil.branchentries(web.repo, web.stripecount)
650 651 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
651 652 return tmpl('branches', node=hex(web.repo.changelog.tip()),
652 653 entries=entries, latestentry=latestentry)
653 654
654 655 @webcommand('summary')
655 656 def summary(web, req, tmpl):
656 657 """
657 658 /summary
658 659 --------
659 660
660 661 Show a summary of repository state.
661 662
662 663 Information about the latest changesets, bookmarks, tags, and branches
663 664 is captured by this handler.
664 665
665 666 The ``summary`` template is rendered.
666 667 """
667 668 i = reversed(web.repo.tagslist())
668 669
669 670 def tagentries(**map):
670 671 parity = paritygen(web.stripecount)
671 672 count = 0
672 673 for k, n in i:
673 674 if k == "tip": # skip tip
674 675 continue
675 676
676 677 count += 1
677 678 if count > 10: # limit to 10 tags
678 679 break
679 680
680 681 yield tmpl("tagentry",
681 682 parity=next(parity),
682 683 tag=k,
683 684 node=hex(n),
684 685 date=web.repo[n].date())
685 686
686 687 def bookmarks(**map):
687 688 parity = paritygen(web.stripecount)
688 689 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
689 690 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
690 691 marks = sorted(marks, key=sortkey, reverse=True)
691 692 for k, n in marks[:10]: # limit to 10 bookmarks
692 693 yield {'parity': next(parity),
693 694 'bookmark': k,
694 695 'date': web.repo[n].date(),
695 696 'node': hex(n)}
696 697
697 698 def changelist(**map):
698 699 parity = paritygen(web.stripecount, offset=start - end)
699 700 l = [] # build a list in forward order for efficiency
700 701 revs = []
701 702 if start < end:
702 703 revs = web.repo.changelog.revs(start, end - 1)
703 704 for i in revs:
704 705 ctx = web.repo[i]
705 706
706 707 l.append(tmpl(
707 708 'shortlogentry',
708 709 parity=next(parity),
709 710 **webutil.commonentry(web.repo, ctx)))
710 711
711 712 for entry in reversed(l):
712 713 yield entry
713 714
714 715 tip = web.repo['tip']
715 716 count = len(web.repo)
716 717 start = max(0, count - web.maxchanges)
717 718 end = min(count, start + web.maxchanges)
718 719
719 720 return tmpl("summary",
720 721 desc=web.config("web", "description", "unknown"),
721 722 owner=get_contact(web.config) or "unknown",
722 723 lastchange=tip.date(),
723 724 tags=tagentries,
724 725 bookmarks=bookmarks,
725 726 branches=webutil.branchentries(web.repo, web.stripecount, 10),
726 727 shortlog=changelist,
727 728 node=tip.hex(),
728 729 symrev='tip',
729 730 archives=web.archivelist("tip"),
730 731 labels=web.configlist('web', 'labels'))
731 732
732 733 @webcommand('filediff')
733 734 def filediff(web, req, tmpl):
734 735 """
735 736 /diff/{revision}/{path}
736 737 -----------------------
737 738
738 739 Show how a file changed in a particular commit.
739 740
740 741 The ``filediff`` template is rendered.
741 742
742 743 This handler is registered under both the ``/diff`` and ``/filediff``
743 744 paths. ``/diff`` is used in modern code.
744 745 """
745 746 fctx, ctx = None, None
746 747 try:
747 748 fctx = webutil.filectx(web.repo, req)
748 749 except LookupError:
749 750 ctx = webutil.changectx(web.repo, req)
750 751 path = webutil.cleanpath(web.repo, req.form['file'][0])
751 752 if path not in ctx.files():
752 753 raise
753 754
754 755 if fctx is not None:
755 756 path = fctx.path()
756 757 ctx = fctx.changectx()
757 758 basectx = ctx.p1()
758 759
759 760 style = web.config('web', 'style', 'paper')
760 761 if 'style' in req.form:
761 762 style = req.form['style'][0]
762 763
763 764 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
764 765 if fctx is not None:
765 766 rename = webutil.renamelink(fctx)
766 767 ctx = fctx
767 768 else:
768 769 rename = []
769 770 ctx = ctx
770 771 return tmpl("filediff",
771 772 file=path,
772 773 symrev=webutil.symrevorshortnode(req, ctx),
773 774 rename=rename,
774 775 diff=diffs,
775 776 **webutil.commonentry(web.repo, ctx))
776 777
777 778 diff = webcommand('diff')(filediff)
778 779
779 780 @webcommand('comparison')
780 781 def comparison(web, req, tmpl):
781 782 """
782 783 /comparison/{revision}/{path}
783 784 -----------------------------
784 785
785 786 Show a comparison between the old and new versions of a file from changes
786 787 made on a particular revision.
787 788
788 789 This is similar to the ``diff`` handler. However, this form features
789 790 a split or side-by-side diff rather than a unified diff.
790 791
791 792 The ``context`` query string argument can be used to control the lines of
792 793 context in the diff.
793 794
794 795 The ``filecomparison`` template is rendered.
795 796 """
796 797 ctx = webutil.changectx(web.repo, req)
797 798 if 'file' not in req.form:
798 799 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
799 800 path = webutil.cleanpath(web.repo, req.form['file'][0])
800 801
801 802 parsecontext = lambda v: v == 'full' and -1 or int(v)
802 803 if 'context' in req.form:
803 804 context = parsecontext(req.form['context'][0])
804 805 else:
805 806 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
806 807
807 808 def filelines(f):
808 809 if util.binary(f.data()):
809 810 mt = mimetypes.guess_type(f.path())[0]
810 811 if not mt:
811 812 mt = 'application/octet-stream'
812 813 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
813 814 return f.data().splitlines()
814 815
815 816 fctx = None
816 817 parent = ctx.p1()
817 818 leftrev = parent.rev()
818 819 leftnode = parent.node()
819 820 rightrev = ctx.rev()
820 821 rightnode = ctx.node()
821 822 if path in ctx:
822 823 fctx = ctx[path]
823 824 rightlines = filelines(fctx)
824 825 if path not in parent:
825 826 leftlines = ()
826 827 else:
827 828 pfctx = parent[path]
828 829 leftlines = filelines(pfctx)
829 830 else:
830 831 rightlines = ()
831 832 pfctx = ctx.parents()[0][path]
832 833 leftlines = filelines(pfctx)
833 834
834 835 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
835 836 if fctx is not None:
836 837 rename = webutil.renamelink(fctx)
837 838 ctx = fctx
838 839 else:
839 840 rename = []
840 841 ctx = ctx
841 842 return tmpl('filecomparison',
842 843 file=path,
843 844 symrev=webutil.symrevorshortnode(req, ctx),
844 845 rename=rename,
845 846 leftrev=leftrev,
846 847 leftnode=hex(leftnode),
847 848 rightrev=rightrev,
848 849 rightnode=hex(rightnode),
849 850 comparison=comparison,
850 851 **webutil.commonentry(web.repo, ctx))
851 852
852 853 @webcommand('annotate')
853 854 def annotate(web, req, tmpl):
854 855 """
855 856 /annotate/{revision}/{path}
856 857 ---------------------------
857 858
858 859 Show changeset information for each line in a file.
859 860
860 861 The ``fileannotate`` template is rendered.
861 862 """
862 863 fctx = webutil.filectx(web.repo, req)
863 864 f = fctx.path()
864 865 parity = paritygen(web.stripecount)
865 866
866 867 # parents() is called once per line and several lines likely belong to
867 868 # same revision. So it is worth caching.
868 869 # TODO there are still redundant operations within basefilectx.parents()
869 870 # and from the fctx.annotate() call itself that could be cached.
870 871 parentscache = {}
871 872 def parents(f):
872 873 rev = f.rev()
873 874 if rev not in parentscache:
874 875 parentscache[rev] = []
875 876 for p in f.parents():
876 877 entry = {
877 878 'node': p.hex(),
878 879 'rev': p.rev(),
879 880 }
880 881 parentscache[rev].append(entry)
881 882
882 883 for p in parentscache[rev]:
883 884 yield p
884 885
885 886 def annotate(**map):
886 887 if util.binary(fctx.data()):
887 888 mt = (mimetypes.guess_type(fctx.path())[0]
888 889 or 'application/octet-stream')
889 890 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
890 891 else:
891 892 lines = webutil.annotate(fctx, web.repo.ui)
892 893
893 894 previousrev = None
894 895 blockparitygen = paritygen(1)
895 896 for lineno, ((f, targetline), l) in enumerate(lines):
896 897 rev = f.rev()
897 898 if rev != previousrev:
898 899 blockhead = True
899 900 blockparity = next(blockparitygen)
900 901 else:
901 902 blockhead = None
902 903 previousrev = rev
903 904 yield {"parity": next(parity),
904 905 "node": f.hex(),
905 906 "rev": rev,
906 907 "author": f.user(),
907 908 "parents": parents(f),
908 909 "desc": f.description(),
909 910 "extra": f.extra(),
910 911 "file": f.path(),
911 912 "blockhead": blockhead,
912 913 "blockparity": blockparity,
913 914 "targetline": targetline,
914 915 "line": l,
915 916 "lineno": lineno + 1,
916 917 "lineid": "l%d" % (lineno + 1),
917 918 "linenumber": "% 6d" % (lineno + 1),
918 919 "revdate": f.date()}
919 920
920 921 return tmpl("fileannotate",
921 922 file=f,
922 923 annotate=annotate,
923 924 path=webutil.up(f),
924 925 symrev=webutil.symrevorshortnode(req, fctx),
925 926 rename=webutil.renamelink(fctx),
926 927 permissions=fctx.manifest().flags(f),
927 928 **webutil.commonentry(web.repo, fctx))
928 929
929 930 @webcommand('filelog')
930 931 def filelog(web, req, tmpl):
931 932 """
932 933 /filelog/{revision}/{path}
933 934 --------------------------
934 935
935 936 Show information about the history of a file in the repository.
936 937
937 938 The ``revcount`` query string argument can be defined to control the
938 939 maximum number of entries to show.
939 940
940 941 The ``filelog`` template will be rendered.
941 942 """
942 943
943 944 try:
944 945 fctx = webutil.filectx(web.repo, req)
945 946 f = fctx.path()
946 947 fl = fctx.filelog()
947 948 except error.LookupError:
948 949 f = webutil.cleanpath(web.repo, req.form['file'][0])
949 950 fl = web.repo.file(f)
950 951 numrevs = len(fl)
951 952 if not numrevs: # file doesn't exist at all
952 953 raise
953 954 rev = webutil.changectx(web.repo, req).rev()
954 955 first = fl.linkrev(0)
955 956 if rev < first: # current rev is from before file existed
956 957 raise
957 958 frev = numrevs - 1
958 959 while fl.linkrev(frev) > rev:
959 960 frev -= 1
960 961 fctx = web.repo.filectx(f, fl.linkrev(frev))
961 962
962 963 revcount = web.maxshortchanges
963 964 if 'revcount' in req.form:
964 965 try:
965 966 revcount = int(req.form.get('revcount', [revcount])[0])
966 967 revcount = max(revcount, 1)
967 968 tmpl.defaults['sessionvars']['revcount'] = revcount
968 969 except ValueError:
969 970 pass
970 971
972 lrange = webutil.linerange(req)
973
971 974 lessvars = copy.copy(tmpl.defaults['sessionvars'])
972 975 lessvars['revcount'] = max(revcount / 2, 1)
973 976 morevars = copy.copy(tmpl.defaults['sessionvars'])
974 977 morevars['revcount'] = revcount * 2
975 978
976 979 patch = 'patch' in req.form
977 980 if patch:
978 981 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
979 982
980 983 count = fctx.filerev() + 1
981 984 start = max(0, count - revcount) # first rev on this page
982 985 end = min(count, start + revcount) # last rev on this page
983 986 parity = paritygen(web.stripecount, offset=start - end)
984 987
985 988 repo = web.repo
986 989 revs = fctx.filelog().revs(start, end - 1)
987 990 entries = []
988 991
989 992 diffstyle = web.config('web', 'style', 'paper')
990 993 if 'style' in req.form:
991 994 diffstyle = req.form['style'][0]
992 995
993 996 def diff(fctx):
994 997 ctx = fctx.changectx()
995 998 basectx = ctx.p1()
996 999 path = fctx.path()
997 1000 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle)
998 1001
999 for i in revs:
1000 iterfctx = fctx.filectx(i)
1001 diffs = None
1002 if patch:
1003 diffs = diff(iterfctx)
1004 entries.append(dict(
1005 parity=next(parity),
1006 filerev=i,
1007 file=f,
1008 diff=diffs,
1009 rename=webutil.renamelink(iterfctx),
1010 **webutil.commonentry(repo, iterfctx)))
1011 entries.reverse()
1002 linerange = None
1003 if lrange is not None:
1004 linerange = webutil.formatlinerange(*lrange)
1005 # deactivate numeric nav links when linerange is specified as this
1006 # would required a dedicated "revnav" class
1007 nav = None
1008 ancestors = context.blockancestors(fctx, *lrange)
1009 for i, (c, lr) in enumerate(ancestors, 1):
1010 diffs = None
1011 if patch:
1012 diffs = diff(c)
1013 # follow renames accross filtered (not in range) revisions
1014 path = c.path()
1015 entries.append(dict(
1016 parity=next(parity),
1017 filerev=c.rev(),
1018 file=path,
1019 diff=diffs,
1020 linerange=webutil.formatlinerange(*lr),
1021 **webutil.commonentry(repo, c)))
1022 if i == revcount:
1023 break
1024 lessvars['linerange'] = webutil.formatlinerange(*lrange)
1025 morevars['linerange'] = lessvars['linerange']
1026 else:
1027 for i in revs:
1028 iterfctx = fctx.filectx(i)
1029 diffs = None
1030 if patch:
1031 diffs = diff(iterfctx)
1032 entries.append(dict(
1033 parity=next(parity),
1034 filerev=i,
1035 file=f,
1036 diff=diffs,
1037 rename=webutil.renamelink(iterfctx),
1038 **webutil.commonentry(repo, iterfctx)))
1039 entries.reverse()
1040 revnav = webutil.filerevnav(web.repo, fctx.path())
1041 nav = revnav.gen(end - 1, revcount, count)
1012 1042
1013 1043 latestentry = entries[:1]
1014 1044
1015 revnav = webutil.filerevnav(web.repo, fctx.path())
1016 nav = revnav.gen(end - 1, revcount, count)
1017 1045 return tmpl("filelog",
1018 1046 file=f,
1019 1047 nav=nav,
1020 1048 symrev=webutil.symrevorshortnode(req, fctx),
1021 1049 entries=entries,
1022 1050 patch=patch,
1023 1051 latestentry=latestentry,
1052 linerange=linerange,
1024 1053 revcount=revcount,
1025 1054 morevars=morevars,
1026 1055 lessvars=lessvars,
1027 1056 **webutil.commonentry(web.repo, fctx))
1028 1057
1029 1058 @webcommand('archive')
1030 1059 def archive(web, req, tmpl):
1031 1060 """
1032 1061 /archive/{revision}.{format}[/{path}]
1033 1062 -------------------------------------
1034 1063
1035 1064 Obtain an archive of repository content.
1036 1065
1037 1066 The content and type of the archive is defined by a URL path parameter.
1038 1067 ``format`` is the file extension of the archive type to be generated. e.g.
1039 1068 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1040 1069 server configuration.
1041 1070
1042 1071 The optional ``path`` URL parameter controls content to include in the
1043 1072 archive. If omitted, every file in the specified revision is present in the
1044 1073 archive. If included, only the specified file or contents of the specified
1045 1074 directory will be included in the archive.
1046 1075
1047 1076 No template is used for this handler. Raw, binary content is generated.
1048 1077 """
1049 1078
1050 1079 type_ = req.form.get('type', [None])[0]
1051 1080 allowed = web.configlist("web", "allow_archive")
1052 1081 key = req.form['node'][0]
1053 1082
1054 1083 if type_ not in web.archivespecs:
1055 1084 msg = 'Unsupported archive type: %s' % type_
1056 1085 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1057 1086
1058 1087 if not ((type_ in allowed or
1059 1088 web.configbool("web", "allow" + type_, False))):
1060 1089 msg = 'Archive type not allowed: %s' % type_
1061 1090 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1062 1091
1063 1092 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1064 1093 cnode = web.repo.lookup(key)
1065 1094 arch_version = key
1066 1095 if cnode == key or key == 'tip':
1067 1096 arch_version = short(cnode)
1068 1097 name = "%s-%s" % (reponame, arch_version)
1069 1098
1070 1099 ctx = webutil.changectx(web.repo, req)
1071 1100 pats = []
1072 1101 matchfn = scmutil.match(ctx, [])
1073 1102 file = req.form.get('file', None)
1074 1103 if file:
1075 1104 pats = ['path:' + file[0]]
1076 1105 matchfn = scmutil.match(ctx, pats, default='path')
1077 1106 if pats:
1078 1107 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1079 1108 if not files:
1080 1109 raise ErrorResponse(HTTP_NOT_FOUND,
1081 1110 'file(s) not found: %s' % file[0])
1082 1111
1083 1112 mimetype, artype, extension, encoding = web.archivespecs[type_]
1084 1113 headers = [
1085 1114 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1086 1115 ]
1087 1116 if encoding:
1088 1117 headers.append(('Content-Encoding', encoding))
1089 1118 req.headers.extend(headers)
1090 1119 req.respond(HTTP_OK, mimetype)
1091 1120
1092 1121 archival.archive(web.repo, req, cnode, artype, prefix=name,
1093 1122 matchfn=matchfn,
1094 1123 subrepos=web.configbool("web", "archivesubrepos"))
1095 1124 return []
1096 1125
1097 1126
1098 1127 @webcommand('static')
1099 1128 def static(web, req, tmpl):
1100 1129 fname = req.form['file'][0]
1101 1130 # a repo owner may set web.static in .hg/hgrc to get any file
1102 1131 # readable by the user running the CGI script
1103 1132 static = web.config("web", "static", None, untrusted=False)
1104 1133 if not static:
1105 1134 tp = web.templatepath or templater.templatepaths()
1106 1135 if isinstance(tp, str):
1107 1136 tp = [tp]
1108 1137 static = [os.path.join(p, 'static') for p in tp]
1109 1138 staticfile(static, fname, req)
1110 1139 return []
1111 1140
1112 1141 @webcommand('graph')
1113 1142 def graph(web, req, tmpl):
1114 1143 """
1115 1144 /graph[/{revision}]
1116 1145 -------------------
1117 1146
1118 1147 Show information about the graphical topology of the repository.
1119 1148
1120 1149 Information rendered by this handler can be used to create visual
1121 1150 representations of repository topology.
1122 1151
1123 1152 The ``revision`` URL parameter controls the starting changeset.
1124 1153
1125 1154 The ``revcount`` query string argument can define the number of changesets
1126 1155 to show information for.
1127 1156
1128 1157 This handler will render the ``graph`` template.
1129 1158 """
1130 1159
1131 1160 if 'node' in req.form:
1132 1161 ctx = webutil.changectx(web.repo, req)
1133 1162 symrev = webutil.symrevorshortnode(req, ctx)
1134 1163 else:
1135 1164 ctx = web.repo['tip']
1136 1165 symrev = 'tip'
1137 1166 rev = ctx.rev()
1138 1167
1139 1168 bg_height = 39
1140 1169 revcount = web.maxshortchanges
1141 1170 if 'revcount' in req.form:
1142 1171 try:
1143 1172 revcount = int(req.form.get('revcount', [revcount])[0])
1144 1173 revcount = max(revcount, 1)
1145 1174 tmpl.defaults['sessionvars']['revcount'] = revcount
1146 1175 except ValueError:
1147 1176 pass
1148 1177
1149 1178 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1150 1179 lessvars['revcount'] = max(revcount / 2, 1)
1151 1180 morevars = copy.copy(tmpl.defaults['sessionvars'])
1152 1181 morevars['revcount'] = revcount * 2
1153 1182
1154 1183 count = len(web.repo)
1155 1184 pos = rev
1156 1185
1157 1186 uprev = min(max(0, count - 1), rev + revcount)
1158 1187 downrev = max(0, rev - revcount)
1159 1188 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1160 1189
1161 1190 tree = []
1162 1191 if pos != -1:
1163 1192 allrevs = web.repo.changelog.revs(pos, 0)
1164 1193 revs = []
1165 1194 for i in allrevs:
1166 1195 revs.append(i)
1167 1196 if len(revs) >= revcount:
1168 1197 break
1169 1198
1170 1199 # We have to feed a baseset to dagwalker as it is expecting smartset
1171 1200 # object. This does not have a big impact on hgweb performance itself
1172 1201 # since hgweb graphing code is not itself lazy yet.
1173 1202 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1174 1203 # As we said one line above... not lazy.
1175 1204 tree = list(graphmod.colored(dag, web.repo))
1176 1205
1177 1206 def getcolumns(tree):
1178 1207 cols = 0
1179 1208 for (id, type, ctx, vtx, edges) in tree:
1180 1209 if type != graphmod.CHANGESET:
1181 1210 continue
1182 1211 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1183 1212 max([edge[1] for edge in edges] or [0]))
1184 1213 return cols
1185 1214
1186 1215 def graphdata(usetuples, encodestr):
1187 1216 data = []
1188 1217
1189 1218 row = 0
1190 1219 for (id, type, ctx, vtx, edges) in tree:
1191 1220 if type != graphmod.CHANGESET:
1192 1221 continue
1193 1222 node = str(ctx)
1194 1223 age = encodestr(templatefilters.age(ctx.date()))
1195 1224 desc = templatefilters.firstline(encodestr(ctx.description()))
1196 1225 desc = cgi.escape(templatefilters.nonempty(desc))
1197 1226 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1198 1227 branch = cgi.escape(encodestr(ctx.branch()))
1199 1228 try:
1200 1229 branchnode = web.repo.branchtip(branch)
1201 1230 except error.RepoLookupError:
1202 1231 branchnode = None
1203 1232 branch = branch, branchnode == ctx.node()
1204 1233
1205 1234 if usetuples:
1206 1235 data.append((node, vtx, edges, desc, user, age, branch,
1207 1236 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1208 1237 [cgi.escape(encodestr(x))
1209 1238 for x in ctx.bookmarks()]))
1210 1239 else:
1211 1240 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1212 1241 'color': (edge[2] - 1) % 6 + 1,
1213 1242 'width': edge[3], 'bcolor': edge[4]}
1214 1243 for edge in edges]
1215 1244
1216 1245 data.append(
1217 1246 {'node': node,
1218 1247 'col': vtx[0],
1219 1248 'color': (vtx[1] - 1) % 6 + 1,
1220 1249 'edges': edgedata,
1221 1250 'row': row,
1222 1251 'nextrow': row + 1,
1223 1252 'desc': desc,
1224 1253 'user': user,
1225 1254 'age': age,
1226 1255 'bookmarks': webutil.nodebookmarksdict(
1227 1256 web.repo, ctx.node()),
1228 1257 'branches': webutil.nodebranchdict(web.repo, ctx),
1229 1258 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1230 1259 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1231 1260
1232 1261 row += 1
1233 1262
1234 1263 return data
1235 1264
1236 1265 cols = getcolumns(tree)
1237 1266 rows = len(tree)
1238 1267 canvasheight = (rows + 1) * bg_height - 27
1239 1268
1240 1269 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1241 1270 uprev=uprev,
1242 1271 lessvars=lessvars, morevars=morevars, downrev=downrev,
1243 1272 cols=cols, rows=rows,
1244 1273 canvaswidth=(cols + 1) * bg_height,
1245 1274 truecanvasheight=rows * bg_height,
1246 1275 canvasheight=canvasheight, bg_height=bg_height,
1247 1276 # {jsdata} will be passed to |json, so it must be in utf-8
1248 1277 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1249 1278 nodes=lambda **x: graphdata(False, str),
1250 1279 node=ctx.hex(), changenav=changenav)
1251 1280
1252 1281 def _getdoc(e):
1253 1282 doc = e[0].__doc__
1254 1283 if doc:
1255 1284 doc = _(doc).partition('\n')[0]
1256 1285 else:
1257 1286 doc = _('(no help text available)')
1258 1287 return doc
1259 1288
1260 1289 @webcommand('help')
1261 1290 def help(web, req, tmpl):
1262 1291 """
1263 1292 /help[/{topic}]
1264 1293 ---------------
1265 1294
1266 1295 Render help documentation.
1267 1296
1268 1297 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1269 1298 is defined, that help topic will be rendered. If not, an index of
1270 1299 available help topics will be rendered.
1271 1300
1272 1301 The ``help`` template will be rendered when requesting help for a topic.
1273 1302 ``helptopics`` will be rendered for the index of help topics.
1274 1303 """
1275 1304 from .. import commands, help as helpmod # avoid cycle
1276 1305
1277 1306 topicname = req.form.get('node', [None])[0]
1278 1307 if not topicname:
1279 1308 def topics(**map):
1280 1309 for entries, summary, _doc in helpmod.helptable:
1281 1310 yield {'topic': entries[0], 'summary': summary}
1282 1311
1283 1312 early, other = [], []
1284 1313 primary = lambda s: s.partition('|')[0]
1285 1314 for c, e in commands.table.iteritems():
1286 1315 doc = _getdoc(e)
1287 1316 if 'DEPRECATED' in doc or c.startswith('debug'):
1288 1317 continue
1289 1318 cmd = primary(c)
1290 1319 if cmd.startswith('^'):
1291 1320 early.append((cmd[1:], doc))
1292 1321 else:
1293 1322 other.append((cmd, doc))
1294 1323
1295 1324 early.sort()
1296 1325 other.sort()
1297 1326
1298 1327 def earlycommands(**map):
1299 1328 for c, doc in early:
1300 1329 yield {'topic': c, 'summary': doc}
1301 1330
1302 1331 def othercommands(**map):
1303 1332 for c, doc in other:
1304 1333 yield {'topic': c, 'summary': doc}
1305 1334
1306 1335 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1307 1336 othercommands=othercommands, title='Index')
1308 1337
1309 1338 # Render an index of sub-topics.
1310 1339 if topicname in helpmod.subtopics:
1311 1340 topics = []
1312 1341 for entries, summary, _doc in helpmod.subtopics[topicname]:
1313 1342 topics.append({
1314 1343 'topic': '%s.%s' % (topicname, entries[0]),
1315 1344 'basename': entries[0],
1316 1345 'summary': summary,
1317 1346 })
1318 1347
1319 1348 return tmpl('helptopics', topics=topics, title=topicname,
1320 1349 subindex=True)
1321 1350
1322 1351 u = webutil.wsgiui.load()
1323 1352 u.verbose = True
1324 1353
1325 1354 # Render a page from a sub-topic.
1326 1355 if '.' in topicname:
1327 1356 # TODO implement support for rendering sections, like
1328 1357 # `hg help` works.
1329 1358 topic, subtopic = topicname.split('.', 1)
1330 1359 if topic not in helpmod.subtopics:
1331 1360 raise ErrorResponse(HTTP_NOT_FOUND)
1332 1361 else:
1333 1362 topic = topicname
1334 1363 subtopic = None
1335 1364
1336 1365 try:
1337 1366 doc = helpmod.help_(u, topic, subtopic=subtopic)
1338 1367 except error.UnknownCommand:
1339 1368 raise ErrorResponse(HTTP_NOT_FOUND)
1340 1369 return tmpl('help', topic=topicname, doc=doc)
1341 1370
1342 1371 # tell hggettext to extract docstrings from these functions:
1343 1372 i18nfunctions = commands.values()
@@ -1,601 +1,622 b''
1 1 # hgweb/webutil.py - utility library for the web interface.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import copy
12 12 import difflib
13 13 import os
14 14 import re
15 15
16 16 from ..i18n import _
17 17 from ..node import hex, nullid, short
18 18
19 19 from .common import (
20 20 ErrorResponse,
21 HTTP_BAD_REQUEST,
21 22 HTTP_NOT_FOUND,
22 23 paritygen,
23 24 )
24 25
25 26 from .. import (
26 27 context,
27 28 error,
28 29 match,
29 30 patch,
30 31 pathutil,
31 32 templatefilters,
32 33 ui as uimod,
33 34 util,
34 35 )
35 36
36 37 def up(p):
37 38 if p[0] != "/":
38 39 p = "/" + p
39 40 if p[-1] == "/":
40 41 p = p[:-1]
41 42 up = os.path.dirname(p)
42 43 if up == "/":
43 44 return "/"
44 45 return up + "/"
45 46
46 47 def _navseq(step, firststep=None):
47 48 if firststep:
48 49 yield firststep
49 50 if firststep >= 20 and firststep <= 40:
50 51 firststep = 50
51 52 yield firststep
52 53 assert step > 0
53 54 assert firststep > 0
54 55 while step <= firststep:
55 56 step *= 10
56 57 while True:
57 58 yield 1 * step
58 59 yield 3 * step
59 60 step *= 10
60 61
61 62 class revnav(object):
62 63
63 64 def __init__(self, repo):
64 65 """Navigation generation object
65 66
66 67 :repo: repo object we generate nav for
67 68 """
68 69 # used for hex generation
69 70 self._revlog = repo.changelog
70 71
71 72 def __nonzero__(self):
72 73 """return True if any revision to navigate over"""
73 74 return self._first() is not None
74 75
75 76 __bool__ = __nonzero__
76 77
77 78 def _first(self):
78 79 """return the minimum non-filtered changeset or None"""
79 80 try:
80 81 return next(iter(self._revlog))
81 82 except StopIteration:
82 83 return None
83 84
84 85 def hex(self, rev):
85 86 return hex(self._revlog.node(rev))
86 87
87 88 def gen(self, pos, pagelen, limit):
88 89 """computes label and revision id for navigation link
89 90
90 91 :pos: is the revision relative to which we generate navigation.
91 92 :pagelen: the size of each navigation page
92 93 :limit: how far shall we link
93 94
94 95 The return is:
95 96 - a single element tuple
96 97 - containing a dictionary with a `before` and `after` key
97 98 - values are generator functions taking arbitrary number of kwargs
98 99 - yield items are dictionaries with `label` and `node` keys
99 100 """
100 101 if not self:
101 102 # empty repo
102 103 return ({'before': (), 'after': ()},)
103 104
104 105 targets = []
105 106 for f in _navseq(1, pagelen):
106 107 if f > limit:
107 108 break
108 109 targets.append(pos + f)
109 110 targets.append(pos - f)
110 111 targets.sort()
111 112
112 113 first = self._first()
113 114 navbefore = [("(%i)" % first, self.hex(first))]
114 115 navafter = []
115 116 for rev in targets:
116 117 if rev not in self._revlog:
117 118 continue
118 119 if pos < rev < limit:
119 120 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
120 121 if 0 < rev < pos:
121 122 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
122 123
123 124
124 125 navafter.append(("tip", "tip"))
125 126
126 127 data = lambda i: {"label": i[0], "node": i[1]}
127 128 return ({'before': lambda **map: (data(i) for i in navbefore),
128 129 'after': lambda **map: (data(i) for i in navafter)},)
129 130
130 131 class filerevnav(revnav):
131 132
132 133 def __init__(self, repo, path):
133 134 """Navigation generation object
134 135
135 136 :repo: repo object we generate nav for
136 137 :path: path of the file we generate nav for
137 138 """
138 139 # used for iteration
139 140 self._changelog = repo.unfiltered().changelog
140 141 # used for hex generation
141 142 self._revlog = repo.file(path)
142 143
143 144 def hex(self, rev):
144 145 return hex(self._changelog.node(self._revlog.linkrev(rev)))
145 146
146 147 class _siblings(object):
147 148 def __init__(self, siblings=None, hiderev=None):
148 149 if siblings is None:
149 150 siblings = []
150 151 self.siblings = [s for s in siblings if s.node() != nullid]
151 152 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
152 153 self.siblings = []
153 154
154 155 def __iter__(self):
155 156 for s in self.siblings:
156 157 d = {
157 158 'node': s.hex(),
158 159 'rev': s.rev(),
159 160 'user': s.user(),
160 161 'date': s.date(),
161 162 'description': s.description(),
162 163 'branch': s.branch(),
163 164 }
164 165 if util.safehasattr(s, 'path'):
165 166 d['file'] = s.path()
166 167 yield d
167 168
168 169 def __len__(self):
169 170 return len(self.siblings)
170 171
171 172 def annotate(fctx, ui):
172 173 diffopts = patch.difffeatureopts(ui, untrusted=True,
173 174 section='annotate', whitespace=True)
174 175 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
175 176
176 177 def parents(ctx, hide=None):
177 178 if isinstance(ctx, context.basefilectx):
178 179 introrev = ctx.introrev()
179 180 if ctx.changectx().rev() != introrev:
180 181 return _siblings([ctx.repo()[introrev]], hide)
181 182 return _siblings(ctx.parents(), hide)
182 183
183 184 def children(ctx, hide=None):
184 185 return _siblings(ctx.children(), hide)
185 186
186 187 def renamelink(fctx):
187 188 r = fctx.renamed()
188 189 if r:
189 190 return [{'file': r[0], 'node': hex(r[1])}]
190 191 return []
191 192
192 193 def nodetagsdict(repo, node):
193 194 return [{"name": i} for i in repo.nodetags(node)]
194 195
195 196 def nodebookmarksdict(repo, node):
196 197 return [{"name": i} for i in repo.nodebookmarks(node)]
197 198
198 199 def nodebranchdict(repo, ctx):
199 200 branches = []
200 201 branch = ctx.branch()
201 202 # If this is an empty repo, ctx.node() == nullid,
202 203 # ctx.branch() == 'default'.
203 204 try:
204 205 branchnode = repo.branchtip(branch)
205 206 except error.RepoLookupError:
206 207 branchnode = None
207 208 if branchnode == ctx.node():
208 209 branches.append({"name": branch})
209 210 return branches
210 211
211 212 def nodeinbranch(repo, ctx):
212 213 branches = []
213 214 branch = ctx.branch()
214 215 try:
215 216 branchnode = repo.branchtip(branch)
216 217 except error.RepoLookupError:
217 218 branchnode = None
218 219 if branch != 'default' and branchnode != ctx.node():
219 220 branches.append({"name": branch})
220 221 return branches
221 222
222 223 def nodebranchnodefault(ctx):
223 224 branches = []
224 225 branch = ctx.branch()
225 226 if branch != 'default':
226 227 branches.append({"name": branch})
227 228 return branches
228 229
229 230 def showtag(repo, tmpl, t1, node=nullid, **args):
230 231 for t in repo.nodetags(node):
231 232 yield tmpl(t1, tag=t, **args)
232 233
233 234 def showbookmark(repo, tmpl, t1, node=nullid, **args):
234 235 for t in repo.nodebookmarks(node):
235 236 yield tmpl(t1, bookmark=t, **args)
236 237
237 238 def branchentries(repo, stripecount, limit=0):
238 239 tips = []
239 240 heads = repo.heads()
240 241 parity = paritygen(stripecount)
241 242 sortkey = lambda item: (not item[1], item[0].rev())
242 243
243 244 def entries(**map):
244 245 count = 0
245 246 if not tips:
246 247 for tag, hs, tip, closed in repo.branchmap().iterbranches():
247 248 tips.append((repo[tip], closed))
248 249 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
249 250 if limit > 0 and count >= limit:
250 251 return
251 252 count += 1
252 253 if closed:
253 254 status = 'closed'
254 255 elif ctx.node() not in heads:
255 256 status = 'inactive'
256 257 else:
257 258 status = 'open'
258 259 yield {
259 260 'parity': next(parity),
260 261 'branch': ctx.branch(),
261 262 'status': status,
262 263 'node': ctx.hex(),
263 264 'date': ctx.date()
264 265 }
265 266
266 267 return entries
267 268
268 269 def cleanpath(repo, path):
269 270 path = path.lstrip('/')
270 271 return pathutil.canonpath(repo.root, '', path)
271 272
272 273 def changeidctx(repo, changeid):
273 274 try:
274 275 ctx = repo[changeid]
275 276 except error.RepoError:
276 277 man = repo.manifestlog._revlog
277 278 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
278 279
279 280 return ctx
280 281
281 282 def changectx(repo, req):
282 283 changeid = "tip"
283 284 if 'node' in req.form:
284 285 changeid = req.form['node'][0]
285 286 ipos = changeid.find(':')
286 287 if ipos != -1:
287 288 changeid = changeid[(ipos + 1):]
288 289 elif 'manifest' in req.form:
289 290 changeid = req.form['manifest'][0]
290 291
291 292 return changeidctx(repo, changeid)
292 293
293 294 def basechangectx(repo, req):
294 295 if 'node' in req.form:
295 296 changeid = req.form['node'][0]
296 297 ipos = changeid.find(':')
297 298 if ipos != -1:
298 299 changeid = changeid[:ipos]
299 300 return changeidctx(repo, changeid)
300 301
301 302 return None
302 303
303 304 def filectx(repo, req):
304 305 if 'file' not in req.form:
305 306 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
306 307 path = cleanpath(repo, req.form['file'][0])
307 308 if 'node' in req.form:
308 309 changeid = req.form['node'][0]
309 310 elif 'filenode' in req.form:
310 311 changeid = req.form['filenode'][0]
311 312 else:
312 313 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
313 314 try:
314 315 fctx = repo[changeid][path]
315 316 except error.RepoError:
316 317 fctx = repo.filectx(path, fileid=changeid)
317 318
318 319 return fctx
319 320
321 def linerange(req):
322 linerange = req.form.get('linerange')
323 if linerange is None:
324 return None
325 if len(linerange) > 1:
326 raise ErrorResponse(HTTP_BAD_REQUEST,
327 'redundant linerange parameter')
328 try:
329 fromline, toline = map(int, linerange[0].split(':', 1))
330 except ValueError:
331 raise ErrorResponse(HTTP_BAD_REQUEST,
332 'invalid linerange parameter')
333 try:
334 return util.processlinerange(fromline, toline)
335 except error.ParseError as exc:
336 raise ErrorResponse(HTTP_BAD_REQUEST, str(exc))
337
338 def formatlinerange(fromline, toline):
339 return '%d:%d' % (fromline + 1, toline)
340
320 341 def commonentry(repo, ctx):
321 342 node = ctx.node()
322 343 return {
323 344 'rev': ctx.rev(),
324 345 'node': hex(node),
325 346 'author': ctx.user(),
326 347 'desc': ctx.description(),
327 348 'date': ctx.date(),
328 349 'extra': ctx.extra(),
329 350 'phase': ctx.phasestr(),
330 351 'branch': nodebranchnodefault(ctx),
331 352 'inbranch': nodeinbranch(repo, ctx),
332 353 'branches': nodebranchdict(repo, ctx),
333 354 'tags': nodetagsdict(repo, node),
334 355 'bookmarks': nodebookmarksdict(repo, node),
335 356 'parent': lambda **x: parents(ctx),
336 357 'child': lambda **x: children(ctx),
337 358 }
338 359
339 360 def changelistentry(web, ctx, tmpl):
340 361 '''Obtain a dictionary to be used for entries in a changelist.
341 362
342 363 This function is called when producing items for the "entries" list passed
343 364 to the "shortlog" and "changelog" templates.
344 365 '''
345 366 repo = web.repo
346 367 rev = ctx.rev()
347 368 n = ctx.node()
348 369 showtags = showtag(repo, tmpl, 'changelogtag', n)
349 370 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
350 371
351 372 entry = commonentry(repo, ctx)
352 373 entry.update(
353 374 allparents=lambda **x: parents(ctx),
354 375 parent=lambda **x: parents(ctx, rev - 1),
355 376 child=lambda **x: children(ctx, rev + 1),
356 377 changelogtag=showtags,
357 378 files=files,
358 379 )
359 380 return entry
360 381
361 382 def symrevorshortnode(req, ctx):
362 383 if 'node' in req.form:
363 384 return templatefilters.revescape(req.form['node'][0])
364 385 else:
365 386 return short(ctx.node())
366 387
367 388 def changesetentry(web, req, tmpl, ctx):
368 389 '''Obtain a dictionary to be used to render the "changeset" template.'''
369 390
370 391 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
371 392 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
372 393 ctx.node())
373 394 showbranch = nodebranchnodefault(ctx)
374 395
375 396 files = []
376 397 parity = paritygen(web.stripecount)
377 398 for blockno, f in enumerate(ctx.files()):
378 399 template = f in ctx and 'filenodelink' or 'filenolink'
379 400 files.append(tmpl(template,
380 401 node=ctx.hex(), file=f, blockno=blockno + 1,
381 402 parity=next(parity)))
382 403
383 404 basectx = basechangectx(web.repo, req)
384 405 if basectx is None:
385 406 basectx = ctx.p1()
386 407
387 408 style = web.config('web', 'style', 'paper')
388 409 if 'style' in req.form:
389 410 style = req.form['style'][0]
390 411
391 412 diff = diffs(web, tmpl, ctx, basectx, None, style)
392 413
393 414 parity = paritygen(web.stripecount)
394 415 diffstatsgen = diffstatgen(ctx, basectx)
395 416 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
396 417
397 418 return dict(
398 419 diff=diff,
399 420 symrev=symrevorshortnode(req, ctx),
400 421 basenode=basectx.hex(),
401 422 changesettag=showtags,
402 423 changesetbookmark=showbookmarks,
403 424 changesetbranch=showbranch,
404 425 files=files,
405 426 diffsummary=lambda **x: diffsummary(diffstatsgen),
406 427 diffstat=diffstats,
407 428 archives=web.archivelist(ctx.hex()),
408 429 **commonentry(web.repo, ctx))
409 430
410 431 def listfilediffs(tmpl, files, node, max):
411 432 for f in files[:max]:
412 433 yield tmpl('filedifflink', node=hex(node), file=f)
413 434 if len(files) > max:
414 435 yield tmpl('fileellipses')
415 436
416 437 def diffs(web, tmpl, ctx, basectx, files, style):
417 438
418 439 def prettyprintlines(lines, blockno):
419 440 for lineno, l in enumerate(lines, 1):
420 441 difflineno = "%d.%d" % (blockno, lineno)
421 442 if l.startswith('+'):
422 443 ltype = "difflineplus"
423 444 elif l.startswith('-'):
424 445 ltype = "difflineminus"
425 446 elif l.startswith('@'):
426 447 ltype = "difflineat"
427 448 else:
428 449 ltype = "diffline"
429 450 yield tmpl(ltype,
430 451 line=l,
431 452 lineno=lineno,
432 453 lineid="l%s" % difflineno,
433 454 linenumber="% 8s" % difflineno)
434 455
435 456 repo = web.repo
436 457 if files:
437 458 m = match.exact(repo.root, repo.getcwd(), files)
438 459 else:
439 460 m = match.always(repo.root, repo.getcwd())
440 461
441 462 diffopts = patch.diffopts(repo.ui, untrusted=True)
442 463 node1 = basectx.node()
443 464 node2 = ctx.node()
444 465 parity = paritygen(web.stripecount)
445 466
446 467 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
447 468 for blockno, (header, hunks) in enumerate(diffhunks, 1):
448 469 if style != 'raw':
449 470 header = header[1:]
450 471 lines = [h + '\n' for h in header]
451 472 for hunkrange, hunklines in hunks:
452 473 lines.extend(hunklines)
453 474 if lines:
454 475 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
455 476 lines=prettyprintlines(lines, blockno))
456 477
457 478 def compare(tmpl, context, leftlines, rightlines):
458 479 '''Generator function that provides side-by-side comparison data.'''
459 480
460 481 def compline(type, leftlineno, leftline, rightlineno, rightline):
461 482 lineid = leftlineno and ("l%s" % leftlineno) or ''
462 483 lineid += rightlineno and ("r%s" % rightlineno) or ''
463 484 return tmpl('comparisonline',
464 485 type=type,
465 486 lineid=lineid,
466 487 leftlineno=leftlineno,
467 488 leftlinenumber="% 6s" % (leftlineno or ''),
468 489 leftline=leftline or '',
469 490 rightlineno=rightlineno,
470 491 rightlinenumber="% 6s" % (rightlineno or ''),
471 492 rightline=rightline or '')
472 493
473 494 def getblock(opcodes):
474 495 for type, llo, lhi, rlo, rhi in opcodes:
475 496 len1 = lhi - llo
476 497 len2 = rhi - rlo
477 498 count = min(len1, len2)
478 499 for i in xrange(count):
479 500 yield compline(type=type,
480 501 leftlineno=llo + i + 1,
481 502 leftline=leftlines[llo + i],
482 503 rightlineno=rlo + i + 1,
483 504 rightline=rightlines[rlo + i])
484 505 if len1 > len2:
485 506 for i in xrange(llo + count, lhi):
486 507 yield compline(type=type,
487 508 leftlineno=i + 1,
488 509 leftline=leftlines[i],
489 510 rightlineno=None,
490 511 rightline=None)
491 512 elif len2 > len1:
492 513 for i in xrange(rlo + count, rhi):
493 514 yield compline(type=type,
494 515 leftlineno=None,
495 516 leftline=None,
496 517 rightlineno=i + 1,
497 518 rightline=rightlines[i])
498 519
499 520 s = difflib.SequenceMatcher(None, leftlines, rightlines)
500 521 if context < 0:
501 522 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
502 523 else:
503 524 for oc in s.get_grouped_opcodes(n=context):
504 525 yield tmpl('comparisonblock', lines=getblock(oc))
505 526
506 527 def diffstatgen(ctx, basectx):
507 528 '''Generator function that provides the diffstat data.'''
508 529
509 530 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
510 531 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
511 532 while True:
512 533 yield stats, maxname, maxtotal, addtotal, removetotal, binary
513 534
514 535 def diffsummary(statgen):
515 536 '''Return a short summary of the diff.'''
516 537
517 538 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
518 539 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
519 540 len(stats), addtotal, removetotal)
520 541
521 542 def diffstat(tmpl, ctx, statgen, parity):
522 543 '''Return a diffstat template for each file in the diff.'''
523 544
524 545 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
525 546 files = ctx.files()
526 547
527 548 def pct(i):
528 549 if maxtotal == 0:
529 550 return 0
530 551 return (float(i) / maxtotal) * 100
531 552
532 553 fileno = 0
533 554 for filename, adds, removes, isbinary in stats:
534 555 template = filename in files and 'diffstatlink' or 'diffstatnolink'
535 556 total = adds + removes
536 557 fileno += 1
537 558 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
538 559 total=total, addpct=pct(adds), removepct=pct(removes),
539 560 parity=next(parity))
540 561
541 562 class sessionvars(object):
542 563 def __init__(self, vars, start='?'):
543 564 self.start = start
544 565 self.vars = vars
545 566 def __getitem__(self, key):
546 567 return self.vars[key]
547 568 def __setitem__(self, key, value):
548 569 self.vars[key] = value
549 570 def __copy__(self):
550 571 return sessionvars(copy.copy(self.vars), self.start)
551 572 def __iter__(self):
552 573 separator = self.start
553 574 for key, value in sorted(self.vars.iteritems()):
554 575 yield {'name': key, 'value': str(value), 'separator': separator}
555 576 separator = '&'
556 577
557 578 class wsgiui(uimod.ui):
558 579 # default termwidth breaks under mod_wsgi
559 580 def termwidth(self):
560 581 return 80
561 582
562 583 def getwebsubs(repo):
563 584 websubtable = []
564 585 websubdefs = repo.ui.configitems('websub')
565 586 # we must maintain interhg backwards compatibility
566 587 websubdefs += repo.ui.configitems('interhg')
567 588 for key, pattern in websubdefs:
568 589 # grab the delimiter from the character after the "s"
569 590 unesc = pattern[1]
570 591 delim = re.escape(unesc)
571 592
572 593 # identify portions of the pattern, taking care to avoid escaped
573 594 # delimiters. the replace format and flags are optional, but
574 595 # delimiters are required.
575 596 match = re.match(
576 597 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
577 598 % (delim, delim, delim), pattern)
578 599 if not match:
579 600 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
580 601 % (key, pattern))
581 602 continue
582 603
583 604 # we need to unescape the delimiter for regexp and format
584 605 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
585 606 regexp = delim_re.sub(unesc, match.group(1))
586 607 format = delim_re.sub(unesc, match.group(2))
587 608
588 609 # the pattern allows for 6 regexp flags, so set them if necessary
589 610 flagin = match.group(3)
590 611 flags = 0
591 612 if flagin:
592 613 for flag in flagin.upper():
593 614 flags |= re.__dict__[flag]
594 615
595 616 try:
596 617 regexp = re.compile(regexp, flags)
597 618 websubtable.append((regexp, format))
598 619 except re.error:
599 620 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
600 621 % (key, regexp))
601 622 return websubtable
@@ -1,85 +1,87 b''
1 1 {header}
2 2 <title>{repo|escape}: {file|escape} history</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="{url|urlescape}atom-log/tip/{file|urlescape}" title="Atom feed for {repo|escape}:{file}" />
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="{url|urlescape}rss-log/tip/{file|urlescape}" title="RSS feed for {repo|escape}:{file}" />
7 7 </head>
8 8 <body>
9 9
10 10 <div class="container">
11 11 <div class="menu">
12 12 <div class="logo">
13 13 <a href="{logourl}">
14 14 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
15 15 </div>
16 16 <ul>
17 17 <li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
18 18 <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
19 19 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
20 20 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
21 21 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
22 22 </ul>
23 23 <ul>
24 24 <li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
25 25 <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
26 26 </ul>
27 27 <ul>
28 28 <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
29 29 <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
30 30 <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
31 31 <li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
32 32 <li class="active">file log</li>
33 33 <li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
34 34 </ul>
35 35 <ul>
36 36 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
37 37 </ul>
38 38 <div class="atom-logo">
39 39 <a href="{url|urlescape}atom-log/tip/{file|urlescape}" title="subscribe to atom feed">
40 40 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
41 41 </a>
42 42 </div>
43 43 </div>
44 44
45 45 <div class="main">
46 46 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
47 47 <h3>
48 48 log {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
49 49 {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
50 {if(linerange,
51 ' (following lines {linerange} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)')}
50 52 </h3>
51 53
52 54 <form class="search" action="{url|urlescape}log">
53 55 {sessionvars%hiddenformentry}
54 56 <p><input name="rev" id="search1" type="text" size="30" /></p>
55 57 <div id="hint">{searchhint}</div>
56 58 </form>
57 59
58 60 <div class="navigate">
59 61 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
60 62 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
61 63 | {nav%filenav}</div>
62 64
63 65 <table class="bigtable">
64 66 <thead>
65 67 <tr>
66 68 <th class="age">age</th>
67 69 <th class="author">author</th>
68 70 <th class="description">description</th>
69 71 </tr>
70 72 </thead>
71 73 <tbody class="stripes2">
72 74 {entries%filelogentry}
73 75 </tbody>
74 76 </table>
75 77
76 78 <div class="navigate">
77 79 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
78 80 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
79 81 | {nav%filenav}
80 82 </div>
81 83
82 84 </div>
83 85 </div>
84 86
85 87 {footer}
@@ -1,1036 +1,1299 b''
1 1 #require serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo b > b
6 6 $ hg ci -Am "b"
7 7 adding b
8 8 $ echo a > a
9 9 $ hg ci -Am "first a"
10 10 adding a
11 11 $ hg tag -r 1 a-tag
12 12 $ hg bookmark -r 1 a-bookmark
13 13 $ hg rm a
14 14 $ hg ci -m "del a"
15 15 $ hg branch a-branch
16 16 marked working directory as branch a-branch
17 17 (branches are permanent and global, did you want a bookmark?)
18 18 $ echo b > a
19 19 $ hg ci -Am "second a"
20 20 adding a
21 21 $ hg rm a
22 22 $ hg ci -m "del2 a"
23 23 $ hg mv b c
24 24 $ hg ci -m "mv b"
25 25 $ echo c >> c
26 26 $ hg ci -m "change c"
27 27 $ hg log -p
28 28 changeset: 7:46c1a66bd8fc
29 29 branch: a-branch
30 30 tag: tip
31 31 user: test
32 32 date: Thu Jan 01 00:00:00 1970 +0000
33 33 summary: change c
34 34
35 35 diff -r c9637d3cc8ef -r 46c1a66bd8fc c
36 36 --- a/c Thu Jan 01 00:00:00 1970 +0000
37 37 +++ b/c Thu Jan 01 00:00:00 1970 +0000
38 38 @@ -1,1 +1,2 @@
39 39 b
40 40 +c
41 41
42 42 changeset: 6:c9637d3cc8ef
43 43 branch: a-branch
44 44 user: test
45 45 date: Thu Jan 01 00:00:00 1970 +0000
46 46 summary: mv b
47 47
48 48 diff -r 958bd88be4eb -r c9637d3cc8ef b
49 49 --- a/b Thu Jan 01 00:00:00 1970 +0000
50 50 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
51 51 @@ -1,1 +0,0 @@
52 52 -b
53 53 diff -r 958bd88be4eb -r c9637d3cc8ef c
54 54 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
55 55 +++ b/c Thu Jan 01 00:00:00 1970 +0000
56 56 @@ -0,0 +1,1 @@
57 57 +b
58 58
59 59 changeset: 5:958bd88be4eb
60 60 branch: a-branch
61 61 user: test
62 62 date: Thu Jan 01 00:00:00 1970 +0000
63 63 summary: del2 a
64 64
65 65 diff -r 3f41bc784e7e -r 958bd88be4eb a
66 66 --- a/a Thu Jan 01 00:00:00 1970 +0000
67 67 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
68 68 @@ -1,1 +0,0 @@
69 69 -b
70 70
71 71 changeset: 4:3f41bc784e7e
72 72 branch: a-branch
73 73 user: test
74 74 date: Thu Jan 01 00:00:00 1970 +0000
75 75 summary: second a
76 76
77 77 diff -r 292258f86fdf -r 3f41bc784e7e a
78 78 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
79 79 +++ b/a Thu Jan 01 00:00:00 1970 +0000
80 80 @@ -0,0 +1,1 @@
81 81 +b
82 82
83 83 changeset: 3:292258f86fdf
84 84 user: test
85 85 date: Thu Jan 01 00:00:00 1970 +0000
86 86 summary: del a
87 87
88 88 diff -r 94c9dd5ca9b4 -r 292258f86fdf a
89 89 --- a/a Thu Jan 01 00:00:00 1970 +0000
90 90 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
91 91 @@ -1,1 +0,0 @@
92 92 -a
93 93
94 94 changeset: 2:94c9dd5ca9b4
95 95 user: test
96 96 date: Thu Jan 01 00:00:00 1970 +0000
97 97 summary: Added tag a-tag for changeset 5ed941583260
98 98
99 99 diff -r 5ed941583260 -r 94c9dd5ca9b4 .hgtags
100 100 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
101 101 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
102 102 @@ -0,0 +1,1 @@
103 103 +5ed941583260248620985524192fdc382ef57c36 a-tag
104 104
105 105 changeset: 1:5ed941583260
106 106 bookmark: a-bookmark
107 107 tag: a-tag
108 108 user: test
109 109 date: Thu Jan 01 00:00:00 1970 +0000
110 110 summary: first a
111 111
112 112 diff -r 6563da9dcf87 -r 5ed941583260 a
113 113 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
114 114 +++ b/a Thu Jan 01 00:00:00 1970 +0000
115 115 @@ -0,0 +1,1 @@
116 116 +a
117 117
118 118 changeset: 0:6563da9dcf87
119 119 user: test
120 120 date: Thu Jan 01 00:00:00 1970 +0000
121 121 summary: b
122 122
123 123 diff -r 000000000000 -r 6563da9dcf87 b
124 124 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
125 125 +++ b/b Thu Jan 01 00:00:00 1970 +0000
126 126 @@ -0,0 +1,1 @@
127 127 +b
128 128
129 129 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
130 130 $ cat hg.pid >> $DAEMON_PIDS
131 131
132 132 tip - two revisions
133 133
134 134 $ (get-with-headers.py localhost:$HGPORT 'log/tip/a')
135 135 200 Script output follows
136 136
137 137 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
138 138 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
139 139 <head>
140 140 <link rel="icon" href="/static/hgicon.png" type="image/png" />
141 141 <meta name="robots" content="index, nofollow" />
142 142 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
143 143 <script type="text/javascript" src="/static/mercurial.js"></script>
144 144
145 145 <title>test: a history</title>
146 146 <link rel="alternate" type="application/atom+xml"
147 147 href="/atom-log/tip/a" title="Atom feed for test:a" />
148 148 <link rel="alternate" type="application/rss+xml"
149 149 href="/rss-log/tip/a" title="RSS feed for test:a" />
150 150 </head>
151 151 <body>
152 152
153 153 <div class="container">
154 154 <div class="menu">
155 155 <div class="logo">
156 156 <a href="https://mercurial-scm.org/">
157 157 <img src="/static/hglogo.png" alt="mercurial" /></a>
158 158 </div>
159 159 <ul>
160 160 <li><a href="/shortlog/tip">log</a></li>
161 161 <li><a href="/graph/tip">graph</a></li>
162 162 <li><a href="/tags">tags</a></li>
163 163 <li><a href="/bookmarks">bookmarks</a></li>
164 164 <li><a href="/branches">branches</a></li>
165 165 </ul>
166 166 <ul>
167 167 <li><a href="/rev/tip">changeset</a></li>
168 168 <li><a href="/file/tip">browse</a></li>
169 169 </ul>
170 170 <ul>
171 171 <li><a href="/file/tip/a">file</a></li>
172 172 <li><a href="/diff/tip/a">diff</a></li>
173 173 <li><a href="/comparison/tip/a">comparison</a></li>
174 174 <li><a href="/annotate/tip/a">annotate</a></li>
175 175 <li class="active">file log</li>
176 176 <li><a href="/raw-file/tip/a">raw</a></li>
177 177 </ul>
178 178 <ul>
179 179 <li><a href="/help">help</a></li>
180 180 </ul>
181 181 <div class="atom-logo">
182 182 <a href="/atom-log/tip/a" title="subscribe to atom feed">
183 183 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
184 184 </a>
185 185 </div>
186 186 </div>
187 187
188 188 <div class="main">
189 189 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
190 190 <h3>
191 191 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
192 192 <span class="branchname">a-branch</span>
193
193 194 </h3>
194 195
195 196 <form class="search" action="/log">
196 197
197 198 <p><input name="rev" id="search1" type="text" size="30" /></p>
198 199 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
199 200 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
200 201 </form>
201 202
202 203 <div class="navigate">
203 204 <a href="/log/tip/a?revcount=30">less</a>
204 205 <a href="/log/tip/a?revcount=120">more</a>
205 206 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
206 207
207 208 <table class="bigtable">
208 209 <thead>
209 210 <tr>
210 211 <th class="age">age</th>
211 212 <th class="author">author</th>
212 213 <th class="description">description</th>
213 214 </tr>
214 215 </thead>
215 216 <tbody class="stripes2">
216 217 <tr>
217 218 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
218 219 <td class="author">test</td>
219 220 <td class="description">
220 221 <a href="/rev/3f41bc784e7e">second a</a>
221 222 <span class="branchname">a-branch</span>
222 223 </td>
223 224 </tr>
224 225
225 226 <tr>
226 227 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
227 228 <td class="author">test</td>
228 229 <td class="description">
229 230 <a href="/rev/5ed941583260">first a</a>
230 231 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
231 232 </td>
232 233 </tr>
233 234
234 235
235 236 </tbody>
236 237 </table>
237 238
238 239 <div class="navigate">
239 240 <a href="/log/tip/a?revcount=30">less</a>
240 241 <a href="/log/tip/a?revcount=120">more</a>
241 242 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
242 243 </div>
243 244
244 245 </div>
245 246 </div>
246 247
247 248
248 249
249 250 </body>
250 251 </html>
251 252
252 253
253 254 second version - two revisions
254 255
255 256 $ (get-with-headers.py localhost:$HGPORT 'log/4/a')
256 257 200 Script output follows
257 258
258 259 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
259 260 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
260 261 <head>
261 262 <link rel="icon" href="/static/hgicon.png" type="image/png" />
262 263 <meta name="robots" content="index, nofollow" />
263 264 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
264 265 <script type="text/javascript" src="/static/mercurial.js"></script>
265 266
266 267 <title>test: a history</title>
267 268 <link rel="alternate" type="application/atom+xml"
268 269 href="/atom-log/tip/a" title="Atom feed for test:a" />
269 270 <link rel="alternate" type="application/rss+xml"
270 271 href="/rss-log/tip/a" title="RSS feed for test:a" />
271 272 </head>
272 273 <body>
273 274
274 275 <div class="container">
275 276 <div class="menu">
276 277 <div class="logo">
277 278 <a href="https://mercurial-scm.org/">
278 279 <img src="/static/hglogo.png" alt="mercurial" /></a>
279 280 </div>
280 281 <ul>
281 282 <li><a href="/shortlog/4">log</a></li>
282 283 <li><a href="/graph/4">graph</a></li>
283 284 <li><a href="/tags">tags</a></li>
284 285 <li><a href="/bookmarks">bookmarks</a></li>
285 286 <li><a href="/branches">branches</a></li>
286 287 </ul>
287 288 <ul>
288 289 <li><a href="/rev/4">changeset</a></li>
289 290 <li><a href="/file/4">browse</a></li>
290 291 </ul>
291 292 <ul>
292 293 <li><a href="/file/4/a">file</a></li>
293 294 <li><a href="/diff/4/a">diff</a></li>
294 295 <li><a href="/comparison/4/a">comparison</a></li>
295 296 <li><a href="/annotate/4/a">annotate</a></li>
296 297 <li class="active">file log</li>
297 298 <li><a href="/raw-file/4/a">raw</a></li>
298 299 </ul>
299 300 <ul>
300 301 <li><a href="/help">help</a></li>
301 302 </ul>
302 303 <div class="atom-logo">
303 304 <a href="/atom-log/tip/a" title="subscribe to atom feed">
304 305 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
305 306 </a>
306 307 </div>
307 308 </div>
308 309
309 310 <div class="main">
310 311 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
311 312 <h3>
312 313 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
313 314 <span class="branchname">a-branch</span>
315
314 316 </h3>
315 317
316 318 <form class="search" action="/log">
317 319
318 320 <p><input name="rev" id="search1" type="text" size="30" /></p>
319 321 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
320 322 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
321 323 </form>
322 324
323 325 <div class="navigate">
324 326 <a href="/log/4/a?revcount=30">less</a>
325 327 <a href="/log/4/a?revcount=120">more</a>
326 328 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
327 329
328 330 <table class="bigtable">
329 331 <thead>
330 332 <tr>
331 333 <th class="age">age</th>
332 334 <th class="author">author</th>
333 335 <th class="description">description</th>
334 336 </tr>
335 337 </thead>
336 338 <tbody class="stripes2">
337 339 <tr>
338 340 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
339 341 <td class="author">test</td>
340 342 <td class="description">
341 343 <a href="/rev/3f41bc784e7e">second a</a>
342 344 <span class="branchname">a-branch</span>
343 345 </td>
344 346 </tr>
345 347
346 348 <tr>
347 349 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
348 350 <td class="author">test</td>
349 351 <td class="description">
350 352 <a href="/rev/5ed941583260">first a</a>
351 353 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
352 354 </td>
353 355 </tr>
354 356
355 357
356 358 </tbody>
357 359 </table>
358 360
359 361 <div class="navigate">
360 362 <a href="/log/4/a?revcount=30">less</a>
361 363 <a href="/log/4/a?revcount=120">more</a>
362 364 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
363 365 </div>
364 366
365 367 </div>
366 368 </div>
367 369
368 370
369 371
370 372 </body>
371 373 </html>
372 374
373 375
374 376 first deleted - one revision
375 377
376 378 $ (get-with-headers.py localhost:$HGPORT 'log/3/a')
377 379 200 Script output follows
378 380
379 381 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
380 382 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
381 383 <head>
382 384 <link rel="icon" href="/static/hgicon.png" type="image/png" />
383 385 <meta name="robots" content="index, nofollow" />
384 386 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
385 387 <script type="text/javascript" src="/static/mercurial.js"></script>
386 388
387 389 <title>test: a history</title>
388 390 <link rel="alternate" type="application/atom+xml"
389 391 href="/atom-log/tip/a" title="Atom feed for test:a" />
390 392 <link rel="alternate" type="application/rss+xml"
391 393 href="/rss-log/tip/a" title="RSS feed for test:a" />
392 394 </head>
393 395 <body>
394 396
395 397 <div class="container">
396 398 <div class="menu">
397 399 <div class="logo">
398 400 <a href="https://mercurial-scm.org/">
399 401 <img src="/static/hglogo.png" alt="mercurial" /></a>
400 402 </div>
401 403 <ul>
402 404 <li><a href="/shortlog/3">log</a></li>
403 405 <li><a href="/graph/3">graph</a></li>
404 406 <li><a href="/tags">tags</a></li>
405 407 <li><a href="/bookmarks">bookmarks</a></li>
406 408 <li><a href="/branches">branches</a></li>
407 409 </ul>
408 410 <ul>
409 411 <li><a href="/rev/3">changeset</a></li>
410 412 <li><a href="/file/3">browse</a></li>
411 413 </ul>
412 414 <ul>
413 415 <li><a href="/file/3/a">file</a></li>
414 416 <li><a href="/diff/3/a">diff</a></li>
415 417 <li><a href="/comparison/3/a">comparison</a></li>
416 418 <li><a href="/annotate/3/a">annotate</a></li>
417 419 <li class="active">file log</li>
418 420 <li><a href="/raw-file/3/a">raw</a></li>
419 421 </ul>
420 422 <ul>
421 423 <li><a href="/help">help</a></li>
422 424 </ul>
423 425 <div class="atom-logo">
424 426 <a href="/atom-log/tip/a" title="subscribe to atom feed">
425 427 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
426 428 </a>
427 429 </div>
428 430 </div>
429 431
430 432 <div class="main">
431 433 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
432 434 <h3>
433 435 log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
434 436 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
437
435 438 </h3>
436 439
437 440 <form class="search" action="/log">
438 441
439 442 <p><input name="rev" id="search1" type="text" size="30" /></p>
440 443 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
441 444 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
442 445 </form>
443 446
444 447 <div class="navigate">
445 448 <a href="/log/3/a?revcount=30">less</a>
446 449 <a href="/log/3/a?revcount=120">more</a>
447 450 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
448 451
449 452 <table class="bigtable">
450 453 <thead>
451 454 <tr>
452 455 <th class="age">age</th>
453 456 <th class="author">author</th>
454 457 <th class="description">description</th>
455 458 </tr>
456 459 </thead>
457 460 <tbody class="stripes2">
458 461 <tr>
459 462 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
460 463 <td class="author">test</td>
461 464 <td class="description">
462 465 <a href="/rev/5ed941583260">first a</a>
463 466 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
464 467 </td>
465 468 </tr>
466 469
467 470
468 471 </tbody>
469 472 </table>
470 473
471 474 <div class="navigate">
472 475 <a href="/log/3/a?revcount=30">less</a>
473 476 <a href="/log/3/a?revcount=120">more</a>
474 477 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
475 478 </div>
476 479
477 480 </div>
478 481 </div>
479 482
480 483
481 484
482 485 </body>
483 486 </html>
484 487
485 488
486 489 first version - one revision
487 490
488 491 $ (get-with-headers.py localhost:$HGPORT 'log/1/a')
489 492 200 Script output follows
490 493
491 494 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
492 495 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
493 496 <head>
494 497 <link rel="icon" href="/static/hgicon.png" type="image/png" />
495 498 <meta name="robots" content="index, nofollow" />
496 499 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
497 500 <script type="text/javascript" src="/static/mercurial.js"></script>
498 501
499 502 <title>test: a history</title>
500 503 <link rel="alternate" type="application/atom+xml"
501 504 href="/atom-log/tip/a" title="Atom feed for test:a" />
502 505 <link rel="alternate" type="application/rss+xml"
503 506 href="/rss-log/tip/a" title="RSS feed for test:a" />
504 507 </head>
505 508 <body>
506 509
507 510 <div class="container">
508 511 <div class="menu">
509 512 <div class="logo">
510 513 <a href="https://mercurial-scm.org/">
511 514 <img src="/static/hglogo.png" alt="mercurial" /></a>
512 515 </div>
513 516 <ul>
514 517 <li><a href="/shortlog/1">log</a></li>
515 518 <li><a href="/graph/1">graph</a></li>
516 519 <li><a href="/tags">tags</a></li>
517 520 <li><a href="/bookmarks">bookmarks</a></li>
518 521 <li><a href="/branches">branches</a></li>
519 522 </ul>
520 523 <ul>
521 524 <li><a href="/rev/1">changeset</a></li>
522 525 <li><a href="/file/1">browse</a></li>
523 526 </ul>
524 527 <ul>
525 528 <li><a href="/file/1/a">file</a></li>
526 529 <li><a href="/diff/1/a">diff</a></li>
527 530 <li><a href="/comparison/1/a">comparison</a></li>
528 531 <li><a href="/annotate/1/a">annotate</a></li>
529 532 <li class="active">file log</li>
530 533 <li><a href="/raw-file/1/a">raw</a></li>
531 534 </ul>
532 535 <ul>
533 536 <li><a href="/help">help</a></li>
534 537 </ul>
535 538 <div class="atom-logo">
536 539 <a href="/atom-log/tip/a" title="subscribe to atom feed">
537 540 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
538 541 </a>
539 542 </div>
540 543 </div>
541 544
542 545 <div class="main">
543 546 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
544 547 <h3>
545 548 log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
546 549 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
550
547 551 </h3>
548 552
549 553 <form class="search" action="/log">
550 554
551 555 <p><input name="rev" id="search1" type="text" size="30" /></p>
552 556 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
553 557 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
554 558 </form>
555 559
556 560 <div class="navigate">
557 561 <a href="/log/1/a?revcount=30">less</a>
558 562 <a href="/log/1/a?revcount=120">more</a>
559 563 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
560 564
561 565 <table class="bigtable">
562 566 <thead>
563 567 <tr>
564 568 <th class="age">age</th>
565 569 <th class="author">author</th>
566 570 <th class="description">description</th>
567 571 </tr>
568 572 </thead>
569 573 <tbody class="stripes2">
570 574 <tr>
571 575 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
572 576 <td class="author">test</td>
573 577 <td class="description">
574 578 <a href="/rev/5ed941583260">first a</a>
575 579 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
576 580 </td>
577 581 </tr>
578 582
579 583
580 584 </tbody>
581 585 </table>
582 586
583 587 <div class="navigate">
584 588 <a href="/log/1/a?revcount=30">less</a>
585 589 <a href="/log/1/a?revcount=120">more</a>
586 590 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
587 591 </div>
588 592
589 593 </div>
590 594 </div>
591 595
592 596
593 597
594 598 </body>
595 599 </html>
596 600
597 601
598 602 before addition - error
599 603
600 604 $ (get-with-headers.py localhost:$HGPORT 'log/0/a')
601 605 404 Not Found
602 606
603 607 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
604 608 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
605 609 <head>
606 610 <link rel="icon" href="/static/hgicon.png" type="image/png" />
607 611 <meta name="robots" content="index, nofollow" />
608 612 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
609 613 <script type="text/javascript" src="/static/mercurial.js"></script>
610 614
611 615 <title>test: error</title>
612 616 </head>
613 617 <body>
614 618
615 619 <div class="container">
616 620 <div class="menu">
617 621 <div class="logo">
618 622 <a href="https://mercurial-scm.org/">
619 623 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
620 624 </div>
621 625 <ul>
622 626 <li><a href="/shortlog">log</a></li>
623 627 <li><a href="/graph">graph</a></li>
624 628 <li><a href="/tags">tags</a></li>
625 629 <li><a href="/bookmarks">bookmarks</a></li>
626 630 <li><a href="/branches">branches</a></li>
627 631 </ul>
628 632 <ul>
629 633 <li><a href="/help">help</a></li>
630 634 </ul>
631 635 </div>
632 636
633 637 <div class="main">
634 638
635 639 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
636 640 <h3>error</h3>
637 641
638 642 <form class="search" action="/log">
639 643
640 644 <p><input name="rev" id="search1" type="text" size="30"></p>
641 645 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
642 646 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
643 647 </form>
644 648
645 649 <div class="description">
646 650 <p>
647 651 An error occurred while processing your request:
648 652 </p>
649 653 <p>
650 654 a@6563da9dcf87: not found in manifest
651 655 </p>
652 656 </div>
653 657 </div>
654 658 </div>
655 659
656 660
657 661
658 662 </body>
659 663 </html>
660 664
661 665 [1]
662 666
667 $ hg log -r 'followlines(c, 1:2, startrev=tip) and follow(c)'
668 changeset: 0:6563da9dcf87
669 user: test
670 date: Thu Jan 01 00:00:00 1970 +0000
671 summary: b
672
673 changeset: 7:46c1a66bd8fc
674 branch: a-branch
675 tag: tip
676 user: test
677 date: Thu Jan 01 00:00:00 1970 +0000
678 summary: change c
679
680 $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?linerange=1:2')
681 200 Script output follows
682
683 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
684 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
685 <head>
686 <link rel="icon" href="/static/hgicon.png" type="image/png" />
687 <meta name="robots" content="index, nofollow" />
688 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
689 <script type="text/javascript" src="/static/mercurial.js"></script>
690
691 <title>test: c history</title>
692 <link rel="alternate" type="application/atom+xml"
693 href="/atom-log/tip/c" title="Atom feed for test:c" />
694 <link rel="alternate" type="application/rss+xml"
695 href="/rss-log/tip/c" title="RSS feed for test:c" />
696 </head>
697 <body>
698
699 <div class="container">
700 <div class="menu">
701 <div class="logo">
702 <a href="https://mercurial-scm.org/">
703 <img src="/static/hglogo.png" alt="mercurial" /></a>
704 </div>
705 <ul>
706 <li><a href="/shortlog/tip">log</a></li>
707 <li><a href="/graph/tip">graph</a></li>
708 <li><a href="/tags">tags</a></li>
709 <li><a href="/bookmarks">bookmarks</a></li>
710 <li><a href="/branches">branches</a></li>
711 </ul>
712 <ul>
713 <li><a href="/rev/tip">changeset</a></li>
714 <li><a href="/file/tip">browse</a></li>
715 </ul>
716 <ul>
717 <li><a href="/file/tip/c">file</a></li>
718 <li><a href="/diff/tip/c">diff</a></li>
719 <li><a href="/comparison/tip/c">comparison</a></li>
720 <li><a href="/annotate/tip/c">annotate</a></li>
721 <li class="active">file log</li>
722 <li><a href="/raw-file/tip/c">raw</a></li>
723 </ul>
724 <ul>
725 <li><a href="/help">help</a></li>
726 </ul>
727 <div class="atom-logo">
728 <a href="/atom-log/tip/c" title="subscribe to atom feed">
729 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
730 </a>
731 </div>
732 </div>
733
734 <div class="main">
735 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
736 <h3>
737 log c @ 7:<a href="/rev/46c1a66bd8fc">46c1a66bd8fc</a>
738 <span class="branchname">a-branch</span> <span class="tag">tip</span>
739 (following lines 1:2 <a href="/log/tip/c">back to filelog</a>)
740 </h3>
741
742 <form class="search" action="/log">
743
744 <p><input name="rev" id="search1" type="text" size="30" /></p>
745 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
746 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
747 </form>
748
749 <div class="navigate">
750 <a href="/log/tip/c?linerange=1%3A2&revcount=30">less</a>
751 <a href="/log/tip/c?linerange=1%3A2&revcount=120">more</a>
752 | </div>
753
754 <table class="bigtable">
755 <thead>
756 <tr>
757 <th class="age">age</th>
758 <th class="author">author</th>
759 <th class="description">description</th>
760 </tr>
761 </thead>
762 <tbody class="stripes2">
763 <tr>
764 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
765 <td class="author">test</td>
766 <td class="description">
767 <a href="/rev/46c1a66bd8fc">change c</a>
768 <span class="branchhead">a-branch</span> <span class="tag">tip</span>
769 </td>
770 </tr>
771
772 <tr>
773 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
774 <td class="author">test</td>
775 <td class="description">
776 <a href="/rev/6563da9dcf87">b</a>
777
778 </td>
779 </tr>
780
781
782 </tbody>
783 </table>
784
785 <div class="navigate">
786 <a href="/log/tip/c?linerange=1%3A2&revcount=30">less</a>
787 <a href="/log/tip/c?linerange=1%3A2&revcount=120">more</a>
788 |
789 </div>
790
791 </div>
792 </div>
793
794
795
796 </body>
797 </html>
798
799 $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?linerange=1%3A2&revcount=1')
800 200 Script output follows
801
802 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
803 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
804 <head>
805 <link rel="icon" href="/static/hgicon.png" type="image/png" />
806 <meta name="robots" content="index, nofollow" />
807 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
808 <script type="text/javascript" src="/static/mercurial.js"></script>
809
810 <title>test: c history</title>
811 <link rel="alternate" type="application/atom+xml"
812 href="/atom-log/tip/c" title="Atom feed for test:c" />
813 <link rel="alternate" type="application/rss+xml"
814 href="/rss-log/tip/c" title="RSS feed for test:c" />
815 </head>
816 <body>
817
818 <div class="container">
819 <div class="menu">
820 <div class="logo">
821 <a href="https://mercurial-scm.org/">
822 <img src="/static/hglogo.png" alt="mercurial" /></a>
823 </div>
824 <ul>
825 <li><a href="/shortlog/tip?revcount=1">log</a></li>
826 <li><a href="/graph/tip?revcount=1">graph</a></li>
827 <li><a href="/tags?revcount=1">tags</a></li>
828 <li><a href="/bookmarks?revcount=1">bookmarks</a></li>
829 <li><a href="/branches?revcount=1">branches</a></li>
830 </ul>
831 <ul>
832 <li><a href="/rev/tip?revcount=1">changeset</a></li>
833 <li><a href="/file/tip?revcount=1">browse</a></li>
834 </ul>
835 <ul>
836 <li><a href="/file/tip/c?revcount=1">file</a></li>
837 <li><a href="/diff/tip/c?revcount=1">diff</a></li>
838 <li><a href="/comparison/tip/c?revcount=1">comparison</a></li>
839 <li><a href="/annotate/tip/c?revcount=1">annotate</a></li>
840 <li class="active">file log</li>
841 <li><a href="/raw-file/tip/c">raw</a></li>
842 </ul>
843 <ul>
844 <li><a href="/help?revcount=1">help</a></li>
845 </ul>
846 <div class="atom-logo">
847 <a href="/atom-log/tip/c" title="subscribe to atom feed">
848 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
849 </a>
850 </div>
851 </div>
852
853 <div class="main">
854 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
855 <h3>
856 log c @ 7:<a href="/rev/46c1a66bd8fc?revcount=1">46c1a66bd8fc</a>
857 <span class="branchname">a-branch</span> <span class="tag">tip</span>
858 (following lines 1:2 <a href="/log/tip/c?revcount=1">back to filelog</a>)
859 </h3>
860
861 <form class="search" action="/log">
862 <input type="hidden" name="revcount" value="1" />
863 <p><input name="rev" id="search1" type="text" size="30" /></p>
864 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
865 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
866 </form>
867
868 <div class="navigate">
869 <a href="/log/tip/c?linerange=1%3A2&revcount=1">less</a>
870 <a href="/log/tip/c?linerange=1%3A2&revcount=2">more</a>
871 | </div>
872
873 <table class="bigtable">
874 <thead>
875 <tr>
876 <th class="age">age</th>
877 <th class="author">author</th>
878 <th class="description">description</th>
879 </tr>
880 </thead>
881 <tbody class="stripes2">
882 <tr>
883 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
884 <td class="author">test</td>
885 <td class="description">
886 <a href="/rev/46c1a66bd8fc?revcount=1">change c</a>
887 <span class="branchhead">a-branch</span> <span class="tag">tip</span>
888 </td>
889 </tr>
890
891
892 </tbody>
893 </table>
894
895 <div class="navigate">
896 <a href="/log/tip/c?linerange=1%3A2&revcount=1">less</a>
897 <a href="/log/tip/c?linerange=1%3A2&revcount=2">more</a>
898 |
899 </div>
900
901 </div>
902 </div>
903
904
905
906 </body>
907 </html>
908
909 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1' --headeronly)
910 400 invalid linerange parameter
911 [1]
912 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1:a' --headeronly)
913 400 invalid linerange parameter
914 [1]
915 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1:2&linerange=3:4' --headeronly)
916 400 redundant linerange parameter
917 [1]
918 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=3:2' --headeronly)
919 400 line range must be positive
920 [1]
921 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=0:1' --headeronly)
922 400 fromline must be strictly positive
923 [1]
924
663 925 should show base link, use spartan because it shows it
664 926
665 927 $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=spartan')
666 928 200 Script output follows
667 929
668 930 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
669 931 <html>
670 932 <head>
671 933 <link rel="icon" href="/static/hgicon.png" type="image/png">
672 934 <meta name="robots" content="index, nofollow" />
673 935 <link rel="stylesheet" href="/static/style.css" type="text/css" />
674 936 <script type="text/javascript" src="/static/mercurial.js"></script>
675 937
676 938 <title>test: c history</title>
677 939 <link rel="alternate" type="application/atom+xml"
678 940 href="/atom-log/tip/c" title="Atom feed for test:c">
679 941 <link rel="alternate" type="application/rss+xml"
680 942 href="/rss-log/tip/c" title="RSS feed for test:c">
681 943 </head>
682 944 <body>
683 945
684 946 <div class="buttons">
685 947 <a href="/log?style=spartan">changelog</a>
686 948 <a href="/shortlog?style=spartan">shortlog</a>
687 949 <a href="/graph?style=spartan">graph</a>
688 950 <a href="/tags?style=spartan">tags</a>
689 951 <a href="/branches?style=spartan">branches</a>
690 952 <a href="/file/tip/c?style=spartan">file</a>
691 953 <a href="/annotate/tip/c?style=spartan">annotate</a>
692 954 <a href="/help?style=spartan">help</a>
693 955 <a type="application/rss+xml" href="/rss-log/tip/c">rss</a>
694 956 <a type="application/atom+xml" href="/atom-log/tip/c" title="Atom feed for test:c">atom</a>
695 957 </div>
696 958
697 959 <h2><a href="/">Mercurial</a> / c revision history</h2>
698 960
699 961 <p>navigate: <small class="navigate"><a href="/log/c9637d3cc8ef/c?style=spartan">(0)</a> <a href="/log/tip/c?style=spartan">tip</a> </small></p>
700 962
701 963 <table class="logEntry parity0">
702 964 <tr>
703 965 <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
704 966 <th class="firstline"><a href="/rev/46c1a66bd8fc?style=spartan">change c</a></th>
705 967 </tr>
706 968 <tr>
707 969 <th class="revision">revision 1:</th>
708 970 <td class="node">
709 971 <a href="/file/46c1a66bd8fc/c?style=spartan">46c1a66bd8fc</a>
710 972 <a href="/diff/46c1a66bd8fc/c?style=spartan">(diff)</a>
711 973 <a href="/annotate/46c1a66bd8fc/c?style=spartan">(annotate)</a>
712 974 </td>
713 975 </tr>
714 976
715 977 <tr>
716 978 <th class="author">author:</th>
717 979 <td class="author">&#116;&#101;&#115;&#116;</td>
718 980 </tr>
719 981 <tr>
720 982 <th class="date">date:</th>
721 983 <td class="date">Thu, 01 Jan 1970 00:00:00 +0000</td>
722 984 </tr>
723 985 </table>
724 986
725 987
726 988 <table class="logEntry parity1">
727 989 <tr>
728 990 <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
729 991 <th class="firstline"><a href="/rev/c9637d3cc8ef?style=spartan">mv b</a></th>
730 992 </tr>
731 993 <tr>
732 994 <th class="revision">revision 0:</th>
733 995 <td class="node">
734 996 <a href="/file/c9637d3cc8ef/c?style=spartan">c9637d3cc8ef</a>
735 997 <a href="/diff/c9637d3cc8ef/c?style=spartan">(diff)</a>
736 998 <a href="/annotate/c9637d3cc8ef/c?style=spartan">(annotate)</a>
737 999 </td>
738 1000 </tr>
739 1001
740 1002 <tr>
741 1003 <th>base:</th>
742 1004 <td>
743 1005 <a href="/file/1e88685f5dde/b?style=spartan">
744 1006 b@1e88685f5dde
745 1007 </a>
746 1008 </td>
747 1009 </tr>
748 1010 <tr>
749 1011 <th class="author">author:</th>
750 1012 <td class="author">&#116;&#101;&#115;&#116;</td>
751 1013 </tr>
752 1014 <tr>
753 1015 <th class="date">date:</th>
754 1016 <td class="date">Thu, 01 Jan 1970 00:00:00 +0000</td>
755 1017 </tr>
756 1018 </table>
757 1019
758 1020
759 1021
760 1022
761 1023
762 1024 <div class="logo">
763 1025 <a href="https://mercurial-scm.org/">
764 1026 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
765 1027 </div>
766 1028
767 1029 </body>
768 1030 </html>
769 1031
770 1032
771 1033 filelog with patch
772 1034
773 1035 $ (get-with-headers.py localhost:$HGPORT 'log/4/a?patch=1')
774 1036 200 Script output follows
775 1037
776 1038 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
777 1039 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
778 1040 <head>
779 1041 <link rel="icon" href="/static/hgicon.png" type="image/png" />
780 1042 <meta name="robots" content="index, nofollow" />
781 1043 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
782 1044 <script type="text/javascript" src="/static/mercurial.js"></script>
783 1045
784 1046 <title>test: a history</title>
785 1047 <link rel="alternate" type="application/atom+xml"
786 1048 href="/atom-log/tip/a" title="Atom feed for test:a" />
787 1049 <link rel="alternate" type="application/rss+xml"
788 1050 href="/rss-log/tip/a" title="RSS feed for test:a" />
789 1051 </head>
790 1052 <body>
791 1053
792 1054 <div class="container">
793 1055 <div class="menu">
794 1056 <div class="logo">
795 1057 <a href="https://mercurial-scm.org/">
796 1058 <img src="/static/hglogo.png" alt="mercurial" /></a>
797 1059 </div>
798 1060 <ul>
799 1061 <li><a href="/shortlog/4">log</a></li>
800 1062 <li><a href="/graph/4">graph</a></li>
801 1063 <li><a href="/tags">tags</a></li>
802 1064 <li><a href="/bookmarks">bookmarks</a></li>
803 1065 <li><a href="/branches">branches</a></li>
804 1066 </ul>
805 1067 <ul>
806 1068 <li><a href="/rev/4">changeset</a></li>
807 1069 <li><a href="/file/4">browse</a></li>
808 1070 </ul>
809 1071 <ul>
810 1072 <li><a href="/file/4/a">file</a></li>
811 1073 <li><a href="/diff/4/a">diff</a></li>
812 1074 <li><a href="/comparison/4/a">comparison</a></li>
813 1075 <li><a href="/annotate/4/a">annotate</a></li>
814 1076 <li class="active">file log</li>
815 1077 <li><a href="/raw-file/4/a">raw</a></li>
816 1078 </ul>
817 1079 <ul>
818 1080 <li><a href="/help">help</a></li>
819 1081 </ul>
820 1082 <div class="atom-logo">
821 1083 <a href="/atom-log/tip/a" title="subscribe to atom feed">
822 1084 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
823 1085 </a>
824 1086 </div>
825 1087 </div>
826 1088
827 1089 <div class="main">
828 1090 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
829 1091 <h3>
830 1092 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
831 1093 <span class="branchname">a-branch</span>
1094
832 1095 </h3>
833 1096
834 1097 <form class="search" action="/log">
835 1098
836 1099 <p><input name="rev" id="search1" type="text" size="30" /></p>
837 1100 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
838 1101 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
839 1102 </form>
840 1103
841 1104 <div class="navigate">
842 1105 <a href="/log/4/a?patch=1&revcount=30">less</a>
843 1106 <a href="/log/4/a?patch=1&revcount=120">more</a>
844 1107 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
845 1108
846 1109 <table class="bigtable">
847 1110 <thead>
848 1111 <tr>
849 1112 <th class="age">age</th>
850 1113 <th class="author">author</th>
851 1114 <th class="description">description</th>
852 1115 </tr>
853 1116 </thead>
854 1117 <tbody class="stripes2">
855 1118 <tr>
856 1119 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
857 1120 <td class="author">test</td>
858 1121 <td class="description">
859 1122 <a href="/rev/3f41bc784e7e">second a</a>
860 1123 <span class="branchname">a-branch</span>
861 1124 </td>
862 1125 </tr>
863 1126 <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
864 1127 <span id="l1.1" class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.1"></a>
865 1128 <span id="l1.2" class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.2"></a>
866 1129 <span id="l1.3" class="atline">@@ -0,0 +1,1 @@</span><a href="#l1.3"></a>
867 1130 <span id="l1.4" class="plusline">+b</span><a href="#l1.4"></a></pre></div></td></tr>
868 1131 <tr>
869 1132 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
870 1133 <td class="author">test</td>
871 1134 <td class="description">
872 1135 <a href="/rev/5ed941583260">first a</a>
873 1136 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
874 1137 </td>
875 1138 </tr>
876 1139 <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
877 1140 <span id="l1.1" class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.1"></a>
878 1141 <span id="l1.2" class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.2"></a>
879 1142 <span id="l1.3" class="atline">@@ -0,0 +1,1 @@</span><a href="#l1.3"></a>
880 1143 <span id="l1.4" class="plusline">+a</span><a href="#l1.4"></a></pre></div></td></tr>
881 1144
882 1145 </tbody>
883 1146 </table>
884 1147
885 1148 <div class="navigate">
886 1149 <a href="/log/4/a?patch=1&revcount=30">less</a>
887 1150 <a href="/log/4/a?patch=1&revcount=120">more</a>
888 1151 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
889 1152 </div>
890 1153
891 1154 </div>
892 1155 </div>
893 1156
894 1157
895 1158
896 1159 </body>
897 1160 </html>
898 1161
899 1162
900 1163 rss log
901 1164
902 1165 $ (get-with-headers.py localhost:$HGPORT 'rss-log/tip/a')
903 1166 200 Script output follows
904 1167
905 1168 <?xml version="1.0" encoding="ascii"?>
906 1169 <rss version="2.0">
907 1170 <channel>
908 1171 <link>http://*:$HGPORT/</link> (glob)
909 1172 <language>en-us</language>
910 1173
911 1174 <title>test: a history</title>
912 1175 <description>a revision history</description>
913 1176 <item>
914 1177 <title>second a</title>
915 1178 <link>http://*:$HGPORT/log/3f41bc784e7e/a</link> (glob)
916 1179 <description><![CDATA[second a]]></description>
917 1180 <author>&#116;&#101;&#115;&#116;</author>
918 1181 <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
919 1182 </item>
920 1183 <item>
921 1184 <title>first a</title>
922 1185 <link>http://*:$HGPORT/log/5ed941583260/a</link> (glob)
923 1186 <description><![CDATA[first a]]></description>
924 1187 <author>&#116;&#101;&#115;&#116;</author>
925 1188 <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
926 1189 </item>
927 1190
928 1191 </channel>
929 1192 </rss>
930 1193
931 1194 atom log
932 1195
933 1196 $ (get-with-headers.py localhost:$HGPORT 'atom-log/tip/a')
934 1197 200 Script output follows
935 1198
936 1199 <?xml version="1.0" encoding="ascii"?>
937 1200 <feed xmlns="http://www.w3.org/2005/Atom">
938 1201 <id>http://*:$HGPORT/atom-log/tip/a</id> (glob)
939 1202 <link rel="self" href="http://*:$HGPORT/atom-log/tip/a"/> (glob)
940 1203 <title>test: a history</title>
941 1204 <updated>1970-01-01T00:00:00+00:00</updated>
942 1205
943 1206 <entry>
944 1207 <title>[a-branch] second a</title>
945 1208 <id>http://*:$HGPORT/#changeset-3f41bc784e7e73035c6d47112c6cc7efb673adf8</id> (glob)
946 1209 <link href="http://*:$HGPORT/rev/3f41bc784e7e"/> (glob)
947 1210 <author>
948 1211 <name>test</name>
949 1212 <email>&#116;&#101;&#115;&#116;</email>
950 1213 </author>
951 1214 <updated>1970-01-01T00:00:00+00:00</updated>
952 1215 <published>1970-01-01T00:00:00+00:00</published>
953 1216 <content type="xhtml">
954 1217 <table xmlns="http://www.w3.org/1999/xhtml">
955 1218 <tr>
956 1219 <th style="text-align:left;">changeset</th>
957 1220 <td>3f41bc784e7e</td>
958 1221 </tr>
959 1222 <tr>
960 1223 <th style="text-align:left;">branch</th>
961 1224 <td>a-branch</td>
962 1225 </tr>
963 1226 <tr>
964 1227 <th style="text-align:left;">bookmark</th>
965 1228 <td></td>
966 1229 </tr>
967 1230 <tr>
968 1231 <th style="text-align:left;">tag</th>
969 1232 <td></td>
970 1233 </tr>
971 1234 <tr>
972 1235 <th style="text-align:left;">user</th>
973 1236 <td>&#116;&#101;&#115;&#116;</td>
974 1237 </tr>
975 1238 <tr>
976 1239 <th style="text-align:left;vertical-align:top;">description</th>
977 1240 <td>second a</td>
978 1241 </tr>
979 1242 <tr>
980 1243 <th style="text-align:left;vertical-align:top;">files</th>
981 1244 <td></td>
982 1245 </tr>
983 1246 </table>
984 1247 </content>
985 1248 </entry>
986 1249 <entry>
987 1250 <title>first a</title>
988 1251 <id>http://*:$HGPORT/#changeset-5ed941583260248620985524192fdc382ef57c36</id> (glob)
989 1252 <link href="http://*:$HGPORT/rev/5ed941583260"/> (glob)
990 1253 <author>
991 1254 <name>test</name>
992 1255 <email>&#116;&#101;&#115;&#116;</email>
993 1256 </author>
994 1257 <updated>1970-01-01T00:00:00+00:00</updated>
995 1258 <published>1970-01-01T00:00:00+00:00</published>
996 1259 <content type="xhtml">
997 1260 <table xmlns="http://www.w3.org/1999/xhtml">
998 1261 <tr>
999 1262 <th style="text-align:left;">changeset</th>
1000 1263 <td>5ed941583260</td>
1001 1264 </tr>
1002 1265 <tr>
1003 1266 <th style="text-align:left;">branch</th>
1004 1267 <td></td>
1005 1268 </tr>
1006 1269 <tr>
1007 1270 <th style="text-align:left;">bookmark</th>
1008 1271 <td>a-bookmark</td>
1009 1272 </tr>
1010 1273 <tr>
1011 1274 <th style="text-align:left;">tag</th>
1012 1275 <td>a-tag</td>
1013 1276 </tr>
1014 1277 <tr>
1015 1278 <th style="text-align:left;">user</th>
1016 1279 <td>&#116;&#101;&#115;&#116;</td>
1017 1280 </tr>
1018 1281 <tr>
1019 1282 <th style="text-align:left;vertical-align:top;">description</th>
1020 1283 <td>first a</td>
1021 1284 </tr>
1022 1285 <tr>
1023 1286 <th style="text-align:left;vertical-align:top;">files</th>
1024 1287 <td></td>
1025 1288 </tr>
1026 1289 </table>
1027 1290 </content>
1028 1291 </entry>
1029 1292
1030 1293 </feed>
1031 1294
1032 1295 errors
1033 1296
1034 1297 $ cat errors.log
1035 1298
1036 1299 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now