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