##// END OF EJS Templates
gitweb: Display branch and tag labels...
Josef "Jeff" Sipek -
r4538:4272ae76 default
parent child Browse files
Show More
@@ -1,1158 +1,1179 b''
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, 2006 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, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 15 from common import get_mtime, staticfile, style_map, paritygen
16 16
17 17 def _up(p):
18 18 if p[0] != "/":
19 19 p = "/" + p
20 20 if p[-1] == "/":
21 21 p = p[:-1]
22 22 up = os.path.dirname(p)
23 23 if up == "/":
24 24 return "/"
25 25 return up + "/"
26 26
27 27 def revnavgen(pos, pagelen, limit, nodefunc):
28 28 def seq(factor, limit=None):
29 29 if limit:
30 30 yield limit
31 31 if limit >= 20 and limit <= 40:
32 32 yield 50
33 33 else:
34 34 yield 1 * factor
35 35 yield 3 * factor
36 36 for f in seq(factor * 10):
37 37 yield f
38 38
39 39 def nav(**map):
40 40 l = []
41 41 last = 0
42 42 for f in seq(1, pagelen):
43 43 if f < pagelen or f <= last:
44 44 continue
45 45 if f > limit:
46 46 break
47 47 last = f
48 48 if pos + f < limit:
49 49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 50 if pos - f >= 0:
51 51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 52
53 53 try:
54 54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 55
56 56 for label, node in l:
57 57 yield {"label": label, "node": node}
58 58
59 59 yield {"label": "tip", "node": "tip"}
60 60 except hg.RepoError:
61 61 pass
62 62
63 63 return nav
64 64
65 65 class hgweb(object):
66 66 def __init__(self, repo, name=None):
67 67 if type(repo) == type(""):
68 68 self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
69 69 else:
70 70 self.repo = repo
71 71
72 72 self.mtime = -1
73 73 self.reponame = name
74 74 self.archives = 'zip', 'gz', 'bz2'
75 75 self.stripecount = 1
76 76 # a repo owner may set web.templates in .hg/hgrc to get any file
77 77 # readable by the user running the CGI script
78 78 self.templatepath = self.config("web", "templates",
79 79 templater.templatepath(),
80 80 untrusted=False)
81 81
82 82 # The CGI scripts are often run by a user different from the repo owner.
83 83 # Trust the settings from the .hg/hgrc files by default.
84 84 def config(self, section, name, default=None, untrusted=True):
85 85 return self.repo.ui.config(section, name, default,
86 86 untrusted=untrusted)
87 87
88 88 def configbool(self, section, name, default=False, untrusted=True):
89 89 return self.repo.ui.configbool(section, name, default,
90 90 untrusted=untrusted)
91 91
92 92 def configlist(self, section, name, default=None, untrusted=True):
93 93 return self.repo.ui.configlist(section, name, default,
94 94 untrusted=untrusted)
95 95
96 96 def refresh(self):
97 97 mtime = get_mtime(self.repo.root)
98 98 if mtime != self.mtime:
99 99 self.mtime = mtime
100 100 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 101 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 102 self.stripecount = int(self.config("web", "stripes", 1))
103 103 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 104 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 105 self.allowpull = self.configbool("web", "allowpull", True)
106 106
107 107 def archivelist(self, nodeid):
108 108 allowed = self.configlist("web", "allow_archive")
109 109 for i, spec in self.archive_specs.iteritems():
110 110 if i in allowed or self.configbool("web", "allow" + i):
111 111 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
112 112
113 113 def listfilediffs(self, files, changeset):
114 114 for f in files[:self.maxfiles]:
115 115 yield self.t("filedifflink", node=hex(changeset), file=f)
116 116 if len(files) > self.maxfiles:
117 117 yield self.t("fileellipses")
118 118
119 119 def siblings(self, siblings=[], hiderev=None, **args):
120 120 siblings = [s for s in siblings if s.node() != nullid]
121 121 if len(siblings) == 1 and siblings[0].rev() == hiderev:
122 122 return
123 123 for s in siblings:
124 124 d = {'node': hex(s.node()), 'rev': s.rev()}
125 125 if hasattr(s, 'path'):
126 126 d['file'] = s.path()
127 127 d.update(args)
128 128 yield d
129 129
130 130 def renamelink(self, fl, node):
131 131 r = fl.renamed(node)
132 132 if r:
133 133 return [dict(file=r[0], node=hex(r[1]))]
134 134 return []
135 135
136 def taglistdict(self,node):
137 return [{"name":i} for i in self.repo.nodetags(node)]
138
139 def branchlistdict(self,node):
140 l=[]
141 for t, tn in self.repo.branchtags().items():
142 if tn == node:
143 l.append({"name":t})
144 return l
145
136 146 def showtag(self, t1, node=nullid, **args):
137 147 for t in self.repo.nodetags(node):
138 148 yield self.t(t1, tag=t, **args)
139 149
140 150 def diff(self, node1, node2, files):
141 151 def filterfiles(filters, files):
142 152 l = [x for x in files if x in filters]
143 153
144 154 for t in filters:
145 155 if t and t[-1] != os.sep:
146 156 t += os.sep
147 157 l += [x for x in files if x.startswith(t)]
148 158 return l
149 159
150 160 parity = paritygen(self.stripecount)
151 161 def diffblock(diff, f, fn):
152 162 yield self.t("diffblock",
153 163 lines=prettyprintlines(diff),
154 164 parity=parity.next(),
155 165 file=f,
156 166 filenode=hex(fn or nullid))
157 167
158 168 def prettyprintlines(diff):
159 169 for l in diff.splitlines(1):
160 170 if l.startswith('+'):
161 171 yield self.t("difflineplus", line=l)
162 172 elif l.startswith('-'):
163 173 yield self.t("difflineminus", line=l)
164 174 elif l.startswith('@'):
165 175 yield self.t("difflineat", line=l)
166 176 else:
167 177 yield self.t("diffline", line=l)
168 178
169 179 r = self.repo
170 180 c1 = r.changectx(node1)
171 181 c2 = r.changectx(node2)
172 182 date1 = util.datestr(c1.date())
173 183 date2 = util.datestr(c2.date())
174 184
175 185 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
176 186 if files:
177 187 modified, added, removed = map(lambda x: filterfiles(files, x),
178 188 (modified, added, removed))
179 189
180 190 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
181 191 for f in modified:
182 192 to = c1.filectx(f).data()
183 193 tn = c2.filectx(f).data()
184 194 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
185 195 opts=diffopts), f, tn)
186 196 for f in added:
187 197 to = None
188 198 tn = c2.filectx(f).data()
189 199 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
190 200 opts=diffopts), f, tn)
191 201 for f in removed:
192 202 to = c1.filectx(f).data()
193 203 tn = None
194 204 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
195 205 opts=diffopts), f, tn)
196 206
197 207 def changelog(self, ctx, shortlog=False):
198 208 def changelist(**map):
199 209 cl = self.repo.changelog
200 210 l = [] # build a list in forward order for efficiency
201 211 for i in xrange(start, end):
202 212 ctx = self.repo.changectx(i)
203 213 n = ctx.node()
204 214
205 215 l.insert(0, {"parity": parity.next(),
206 216 "author": ctx.user(),
207 217 "parent": self.siblings(ctx.parents(), i - 1),
208 218 "child": self.siblings(ctx.children(), i + 1),
209 219 "changelogtag": self.showtag("changelogtag",n),
210 220 "desc": ctx.description(),
211 221 "date": ctx.date(),
212 222 "files": self.listfilediffs(ctx.files(), n),
213 223 "rev": i,
214 "node": hex(n)})
224 "node": hex(n),
225 "tags": self.taglistdict(n),
226 "branches": self.branchlistdict(n)})
215 227
216 228 for e in l:
217 229 yield e
218 230
219 231 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
220 232 cl = self.repo.changelog
221 233 count = cl.count()
222 234 pos = ctx.rev()
223 235 start = max(0, pos - maxchanges + 1)
224 236 end = min(count, start + maxchanges)
225 237 pos = end - 1
226 238 parity = paritygen(self.stripecount, offset=start-end)
227 239
228 240 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
229 241
230 242 yield self.t(shortlog and 'shortlog' or 'changelog',
231 243 changenav=changenav,
232 244 node=hex(cl.tip()),
233 245 rev=pos, changesets=count, entries=changelist,
234 246 archives=self.archivelist("tip"))
235 247
236 248 def search(self, query):
237 249
238 250 def changelist(**map):
239 251 cl = self.repo.changelog
240 252 count = 0
241 253 qw = query.lower().split()
242 254
243 255 def revgen():
244 256 for i in xrange(cl.count() - 1, 0, -100):
245 257 l = []
246 258 for j in xrange(max(0, i - 100), i):
247 259 ctx = self.repo.changectx(j)
248 260 l.append(ctx)
249 261 l.reverse()
250 262 for e in l:
251 263 yield e
252 264
253 265 for ctx in revgen():
254 266 miss = 0
255 267 for q in qw:
256 268 if not (q in ctx.user().lower() or
257 269 q in ctx.description().lower() or
258 270 q in " ".join(ctx.files()).lower()):
259 271 miss = 1
260 272 break
261 273 if miss:
262 274 continue
263 275
264 276 count += 1
265 277 n = ctx.node()
266 278
267 279 yield self.t('searchentry',
268 280 parity=parity.next(),
269 281 author=ctx.user(),
270 282 parent=self.siblings(ctx.parents()),
271 283 child=self.siblings(ctx.children()),
272 284 changelogtag=self.showtag("changelogtag",n),
273 285 desc=ctx.description(),
274 286 date=ctx.date(),
275 287 files=self.listfilediffs(ctx.files(), n),
276 288 rev=ctx.rev(),
277 node=hex(n))
289 node=hex(n),
290 tags=self.taglistdict(n),
291 branches=self.branchlistdict(n))
278 292
279 293 if count >= self.maxchanges:
280 294 break
281 295
282 296 cl = self.repo.changelog
283 297 parity = paritygen(self.stripecount)
284 298
285 299 yield self.t('search',
286 300 query=query,
287 301 node=hex(cl.tip()),
288 302 entries=changelist,
289 303 archives=self.archivelist("tip"))
290 304
291 305 def changeset(self, ctx):
292 306 n = ctx.node()
293 307 parents = ctx.parents()
294 308 p1 = parents[0].node()
295 309
296 310 files = []
297 311 parity = paritygen(self.stripecount)
298 312 for f in ctx.files():
299 313 files.append(self.t("filenodelink",
300 314 node=hex(n), file=f,
301 315 parity=parity.next()))
302 316
303 317 def diff(**map):
304 318 yield self.diff(p1, n, None)
305 319
306 320 yield self.t('changeset',
307 321 diff=diff,
308 322 rev=ctx.rev(),
309 323 node=hex(n),
310 324 parent=self.siblings(parents),
311 325 child=self.siblings(ctx.children()),
312 326 changesettag=self.showtag("changesettag",n),
313 327 author=ctx.user(),
314 328 desc=ctx.description(),
315 329 date=ctx.date(),
316 330 files=files,
317 archives=self.archivelist(hex(n)))
331 archives=self.archivelist(hex(n)),
332 tags=self.taglistdict(n),
333 branches=self.branchlistdict(n))
318 334
319 335 def filelog(self, fctx):
320 336 f = fctx.path()
321 337 fl = fctx.filelog()
322 338 count = fl.count()
323 339 pagelen = self.maxshortchanges
324 340 pos = fctx.filerev()
325 341 start = max(0, pos - pagelen + 1)
326 342 end = min(count, start + pagelen)
327 343 pos = end - 1
328 344 parity = paritygen(self.stripecount, offset=start-end)
329 345
330 346 def entries(**map):
331 347 l = []
332 348
333 349 for i in xrange(start, end):
334 350 ctx = fctx.filectx(i)
335 351 n = fl.node(i)
336 352
337 353 l.insert(0, {"parity": parity.next(),
338 354 "filerev": i,
339 355 "file": f,
340 356 "node": hex(ctx.node()),
341 357 "author": ctx.user(),
342 358 "date": ctx.date(),
343 359 "rename": self.renamelink(fl, n),
344 360 "parent": self.siblings(fctx.parents()),
345 361 "child": self.siblings(fctx.children()),
346 362 "desc": ctx.description()})
347 363
348 364 for e in l:
349 365 yield e
350 366
351 367 nodefunc = lambda x: fctx.filectx(fileid=x)
352 368 nav = revnavgen(pos, pagelen, count, nodefunc)
353 369 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
354 370 entries=entries)
355 371
356 372 def filerevision(self, fctx):
357 373 f = fctx.path()
358 374 text = fctx.data()
359 375 fl = fctx.filelog()
360 376 n = fctx.filenode()
361 377 parity = paritygen(self.stripecount)
362 378
363 379 mt = mimetypes.guess_type(f)[0]
364 380 rawtext = text
365 381 if util.binary(text):
366 382 mt = mt or 'application/octet-stream'
367 383 text = "(binary:%s)" % mt
368 384 mt = mt or 'text/plain'
369 385
370 386 def lines():
371 387 for l, t in enumerate(text.splitlines(1)):
372 388 yield {"line": t,
373 389 "linenumber": "% 6d" % (l + 1),
374 390 "parity": parity.next()}
375 391
376 392 yield self.t("filerevision",
377 393 file=f,
378 394 path=_up(f),
379 395 text=lines(),
380 396 raw=rawtext,
381 397 mimetype=mt,
382 398 rev=fctx.rev(),
383 399 node=hex(fctx.node()),
384 400 author=fctx.user(),
385 401 date=fctx.date(),
386 402 desc=fctx.description(),
387 403 parent=self.siblings(fctx.parents()),
388 404 child=self.siblings(fctx.children()),
389 405 rename=self.renamelink(fl, n),
390 406 permissions=fctx.manifest().execf(f))
391 407
392 408 def fileannotate(self, fctx):
393 409 f = fctx.path()
394 410 n = fctx.filenode()
395 411 fl = fctx.filelog()
396 412 parity = paritygen(self.stripecount)
397 413
398 414 def annotate(**map):
399 415 last = None
400 416 for f, l in fctx.annotate(follow=True):
401 417 fnode = f.filenode()
402 418 name = self.repo.ui.shortuser(f.user())
403 419
404 420 if last != fnode:
405 421 last = fnode
406 422
407 423 yield {"parity": parity.next(),
408 424 "node": hex(f.node()),
409 425 "rev": f.rev(),
410 426 "author": name,
411 427 "file": f.path(),
412 428 "line": l}
413 429
414 430 yield self.t("fileannotate",
415 431 file=f,
416 432 annotate=annotate,
417 433 path=_up(f),
418 434 rev=fctx.rev(),
419 435 node=hex(fctx.node()),
420 436 author=fctx.user(),
421 437 date=fctx.date(),
422 438 desc=fctx.description(),
423 439 rename=self.renamelink(fl, n),
424 440 parent=self.siblings(fctx.parents()),
425 441 child=self.siblings(fctx.children()),
426 442 permissions=fctx.manifest().execf(f))
427 443
428 444 def manifest(self, ctx, path):
429 445 mf = ctx.manifest()
430 446 node = ctx.node()
431 447
432 448 files = {}
433 449 parity = paritygen(self.stripecount)
434 450
435 451 if path and path[-1] != "/":
436 452 path += "/"
437 453 l = len(path)
438 454 abspath = "/" + path
439 455
440 456 for f, n in mf.items():
441 457 if f[:l] != path:
442 458 continue
443 459 remain = f[l:]
444 460 if "/" in remain:
445 461 short = remain[:remain.index("/") + 1] # bleah
446 462 files[short] = (f, None)
447 463 else:
448 464 short = os.path.basename(remain)
449 465 files[short] = (f, n)
450 466
451 467 def filelist(**map):
452 468 fl = files.keys()
453 469 fl.sort()
454 470 for f in fl:
455 471 full, fnode = files[f]
456 472 if not fnode:
457 473 continue
458 474
459 475 yield {"file": full,
460 476 "parity": parity.next(),
461 477 "basename": f,
462 478 "size": ctx.filectx(full).size(),
463 479 "permissions": mf.execf(full)}
464 480
465 481 def dirlist(**map):
466 482 fl = files.keys()
467 483 fl.sort()
468 484 for f in fl:
469 485 full, fnode = files[f]
470 486 if fnode:
471 487 continue
472 488
473 489 yield {"parity": parity.next(),
474 490 "path": os.path.join(abspath, f),
475 491 "basename": f[:-1]}
476 492
477 493 yield self.t("manifest",
478 494 rev=ctx.rev(),
479 495 node=hex(node),
480 496 path=abspath,
481 497 up=_up(abspath),
482 498 upparity=parity.next(),
483 499 fentries=filelist,
484 500 dentries=dirlist,
485 archives=self.archivelist(hex(node)))
501 archives=self.archivelist(hex(node)),
502 tags=self.taglistdict(node),
503 branches=self.branchlistdict(node))
486 504
487 505 def tags(self):
488 506 i = self.repo.tagslist()
489 507 i.reverse()
490 508 parity = paritygen(self.stripecount)
491 509
492 510 def entries(notip=False, **map):
493 511 for k, n in i:
494 512 if notip and k == "tip":
495 513 continue
496 514 yield {"parity": parity.next(),
497 515 "tag": k,
498 516 "date": self.repo.changectx(n).date(),
499 517 "node": hex(n)}
500 518
501 519 yield self.t("tags",
502 520 node=hex(self.repo.changelog.tip()),
503 521 entries=lambda **x: entries(False, **x),
504 522 entriesnotip=lambda **x: entries(True, **x))
505 523
506 524 def summary(self):
507 525 i = self.repo.tagslist()
508 526 i.reverse()
509 527
510 528 def tagentries(**map):
511 529 parity = paritygen(self.stripecount)
512 530 count = 0
513 531 for k, n in i:
514 532 if k == "tip": # skip tip
515 533 continue;
516 534
517 535 count += 1
518 536 if count > 10: # limit to 10 tags
519 537 break;
520 538
521 539 yield self.t("tagentry",
522 540 parity=parity.next(),
523 541 tag=k,
524 542 node=hex(n),
525 543 date=self.repo.changectx(n).date())
526 544
527 545
528 546 def branches(**map):
529 547 parity = paritygen(self.stripecount)
530 548
531 549 b = self.repo.branchtags()
532 550 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
533 551 l.sort()
534 552
535 553 for r,n,t in l:
536 554 ctx = self.repo.changectx(n)
537 555
538 556 yield {'parity': parity.next(),
539 557 'branch': t,
540 558 'node': hex(n),
541 559 'date': ctx.date()}
542 560
543 561 def changelist(**map):
544 562 parity = paritygen(self.stripecount, offset=start-end)
545 563 l = [] # build a list in forward order for efficiency
546 564 for i in xrange(start, end):
547 565 ctx = self.repo.changectx(i)
548 hn = hex(ctx.node())
566 n = ctx.node()
567 hn = hex(n)
549 568
550 569 l.insert(0, self.t(
551 570 'shortlogentry',
552 571 parity=parity.next(),
553 572 author=ctx.user(),
554 573 desc=ctx.description(),
555 574 date=ctx.date(),
556 575 rev=i,
557 node=hn))
576 node=hn,
577 tags=self.taglistdict(n),
578 branches=self.branchlistdict(n)))
558 579
559 580 yield l
560 581
561 582 cl = self.repo.changelog
562 583 count = cl.count()
563 584 start = max(0, count - self.maxchanges)
564 585 end = min(count, start + self.maxchanges)
565 586
566 587 yield self.t("summary",
567 588 desc=self.config("web", "description", "unknown"),
568 589 owner=(self.config("ui", "username") or # preferred
569 590 self.config("web", "contact") or # deprecated
570 591 self.config("web", "author", "unknown")), # also
571 592 lastchange=cl.read(cl.tip())[2],
572 593 tags=tagentries,
573 594 branches=branches,
574 595 shortlog=changelist,
575 596 node=hex(cl.tip()),
576 597 archives=self.archivelist("tip"))
577 598
578 599 def filediff(self, fctx):
579 600 n = fctx.node()
580 601 path = fctx.path()
581 602 parents = fctx.parents()
582 603 p1 = parents and parents[0].node() or nullid
583 604
584 605 def diff(**map):
585 606 yield self.diff(p1, n, [path])
586 607
587 608 yield self.t("filediff",
588 609 file=path,
589 610 node=hex(n),
590 611 rev=fctx.rev(),
591 612 parent=self.siblings(parents),
592 613 child=self.siblings(fctx.children()),
593 614 diff=diff)
594 615
595 616 archive_specs = {
596 617 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
597 618 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
598 619 'zip': ('application/zip', 'zip', '.zip', None),
599 620 }
600 621
601 622 def archive(self, req, id, type_):
602 623 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
603 624 cnode = self.repo.lookup(id)
604 625 arch_version = id
605 626 if cnode == id or id == 'tip':
606 627 arch_version = short(cnode)
607 628 name = "%s-%s" % (reponame, arch_version)
608 629 mimetype, artype, extension, encoding = self.archive_specs[type_]
609 630 headers = [('Content-type', mimetype),
610 631 ('Content-disposition', 'attachment; filename=%s%s' %
611 632 (name, extension))]
612 633 if encoding:
613 634 headers.append(('Content-encoding', encoding))
614 635 req.header(headers)
615 636 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
616 637
617 638 # add tags to things
618 639 # tags -> list of changesets corresponding to tags
619 640 # find tag, changeset, file
620 641
621 642 def cleanpath(self, path):
622 643 path = path.lstrip('/')
623 644 return util.canonpath(self.repo.root, '', path)
624 645
625 646 def run(self):
626 647 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
627 648 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
628 649 import mercurial.hgweb.wsgicgi as wsgicgi
629 650 from request import wsgiapplication
630 651 def make_web_app():
631 652 return self
632 653 wsgicgi.launch(wsgiapplication(make_web_app))
633 654
634 655 def run_wsgi(self, req):
635 656 def header(**map):
636 657 header_file = cStringIO.StringIO(
637 658 ''.join(self.t("header", encoding=util._encoding, **map)))
638 659 msg = mimetools.Message(header_file, 0)
639 660 req.header(msg.items())
640 661 yield header_file.read()
641 662
642 663 def rawfileheader(**map):
643 664 req.header([('Content-type', map['mimetype']),
644 665 ('Content-disposition', 'filename=%s' % map['file']),
645 666 ('Content-length', str(len(map['raw'])))])
646 667 yield ''
647 668
648 669 def footer(**map):
649 670 yield self.t("footer", **map)
650 671
651 672 def motd(**map):
652 673 yield self.config("web", "motd", "")
653 674
654 675 def expand_form(form):
655 676 shortcuts = {
656 677 'cl': [('cmd', ['changelog']), ('rev', None)],
657 678 'sl': [('cmd', ['shortlog']), ('rev', None)],
658 679 'cs': [('cmd', ['changeset']), ('node', None)],
659 680 'f': [('cmd', ['file']), ('filenode', None)],
660 681 'fl': [('cmd', ['filelog']), ('filenode', None)],
661 682 'fd': [('cmd', ['filediff']), ('node', None)],
662 683 'fa': [('cmd', ['annotate']), ('filenode', None)],
663 684 'mf': [('cmd', ['manifest']), ('manifest', None)],
664 685 'ca': [('cmd', ['archive']), ('node', None)],
665 686 'tags': [('cmd', ['tags'])],
666 687 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
667 688 'static': [('cmd', ['static']), ('file', None)]
668 689 }
669 690
670 691 for k in shortcuts.iterkeys():
671 692 if form.has_key(k):
672 693 for name, value in shortcuts[k]:
673 694 if value is None:
674 695 value = form[k]
675 696 form[name] = value
676 697 del form[k]
677 698
678 699 def rewrite_request(req):
679 700 '''translate new web interface to traditional format'''
680 701
681 702 def spliturl(req):
682 703 def firstitem(query):
683 704 return query.split('&', 1)[0].split(';', 1)[0]
684 705
685 706 def normurl(url):
686 707 inner = '/'.join([x for x in url.split('/') if x])
687 708 tl = len(url) > 1 and url.endswith('/') and '/' or ''
688 709
689 710 return '%s%s%s' % (url.startswith('/') and '/' or '',
690 711 inner, tl)
691 712
692 713 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
693 714 pi = normurl(req.env.get('PATH_INFO', ''))
694 715 if pi:
695 716 # strip leading /
696 717 pi = pi[1:]
697 718 if pi:
698 719 root = root[:root.rfind(pi)]
699 720 if req.env.has_key('REPO_NAME'):
700 721 rn = req.env['REPO_NAME'] + '/'
701 722 root += rn
702 723 query = pi[len(rn):]
703 724 else:
704 725 query = pi
705 726 else:
706 727 root += '?'
707 728 query = firstitem(req.env['QUERY_STRING'])
708 729
709 730 return (root, query)
710 731
711 732 req.url, query = spliturl(req)
712 733
713 734 if req.form.has_key('cmd'):
714 735 # old style
715 736 return
716 737
717 738 args = query.split('/', 2)
718 739 if not args or not args[0]:
719 740 return
720 741
721 742 cmd = args.pop(0)
722 743 style = cmd.rfind('-')
723 744 if style != -1:
724 745 req.form['style'] = [cmd[:style]]
725 746 cmd = cmd[style+1:]
726 747 # avoid accepting e.g. style parameter as command
727 748 if hasattr(self, 'do_' + cmd):
728 749 req.form['cmd'] = [cmd]
729 750
730 751 if args and args[0]:
731 752 node = args.pop(0)
732 753 req.form['node'] = [node]
733 754 if args:
734 755 req.form['file'] = args
735 756
736 757 if cmd == 'static':
737 758 req.form['file'] = req.form['node']
738 759 elif cmd == 'archive':
739 760 fn = req.form['node'][0]
740 761 for type_, spec in self.archive_specs.iteritems():
741 762 ext = spec[2]
742 763 if fn.endswith(ext):
743 764 req.form['node'] = [fn[:-len(ext)]]
744 765 req.form['type'] = [type_]
745 766
746 767 def sessionvars(**map):
747 768 fields = []
748 769 if req.form.has_key('style'):
749 770 style = req.form['style'][0]
750 771 if style != self.config('web', 'style', ''):
751 772 fields.append(('style', style))
752 773
753 774 separator = req.url[-1] == '?' and ';' or '?'
754 775 for name, value in fields:
755 776 yield dict(name=name, value=value, separator=separator)
756 777 separator = ';'
757 778
758 779 self.refresh()
759 780
760 781 expand_form(req.form)
761 782 rewrite_request(req)
762 783
763 784 style = self.config("web", "style", "")
764 785 if req.form.has_key('style'):
765 786 style = req.form['style'][0]
766 787 mapfile = style_map(self.templatepath, style)
767 788
768 789 port = req.env["SERVER_PORT"]
769 790 port = port != "80" and (":" + port) or ""
770 791 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
771 792 staticurl = self.config("web", "staticurl") or req.url + 'static/'
772 793 if not staticurl.endswith('/'):
773 794 staticurl += '/'
774 795
775 796 if not self.reponame:
776 797 self.reponame = (self.config("web", "name")
777 798 or req.env.get('REPO_NAME')
778 799 or req.url.strip('/') or self.repo.root)
779 800
780 801 self.t = templater.templater(mapfile, templater.common_filters,
781 802 defaults={"url": req.url,
782 803 "staticurl": staticurl,
783 804 "urlbase": urlbase,
784 805 "repo": self.reponame,
785 806 "header": header,
786 807 "footer": footer,
787 808 "motd": motd,
788 809 "rawfileheader": rawfileheader,
789 810 "sessionvars": sessionvars
790 811 })
791 812
792 813 try:
793 814 if not req.form.has_key('cmd'):
794 815 req.form['cmd'] = [self.t.cache['default']]
795 816
796 817 cmd = req.form['cmd'][0]
797 818
798 819 method = getattr(self, 'do_' + cmd, None)
799 820 if method:
800 821 try:
801 822 method(req)
802 823 except (hg.RepoError, revlog.RevlogError), inst:
803 824 req.write(self.t("error", error=str(inst)))
804 825 else:
805 826 req.write(self.t("error", error='No such method: ' + cmd))
806 827 finally:
807 828 self.t = None
808 829
809 830 def changectx(self, req):
810 831 if req.form.has_key('node'):
811 832 changeid = req.form['node'][0]
812 833 elif req.form.has_key('manifest'):
813 834 changeid = req.form['manifest'][0]
814 835 else:
815 836 changeid = self.repo.changelog.count() - 1
816 837
817 838 try:
818 839 ctx = self.repo.changectx(changeid)
819 840 except hg.RepoError:
820 841 man = self.repo.manifest
821 842 mn = man.lookup(changeid)
822 843 ctx = self.repo.changectx(man.linkrev(mn))
823 844
824 845 return ctx
825 846
826 847 def filectx(self, req):
827 848 path = self.cleanpath(req.form['file'][0])
828 849 if req.form.has_key('node'):
829 850 changeid = req.form['node'][0]
830 851 else:
831 852 changeid = req.form['filenode'][0]
832 853 try:
833 854 ctx = self.repo.changectx(changeid)
834 855 fctx = ctx.filectx(path)
835 856 except hg.RepoError:
836 857 fctx = self.repo.filectx(path, fileid=changeid)
837 858
838 859 return fctx
839 860
840 861 def do_log(self, req):
841 862 if req.form.has_key('file') and req.form['file'][0]:
842 863 self.do_filelog(req)
843 864 else:
844 865 self.do_changelog(req)
845 866
846 867 def do_rev(self, req):
847 868 self.do_changeset(req)
848 869
849 870 def do_file(self, req):
850 871 path = self.cleanpath(req.form.get('file', [''])[0])
851 872 if path:
852 873 try:
853 874 req.write(self.filerevision(self.filectx(req)))
854 875 return
855 876 except revlog.LookupError:
856 877 pass
857 878
858 879 req.write(self.manifest(self.changectx(req), path))
859 880
860 881 def do_diff(self, req):
861 882 self.do_filediff(req)
862 883
863 884 def do_changelog(self, req, shortlog = False):
864 885 if req.form.has_key('node'):
865 886 ctx = self.changectx(req)
866 887 else:
867 888 if req.form.has_key('rev'):
868 889 hi = req.form['rev'][0]
869 890 else:
870 891 hi = self.repo.changelog.count() - 1
871 892 try:
872 893 ctx = self.repo.changectx(hi)
873 894 except hg.RepoError:
874 895 req.write(self.search(hi)) # XXX redirect to 404 page?
875 896 return
876 897
877 898 req.write(self.changelog(ctx, shortlog = shortlog))
878 899
879 900 def do_shortlog(self, req):
880 901 self.do_changelog(req, shortlog = True)
881 902
882 903 def do_changeset(self, req):
883 904 req.write(self.changeset(self.changectx(req)))
884 905
885 906 def do_manifest(self, req):
886 907 req.write(self.manifest(self.changectx(req),
887 908 self.cleanpath(req.form['path'][0])))
888 909
889 910 def do_tags(self, req):
890 911 req.write(self.tags())
891 912
892 913 def do_summary(self, req):
893 914 req.write(self.summary())
894 915
895 916 def do_filediff(self, req):
896 917 req.write(self.filediff(self.filectx(req)))
897 918
898 919 def do_annotate(self, req):
899 920 req.write(self.fileannotate(self.filectx(req)))
900 921
901 922 def do_filelog(self, req):
902 923 req.write(self.filelog(self.filectx(req)))
903 924
904 925 def do_lookup(self, req):
905 926 try:
906 927 r = hex(self.repo.lookup(req.form['key'][0]))
907 928 success = 1
908 929 except Exception,inst:
909 930 r = str(inst)
910 931 success = 0
911 932 resp = "%s %s\n" % (success, r)
912 933 req.httphdr("application/mercurial-0.1", length=len(resp))
913 934 req.write(resp)
914 935
915 936 def do_heads(self, req):
916 937 resp = " ".join(map(hex, self.repo.heads())) + "\n"
917 938 req.httphdr("application/mercurial-0.1", length=len(resp))
918 939 req.write(resp)
919 940
920 941 def do_branches(self, req):
921 942 nodes = []
922 943 if req.form.has_key('nodes'):
923 944 nodes = map(bin, req.form['nodes'][0].split(" "))
924 945 resp = cStringIO.StringIO()
925 946 for b in self.repo.branches(nodes):
926 947 resp.write(" ".join(map(hex, b)) + "\n")
927 948 resp = resp.getvalue()
928 949 req.httphdr("application/mercurial-0.1", length=len(resp))
929 950 req.write(resp)
930 951
931 952 def do_between(self, req):
932 953 if req.form.has_key('pairs'):
933 954 pairs = [map(bin, p.split("-"))
934 955 for p in req.form['pairs'][0].split(" ")]
935 956 resp = cStringIO.StringIO()
936 957 for b in self.repo.between(pairs):
937 958 resp.write(" ".join(map(hex, b)) + "\n")
938 959 resp = resp.getvalue()
939 960 req.httphdr("application/mercurial-0.1", length=len(resp))
940 961 req.write(resp)
941 962
942 963 def do_changegroup(self, req):
943 964 req.httphdr("application/mercurial-0.1")
944 965 nodes = []
945 966 if not self.allowpull:
946 967 return
947 968
948 969 if req.form.has_key('roots'):
949 970 nodes = map(bin, req.form['roots'][0].split(" "))
950 971
951 972 z = zlib.compressobj()
952 973 f = self.repo.changegroup(nodes, 'serve')
953 974 while 1:
954 975 chunk = f.read(4096)
955 976 if not chunk:
956 977 break
957 978 req.write(z.compress(chunk))
958 979
959 980 req.write(z.flush())
960 981
961 982 def do_changegroupsubset(self, req):
962 983 req.httphdr("application/mercurial-0.1")
963 984 bases = []
964 985 heads = []
965 986 if not self.allowpull:
966 987 return
967 988
968 989 if req.form.has_key('bases'):
969 990 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
970 991 if req.form.has_key('heads'):
971 992 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
972 993
973 994 z = zlib.compressobj()
974 995 f = self.repo.changegroupsubset(bases, heads, 'serve')
975 996 while 1:
976 997 chunk = f.read(4096)
977 998 if not chunk:
978 999 break
979 1000 req.write(z.compress(chunk))
980 1001
981 1002 req.write(z.flush())
982 1003
983 1004 def do_archive(self, req):
984 1005 type_ = req.form['type'][0]
985 1006 allowed = self.configlist("web", "allow_archive")
986 1007 if (type_ in self.archives and (type_ in allowed or
987 1008 self.configbool("web", "allow" + type_, False))):
988 1009 self.archive(req, req.form['node'][0], type_)
989 1010 return
990 1011
991 1012 req.write(self.t("error"))
992 1013
993 1014 def do_static(self, req):
994 1015 fname = req.form['file'][0]
995 1016 # a repo owner may set web.static in .hg/hgrc to get any file
996 1017 # readable by the user running the CGI script
997 1018 static = self.config("web", "static",
998 1019 os.path.join(self.templatepath, "static"),
999 1020 untrusted=False)
1000 1021 req.write(staticfile(static, fname, req)
1001 1022 or self.t("error", error="%r not found" % fname))
1002 1023
1003 1024 def do_capabilities(self, req):
1004 1025 caps = ['lookup', 'changegroupsubset']
1005 1026 if self.configbool('server', 'uncompressed'):
1006 1027 caps.append('stream=%d' % self.repo.changelog.version)
1007 1028 # XXX: make configurable and/or share code with do_unbundle:
1008 1029 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1009 1030 if unbundleversions:
1010 1031 caps.append('unbundle=%s' % ','.join(unbundleversions))
1011 1032 resp = ' '.join(caps)
1012 1033 req.httphdr("application/mercurial-0.1", length=len(resp))
1013 1034 req.write(resp)
1014 1035
1015 1036 def check_perm(self, req, op, default):
1016 1037 '''check permission for operation based on user auth.
1017 1038 return true if op allowed, else false.
1018 1039 default is policy to use if no config given.'''
1019 1040
1020 1041 user = req.env.get('REMOTE_USER')
1021 1042
1022 1043 deny = self.configlist('web', 'deny_' + op)
1023 1044 if deny and (not user or deny == ['*'] or user in deny):
1024 1045 return False
1025 1046
1026 1047 allow = self.configlist('web', 'allow_' + op)
1027 1048 return (allow and (allow == ['*'] or user in allow)) or default
1028 1049
1029 1050 def do_unbundle(self, req):
1030 1051 def bail(response, headers={}):
1031 1052 length = int(req.env['CONTENT_LENGTH'])
1032 1053 for s in util.filechunkiter(req, limit=length):
1033 1054 # drain incoming bundle, else client will not see
1034 1055 # response when run outside cgi script
1035 1056 pass
1036 1057 req.httphdr("application/mercurial-0.1", headers=headers)
1037 1058 req.write('0\n')
1038 1059 req.write(response)
1039 1060
1040 1061 # require ssl by default, auth info cannot be sniffed and
1041 1062 # replayed
1042 1063 ssl_req = self.configbool('web', 'push_ssl', True)
1043 1064 if ssl_req:
1044 1065 if not req.env.get('HTTPS'):
1045 1066 bail(_('ssl required\n'))
1046 1067 return
1047 1068 proto = 'https'
1048 1069 else:
1049 1070 proto = 'http'
1050 1071
1051 1072 # do not allow push unless explicitly allowed
1052 1073 if not self.check_perm(req, 'push', False):
1053 1074 bail(_('push not authorized\n'),
1054 1075 headers={'status': '401 Unauthorized'})
1055 1076 return
1056 1077
1057 1078 their_heads = req.form['heads'][0].split(' ')
1058 1079
1059 1080 def check_heads():
1060 1081 heads = map(hex, self.repo.heads())
1061 1082 return their_heads == [hex('force')] or their_heads == heads
1062 1083
1063 1084 # fail early if possible
1064 1085 if not check_heads():
1065 1086 bail(_('unsynced changes\n'))
1066 1087 return
1067 1088
1068 1089 req.httphdr("application/mercurial-0.1")
1069 1090
1070 1091 # do not lock repo until all changegroup data is
1071 1092 # streamed. save to temporary file.
1072 1093
1073 1094 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1074 1095 fp = os.fdopen(fd, 'wb+')
1075 1096 try:
1076 1097 length = int(req.env['CONTENT_LENGTH'])
1077 1098 for s in util.filechunkiter(req, limit=length):
1078 1099 fp.write(s)
1079 1100
1080 1101 try:
1081 1102 lock = self.repo.lock()
1082 1103 try:
1083 1104 if not check_heads():
1084 1105 req.write('0\n')
1085 1106 req.write(_('unsynced changes\n'))
1086 1107 return
1087 1108
1088 1109 fp.seek(0)
1089 1110 header = fp.read(6)
1090 1111 if not header.startswith("HG"):
1091 1112 # old client with uncompressed bundle
1092 1113 def generator(f):
1093 1114 yield header
1094 1115 for chunk in f:
1095 1116 yield chunk
1096 1117 elif not header.startswith("HG10"):
1097 1118 req.write("0\n")
1098 1119 req.write(_("unknown bundle version\n"))
1099 1120 return
1100 1121 elif header == "HG10GZ":
1101 1122 def generator(f):
1102 1123 zd = zlib.decompressobj()
1103 1124 for chunk in f:
1104 1125 yield zd.decompress(chunk)
1105 1126 elif header == "HG10BZ":
1106 1127 def generator(f):
1107 1128 zd = bz2.BZ2Decompressor()
1108 1129 zd.decompress("BZ")
1109 1130 for chunk in f:
1110 1131 yield zd.decompress(chunk)
1111 1132 elif header == "HG10UN":
1112 1133 def generator(f):
1113 1134 for chunk in f:
1114 1135 yield chunk
1115 1136 else:
1116 1137 req.write("0\n")
1117 1138 req.write(_("unknown bundle compression type\n"))
1118 1139 return
1119 1140 gen = generator(util.filechunkiter(fp, 4096))
1120 1141
1121 1142 # send addchangegroup output to client
1122 1143
1123 1144 old_stdout = sys.stdout
1124 1145 sys.stdout = cStringIO.StringIO()
1125 1146
1126 1147 try:
1127 1148 url = 'remote:%s:%s' % (proto,
1128 1149 req.env.get('REMOTE_HOST', ''))
1129 1150 try:
1130 1151 ret = self.repo.addchangegroup(
1131 1152 util.chunkbuffer(gen), 'serve', url)
1132 1153 except util.Abort, inst:
1133 1154 sys.stdout.write("abort: %s\n" % inst)
1134 1155 ret = 0
1135 1156 finally:
1136 1157 val = sys.stdout.getvalue()
1137 1158 sys.stdout = old_stdout
1138 1159 req.write('%d\n' % ret)
1139 1160 req.write(val)
1140 1161 finally:
1141 1162 lock.release()
1142 1163 except (OSError, IOError), inst:
1143 1164 req.write('0\n')
1144 1165 filename = getattr(inst, 'filename', '')
1145 1166 # Don't send our filesystem layout to the client
1146 1167 if filename.startswith(self.repo.root):
1147 1168 filename = filename[len(self.repo.root)+1:]
1148 1169 else:
1149 1170 filename = ''
1150 1171 error = getattr(inst, 'strerror', 'Unknown error')
1151 1172 req.write('%s: %s\n' % (error, filename))
1152 1173 finally:
1153 1174 fp.close()
1154 1175 os.unlink(tempname)
1155 1176
1156 1177 def do_stream_out(self, req):
1157 1178 req.httphdr("application/mercurial-0.1")
1158 1179 streamclone.stream_out(self.repo, req)
@@ -1,14 +1,14 b''
1 1 <div>
2 <a class="title" href="{url}rev/#node|short#{sessionvars%urlparameter}"><span class="age">#date|age# ago</span>#desc|strip|firstline|escape#</a>
2 <a class="title" href="{url}rev/#node|short#{sessionvars%urlparameter}"><span class="age">#date|age# ago</span>#desc|strip|firstline|escape#<span class="logtags"> {branches%branchtag}{tags%tagtag}</span></a>
3 3 </div>
4 4 <div class="title_text">
5 5 <div class="log_link">
6 6 <a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a><br/>
7 7 </div>
8 8 <i>#author|obfuscate# [#date|rfc822date#] rev #rev#</i><br/>
9 9 </div>
10 10 <div class="log_body">
11 11 #desc|strip|escape|addbreaks#
12 12 <br/>
13 13 <br/>
14 14 </div>
@@ -1,40 +1,39 b''
1 1 #header#
2 2 <title>{repo|escape}: changeset {rev}:{node|short}</title>
3 3 <link rel="alternate" type="application/rss+xml"
4 4 href="{url}rss-log" title="RSS feed for #repo|escape#">
5 5 </head>
6 6 <body>
7 7
8 8 <div class="page_header">
9 9 <a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="#url#summary{sessionvars%urlparameter}">#repo|escape#</a> / changeset
10 10 </div>
11 11
12 12 <div class="page_nav">
13 13 <a href="{url}summary{sessionvars%urlparameter}">summary</a> | <a href="{url}shortlog/#rev#{sessionvars%urlparameter}">shortlog</a> | <a href="{url}log/#rev#{sessionvars%urlparameter}">changelog</a> | <a href="{url}tags{sessionvars%urlparameter}">tags</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a> | changeset | <a href="{url}raw-rev/#node|short#">raw</a> #archives%archiveentry#<br/>
14 14 </div>
15 15
16 16 <div>
17 <a class="title" href="{url}raw-rev/#node|short#">#desc|strip|escape|firstline#</a>
17 <a class="title" href="{url}raw-rev/#node|short#">#desc|strip|escape|firstline# <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a>
18 18 </div>
19 19 <div class="title_text">
20 20 <table cellspacing="0">
21 21 <tr><td>author</td><td>#author|obfuscate#</td></tr>
22 22 <tr><td></td><td>#date|date# (#date|age# ago)</td></tr>
23 23 <tr><td>changeset {rev}</td><td style="font-family:monospace">{node|short}</td></tr>
24 24 #parent%changesetparent#
25 25 #child%changesetchild#
26 #changesettag#
27 26 </table></div>
28 27
29 28 <div class="page_body">
30 29 #desc|strip|escape|addbreaks#
31 30 </div>
32 31 <div class="list_head"></div>
33 32 <div class="title_text">
34 33 <table cellspacing="0">
35 34 #files#
36 35 </table></div>
37 36
38 37 <div class="page_body">#diff#</div>
39 38
40 39 #footer#
@@ -1,33 +1,33 b''
1 1 #header#
2 2 <title>#repo|escape#: Manifest</title>
3 3 <link rel="alternate" type="application/rss+xml"
4 4 href="{url}rss-log" title="RSS feed for #repo|escape#">
5 5 </head>
6 6 <body>
7 7
8 8 <div class="page_header">
9 9 <a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="{url}summary{sessionvars%urlparameter}">#repo|escape#</a> / manifest
10 10 </div>
11 11
12 12 <div class="page_nav">
13 13 <a href="{url}summary{sessionvars%urlparameter}">summary</a> |
14 14 <a href="{url}shortlog{sessionvars%urlparameter}">shortlog</a> |
15 15 <a href="{url}log{sessionvars%urlparameter}">changelog</a> |
16 16 <a href="{url}tags{sessionvars%urlparameter}">tags</a> |
17 17 manifest |
18 18 <a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> #archives%archiveentry#<br/>
19 19 </div>
20 20
21 <div class="title" >#path|escape#</div>
21 <div class="title">#path|escape# <span class="logtags">{branches%branchtag}{tags%tagtag}</span></div>
22 22 <table cellspacing="0">
23 23 <tr class="parity#upparity#">
24 24 <td style="font-family:monospace">drwxr-xr-x</td>
25 25 <td style="font-family:monospace"></td>
26 26 <td><a href="{url}file/#node|short##up|urlescape#{sessionvars%urlparameter}">[up]</a></td>
27 27 <td class="link">&nbsp;</td>
28 28 </tr>
29 29 #dentries%manifestdirentry#
30 30 #fentries%manifestfileentry#
31 31 </table>
32 32
33 33 #footer#
@@ -1,57 +1,57 b''
1 1 default = 'summary'
2 2 header = header.tmpl
3 3 footer = footer.tmpl
4 4 search = search.tmpl
5 5 changelog = changelog.tmpl
6 6 summary = summary.tmpl
7 7 error = error.tmpl
8 8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 12 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>'
13 13 fileellipses = '...'
14 14 changelogentry = changelogentry.tmpl
15 15 searchentry = changelogentry.tmpl
16 16 changeset = changeset.tmpl
17 17 manifest = manifest.tmpl
18 18 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</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>'
19 19 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</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>'
20 20 filerevision = filerevision.tmpl
21 21 fileannotate = fileannotate.tmpl
22 22 filediff = filediff.tmpl
23 23 filelog = filelog.tmpl
24 24 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr"> #linenumber#</span> #line|escape#</pre></div>'
25 25 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>#line|escape#</pre></td></tr>'
26 26 difflineplus = '<div style="color:#008800;">#line|escape#</div>'
27 27 difflineminus = '<div style="color:#cc0000;">#line|escape#</div>'
28 28 difflineat = '<div style="color:#990099;">#line|escape#</div>'
29 29 diffline = '<div>#line|escape#</div>'
30 30 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
31 31 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>'
32 32 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>'
33 33 filerename = '{file|escape}@'
34 34 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
35 35 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>'
36 36 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
37 37 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>'
38 38 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>'
39 39 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>'
40 40 tags = tags.tmpl
41 41 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>'
42 42 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></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>'
43 43 diffblock = '<pre>#lines#</pre>'
44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 changesettag = '<tr><td>tag</td><td>#tag|escape#</td></tr>'
46 44 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 45 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 46 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 47 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 48 shortlog = shortlog.tmpl
51 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author|obfuscate#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></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>'
49 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
50 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
51 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</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>'
52 52 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>'
53 53 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
54 54 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"><a class="rss_logo" href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>'
55 55 index = index.tmpl
56 56 urlparameter = '#separator##name#=#value|urlescape#'
57 57 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,50 +1,66 b''
1 1 body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
2 2 a { color:#0000cc; }
3 3 a:hover, a:visited, a:active { color:#880000; }
4 4 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
5 5 div.page_header a:visited { color:#0000cc; }
6 6 div.page_header a:hover { color:#880000; }
7 7 div.page_nav { padding:8px; }
8 8 div.page_nav a:visited { color:#0000cc; }
9 9 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
10 10 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
11 11 div.page_footer_text { float:left; color:#555555; font-style:italic; }
12 12 div.page_body { padding:8px; }
13 13 div.title, a.title {
14 14 display:block; padding:6px 8px;
15 15 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
16 16 }
17 17 a.title:hover { background-color: #d9d8d1; }
18 18 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
19 19 div.log_body { padding:8px 8px 8px 150px; }
20 20 .age { white-space:nowrap; }
21 21 span.age { position:relative; float:left; width:142px; font-style:italic; }
22 22 div.log_link {
23 23 padding:0px 8px;
24 24 font-size:10px; font-family:sans-serif; font-style:normal;
25 25 position:relative; float:left; width:136px;
26 26 }
27 27 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
28 28 a.list { text-decoration:none; color:#000000; }
29 29 a.list:hover { text-decoration:underline; color:#880000; }
30 30 table { padding:8px 4px; }
31 31 th { padding:2px 5px; font-size:12px; text-align:left; }
32 32 tr.light:hover, .parity0:hover { background-color:#edece6; }
33 33 tr.dark, .parity1 { background-color:#f6f6f0; }
34 34 tr.dark:hover, .parity1:hover { background-color:#edece6; }
35 35 td { padding:2px 5px; font-size:12px; vertical-align:top; }
36 36 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
37 37 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
38 38 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
39 39 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
40 40 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
41 41 .linenr { color:#999999; text-decoration:none }
42 42 a.rss_logo {
43 43 float:right; padding:3px 0px; width:35px; line-height:10px;
44 44 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
45 45 color:#ffffff; background-color:#ff6600;
46 46 font-weight:bold; font-family:sans-serif; font-size:10px;
47 47 text-align:center; text-decoration:none;
48 48 }
49 49 a.rss_logo:hover { background-color:#ee5500; }
50 50 pre { margin: 0; }
51 span.logtags span {
52 padding: 0px 4px;
53 font-size: 10px;
54 font-weight: normal;
55 border: 1px solid;
56 background-color: #ffaaff;
57 border-color: #ffccff #ff00ee #ff00ee #ffccff;
58 }
59 span.logtags span.tagtag {
60 background-color: #ffffaa;
61 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
62 }
63 span.logtags span.branchtag {
64 background-color: #aaffaa;
65 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
66 }
General Comments 0
You need to be logged in to leave comments. Login now