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