##// END OF EJS Templates
Make hgweb annotate link to target line numbers (issue623)
Thomas Arendsen Hein -
r6125:74406f50 default
parent child Browse files
Show More
@@ -1,925 +1,927
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
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
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes, re
10 10 from mercurial.node import *
11 11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 12 from mercurial import revlog, templater, templatefilters
13 13 from common import get_mtime, style_map, paritygen, countgen, get_contact
14 14 from common import ErrorResponse
15 15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 16 from request import wsgirequest
17 17 import webcommands, protocol
18 18
19 19 shortcuts = {
20 20 'cl': [('cmd', ['changelog']), ('rev', None)],
21 21 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 22 'cs': [('cmd', ['changeset']), ('node', None)],
23 23 'f': [('cmd', ['file']), ('filenode', None)],
24 24 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 25 'fd': [('cmd', ['filediff']), ('node', None)],
26 26 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 27 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 28 'ca': [('cmd', ['archive']), ('node', None)],
29 29 'tags': [('cmd', ['tags'])],
30 30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 31 'static': [('cmd', ['static']), ('file', None)]
32 32 }
33 33
34 34 def _up(p):
35 35 if p[0] != "/":
36 36 p = "/" + p
37 37 if p[-1] == "/":
38 38 p = p[:-1]
39 39 up = os.path.dirname(p)
40 40 if up == "/":
41 41 return "/"
42 42 return up + "/"
43 43
44 44 def revnavgen(pos, pagelen, limit, nodefunc):
45 45 def seq(factor, limit=None):
46 46 if limit:
47 47 yield limit
48 48 if limit >= 20 and limit <= 40:
49 49 yield 50
50 50 else:
51 51 yield 1 * factor
52 52 yield 3 * factor
53 53 for f in seq(factor * 10):
54 54 yield f
55 55
56 56 def nav(**map):
57 57 l = []
58 58 last = 0
59 59 for f in seq(1, pagelen):
60 60 if f < pagelen or f <= last:
61 61 continue
62 62 if f > limit:
63 63 break
64 64 last = f
65 65 if pos + f < limit:
66 66 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
67 67 if pos - f >= 0:
68 68 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
69 69
70 70 try:
71 71 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
72 72
73 73 for label, node in l:
74 74 yield {"label": label, "node": node}
75 75
76 76 yield {"label": "tip", "node": "tip"}
77 77 except hg.RepoError:
78 78 pass
79 79
80 80 return nav
81 81
82 82 class hgweb(object):
83 83 def __init__(self, repo, name=None, parentui=None):
84 84 if isinstance(repo, str):
85 85 parentui = (parentui or
86 86 ui.ui(report_untrusted=False, interactive=False))
87 87 self.repo = hg.repository(parentui, repo)
88 88 else:
89 89 self.repo = repo
90 90
91 91 hook.redirect(True)
92 92 self.mtime = -1
93 93 self.reponame = name
94 94 self.archives = 'zip', 'gz', 'bz2'
95 95 self.stripecount = 1
96 96 # a repo owner may set web.templates in .hg/hgrc to get any file
97 97 # readable by the user running the CGI script
98 98 self.templatepath = self.config("web", "templates",
99 99 templater.templatepath(),
100 100 untrusted=False)
101 101
102 102 # The CGI scripts are often run by a user different from the repo owner.
103 103 # Trust the settings from the .hg/hgrc files by default.
104 104 def config(self, section, name, default=None, untrusted=True):
105 105 return self.repo.ui.config(section, name, default,
106 106 untrusted=untrusted)
107 107
108 108 def configbool(self, section, name, default=False, untrusted=True):
109 109 return self.repo.ui.configbool(section, name, default,
110 110 untrusted=untrusted)
111 111
112 112 def configlist(self, section, name, default=None, untrusted=True):
113 113 return self.repo.ui.configlist(section, name, default,
114 114 untrusted=untrusted)
115 115
116 116 def refresh(self):
117 117 mtime = get_mtime(self.repo.root)
118 118 if mtime != self.mtime:
119 119 self.mtime = mtime
120 120 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 121 self.maxchanges = int(self.config("web", "maxchanges", 10))
122 122 self.stripecount = int(self.config("web", "stripes", 1))
123 123 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
124 124 self.maxfiles = int(self.config("web", "maxfiles", 10))
125 125 self.allowpull = self.configbool("web", "allowpull", True)
126 126 self.encoding = self.config("web", "encoding", util._encoding)
127 127
128 128 def run(self):
129 129 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
130 130 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
131 131 import mercurial.hgweb.wsgicgi as wsgicgi
132 132 wsgicgi.launch(self)
133 133
134 134 def __call__(self, env, respond):
135 135 req = wsgirequest(env, respond)
136 136 self.run_wsgi(req)
137 137 return req
138 138
139 139 def run_wsgi(self, req):
140 140
141 141 self.refresh()
142 142
143 143 # expand form shortcuts
144 144
145 145 for k in shortcuts.iterkeys():
146 146 if k in req.form:
147 147 for name, value in shortcuts[k]:
148 148 if value is None:
149 149 value = req.form[k]
150 150 req.form[name] = value
151 151 del req.form[k]
152 152
153 153 # work with CGI variables to create coherent structure
154 154 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
155 155
156 156 req.url = req.env['SCRIPT_NAME']
157 157 if not req.url.endswith('/'):
158 158 req.url += '/'
159 159 if 'REPO_NAME' in req.env:
160 160 req.url += req.env['REPO_NAME'] + '/'
161 161
162 162 if req.env.get('PATH_INFO'):
163 163 parts = req.env.get('PATH_INFO').strip('/').split('/')
164 164 repo_parts = req.env.get('REPO_NAME', '').split('/')
165 165 if parts[:len(repo_parts)] == repo_parts:
166 166 parts = parts[len(repo_parts):]
167 167 query = '/'.join(parts)
168 168 else:
169 169 query = req.env['QUERY_STRING'].split('&', 1)[0]
170 170 query = query.split(';', 1)[0]
171 171
172 172 # translate user-visible url structure to internal structure
173 173
174 174 args = query.split('/', 2)
175 175 if 'cmd' not in req.form and args and args[0]:
176 176
177 177 cmd = args.pop(0)
178 178 style = cmd.rfind('-')
179 179 if style != -1:
180 180 req.form['style'] = [cmd[:style]]
181 181 cmd = cmd[style+1:]
182 182
183 183 # avoid accepting e.g. style parameter as command
184 184 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
185 185 req.form['cmd'] = [cmd]
186 186
187 187 if args and args[0]:
188 188 node = args.pop(0)
189 189 req.form['node'] = [node]
190 190 if args:
191 191 req.form['file'] = args
192 192
193 193 if cmd == 'static':
194 194 req.form['file'] = req.form['node']
195 195 elif cmd == 'archive':
196 196 fn = req.form['node'][0]
197 197 for type_, spec in self.archive_specs.iteritems():
198 198 ext = spec[2]
199 199 if fn.endswith(ext):
200 200 req.form['node'] = [fn[:-len(ext)]]
201 201 req.form['type'] = [type_]
202 202
203 203 # actually process the request
204 204
205 205 try:
206 206
207 207 cmd = req.form.get('cmd', [''])[0]
208 208 if cmd in protocol.__all__:
209 209 method = getattr(protocol, cmd)
210 210 method(self, req)
211 211 else:
212 212 tmpl = self.templater(req)
213 213 ctype = tmpl('mimetype', encoding=self.encoding)
214 214 ctype = templater.stringify(ctype)
215 215
216 216 if cmd == '':
217 217 req.form['cmd'] = [tmpl.cache['default']]
218 218 cmd = req.form['cmd'][0]
219 219
220 220 if cmd not in webcommands.__all__:
221 221 msg = 'No such method: %s' % cmd
222 222 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
223 223 elif cmd == 'file' and 'raw' in req.form.get('style', []):
224 224 self.ctype = ctype
225 225 content = webcommands.rawfile(self, req, tmpl)
226 226 else:
227 227 content = getattr(webcommands, cmd)(self, req, tmpl)
228 228 req.respond(HTTP_OK, ctype)
229 229
230 230 req.write(content)
231 231 del tmpl
232 232
233 233 except revlog.LookupError, err:
234 234 req.respond(HTTP_NOT_FOUND, ctype)
235 235 req.write(tmpl('error', error='revision not found: %s' % err.name))
236 236 except (hg.RepoError, revlog.RevlogError), inst:
237 237 req.respond(HTTP_SERVER_ERROR, ctype)
238 238 req.write(tmpl('error', error=str(inst)))
239 239 except ErrorResponse, inst:
240 240 req.respond(inst.code, ctype)
241 241 req.write(tmpl('error', error=inst.message))
242 242
243 243 def templater(self, req):
244 244
245 245 # determine scheme, port and server name
246 246 # this is needed to create absolute urls
247 247
248 248 proto = req.env.get('wsgi.url_scheme')
249 249 if proto == 'https':
250 250 proto = 'https'
251 251 default_port = "443"
252 252 else:
253 253 proto = 'http'
254 254 default_port = "80"
255 255
256 256 port = req.env["SERVER_PORT"]
257 257 port = port != default_port and (":" + port) or ""
258 258 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
259 259 staticurl = self.config("web", "staticurl") or req.url + 'static/'
260 260 if not staticurl.endswith('/'):
261 261 staticurl += '/'
262 262
263 263 # some functions for the templater
264 264
265 265 def header(**map):
266 266 yield tmpl('header', encoding=self.encoding, **map)
267 267
268 268 def footer(**map):
269 269 yield tmpl("footer", **map)
270 270
271 271 def motd(**map):
272 272 yield self.config("web", "motd", "")
273 273
274 274 def sessionvars(**map):
275 275 fields = []
276 276 if 'style' in req.form:
277 277 style = req.form['style'][0]
278 278 if style != self.config('web', 'style', ''):
279 279 fields.append(('style', style))
280 280
281 281 separator = req.url[-1] == '?' and ';' or '?'
282 282 for name, value in fields:
283 283 yield dict(name=name, value=value, separator=separator)
284 284 separator = ';'
285 285
286 286 # figure out which style to use
287 287
288 288 style = self.config("web", "style", "")
289 289 if 'style' in req.form:
290 290 style = req.form['style'][0]
291 291 mapfile = style_map(self.templatepath, style)
292 292
293 293 if not self.reponame:
294 294 self.reponame = (self.config("web", "name")
295 295 or req.env.get('REPO_NAME')
296 296 or req.url.strip('/') or self.repo.root)
297 297
298 298 # create the templater
299 299
300 300 tmpl = templater.templater(mapfile, templatefilters.filters,
301 301 defaults={"url": req.url,
302 302 "staticurl": staticurl,
303 303 "urlbase": urlbase,
304 304 "repo": self.reponame,
305 305 "header": header,
306 306 "footer": footer,
307 307 "motd": motd,
308 308 "sessionvars": sessionvars
309 309 })
310 310 return tmpl
311 311
312 312 def archivelist(self, nodeid):
313 313 allowed = self.configlist("web", "allow_archive")
314 314 for i, spec in self.archive_specs.iteritems():
315 315 if i in allowed or self.configbool("web", "allow" + i):
316 316 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
317 317
318 318 def listfilediffs(self, tmpl, files, changeset):
319 319 for f in files[:self.maxfiles]:
320 320 yield tmpl("filedifflink", node=hex(changeset), file=f)
321 321 if len(files) > self.maxfiles:
322 322 yield tmpl("fileellipses")
323 323
324 324 def siblings(self, siblings=[], hiderev=None, **args):
325 325 siblings = [s for s in siblings if s.node() != nullid]
326 326 if len(siblings) == 1 and siblings[0].rev() == hiderev:
327 327 return
328 328 for s in siblings:
329 329 d = {'node': hex(s.node()), 'rev': s.rev()}
330 330 if hasattr(s, 'path'):
331 331 d['file'] = s.path()
332 332 d.update(args)
333 333 yield d
334 334
335 335 def renamelink(self, fl, node):
336 336 r = fl.renamed(node)
337 337 if r:
338 338 return [dict(file=r[0], node=hex(r[1]))]
339 339 return []
340 340
341 341 def nodetagsdict(self, node):
342 342 return [{"name": i} for i in self.repo.nodetags(node)]
343 343
344 344 def nodebranchdict(self, ctx):
345 345 branches = []
346 346 branch = ctx.branch()
347 347 # If this is an empty repo, ctx.node() == nullid,
348 348 # ctx.branch() == 'default', but branchtags() is
349 349 # an empty dict. Using dict.get avoids a traceback.
350 350 if self.repo.branchtags().get(branch) == ctx.node():
351 351 branches.append({"name": branch})
352 352 return branches
353 353
354 354 def showtag(self, tmpl, t1, node=nullid, **args):
355 355 for t in self.repo.nodetags(node):
356 356 yield tmpl(t1, tag=t, **args)
357 357
358 358 def diff(self, tmpl, node1, node2, files):
359 359 def filterfiles(filters, files):
360 360 l = [x for x in files if x in filters]
361 361
362 362 for t in filters:
363 363 if t and t[-1] != os.sep:
364 364 t += os.sep
365 365 l += [x for x in files if x.startswith(t)]
366 366 return l
367 367
368 368 parity = paritygen(self.stripecount)
369 369 def diffblock(diff, f, fn):
370 370 yield tmpl("diffblock",
371 371 lines=prettyprintlines(diff),
372 372 parity=parity.next(),
373 373 file=f,
374 374 filenode=hex(fn or nullid))
375 375
376 376 blockcount = countgen()
377 377 def prettyprintlines(diff):
378 378 blockno = blockcount.next()
379 379 for lineno, l in enumerate(diff.splitlines(1)):
380 380 if blockno == 0:
381 381 lineno = lineno + 1
382 382 else:
383 383 lineno = "%d.%d" % (blockno, lineno + 1)
384 384 if l.startswith('+'):
385 385 ltype = "difflineplus"
386 386 elif l.startswith('-'):
387 387 ltype = "difflineminus"
388 388 elif l.startswith('@'):
389 389 ltype = "difflineat"
390 390 else:
391 391 ltype = "diffline"
392 392 yield tmpl(ltype,
393 393 line=l,
394 394 lineid="l%s" % lineno,
395 395 linenumber="% 8s" % lineno)
396 396
397 397 r = self.repo
398 398 c1 = r.changectx(node1)
399 399 c2 = r.changectx(node2)
400 400 date1 = util.datestr(c1.date())
401 401 date2 = util.datestr(c2.date())
402 402
403 403 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
404 404 if files:
405 405 modified, added, removed = map(lambda x: filterfiles(files, x),
406 406 (modified, added, removed))
407 407
408 408 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
409 409 for f in modified:
410 410 to = c1.filectx(f).data()
411 411 tn = c2.filectx(f).data()
412 412 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
413 413 opts=diffopts), f, tn)
414 414 for f in added:
415 415 to = None
416 416 tn = c2.filectx(f).data()
417 417 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
418 418 opts=diffopts), f, tn)
419 419 for f in removed:
420 420 to = c1.filectx(f).data()
421 421 tn = None
422 422 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
423 423 opts=diffopts), f, tn)
424 424
425 425 def changelog(self, tmpl, ctx, shortlog=False):
426 426 def changelist(limit=0,**map):
427 427 cl = self.repo.changelog
428 428 l = [] # build a list in forward order for efficiency
429 429 for i in xrange(start, end):
430 430 ctx = self.repo.changectx(i)
431 431 n = ctx.node()
432 432
433 433 l.insert(0, {"parity": parity.next(),
434 434 "author": ctx.user(),
435 435 "parent": self.siblings(ctx.parents(), i - 1),
436 436 "child": self.siblings(ctx.children(), i + 1),
437 437 "changelogtag": self.showtag("changelogtag",n),
438 438 "desc": ctx.description(),
439 439 "date": ctx.date(),
440 440 "files": self.listfilediffs(tmpl, ctx.files(), n),
441 441 "rev": i,
442 442 "node": hex(n),
443 443 "tags": self.nodetagsdict(n),
444 444 "branches": self.nodebranchdict(ctx)})
445 445
446 446 if limit > 0:
447 447 l = l[:limit]
448 448
449 449 for e in l:
450 450 yield e
451 451
452 452 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
453 453 cl = self.repo.changelog
454 454 count = cl.count()
455 455 pos = ctx.rev()
456 456 start = max(0, pos - maxchanges + 1)
457 457 end = min(count, start + maxchanges)
458 458 pos = end - 1
459 459 parity = paritygen(self.stripecount, offset=start-end)
460 460
461 461 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
462 462
463 463 return tmpl(shortlog and 'shortlog' or 'changelog',
464 464 changenav=changenav,
465 465 node=hex(cl.tip()),
466 466 rev=pos, changesets=count,
467 467 entries=lambda **x: changelist(limit=0,**x),
468 468 latestentry=lambda **x: changelist(limit=1,**x),
469 469 archives=self.archivelist("tip"))
470 470
471 471 def search(self, tmpl, query):
472 472
473 473 def changelist(**map):
474 474 cl = self.repo.changelog
475 475 count = 0
476 476 qw = query.lower().split()
477 477
478 478 def revgen():
479 479 for i in xrange(cl.count() - 1, 0, -100):
480 480 l = []
481 481 for j in xrange(max(0, i - 100), i + 1):
482 482 ctx = self.repo.changectx(j)
483 483 l.append(ctx)
484 484 l.reverse()
485 485 for e in l:
486 486 yield e
487 487
488 488 for ctx in revgen():
489 489 miss = 0
490 490 for q in qw:
491 491 if not (q in ctx.user().lower() or
492 492 q in ctx.description().lower() or
493 493 q in " ".join(ctx.files()).lower()):
494 494 miss = 1
495 495 break
496 496 if miss:
497 497 continue
498 498
499 499 count += 1
500 500 n = ctx.node()
501 501
502 502 yield tmpl('searchentry',
503 503 parity=parity.next(),
504 504 author=ctx.user(),
505 505 parent=self.siblings(ctx.parents()),
506 506 child=self.siblings(ctx.children()),
507 507 changelogtag=self.showtag("changelogtag",n),
508 508 desc=ctx.description(),
509 509 date=ctx.date(),
510 510 files=self.listfilediffs(tmpl, ctx.files(), n),
511 511 rev=ctx.rev(),
512 512 node=hex(n),
513 513 tags=self.nodetagsdict(n),
514 514 branches=self.nodebranchdict(ctx))
515 515
516 516 if count >= self.maxchanges:
517 517 break
518 518
519 519 cl = self.repo.changelog
520 520 parity = paritygen(self.stripecount)
521 521
522 522 return tmpl('search',
523 523 query=query,
524 524 node=hex(cl.tip()),
525 525 entries=changelist,
526 526 archives=self.archivelist("tip"))
527 527
528 528 def changeset(self, tmpl, ctx):
529 529 n = ctx.node()
530 530 parents = ctx.parents()
531 531 p1 = parents[0].node()
532 532
533 533 files = []
534 534 parity = paritygen(self.stripecount)
535 535 for f in ctx.files():
536 536 files.append(tmpl("filenodelink",
537 537 node=hex(n), file=f,
538 538 parity=parity.next()))
539 539
540 540 def diff(**map):
541 541 yield self.diff(tmpl, p1, n, None)
542 542
543 543 return tmpl('changeset',
544 544 diff=diff,
545 545 rev=ctx.rev(),
546 546 node=hex(n),
547 547 parent=self.siblings(parents),
548 548 child=self.siblings(ctx.children()),
549 549 changesettag=self.showtag("changesettag",n),
550 550 author=ctx.user(),
551 551 desc=ctx.description(),
552 552 date=ctx.date(),
553 553 files=files,
554 554 archives=self.archivelist(hex(n)),
555 555 tags=self.nodetagsdict(n),
556 556 branches=self.nodebranchdict(ctx))
557 557
558 558 def filelog(self, tmpl, fctx):
559 559 f = fctx.path()
560 560 fl = fctx.filelog()
561 561 count = fl.count()
562 562 pagelen = self.maxshortchanges
563 563 pos = fctx.filerev()
564 564 start = max(0, pos - pagelen + 1)
565 565 end = min(count, start + pagelen)
566 566 pos = end - 1
567 567 parity = paritygen(self.stripecount, offset=start-end)
568 568
569 569 def entries(limit=0, **map):
570 570 l = []
571 571
572 572 for i in xrange(start, end):
573 573 ctx = fctx.filectx(i)
574 574 n = fl.node(i)
575 575
576 576 l.insert(0, {"parity": parity.next(),
577 577 "filerev": i,
578 578 "file": f,
579 579 "node": hex(ctx.node()),
580 580 "author": ctx.user(),
581 581 "date": ctx.date(),
582 582 "rename": self.renamelink(fl, n),
583 583 "parent": self.siblings(fctx.parents()),
584 584 "child": self.siblings(fctx.children()),
585 585 "desc": ctx.description()})
586 586
587 587 if limit > 0:
588 588 l = l[:limit]
589 589
590 590 for e in l:
591 591 yield e
592 592
593 593 nodefunc = lambda x: fctx.filectx(fileid=x)
594 594 nav = revnavgen(pos, pagelen, count, nodefunc)
595 595 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
596 596 entries=lambda **x: entries(limit=0, **x),
597 597 latestentry=lambda **x: entries(limit=1, **x))
598 598
599 599 def filerevision(self, tmpl, fctx):
600 600 f = fctx.path()
601 601 text = fctx.data()
602 602 fl = fctx.filelog()
603 603 n = fctx.filenode()
604 604 parity = paritygen(self.stripecount)
605 605
606 606 if util.binary(text):
607 607 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
608 608 text = '(binary:%s)' % mt
609 609
610 610 def lines():
611 611 for lineno, t in enumerate(text.splitlines(1)):
612 612 yield {"line": t,
613 613 "lineid": "l%d" % (lineno + 1),
614 614 "linenumber": "% 6d" % (lineno + 1),
615 615 "parity": parity.next()}
616 616
617 617 return tmpl("filerevision",
618 618 file=f,
619 619 path=_up(f),
620 620 text=lines(),
621 621 rev=fctx.rev(),
622 622 node=hex(fctx.node()),
623 623 author=fctx.user(),
624 624 date=fctx.date(),
625 625 desc=fctx.description(),
626 626 parent=self.siblings(fctx.parents()),
627 627 child=self.siblings(fctx.children()),
628 628 rename=self.renamelink(fl, n),
629 629 permissions=fctx.manifest().flags(f))
630 630
631 631 def fileannotate(self, tmpl, fctx):
632 632 f = fctx.path()
633 633 n = fctx.filenode()
634 634 fl = fctx.filelog()
635 635 parity = paritygen(self.stripecount)
636 636
637 637 def annotate(**map):
638 638 last = None
639 for lineno, (f, l) in enumerate(fctx.annotate(follow=True)):
639 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
640 for lineno, ((f, targetline), l) in lines:
640 641 fnode = f.filenode()
641 642 name = self.repo.ui.shortuser(f.user())
642 643
643 644 if last != fnode:
644 645 last = fnode
645 646
646 647 yield {"parity": parity.next(),
647 648 "node": hex(f.node()),
648 649 "rev": f.rev(),
649 650 "author": name,
650 651 "file": f.path(),
652 "targetline": targetline,
651 653 "line": l,
652 654 "lineid": "l%d" % (lineno + 1),
653 655 "linenumber": "% 6d" % (lineno + 1)}
654 656
655 657 return tmpl("fileannotate",
656 658 file=f,
657 659 annotate=annotate,
658 660 path=_up(f),
659 661 rev=fctx.rev(),
660 662 node=hex(fctx.node()),
661 663 author=fctx.user(),
662 664 date=fctx.date(),
663 665 desc=fctx.description(),
664 666 rename=self.renamelink(fl, n),
665 667 parent=self.siblings(fctx.parents()),
666 668 child=self.siblings(fctx.children()),
667 669 permissions=fctx.manifest().flags(f))
668 670
669 671 def manifest(self, tmpl, ctx, path):
670 672 mf = ctx.manifest()
671 673 node = ctx.node()
672 674
673 675 files = {}
674 676 parity = paritygen(self.stripecount)
675 677
676 678 if path and path[-1] != "/":
677 679 path += "/"
678 680 l = len(path)
679 681 abspath = "/" + path
680 682
681 683 for f, n in mf.items():
682 684 if f[:l] != path:
683 685 continue
684 686 remain = f[l:]
685 687 if "/" in remain:
686 688 short = remain[:remain.index("/") + 1] # bleah
687 689 files[short] = (f, None)
688 690 else:
689 691 short = os.path.basename(remain)
690 692 files[short] = (f, n)
691 693
692 694 if not files:
693 695 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
694 696
695 697 def filelist(**map):
696 698 fl = files.keys()
697 699 fl.sort()
698 700 for f in fl:
699 701 full, fnode = files[f]
700 702 if not fnode:
701 703 continue
702 704
703 705 fctx = ctx.filectx(full)
704 706 yield {"file": full,
705 707 "parity": parity.next(),
706 708 "basename": f,
707 709 "date": fctx.changectx().date(),
708 710 "size": fctx.size(),
709 711 "permissions": mf.flags(full)}
710 712
711 713 def dirlist(**map):
712 714 fl = files.keys()
713 715 fl.sort()
714 716 for f in fl:
715 717 full, fnode = files[f]
716 718 if fnode:
717 719 continue
718 720
719 721 yield {"parity": parity.next(),
720 722 "path": "%s%s" % (abspath, f),
721 723 "basename": f[:-1]}
722 724
723 725 return tmpl("manifest",
724 726 rev=ctx.rev(),
725 727 node=hex(node),
726 728 path=abspath,
727 729 up=_up(abspath),
728 730 upparity=parity.next(),
729 731 fentries=filelist,
730 732 dentries=dirlist,
731 733 archives=self.archivelist(hex(node)),
732 734 tags=self.nodetagsdict(node),
733 735 branches=self.nodebranchdict(ctx))
734 736
735 737 def tags(self, tmpl):
736 738 i = self.repo.tagslist()
737 739 i.reverse()
738 740 parity = paritygen(self.stripecount)
739 741
740 742 def entries(notip=False,limit=0, **map):
741 743 count = 0
742 744 for k, n in i:
743 745 if notip and k == "tip":
744 746 continue
745 747 if limit > 0 and count >= limit:
746 748 continue
747 749 count = count + 1
748 750 yield {"parity": parity.next(),
749 751 "tag": k,
750 752 "date": self.repo.changectx(n).date(),
751 753 "node": hex(n)}
752 754
753 755 return tmpl("tags",
754 756 node=hex(self.repo.changelog.tip()),
755 757 entries=lambda **x: entries(False,0, **x),
756 758 entriesnotip=lambda **x: entries(True,0, **x),
757 759 latestentry=lambda **x: entries(True,1, **x))
758 760
759 761 def summary(self, tmpl):
760 762 i = self.repo.tagslist()
761 763 i.reverse()
762 764
763 765 def tagentries(**map):
764 766 parity = paritygen(self.stripecount)
765 767 count = 0
766 768 for k, n in i:
767 769 if k == "tip": # skip tip
768 770 continue;
769 771
770 772 count += 1
771 773 if count > 10: # limit to 10 tags
772 774 break;
773 775
774 776 yield tmpl("tagentry",
775 777 parity=parity.next(),
776 778 tag=k,
777 779 node=hex(n),
778 780 date=self.repo.changectx(n).date())
779 781
780 782
781 783 def branches(**map):
782 784 parity = paritygen(self.stripecount)
783 785
784 786 b = self.repo.branchtags()
785 787 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
786 788 l.sort()
787 789
788 790 for r,n,t in l:
789 791 ctx = self.repo.changectx(n)
790 792
791 793 yield {'parity': parity.next(),
792 794 'branch': t,
793 795 'node': hex(n),
794 796 'date': ctx.date()}
795 797
796 798 def changelist(**map):
797 799 parity = paritygen(self.stripecount, offset=start-end)
798 800 l = [] # build a list in forward order for efficiency
799 801 for i in xrange(start, end):
800 802 ctx = self.repo.changectx(i)
801 803 n = ctx.node()
802 804 hn = hex(n)
803 805
804 806 l.insert(0, tmpl(
805 807 'shortlogentry',
806 808 parity=parity.next(),
807 809 author=ctx.user(),
808 810 desc=ctx.description(),
809 811 date=ctx.date(),
810 812 rev=i,
811 813 node=hn,
812 814 tags=self.nodetagsdict(n),
813 815 branches=self.nodebranchdict(ctx)))
814 816
815 817 yield l
816 818
817 819 cl = self.repo.changelog
818 820 count = cl.count()
819 821 start = max(0, count - self.maxchanges)
820 822 end = min(count, start + self.maxchanges)
821 823
822 824 return tmpl("summary",
823 825 desc=self.config("web", "description", "unknown"),
824 826 owner=get_contact(self.config) or "unknown",
825 827 lastchange=cl.read(cl.tip())[2],
826 828 tags=tagentries,
827 829 branches=branches,
828 830 shortlog=changelist,
829 831 node=hex(cl.tip()),
830 832 archives=self.archivelist("tip"))
831 833
832 834 def filediff(self, tmpl, fctx):
833 835 n = fctx.node()
834 836 path = fctx.path()
835 837 parents = fctx.parents()
836 838 p1 = parents and parents[0].node() or nullid
837 839
838 840 def diff(**map):
839 841 yield self.diff(tmpl, p1, n, [path])
840 842
841 843 return tmpl("filediff",
842 844 file=path,
843 845 node=hex(n),
844 846 rev=fctx.rev(),
845 847 parent=self.siblings(parents),
846 848 child=self.siblings(fctx.children()),
847 849 diff=diff)
848 850
849 851 archive_specs = {
850 852 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
851 853 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
852 854 'zip': ('application/zip', 'zip', '.zip', None),
853 855 }
854 856
855 857 def archive(self, tmpl, req, key, type_):
856 858 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
857 859 cnode = self.repo.lookup(key)
858 860 arch_version = key
859 861 if cnode == key or key == 'tip':
860 862 arch_version = short(cnode)
861 863 name = "%s-%s" % (reponame, arch_version)
862 864 mimetype, artype, extension, encoding = self.archive_specs[type_]
863 865 headers = [
864 866 ('Content-Type', mimetype),
865 867 ('Content-Disposition', 'attachment; filename=%s%s' %
866 868 (name, extension))
867 869 ]
868 870 if encoding:
869 871 headers.append(('Content-Encoding', encoding))
870 872 req.header(headers)
871 873 req.respond(HTTP_OK)
872 874 archival.archive(self.repo, req, cnode, artype, prefix=name)
873 875
874 876 # add tags to things
875 877 # tags -> list of changesets corresponding to tags
876 878 # find tag, changeset, file
877 879
878 880 def cleanpath(self, path):
879 881 path = path.lstrip('/')
880 882 return util.canonpath(self.repo.root, '', path)
881 883
882 884 def changectx(self, req):
883 885 if 'node' in req.form:
884 886 changeid = req.form['node'][0]
885 887 elif 'manifest' in req.form:
886 888 changeid = req.form['manifest'][0]
887 889 else:
888 890 changeid = self.repo.changelog.count() - 1
889 891
890 892 try:
891 893 ctx = self.repo.changectx(changeid)
892 894 except hg.RepoError:
893 895 man = self.repo.manifest
894 896 mn = man.lookup(changeid)
895 897 ctx = self.repo.changectx(man.linkrev(mn))
896 898
897 899 return ctx
898 900
899 901 def filectx(self, req):
900 902 path = self.cleanpath(req.form['file'][0])
901 903 if 'node' in req.form:
902 904 changeid = req.form['node'][0]
903 905 else:
904 906 changeid = req.form['filenode'][0]
905 907 try:
906 908 ctx = self.repo.changectx(changeid)
907 909 fctx = ctx.filectx(path)
908 910 except hg.RepoError:
909 911 fctx = self.repo.filectx(path, fileid=changeid)
910 912
911 913 return fctx
912 914
913 915 def check_perm(self, req, op, default):
914 916 '''check permission for operation based on user auth.
915 917 return true if op allowed, else false.
916 918 default is policy to use if no config given.'''
917 919
918 920 user = req.env.get('REMOTE_USER')
919 921
920 922 deny = self.configlist('web', 'deny_' + op)
921 923 if deny and (not user or deny == ['*'] or user in deny):
922 924 return False
923 925
924 926 allow = self.configlist('web', 'allow_' + op)
925 927 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,59 +1,59
1 1 default = 'summary'
2 2 mimetype = 'text/html; charset={encoding}'
3 3 header = header.tmpl
4 4 footer = footer.tmpl
5 5 search = search.tmpl
6 6 changelog = changelog.tmpl
7 7 summary = summary.tmpl
8 8 error = error.tmpl
9 9 notfound = notfound.tmpl
10 10 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
11 11 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
12 12 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
13 13 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
14 14 filenodelink = '<tr class="parity#parity#"><td><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">#file|escape#</a></td><td></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> | <a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a></td></tr>'
15 15 fileellipses = '...'
16 16 changelogentry = changelogentry.tmpl
17 17 searchentry = changelogentry.tmpl
18 18 changeset = changeset.tmpl
19 19 manifest = manifest.tmpl
20 20 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a></td></tr>'
21 21 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#date|isodate#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
22 22 filerevision = filerevision.tmpl
23 23 fileannotate = fileannotate.tmpl
24 24 filediff = filediff.tmpl
25 25 filelog = filelog.tmpl
26 26 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</pre></div>'
27 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a></pre></td><td><pre>#line|escape#</pre></td></tr>'
27 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}#l{targetline}">#author|obfuscate#@#rev#</a></td><td><pre><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a></pre></td><td><pre>#line|escape#</pre></td></tr>'
28 28 difflineplus = '<span style="color:#008800;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</span>'
29 29 difflineminus = '<span style="color:#cc0000;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</span>'
30 30 difflineat = '<span style="color:#990099;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</span>'
31 31 diffline = '<span><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</span>'
32 32 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
33 33 changesetparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
34 34 filerevparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
35 35 filerename = '{file|escape}@'
36 36 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
37 37 fileannotateparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
38 38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 39 changesetchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
40 40 filerevchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
41 41 fileannotatechild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
42 42 tags = tags.tmpl
43 43 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
44 44 branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></a></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
45 45 diffblock = '<pre>#lines#</pre>'
46 46 filediffparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
47 47 filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="{url}file/{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 48 filediffchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
49 49 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
50 50 shortlog = shortlog.tmpl
51 51 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
52 52 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
53 53 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author|person#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
54 54 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
55 55 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
56 56 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks">#archives%archiveentry#</td><td><a class="rss_logo" href="#url#rss-log">RSS</a> <a class="rss_logo" href="#url#atom-log">Atom</a></td></tr>'
57 57 index = index.tmpl
58 58 urlparameter = '#separator##name#=#value|urlescape#'
59 59 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,57 +1,57
1 1 default = 'shortlog'
2 2 mimetype = 'text/html; charset={encoding}'
3 3 header = header.tmpl
4 4 footer = footer.tmpl
5 5 search = search.tmpl
6 6 changelog = changelog.tmpl
7 7 shortlog = shortlog.tmpl
8 8 shortlogentry = shortlogentry.tmpl
9 9 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 10 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
11 11 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
12 12 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 13 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
14 14 fileellipses = '...'
15 15 changelogentry = changelogentry.tmpl
16 16 searchentry = changelogentry.tmpl
17 17 changeset = changeset.tmpl
18 18 manifest = manifest.tmpl
19 19 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td>&nbsp;<td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
20 20 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td align=right><tt class="date">#date|isodate#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
21 21 filerevision = filerevision.tmpl
22 22 fileannotate = fileannotate.tmpl
23 23 filediff = filediff.tmpl
24 24 filelog = filelog.tmpl
25 25 fileline = '<div class="parity#parity#"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</div>'
26 26 filelogentry = filelogentry.tmpl
27 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a></td><td><pre>#line|escape#</pre></td></tr>'
27 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}#l{targetline}">#author|obfuscate#@#rev#</a></td><td><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a></td><td><pre>#line|escape#</pre></td></tr>'
28 28 difflineplus = '<span class="plusline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
29 29 difflineminus = '<span class="minusline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
30 30 difflineat = '<span class="atline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
31 31 diffline = '<a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#'
32 32 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
33 33 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
34 34 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
35 35 filerename = '{file|escape}@'
36 36 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
37 37 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
38 38 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 39 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 40 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
41 41 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
42 42 tags = tags.tmpl
43 43 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
44 44 diffblock = '<pre class="parity#parity#">#lines#</pre>'
45 45 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
46 46 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
47 47 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 48 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 49 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
50 50 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
51 51 indexentry = '<tr class="parity#parity#"><td><a href="#url#{sessionvars%urlparameter}">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#rss-log">RSS</a> <a href="#url#atom-log">Atom</a> #archives%archiveentry#</td></tr>'
52 52 index = index.tmpl
53 53 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
54 54 notfound = notfound.tmpl
55 55 error = error.tmpl
56 56 urlparameter = '#separator##name#=#value|urlescape#'
57 57 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
General Comments 0
You need to be logged in to leave comments. Login now