##// END OF EJS Templates
Final stage of the hgweb split up....
Eric Hopper -
r2356:2db831b3 default
parent child Browse files
Show More
@@ -10,9 +10,11 b' from node import *'
10 10 from i18n import gettext as _
11 11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
13 demandload(globals(), "fnmatch mdiff random signal tempfile time")
14 14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 15 demandload(globals(), "archival changegroup")
16 demandload(globals(), "mercurial.hgweb.server:create_server")
17 demandload(globals(), "mercurial.hgweb:hgweb,hgwebdir")
16 18
17 19 class UnknownCommand(Exception):
18 20 """Exception raised if command is not in the command table."""
@@ -2542,7 +2544,7 b' def serve(ui, repo, **opts):'
2542 2544 os._exit(0)
2543 2545
2544 2546 try:
2545 httpd = hgweb.create_server(ui, repo, hgweb.hgwebdir, hgweb.hgweb)
2547 httpd = create_server(ui, repo, hgwebdir, hgweb)
2546 2548 except socket.error, inst:
2547 2549 raise util.Abort(_('cannot start server: ') + inst.args[1])
2548 2550
This diff has been collapsed as it changes many lines, (981 lines changed) Show them Hide them
@@ -6,983 +6,6 b''
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 import os, cgi, sys
10 import mimetypes
11 9 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
13 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 from mercurial.node import *
17 from mercurial.i18n import gettext as _
18
19 def up(p):
20 if p[0] != "/":
21 p = "/" + p
22 if p[-1] == "/":
23 p = p[:-1]
24 up = os.path.dirname(p)
25 if up == "/":
26 return "/"
27 return up + "/"
28
29 def get_mtime(repo_path):
30 hg_path = os.path.join(repo_path, ".hg")
31 cl_path = os.path.join(hg_path, "00changelog.i")
32 if os.path.exists(os.path.join(cl_path)):
33 return os.stat(cl_path).st_mtime
34 else:
35 return os.stat(hg_path).st_mtime
36
37 def staticfile(directory, fname):
38 """return a file inside directory with guessed content-type header
39
40 fname always uses '/' as directory separator and isn't allowed to
41 contain unusual path components.
42 Content-type is guessed using the mimetypes module.
43 Return an empty string if fname is illegal or file not found.
44
45 """
46 parts = fname.split('/')
47 path = directory
48 for part in parts:
49 if (part in ('', os.curdir, os.pardir) or
50 os.sep in part or os.altsep is not None and os.altsep in part):
51 return ""
52 path = os.path.join(path, part)
53 try:
54 os.stat(path)
55 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 return "Content-type: %s\n\n%s" % (ct, file(path).read())
57 except (TypeError, OSError):
58 # illegal fname or unreadable file
59 return ""
60
61 class hgweb(object):
62 def __init__(self, repo, name=None):
63 if type(repo) == type(""):
64 self.repo = hg.repository(ui.ui(), repo)
65 else:
66 self.repo = repo
67
68 self.mtime = -1
69 self.reponame = name
70 self.archives = 'zip', 'gz', 'bz2'
71
72 def refresh(self):
73 mtime = get_mtime(self.repo.root)
74 if mtime != self.mtime:
75 self.mtime = mtime
76 self.repo = hg.repository(self.repo.ui, self.repo.root)
77 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
78 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
79 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
80
81 def archivelist(self, nodeid):
82 for i in self.archives:
83 if self.repo.ui.configbool("web", "allow" + i, False):
84 yield {"type" : i, "node" : nodeid, "url": ""}
85
86 def listfiles(self, files, mf):
87 for f in files[:self.maxfiles]:
88 yield self.t("filenodelink", node=hex(mf[f]), file=f)
89 if len(files) > self.maxfiles:
90 yield self.t("fileellipses")
91
92 def listfilediffs(self, files, changeset):
93 for f in files[:self.maxfiles]:
94 yield self.t("filedifflink", node=hex(changeset), file=f)
95 if len(files) > self.maxfiles:
96 yield self.t("fileellipses")
97
98 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
99 if not rev:
100 rev = lambda x: ""
101 siblings = [s for s in siblings if s != nullid]
102 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
103 return
104 for s in siblings:
105 yield dict(node=hex(s), rev=rev(s), **args)
106
107 def renamelink(self, fl, node):
108 r = fl.renamed(node)
109 if r:
110 return [dict(file=r[0], node=hex(r[1]))]
111 return []
112
113 def showtag(self, t1, node=nullid, **args):
114 for t in self.repo.nodetags(node):
115 yield self.t(t1, tag=t, **args)
116
117 def diff(self, node1, node2, files):
118 def filterfiles(filters, files):
119 l = [x for x in files if x in filters]
120
121 for t in filters:
122 if t and t[-1] != os.sep:
123 t += os.sep
124 l += [x for x in files if x.startswith(t)]
125 return l
126
127 parity = [0]
128 def diffblock(diff, f, fn):
129 yield self.t("diffblock",
130 lines=prettyprintlines(diff),
131 parity=parity[0],
132 file=f,
133 filenode=hex(fn or nullid))
134 parity[0] = 1 - parity[0]
135
136 def prettyprintlines(diff):
137 for l in diff.splitlines(1):
138 if l.startswith('+'):
139 yield self.t("difflineplus", line=l)
140 elif l.startswith('-'):
141 yield self.t("difflineminus", line=l)
142 elif l.startswith('@'):
143 yield self.t("difflineat", line=l)
144 else:
145 yield self.t("diffline", line=l)
146
147 r = self.repo
148 cl = r.changelog
149 mf = r.manifest
150 change1 = cl.read(node1)
151 change2 = cl.read(node2)
152 mmap1 = mf.read(change1[0])
153 mmap2 = mf.read(change2[0])
154 date1 = util.datestr(change1[2])
155 date2 = util.datestr(change2[2])
156
157 modified, added, removed, deleted, unknown = r.changes(node1, node2)
158 if files:
159 modified, added, removed = map(lambda x: filterfiles(files, x),
160 (modified, added, removed))
161
162 diffopts = self.repo.ui.diffopts()
163 showfunc = diffopts['showfunc']
164 ignorews = diffopts['ignorews']
165 for f in modified:
166 to = r.file(f).read(mmap1[f])
167 tn = r.file(f).read(mmap2[f])
168 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
169 showfunc=showfunc, ignorews=ignorews), f, tn)
170 for f in added:
171 to = None
172 tn = r.file(f).read(mmap2[f])
173 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
174 showfunc=showfunc, ignorews=ignorews), f, tn)
175 for f in removed:
176 to = r.file(f).read(mmap1[f])
177 tn = None
178 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
179 showfunc=showfunc, ignorews=ignorews), f, tn)
180
181 def changelog(self, pos):
182 def changenav(**map):
183 def seq(factor, maxchanges=None):
184 if maxchanges:
185 yield maxchanges
186 if maxchanges >= 20 and maxchanges <= 40:
187 yield 50
188 else:
189 yield 1 * factor
190 yield 3 * factor
191 for f in seq(factor * 10):
192 yield f
193
194 l = []
195 last = 0
196 for f in seq(1, self.maxchanges):
197 if f < self.maxchanges or f <= last:
198 continue
199 if f > count:
200 break
201 last = f
202 r = "%d" % f
203 if pos + f < count:
204 l.append(("+" + r, pos + f))
205 if pos - f >= 0:
206 l.insert(0, ("-" + r, pos - f))
207
208 yield {"rev": 0, "label": "(0)"}
209
210 for label, rev in l:
211 yield {"label": label, "rev": rev}
212
213 yield {"label": "tip", "rev": "tip"}
214
215 def changelist(**map):
216 parity = (start - end) & 1
217 cl = self.repo.changelog
218 l = [] # build a list in forward order for efficiency
219 for i in range(start, end):
220 n = cl.node(i)
221 changes = cl.read(n)
222 hn = hex(n)
223
224 l.insert(0, {"parity": parity,
225 "author": changes[1],
226 "parent": self.siblings(cl.parents(n), cl.rev,
227 cl.rev(n) - 1),
228 "child": self.siblings(cl.children(n), cl.rev,
229 cl.rev(n) + 1),
230 "changelogtag": self.showtag("changelogtag",n),
231 "manifest": hex(changes[0]),
232 "desc": changes[4],
233 "date": changes[2],
234 "files": self.listfilediffs(changes[3], n),
235 "rev": i,
236 "node": hn})
237 parity = 1 - parity
238
239 for e in l:
240 yield e
241
242 cl = self.repo.changelog
243 mf = cl.read(cl.tip())[0]
244 count = cl.count()
245 start = max(0, pos - self.maxchanges + 1)
246 end = min(count, start + self.maxchanges)
247 pos = end - 1
248
249 yield self.t('changelog',
250 changenav=changenav,
251 manifest=hex(mf),
252 rev=pos, changesets=count, entries=changelist,
253 archives=self.archivelist("tip"))
254
255 def search(self, query):
256
257 def changelist(**map):
258 cl = self.repo.changelog
259 count = 0
260 qw = query.lower().split()
261
262 def revgen():
263 for i in range(cl.count() - 1, 0, -100):
264 l = []
265 for j in range(max(0, i - 100), i):
266 n = cl.node(j)
267 changes = cl.read(n)
268 l.append((n, j, changes))
269 l.reverse()
270 for e in l:
271 yield e
272
273 for n, i, changes in revgen():
274 miss = 0
275 for q in qw:
276 if not (q in changes[1].lower() or
277 q in changes[4].lower() or
278 q in " ".join(changes[3][:20]).lower()):
279 miss = 1
280 break
281 if miss:
282 continue
283
284 count += 1
285 hn = hex(n)
286
287 yield self.t('searchentry',
288 parity=count & 1,
289 author=changes[1],
290 parent=self.siblings(cl.parents(n), cl.rev),
291 child=self.siblings(cl.children(n), cl.rev),
292 changelogtag=self.showtag("changelogtag",n),
293 manifest=hex(changes[0]),
294 desc=changes[4],
295 date=changes[2],
296 files=self.listfilediffs(changes[3], n),
297 rev=i,
298 node=hn)
299
300 if count >= self.maxchanges:
301 break
302
303 cl = self.repo.changelog
304 mf = cl.read(cl.tip())[0]
305
306 yield self.t('search',
307 query=query,
308 manifest=hex(mf),
309 entries=changelist)
310
311 def changeset(self, nodeid):
312 cl = self.repo.changelog
313 n = self.repo.lookup(nodeid)
314 nodeid = hex(n)
315 changes = cl.read(n)
316 p1 = cl.parents(n)[0]
317
318 files = []
319 mf = self.repo.manifest.read(changes[0])
320 for f in changes[3]:
321 files.append(self.t("filenodelink",
322 filenode=hex(mf.get(f, nullid)), file=f))
323
324 def diff(**map):
325 yield self.diff(p1, n, None)
326
327 yield self.t('changeset',
328 diff=diff,
329 rev=cl.rev(n),
330 node=nodeid,
331 parent=self.siblings(cl.parents(n), cl.rev),
332 child=self.siblings(cl.children(n), cl.rev),
333 changesettag=self.showtag("changesettag",n),
334 manifest=hex(changes[0]),
335 author=changes[1],
336 desc=changes[4],
337 date=changes[2],
338 files=files,
339 archives=self.archivelist(nodeid))
340
341 def filelog(self, f, filenode):
342 cl = self.repo.changelog
343 fl = self.repo.file(f)
344 filenode = hex(fl.lookup(filenode))
345 count = fl.count()
346
347 def entries(**map):
348 l = []
349 parity = (count - 1) & 1
350
351 for i in range(count):
352 n = fl.node(i)
353 lr = fl.linkrev(n)
354 cn = cl.node(lr)
355 cs = cl.read(cl.node(lr))
356
357 l.insert(0, {"parity": parity,
358 "filenode": hex(n),
359 "filerev": i,
360 "file": f,
361 "node": hex(cn),
362 "author": cs[1],
363 "date": cs[2],
364 "rename": self.renamelink(fl, n),
365 "parent": self.siblings(fl.parents(n),
366 fl.rev, file=f),
367 "child": self.siblings(fl.children(n),
368 fl.rev, file=f),
369 "desc": cs[4]})
370 parity = 1 - parity
371
372 for e in l:
373 yield e
374
375 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
376
377 def filerevision(self, f, node):
378 fl = self.repo.file(f)
379 n = fl.lookup(node)
380 node = hex(n)
381 text = fl.read(n)
382 changerev = fl.linkrev(n)
383 cl = self.repo.changelog
384 cn = cl.node(changerev)
385 cs = cl.read(cn)
386 mfn = cs[0]
387
388 mt = mimetypes.guess_type(f)[0]
389 rawtext = text
390 if util.binary(text):
391 mt = mt or 'application/octet-stream'
392 text = "(binary:%s)" % mt
393 mt = mt or 'text/plain'
394
395 def lines():
396 for l, t in enumerate(text.splitlines(1)):
397 yield {"line": t,
398 "linenumber": "% 6d" % (l + 1),
399 "parity": l & 1}
400
401 yield self.t("filerevision",
402 file=f,
403 filenode=node,
404 path=up(f),
405 text=lines(),
406 raw=rawtext,
407 mimetype=mt,
408 rev=changerev,
409 node=hex(cn),
410 manifest=hex(mfn),
411 author=cs[1],
412 date=cs[2],
413 parent=self.siblings(fl.parents(n), fl.rev, file=f),
414 child=self.siblings(fl.children(n), fl.rev, file=f),
415 rename=self.renamelink(fl, n),
416 permissions=self.repo.manifest.readflags(mfn)[f])
417
418 def fileannotate(self, f, node):
419 bcache = {}
420 ncache = {}
421 fl = self.repo.file(f)
422 n = fl.lookup(node)
423 node = hex(n)
424 changerev = fl.linkrev(n)
425
426 cl = self.repo.changelog
427 cn = cl.node(changerev)
428 cs = cl.read(cn)
429 mfn = cs[0]
430
431 def annotate(**map):
432 parity = 1
433 last = None
434 for r, l in fl.annotate(n):
435 try:
436 cnode = ncache[r]
437 except KeyError:
438 cnode = ncache[r] = self.repo.changelog.node(r)
439
440 try:
441 name = bcache[r]
442 except KeyError:
443 cl = self.repo.changelog.read(cnode)
444 bcache[r] = name = self.repo.ui.shortuser(cl[1])
445
446 if last != cnode:
447 parity = 1 - parity
448 last = cnode
449
450 yield {"parity": parity,
451 "node": hex(cnode),
452 "rev": r,
453 "author": name,
454 "file": f,
455 "line": l}
456
457 yield self.t("fileannotate",
458 file=f,
459 filenode=node,
460 annotate=annotate,
461 path=up(f),
462 rev=changerev,
463 node=hex(cn),
464 manifest=hex(mfn),
465 author=cs[1],
466 date=cs[2],
467 rename=self.renamelink(fl, n),
468 parent=self.siblings(fl.parents(n), fl.rev, file=f),
469 child=self.siblings(fl.children(n), fl.rev, file=f),
470 permissions=self.repo.manifest.readflags(mfn)[f])
471
472 def manifest(self, mnode, path):
473 man = self.repo.manifest
474 mn = man.lookup(mnode)
475 mnode = hex(mn)
476 mf = man.read(mn)
477 rev = man.rev(mn)
478 changerev = man.linkrev(mn)
479 node = self.repo.changelog.node(changerev)
480 mff = man.readflags(mn)
481
482 files = {}
483
484 p = path[1:]
485 if p and p[-1] != "/":
486 p += "/"
487 l = len(p)
488
489 for f,n in mf.items():
490 if f[:l] != p:
491 continue
492 remain = f[l:]
493 if "/" in remain:
494 short = remain[:remain.find("/") + 1] # bleah
495 files[short] = (f, None)
496 else:
497 short = os.path.basename(remain)
498 files[short] = (f, n)
499
500 def filelist(**map):
501 parity = 0
502 fl = files.keys()
503 fl.sort()
504 for f in fl:
505 full, fnode = files[f]
506 if not fnode:
507 continue
508
509 yield {"file": full,
510 "manifest": mnode,
511 "filenode": hex(fnode),
512 "parity": parity,
513 "basename": f,
514 "permissions": mff[full]}
515 parity = 1 - parity
516
517 def dirlist(**map):
518 parity = 0
519 fl = files.keys()
520 fl.sort()
521 for f in fl:
522 full, fnode = files[f]
523 if fnode:
524 continue
525
526 yield {"parity": parity,
527 "path": os.path.join(path, f),
528 "manifest": mnode,
529 "basename": f[:-1]}
530 parity = 1 - parity
531
532 yield self.t("manifest",
533 manifest=mnode,
534 rev=rev,
535 node=hex(node),
536 path=path,
537 up=up(path),
538 fentries=filelist,
539 dentries=dirlist,
540 archives=self.archivelist(hex(node)))
541
542 def tags(self):
543 cl = self.repo.changelog
544 mf = cl.read(cl.tip())[0]
545
546 i = self.repo.tagslist()
547 i.reverse()
548
549 def entries(notip=False, **map):
550 parity = 0
551 for k,n in i:
552 if notip and k == "tip": continue
553 yield {"parity": parity,
554 "tag": k,
555 "tagmanifest": hex(cl.read(n)[0]),
556 "date": cl.read(n)[2],
557 "node": hex(n)}
558 parity = 1 - parity
559
560 yield self.t("tags",
561 manifest=hex(mf),
562 entries=lambda **x: entries(False, **x),
563 entriesnotip=lambda **x: entries(True, **x))
564
565 def summary(self):
566 cl = self.repo.changelog
567 mf = cl.read(cl.tip())[0]
568
569 i = self.repo.tagslist()
570 i.reverse()
571
572 def tagentries(**map):
573 parity = 0
574 count = 0
575 for k,n in i:
576 if k == "tip": # skip tip
577 continue;
578
579 count += 1
580 if count > 10: # limit to 10 tags
581 break;
582
583 c = cl.read(n)
584 m = c[0]
585 t = c[2]
586
587 yield self.t("tagentry",
588 parity = parity,
589 tag = k,
590 node = hex(n),
591 date = t,
592 tagmanifest = hex(m))
593 parity = 1 - parity
594
595 def changelist(**map):
596 parity = 0
597 cl = self.repo.changelog
598 l = [] # build a list in forward order for efficiency
599 for i in range(start, end):
600 n = cl.node(i)
601 changes = cl.read(n)
602 hn = hex(n)
603 t = changes[2]
604
605 l.insert(0, self.t(
606 'shortlogentry',
607 parity = parity,
608 author = changes[1],
609 manifest = hex(changes[0]),
610 desc = changes[4],
611 date = t,
612 rev = i,
613 node = hn))
614 parity = 1 - parity
615
616 yield l
617
618 cl = self.repo.changelog
619 mf = cl.read(cl.tip())[0]
620 count = cl.count()
621 start = max(0, count - self.maxchanges)
622 end = min(count, start + self.maxchanges)
623 pos = end - 1
624
625 yield self.t("summary",
626 desc = self.repo.ui.config("web", "description", "unknown"),
627 owner = (self.repo.ui.config("ui", "username") or # preferred
628 self.repo.ui.config("web", "contact") or # deprecated
629 self.repo.ui.config("web", "author", "unknown")), # also
630 lastchange = (0, 0), # FIXME
631 manifest = hex(mf),
632 tags = tagentries,
633 shortlog = changelist)
634
635 def filediff(self, file, changeset):
636 cl = self.repo.changelog
637 n = self.repo.lookup(changeset)
638 changeset = hex(n)
639 p1 = cl.parents(n)[0]
640 cs = cl.read(n)
641 mf = self.repo.manifest.read(cs[0])
642
643 def diff(**map):
644 yield self.diff(p1, n, [file])
645
646 yield self.t("filediff",
647 file=file,
648 filenode=hex(mf.get(file, nullid)),
649 node=changeset,
650 rev=self.repo.changelog.rev(n),
651 parent=self.siblings(cl.parents(n), cl.rev),
652 child=self.siblings(cl.children(n), cl.rev),
653 diff=diff)
654
655 archive_specs = {
656 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
657 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
658 'zip': ('application/zip', 'zip', '.zip', None),
659 }
660
661 def archive(self, req, cnode, type):
662 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
663 name = "%s-%s" % (reponame, short(cnode))
664 mimetype, artype, extension, encoding = self.archive_specs[type]
665 headers = [('Content-type', mimetype),
666 ('Content-disposition', 'attachment; filename=%s%s' %
667 (name, extension))]
668 if encoding:
669 headers.append(('Content-encoding', encoding))
670 req.header(headers)
671 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
672
673 # add tags to things
674 # tags -> list of changesets corresponding to tags
675 # find tag, changeset, file
676
677 def run(self, req=hgrequest()):
678 def clean(path):
679 p = util.normpath(path)
680 if p[:2] == "..":
681 raise "suspicious path"
682 return p
683
684 def header(**map):
685 yield self.t("header", **map)
686
687 def footer(**map):
688 yield self.t("footer",
689 motd=self.repo.ui.config("web", "motd", ""),
690 **map)
691
692 def expand_form(form):
693 shortcuts = {
694 'cl': [('cmd', ['changelog']), ('rev', None)],
695 'cs': [('cmd', ['changeset']), ('node', None)],
696 'f': [('cmd', ['file']), ('filenode', None)],
697 'fl': [('cmd', ['filelog']), ('filenode', None)],
698 'fd': [('cmd', ['filediff']), ('node', None)],
699 'fa': [('cmd', ['annotate']), ('filenode', None)],
700 'mf': [('cmd', ['manifest']), ('manifest', None)],
701 'ca': [('cmd', ['archive']), ('node', None)],
702 'tags': [('cmd', ['tags'])],
703 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
704 'static': [('cmd', ['static']), ('file', None)]
705 }
706
707 for k in shortcuts.iterkeys():
708 if form.has_key(k):
709 for name, value in shortcuts[k]:
710 if value is None:
711 value = form[k]
712 form[name] = value
713 del form[k]
714
715 self.refresh()
716
717 expand_form(req.form)
718
719 t = self.repo.ui.config("web", "templates", templater.templatepath())
720 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
721 m = os.path.join(t, "map")
722 style = self.repo.ui.config("web", "style", "")
723 if req.form.has_key('style'):
724 style = req.form['style'][0]
725 if style:
726 b = os.path.basename("map-" + style)
727 p = os.path.join(t, b)
728 if os.path.isfile(p):
729 m = p
730
731 port = req.env["SERVER_PORT"]
732 port = port != "80" and (":" + port) or ""
733 uri = req.env["REQUEST_URI"]
734 if "?" in uri:
735 uri = uri.split("?")[0]
736 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
737 if not self.reponame:
738 self.reponame = (self.repo.ui.config("web", "name")
739 or uri.strip('/') or self.repo.root)
740
741 self.t = templater.templater(m, templater.common_filters,
742 defaults={"url": url,
743 "repo": self.reponame,
744 "header": header,
745 "footer": footer,
746 })
747
748 if not req.form.has_key('cmd'):
749 req.form['cmd'] = [self.t.cache['default'],]
750
751 cmd = req.form['cmd'][0]
752 if cmd == 'changelog':
753 hi = self.repo.changelog.count() - 1
754 if req.form.has_key('rev'):
755 hi = req.form['rev'][0]
756 try:
757 hi = self.repo.changelog.rev(self.repo.lookup(hi))
758 except hg.RepoError:
759 req.write(self.search(hi)) # XXX redirect to 404 page?
760 return
761
762 req.write(self.changelog(hi))
763
764 elif cmd == 'changeset':
765 req.write(self.changeset(req.form['node'][0]))
766
767 elif cmd == 'manifest':
768 req.write(self.manifest(req.form['manifest'][0],
769 clean(req.form['path'][0])))
770
771 elif cmd == 'tags':
772 req.write(self.tags())
773
774 elif cmd == 'summary':
775 req.write(self.summary())
776
777 elif cmd == 'filediff':
778 req.write(self.filediff(clean(req.form['file'][0]),
779 req.form['node'][0]))
780
781 elif cmd == 'file':
782 req.write(self.filerevision(clean(req.form['file'][0]),
783 req.form['filenode'][0]))
784
785 elif cmd == 'annotate':
786 req.write(self.fileannotate(clean(req.form['file'][0]),
787 req.form['filenode'][0]))
788
789 elif cmd == 'filelog':
790 req.write(self.filelog(clean(req.form['file'][0]),
791 req.form['filenode'][0]))
792
793 elif cmd == 'heads':
794 req.httphdr("application/mercurial-0.1")
795 h = self.repo.heads()
796 req.write(" ".join(map(hex, h)) + "\n")
797
798 elif cmd == 'branches':
799 req.httphdr("application/mercurial-0.1")
800 nodes = []
801 if req.form.has_key('nodes'):
802 nodes = map(bin, req.form['nodes'][0].split(" "))
803 for b in self.repo.branches(nodes):
804 req.write(" ".join(map(hex, b)) + "\n")
805
806 elif cmd == 'between':
807 req.httphdr("application/mercurial-0.1")
808 nodes = []
809 if req.form.has_key('pairs'):
810 pairs = [map(bin, p.split("-"))
811 for p in req.form['pairs'][0].split(" ")]
812 for b in self.repo.between(pairs):
813 req.write(" ".join(map(hex, b)) + "\n")
814
815 elif cmd == 'changegroup':
816 req.httphdr("application/mercurial-0.1")
817 nodes = []
818 if not self.allowpull:
819 return
820
821 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
823
824 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
827 chunk = f.read(4096)
828 if not chunk:
829 break
830 req.write(z.compress(chunk))
831
832 req.write(z.flush())
833
834 elif cmd == 'archive':
835 changeset = self.repo.lookup(req.form['node'][0])
836 type = req.form['type'][0]
837 if (type in self.archives and
838 self.repo.ui.configbool("web", "allow" + type, False)):
839 self.archive(req, changeset, type)
840 return
841
842 req.write(self.t("error"))
843
844 elif cmd == 'static':
845 fname = req.form['file'][0]
846 req.write(staticfile(static, fname)
847 or self.t("error", error="%r not found" % fname))
848
849 else:
850 req.write(self.t("error"))
851
852 # This is a stopgap
853 class hgwebdir(object):
854 def __init__(self, config):
855 def cleannames(items):
856 return [(name.strip(os.sep), path) for name, path in items]
857
858 self.motd = ""
859 self.repos_sorted = ('name', False)
860 if isinstance(config, (list, tuple)):
861 self.repos = cleannames(config)
862 self.repos_sorted = ('', False)
863 elif isinstance(config, dict):
864 self.repos = cleannames(config.items())
865 self.repos.sort()
866 else:
867 cp = ConfigParser.SafeConfigParser()
868 cp.read(config)
869 self.repos = []
870 if cp.has_section('web') and cp.has_option('web', 'motd'):
871 self.motd = cp.get('web', 'motd')
872 if cp.has_section('paths'):
873 self.repos.extend(cleannames(cp.items('paths')))
874 if cp.has_section('collections'):
875 for prefix, root in cp.items('collections'):
876 for path in util.walkrepos(root):
877 repo = os.path.normpath(path)
878 name = repo
879 if name.startswith(prefix):
880 name = name[len(prefix):]
881 self.repos.append((name.lstrip(os.sep), repo))
882 self.repos.sort()
883
884 def run(self, req=hgrequest()):
885 def header(**map):
886 yield tmpl("header", **map)
887
888 def footer(**map):
889 yield tmpl("footer", motd=self.motd, **map)
890
891 m = os.path.join(templater.templatepath(), "map")
892 tmpl = templater.templater(m, templater.common_filters,
893 defaults={"header": header,
894 "footer": footer})
895
896 def archivelist(ui, nodeid, url):
897 for i in ['zip', 'gz', 'bz2']:
898 if ui.configbool("web", "allow" + i, False):
899 yield {"type" : i, "node": nodeid, "url": url}
900
901 def entries(sortcolumn="", descending=False, **map):
902 rows = []
903 parity = 0
904 for name, path in self.repos:
905 u = ui.ui()
906 try:
907 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
908 except IOError:
909 pass
910 get = u.config
911
912 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
913 .replace("//", "/"))
914
915 # update time with local timezone
916 try:
917 d = (get_mtime(path), util.makedate()[1])
918 except OSError:
919 continue
920
921 contact = (get("ui", "username") or # preferred
922 get("web", "contact") or # deprecated
923 get("web", "author", "")) # also
924 description = get("web", "description", "")
925 name = get("web", "name", name)
926 row = dict(contact=contact or "unknown",
927 contact_sort=contact.upper() or "unknown",
928 name=name,
929 name_sort=name,
930 url=url,
931 description=description or "unknown",
932 description_sort=description.upper() or "unknown",
933 lastchange=d,
934 lastchange_sort=d[1]-d[0],
935 archives=archivelist(u, "tip", url))
936 if (not sortcolumn
937 or (sortcolumn, descending) == self.repos_sorted):
938 # fast path for unsorted output
939 row['parity'] = parity
940 parity = 1 - parity
941 yield row
942 else:
943 rows.append((row["%s_sort" % sortcolumn], row))
944 if rows:
945 rows.sort()
946 if descending:
947 rows.reverse()
948 for key, row in rows:
949 row['parity'] = parity
950 parity = 1 - parity
951 yield row
952
953 virtual = req.env.get("PATH_INFO", "").strip('/')
954 if virtual:
955 real = dict(self.repos).get(virtual)
956 if real:
957 try:
958 hgweb(real).run(req)
959 except IOError, inst:
960 req.write(tmpl("error", error=inst.strerror))
961 except hg.RepoError, inst:
962 req.write(tmpl("error", error=str(inst)))
963 else:
964 req.write(tmpl("notfound", repo=virtual))
965 else:
966 if req.form.has_key('static'):
967 static = os.path.join(templater.templatepath(), "static")
968 fname = req.form['static'][0]
969 req.write(staticfile(static, fname)
970 or tmpl("error", error="%r not found" % fname))
971 else:
972 sortable = ["name", "description", "contact", "lastchange"]
973 sortcolumn, descending = self.repos_sorted
974 if req.form.has_key('sort'):
975 sortcolumn = req.form['sort'][0]
976 descending = sortcolumn.startswith('-')
977 if descending:
978 sortcolumn = sortcolumn[1:]
979 if sortcolumn not in sortable:
980 sortcolumn = ""
981
982 sort = [("sort_%s" % column,
983 "%s%s" % ((not descending and column == sortcolumn)
984 and "-" or "", column))
985 for column in sortable]
986 req.write(tmpl("index", entries=entries,
987 sortcolumn=sortcolumn, descending=descending,
988 **dict(sort)))
10 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
11 demandload(globals(), "mercurial.hgweb.hgwebdir_mod:hgwebdir")
This diff has been collapsed as it changes many lines, (950 lines changed) Show them Hide them
@@ -6,25 +6,8 b''
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 import os, cgi, sys
10 import mimetypes
11 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
13 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 from mercurial.node import *
17 from mercurial.i18n import gettext as _
18
19 def up(p):
20 if p[0] != "/":
21 p = "/" + p
22 if p[-1] == "/":
23 p = p[:-1]
24 up = os.path.dirname(p)
25 if up == "/":
26 return "/"
27 return up + "/"
9 import os, mimetypes
10 import os.path
28 11
29 12 def get_mtime(repo_path):
30 13 hg_path = os.path.join(repo_path, ".hg")
@@ -57,932 +40,3 b' def staticfile(directory, fname):'
57 40 except (TypeError, OSError):
58 41 # illegal fname or unreadable file
59 42 return ""
60
61 class hgweb(object):
62 def __init__(self, repo, name=None):
63 if type(repo) == type(""):
64 self.repo = hg.repository(ui.ui(), repo)
65 else:
66 self.repo = repo
67
68 self.mtime = -1
69 self.reponame = name
70 self.archives = 'zip', 'gz', 'bz2'
71
72 def refresh(self):
73 mtime = get_mtime(self.repo.root)
74 if mtime != self.mtime:
75 self.mtime = mtime
76 self.repo = hg.repository(self.repo.ui, self.repo.root)
77 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
78 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
79 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
80
81 def archivelist(self, nodeid):
82 for i in self.archives:
83 if self.repo.ui.configbool("web", "allow" + i, False):
84 yield {"type" : i, "node" : nodeid, "url": ""}
85
86 def listfiles(self, files, mf):
87 for f in files[:self.maxfiles]:
88 yield self.t("filenodelink", node=hex(mf[f]), file=f)
89 if len(files) > self.maxfiles:
90 yield self.t("fileellipses")
91
92 def listfilediffs(self, files, changeset):
93 for f in files[:self.maxfiles]:
94 yield self.t("filedifflink", node=hex(changeset), file=f)
95 if len(files) > self.maxfiles:
96 yield self.t("fileellipses")
97
98 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
99 if not rev:
100 rev = lambda x: ""
101 siblings = [s for s in siblings if s != nullid]
102 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
103 return
104 for s in siblings:
105 yield dict(node=hex(s), rev=rev(s), **args)
106
107 def renamelink(self, fl, node):
108 r = fl.renamed(node)
109 if r:
110 return [dict(file=r[0], node=hex(r[1]))]
111 return []
112
113 def showtag(self, t1, node=nullid, **args):
114 for t in self.repo.nodetags(node):
115 yield self.t(t1, tag=t, **args)
116
117 def diff(self, node1, node2, files):
118 def filterfiles(filters, files):
119 l = [x for x in files if x in filters]
120
121 for t in filters:
122 if t and t[-1] != os.sep:
123 t += os.sep
124 l += [x for x in files if x.startswith(t)]
125 return l
126
127 parity = [0]
128 def diffblock(diff, f, fn):
129 yield self.t("diffblock",
130 lines=prettyprintlines(diff),
131 parity=parity[0],
132 file=f,
133 filenode=hex(fn or nullid))
134 parity[0] = 1 - parity[0]
135
136 def prettyprintlines(diff):
137 for l in diff.splitlines(1):
138 if l.startswith('+'):
139 yield self.t("difflineplus", line=l)
140 elif l.startswith('-'):
141 yield self.t("difflineminus", line=l)
142 elif l.startswith('@'):
143 yield self.t("difflineat", line=l)
144 else:
145 yield self.t("diffline", line=l)
146
147 r = self.repo
148 cl = r.changelog
149 mf = r.manifest
150 change1 = cl.read(node1)
151 change2 = cl.read(node2)
152 mmap1 = mf.read(change1[0])
153 mmap2 = mf.read(change2[0])
154 date1 = util.datestr(change1[2])
155 date2 = util.datestr(change2[2])
156
157 modified, added, removed, deleted, unknown = r.changes(node1, node2)
158 if files:
159 modified, added, removed = map(lambda x: filterfiles(files, x),
160 (modified, added, removed))
161
162 diffopts = self.repo.ui.diffopts()
163 showfunc = diffopts['showfunc']
164 ignorews = diffopts['ignorews']
165 for f in modified:
166 to = r.file(f).read(mmap1[f])
167 tn = r.file(f).read(mmap2[f])
168 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
169 showfunc=showfunc, ignorews=ignorews), f, tn)
170 for f in added:
171 to = None
172 tn = r.file(f).read(mmap2[f])
173 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
174 showfunc=showfunc, ignorews=ignorews), f, tn)
175 for f in removed:
176 to = r.file(f).read(mmap1[f])
177 tn = None
178 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
179 showfunc=showfunc, ignorews=ignorews), f, tn)
180
181 def changelog(self, pos):
182 def changenav(**map):
183 def seq(factor, maxchanges=None):
184 if maxchanges:
185 yield maxchanges
186 if maxchanges >= 20 and maxchanges <= 40:
187 yield 50
188 else:
189 yield 1 * factor
190 yield 3 * factor
191 for f in seq(factor * 10):
192 yield f
193
194 l = []
195 last = 0
196 for f in seq(1, self.maxchanges):
197 if f < self.maxchanges or f <= last:
198 continue
199 if f > count:
200 break
201 last = f
202 r = "%d" % f
203 if pos + f < count:
204 l.append(("+" + r, pos + f))
205 if pos - f >= 0:
206 l.insert(0, ("-" + r, pos - f))
207
208 yield {"rev": 0, "label": "(0)"}
209
210 for label, rev in l:
211 yield {"label": label, "rev": rev}
212
213 yield {"label": "tip", "rev": "tip"}
214
215 def changelist(**map):
216 parity = (start - end) & 1
217 cl = self.repo.changelog
218 l = [] # build a list in forward order for efficiency
219 for i in range(start, end):
220 n = cl.node(i)
221 changes = cl.read(n)
222 hn = hex(n)
223
224 l.insert(0, {"parity": parity,
225 "author": changes[1],
226 "parent": self.siblings(cl.parents(n), cl.rev,
227 cl.rev(n) - 1),
228 "child": self.siblings(cl.children(n), cl.rev,
229 cl.rev(n) + 1),
230 "changelogtag": self.showtag("changelogtag",n),
231 "manifest": hex(changes[0]),
232 "desc": changes[4],
233 "date": changes[2],
234 "files": self.listfilediffs(changes[3], n),
235 "rev": i,
236 "node": hn})
237 parity = 1 - parity
238
239 for e in l:
240 yield e
241
242 cl = self.repo.changelog
243 mf = cl.read(cl.tip())[0]
244 count = cl.count()
245 start = max(0, pos - self.maxchanges + 1)
246 end = min(count, start + self.maxchanges)
247 pos = end - 1
248
249 yield self.t('changelog',
250 changenav=changenav,
251 manifest=hex(mf),
252 rev=pos, changesets=count, entries=changelist,
253 archives=self.archivelist("tip"))
254
255 def search(self, query):
256
257 def changelist(**map):
258 cl = self.repo.changelog
259 count = 0
260 qw = query.lower().split()
261
262 def revgen():
263 for i in range(cl.count() - 1, 0, -100):
264 l = []
265 for j in range(max(0, i - 100), i):
266 n = cl.node(j)
267 changes = cl.read(n)
268 l.append((n, j, changes))
269 l.reverse()
270 for e in l:
271 yield e
272
273 for n, i, changes in revgen():
274 miss = 0
275 for q in qw:
276 if not (q in changes[1].lower() or
277 q in changes[4].lower() or
278 q in " ".join(changes[3][:20]).lower()):
279 miss = 1
280 break
281 if miss:
282 continue
283
284 count += 1
285 hn = hex(n)
286
287 yield self.t('searchentry',
288 parity=count & 1,
289 author=changes[1],
290 parent=self.siblings(cl.parents(n), cl.rev),
291 child=self.siblings(cl.children(n), cl.rev),
292 changelogtag=self.showtag("changelogtag",n),
293 manifest=hex(changes[0]),
294 desc=changes[4],
295 date=changes[2],
296 files=self.listfilediffs(changes[3], n),
297 rev=i,
298 node=hn)
299
300 if count >= self.maxchanges:
301 break
302
303 cl = self.repo.changelog
304 mf = cl.read(cl.tip())[0]
305
306 yield self.t('search',
307 query=query,
308 manifest=hex(mf),
309 entries=changelist)
310
311 def changeset(self, nodeid):
312 cl = self.repo.changelog
313 n = self.repo.lookup(nodeid)
314 nodeid = hex(n)
315 changes = cl.read(n)
316 p1 = cl.parents(n)[0]
317
318 files = []
319 mf = self.repo.manifest.read(changes[0])
320 for f in changes[3]:
321 files.append(self.t("filenodelink",
322 filenode=hex(mf.get(f, nullid)), file=f))
323
324 def diff(**map):
325 yield self.diff(p1, n, None)
326
327 yield self.t('changeset',
328 diff=diff,
329 rev=cl.rev(n),
330 node=nodeid,
331 parent=self.siblings(cl.parents(n), cl.rev),
332 child=self.siblings(cl.children(n), cl.rev),
333 changesettag=self.showtag("changesettag",n),
334 manifest=hex(changes[0]),
335 author=changes[1],
336 desc=changes[4],
337 date=changes[2],
338 files=files,
339 archives=self.archivelist(nodeid))
340
341 def filelog(self, f, filenode):
342 cl = self.repo.changelog
343 fl = self.repo.file(f)
344 filenode = hex(fl.lookup(filenode))
345 count = fl.count()
346
347 def entries(**map):
348 l = []
349 parity = (count - 1) & 1
350
351 for i in range(count):
352 n = fl.node(i)
353 lr = fl.linkrev(n)
354 cn = cl.node(lr)
355 cs = cl.read(cl.node(lr))
356
357 l.insert(0, {"parity": parity,
358 "filenode": hex(n),
359 "filerev": i,
360 "file": f,
361 "node": hex(cn),
362 "author": cs[1],
363 "date": cs[2],
364 "rename": self.renamelink(fl, n),
365 "parent": self.siblings(fl.parents(n),
366 fl.rev, file=f),
367 "child": self.siblings(fl.children(n),
368 fl.rev, file=f),
369 "desc": cs[4]})
370 parity = 1 - parity
371
372 for e in l:
373 yield e
374
375 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
376
377 def filerevision(self, f, node):
378 fl = self.repo.file(f)
379 n = fl.lookup(node)
380 node = hex(n)
381 text = fl.read(n)
382 changerev = fl.linkrev(n)
383 cl = self.repo.changelog
384 cn = cl.node(changerev)
385 cs = cl.read(cn)
386 mfn = cs[0]
387
388 mt = mimetypes.guess_type(f)[0]
389 rawtext = text
390 if util.binary(text):
391 mt = mt or 'application/octet-stream'
392 text = "(binary:%s)" % mt
393 mt = mt or 'text/plain'
394
395 def lines():
396 for l, t in enumerate(text.splitlines(1)):
397 yield {"line": t,
398 "linenumber": "% 6d" % (l + 1),
399 "parity": l & 1}
400
401 yield self.t("filerevision",
402 file=f,
403 filenode=node,
404 path=up(f),
405 text=lines(),
406 raw=rawtext,
407 mimetype=mt,
408 rev=changerev,
409 node=hex(cn),
410 manifest=hex(mfn),
411 author=cs[1],
412 date=cs[2],
413 parent=self.siblings(fl.parents(n), fl.rev, file=f),
414 child=self.siblings(fl.children(n), fl.rev, file=f),
415 rename=self.renamelink(fl, n),
416 permissions=self.repo.manifest.readflags(mfn)[f])
417
418 def fileannotate(self, f, node):
419 bcache = {}
420 ncache = {}
421 fl = self.repo.file(f)
422 n = fl.lookup(node)
423 node = hex(n)
424 changerev = fl.linkrev(n)
425
426 cl = self.repo.changelog
427 cn = cl.node(changerev)
428 cs = cl.read(cn)
429 mfn = cs[0]
430
431 def annotate(**map):
432 parity = 1
433 last = None
434 for r, l in fl.annotate(n):
435 try:
436 cnode = ncache[r]
437 except KeyError:
438 cnode = ncache[r] = self.repo.changelog.node(r)
439
440 try:
441 name = bcache[r]
442 except KeyError:
443 cl = self.repo.changelog.read(cnode)
444 bcache[r] = name = self.repo.ui.shortuser(cl[1])
445
446 if last != cnode:
447 parity = 1 - parity
448 last = cnode
449
450 yield {"parity": parity,
451 "node": hex(cnode),
452 "rev": r,
453 "author": name,
454 "file": f,
455 "line": l}
456
457 yield self.t("fileannotate",
458 file=f,
459 filenode=node,
460 annotate=annotate,
461 path=up(f),
462 rev=changerev,
463 node=hex(cn),
464 manifest=hex(mfn),
465 author=cs[1],
466 date=cs[2],
467 rename=self.renamelink(fl, n),
468 parent=self.siblings(fl.parents(n), fl.rev, file=f),
469 child=self.siblings(fl.children(n), fl.rev, file=f),
470 permissions=self.repo.manifest.readflags(mfn)[f])
471
472 def manifest(self, mnode, path):
473 man = self.repo.manifest
474 mn = man.lookup(mnode)
475 mnode = hex(mn)
476 mf = man.read(mn)
477 rev = man.rev(mn)
478 changerev = man.linkrev(mn)
479 node = self.repo.changelog.node(changerev)
480 mff = man.readflags(mn)
481
482 files = {}
483
484 p = path[1:]
485 if p and p[-1] != "/":
486 p += "/"
487 l = len(p)
488
489 for f,n in mf.items():
490 if f[:l] != p:
491 continue
492 remain = f[l:]
493 if "/" in remain:
494 short = remain[:remain.find("/") + 1] # bleah
495 files[short] = (f, None)
496 else:
497 short = os.path.basename(remain)
498 files[short] = (f, n)
499
500 def filelist(**map):
501 parity = 0
502 fl = files.keys()
503 fl.sort()
504 for f in fl:
505 full, fnode = files[f]
506 if not fnode:
507 continue
508
509 yield {"file": full,
510 "manifest": mnode,
511 "filenode": hex(fnode),
512 "parity": parity,
513 "basename": f,
514 "permissions": mff[full]}
515 parity = 1 - parity
516
517 def dirlist(**map):
518 parity = 0
519 fl = files.keys()
520 fl.sort()
521 for f in fl:
522 full, fnode = files[f]
523 if fnode:
524 continue
525
526 yield {"parity": parity,
527 "path": os.path.join(path, f),
528 "manifest": mnode,
529 "basename": f[:-1]}
530 parity = 1 - parity
531
532 yield self.t("manifest",
533 manifest=mnode,
534 rev=rev,
535 node=hex(node),
536 path=path,
537 up=up(path),
538 fentries=filelist,
539 dentries=dirlist,
540 archives=self.archivelist(hex(node)))
541
542 def tags(self):
543 cl = self.repo.changelog
544 mf = cl.read(cl.tip())[0]
545
546 i = self.repo.tagslist()
547 i.reverse()
548
549 def entries(notip=False, **map):
550 parity = 0
551 for k,n in i:
552 if notip and k == "tip": continue
553 yield {"parity": parity,
554 "tag": k,
555 "tagmanifest": hex(cl.read(n)[0]),
556 "date": cl.read(n)[2],
557 "node": hex(n)}
558 parity = 1 - parity
559
560 yield self.t("tags",
561 manifest=hex(mf),
562 entries=lambda **x: entries(False, **x),
563 entriesnotip=lambda **x: entries(True, **x))
564
565 def summary(self):
566 cl = self.repo.changelog
567 mf = cl.read(cl.tip())[0]
568
569 i = self.repo.tagslist()
570 i.reverse()
571
572 def tagentries(**map):
573 parity = 0
574 count = 0
575 for k,n in i:
576 if k == "tip": # skip tip
577 continue;
578
579 count += 1
580 if count > 10: # limit to 10 tags
581 break;
582
583 c = cl.read(n)
584 m = c[0]
585 t = c[2]
586
587 yield self.t("tagentry",
588 parity = parity,
589 tag = k,
590 node = hex(n),
591 date = t,
592 tagmanifest = hex(m))
593 parity = 1 - parity
594
595 def changelist(**map):
596 parity = 0
597 cl = self.repo.changelog
598 l = [] # build a list in forward order for efficiency
599 for i in range(start, end):
600 n = cl.node(i)
601 changes = cl.read(n)
602 hn = hex(n)
603 t = changes[2]
604
605 l.insert(0, self.t(
606 'shortlogentry',
607 parity = parity,
608 author = changes[1],
609 manifest = hex(changes[0]),
610 desc = changes[4],
611 date = t,
612 rev = i,
613 node = hn))
614 parity = 1 - parity
615
616 yield l
617
618 cl = self.repo.changelog
619 mf = cl.read(cl.tip())[0]
620 count = cl.count()
621 start = max(0, count - self.maxchanges)
622 end = min(count, start + self.maxchanges)
623 pos = end - 1
624
625 yield self.t("summary",
626 desc = self.repo.ui.config("web", "description", "unknown"),
627 owner = (self.repo.ui.config("ui", "username") or # preferred
628 self.repo.ui.config("web", "contact") or # deprecated
629 self.repo.ui.config("web", "author", "unknown")), # also
630 lastchange = (0, 0), # FIXME
631 manifest = hex(mf),
632 tags = tagentries,
633 shortlog = changelist)
634
635 def filediff(self, file, changeset):
636 cl = self.repo.changelog
637 n = self.repo.lookup(changeset)
638 changeset = hex(n)
639 p1 = cl.parents(n)[0]
640 cs = cl.read(n)
641 mf = self.repo.manifest.read(cs[0])
642
643 def diff(**map):
644 yield self.diff(p1, n, [file])
645
646 yield self.t("filediff",
647 file=file,
648 filenode=hex(mf.get(file, nullid)),
649 node=changeset,
650 rev=self.repo.changelog.rev(n),
651 parent=self.siblings(cl.parents(n), cl.rev),
652 child=self.siblings(cl.children(n), cl.rev),
653 diff=diff)
654
655 archive_specs = {
656 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
657 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
658 'zip': ('application/zip', 'zip', '.zip', None),
659 }
660
661 def archive(self, req, cnode, type):
662 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
663 name = "%s-%s" % (reponame, short(cnode))
664 mimetype, artype, extension, encoding = self.archive_specs[type]
665 headers = [('Content-type', mimetype),
666 ('Content-disposition', 'attachment; filename=%s%s' %
667 (name, extension))]
668 if encoding:
669 headers.append(('Content-encoding', encoding))
670 req.header(headers)
671 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
672
673 # add tags to things
674 # tags -> list of changesets corresponding to tags
675 # find tag, changeset, file
676
677 def run(self, req=hgrequest()):
678 def clean(path):
679 p = util.normpath(path)
680 if p[:2] == "..":
681 raise "suspicious path"
682 return p
683
684 def header(**map):
685 yield self.t("header", **map)
686
687 def footer(**map):
688 yield self.t("footer",
689 motd=self.repo.ui.config("web", "motd", ""),
690 **map)
691
692 def expand_form(form):
693 shortcuts = {
694 'cl': [('cmd', ['changelog']), ('rev', None)],
695 'cs': [('cmd', ['changeset']), ('node', None)],
696 'f': [('cmd', ['file']), ('filenode', None)],
697 'fl': [('cmd', ['filelog']), ('filenode', None)],
698 'fd': [('cmd', ['filediff']), ('node', None)],
699 'fa': [('cmd', ['annotate']), ('filenode', None)],
700 'mf': [('cmd', ['manifest']), ('manifest', None)],
701 'ca': [('cmd', ['archive']), ('node', None)],
702 'tags': [('cmd', ['tags'])],
703 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
704 'static': [('cmd', ['static']), ('file', None)]
705 }
706
707 for k in shortcuts.iterkeys():
708 if form.has_key(k):
709 for name, value in shortcuts[k]:
710 if value is None:
711 value = form[k]
712 form[name] = value
713 del form[k]
714
715 self.refresh()
716
717 expand_form(req.form)
718
719 t = self.repo.ui.config("web", "templates", templater.templatepath())
720 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
721 m = os.path.join(t, "map")
722 style = self.repo.ui.config("web", "style", "")
723 if req.form.has_key('style'):
724 style = req.form['style'][0]
725 if style:
726 b = os.path.basename("map-" + style)
727 p = os.path.join(t, b)
728 if os.path.isfile(p):
729 m = p
730
731 port = req.env["SERVER_PORT"]
732 port = port != "80" and (":" + port) or ""
733 uri = req.env["REQUEST_URI"]
734 if "?" in uri:
735 uri = uri.split("?")[0]
736 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
737 if not self.reponame:
738 self.reponame = (self.repo.ui.config("web", "name")
739 or uri.strip('/') or self.repo.root)
740
741 self.t = templater.templater(m, templater.common_filters,
742 defaults={"url": url,
743 "repo": self.reponame,
744 "header": header,
745 "footer": footer,
746 })
747
748 if not req.form.has_key('cmd'):
749 req.form['cmd'] = [self.t.cache['default'],]
750
751 cmd = req.form['cmd'][0]
752 if cmd == 'changelog':
753 hi = self.repo.changelog.count() - 1
754 if req.form.has_key('rev'):
755 hi = req.form['rev'][0]
756 try:
757 hi = self.repo.changelog.rev(self.repo.lookup(hi))
758 except hg.RepoError:
759 req.write(self.search(hi)) # XXX redirect to 404 page?
760 return
761
762 req.write(self.changelog(hi))
763
764 elif cmd == 'changeset':
765 req.write(self.changeset(req.form['node'][0]))
766
767 elif cmd == 'manifest':
768 req.write(self.manifest(req.form['manifest'][0],
769 clean(req.form['path'][0])))
770
771 elif cmd == 'tags':
772 req.write(self.tags())
773
774 elif cmd == 'summary':
775 req.write(self.summary())
776
777 elif cmd == 'filediff':
778 req.write(self.filediff(clean(req.form['file'][0]),
779 req.form['node'][0]))
780
781 elif cmd == 'file':
782 req.write(self.filerevision(clean(req.form['file'][0]),
783 req.form['filenode'][0]))
784
785 elif cmd == 'annotate':
786 req.write(self.fileannotate(clean(req.form['file'][0]),
787 req.form['filenode'][0]))
788
789 elif cmd == 'filelog':
790 req.write(self.filelog(clean(req.form['file'][0]),
791 req.form['filenode'][0]))
792
793 elif cmd == 'heads':
794 req.httphdr("application/mercurial-0.1")
795 h = self.repo.heads()
796 req.write(" ".join(map(hex, h)) + "\n")
797
798 elif cmd == 'branches':
799 req.httphdr("application/mercurial-0.1")
800 nodes = []
801 if req.form.has_key('nodes'):
802 nodes = map(bin, req.form['nodes'][0].split(" "))
803 for b in self.repo.branches(nodes):
804 req.write(" ".join(map(hex, b)) + "\n")
805
806 elif cmd == 'between':
807 req.httphdr("application/mercurial-0.1")
808 nodes = []
809 if req.form.has_key('pairs'):
810 pairs = [map(bin, p.split("-"))
811 for p in req.form['pairs'][0].split(" ")]
812 for b in self.repo.between(pairs):
813 req.write(" ".join(map(hex, b)) + "\n")
814
815 elif cmd == 'changegroup':
816 req.httphdr("application/mercurial-0.1")
817 nodes = []
818 if not self.allowpull:
819 return
820
821 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
823
824 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
827 chunk = f.read(4096)
828 if not chunk:
829 break
830 req.write(z.compress(chunk))
831
832 req.write(z.flush())
833
834 elif cmd == 'archive':
835 changeset = self.repo.lookup(req.form['node'][0])
836 type = req.form['type'][0]
837 if (type in self.archives and
838 self.repo.ui.configbool("web", "allow" + type, False)):
839 self.archive(req, changeset, type)
840 return
841
842 req.write(self.t("error"))
843
844 elif cmd == 'static':
845 fname = req.form['file'][0]
846 req.write(staticfile(static, fname)
847 or self.t("error", error="%r not found" % fname))
848
849 else:
850 req.write(self.t("error"))
851
852 # This is a stopgap
853 class hgwebdir(object):
854 def __init__(self, config):
855 def cleannames(items):
856 return [(name.strip(os.sep), path) for name, path in items]
857
858 self.motd = ""
859 self.repos_sorted = ('name', False)
860 if isinstance(config, (list, tuple)):
861 self.repos = cleannames(config)
862 self.repos_sorted = ('', False)
863 elif isinstance(config, dict):
864 self.repos = cleannames(config.items())
865 self.repos.sort()
866 else:
867 cp = ConfigParser.SafeConfigParser()
868 cp.read(config)
869 self.repos = []
870 if cp.has_section('web') and cp.has_option('web', 'motd'):
871 self.motd = cp.get('web', 'motd')
872 if cp.has_section('paths'):
873 self.repos.extend(cleannames(cp.items('paths')))
874 if cp.has_section('collections'):
875 for prefix, root in cp.items('collections'):
876 for path in util.walkrepos(root):
877 repo = os.path.normpath(path)
878 name = repo
879 if name.startswith(prefix):
880 name = name[len(prefix):]
881 self.repos.append((name.lstrip(os.sep), repo))
882 self.repos.sort()
883
884 def run(self, req=hgrequest()):
885 def header(**map):
886 yield tmpl("header", **map)
887
888 def footer(**map):
889 yield tmpl("footer", motd=self.motd, **map)
890
891 m = os.path.join(templater.templatepath(), "map")
892 tmpl = templater.templater(m, templater.common_filters,
893 defaults={"header": header,
894 "footer": footer})
895
896 def archivelist(ui, nodeid, url):
897 for i in ['zip', 'gz', 'bz2']:
898 if ui.configbool("web", "allow" + i, False):
899 yield {"type" : i, "node": nodeid, "url": url}
900
901 def entries(sortcolumn="", descending=False, **map):
902 rows = []
903 parity = 0
904 for name, path in self.repos:
905 u = ui.ui()
906 try:
907 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
908 except IOError:
909 pass
910 get = u.config
911
912 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
913 .replace("//", "/"))
914
915 # update time with local timezone
916 try:
917 d = (get_mtime(path), util.makedate()[1])
918 except OSError:
919 continue
920
921 contact = (get("ui", "username") or # preferred
922 get("web", "contact") or # deprecated
923 get("web", "author", "")) # also
924 description = get("web", "description", "")
925 name = get("web", "name", name)
926 row = dict(contact=contact or "unknown",
927 contact_sort=contact.upper() or "unknown",
928 name=name,
929 name_sort=name,
930 url=url,
931 description=description or "unknown",
932 description_sort=description.upper() or "unknown",
933 lastchange=d,
934 lastchange_sort=d[1]-d[0],
935 archives=archivelist(u, "tip", url))
936 if (not sortcolumn
937 or (sortcolumn, descending) == self.repos_sorted):
938 # fast path for unsorted output
939 row['parity'] = parity
940 parity = 1 - parity
941 yield row
942 else:
943 rows.append((row["%s_sort" % sortcolumn], row))
944 if rows:
945 rows.sort()
946 if descending:
947 rows.reverse()
948 for key, row in rows:
949 row['parity'] = parity
950 parity = 1 - parity
951 yield row
952
953 virtual = req.env.get("PATH_INFO", "").strip('/')
954 if virtual:
955 real = dict(self.repos).get(virtual)
956 if real:
957 try:
958 hgweb(real).run(req)
959 except IOError, inst:
960 req.write(tmpl("error", error=inst.strerror))
961 except hg.RepoError, inst:
962 req.write(tmpl("error", error=str(inst)))
963 else:
964 req.write(tmpl("notfound", repo=virtual))
965 else:
966 if req.form.has_key('static'):
967 static = os.path.join(templater.templatepath(), "static")
968 fname = req.form['static'][0]
969 req.write(staticfile(static, fname)
970 or tmpl("error", error="%r not found" % fname))
971 else:
972 sortable = ["name", "description", "contact", "lastchange"]
973 sortcolumn, descending = self.repos_sorted
974 if req.form.has_key('sort'):
975 sortcolumn = req.form['sort'][0]
976 descending = sortcolumn.startswith('-')
977 if descending:
978 sortcolumn = sortcolumn[1:]
979 if sortcolumn not in sortable:
980 sortcolumn = ""
981
982 sort = [("sort_%s" % column,
983 "%s%s" % ((not descending and column == sortcolumn)
984 and "-" or "", column))
985 for column in sortable]
986 req.write(tmpl("index", entries=entries,
987 sortcolumn=sortcolumn, descending=descending,
988 **dict(sort)))
@@ -6,17 +6,18 b''
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 import os, cgi, sys
9 import os
10 import os.path
10 11 import mimetypes
11 12 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
13 demandload(globals(), "re zlib ConfigParser")
13 14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 17 from mercurial.node import *
17 18 from mercurial.i18n import gettext as _
18 19
19 def up(p):
20 def _up(p):
20 21 if p[0] != "/":
21 22 p = "/" + p
22 23 if p[-1] == "/":
@@ -26,38 +27,6 b' def up(p):'
26 27 return "/"
27 28 return up + "/"
28 29
29 def get_mtime(repo_path):
30 hg_path = os.path.join(repo_path, ".hg")
31 cl_path = os.path.join(hg_path, "00changelog.i")
32 if os.path.exists(os.path.join(cl_path)):
33 return os.stat(cl_path).st_mtime
34 else:
35 return os.stat(hg_path).st_mtime
36
37 def staticfile(directory, fname):
38 """return a file inside directory with guessed content-type header
39
40 fname always uses '/' as directory separator and isn't allowed to
41 contain unusual path components.
42 Content-type is guessed using the mimetypes module.
43 Return an empty string if fname is illegal or file not found.
44
45 """
46 parts = fname.split('/')
47 path = directory
48 for part in parts:
49 if (part in ('', os.curdir, os.pardir) or
50 os.sep in part or os.altsep is not None and os.altsep in part):
51 return ""
52 path = os.path.join(path, part)
53 try:
54 os.stat(path)
55 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 return "Content-type: %s\n\n%s" % (ct, file(path).read())
57 except (TypeError, OSError):
58 # illegal fname or unreadable file
59 return ""
60
61 30 class hgweb(object):
62 31 def __init__(self, repo, name=None):
63 32 if type(repo) == type(""):
@@ -401,7 +370,7 b' class hgweb(object):'
401 370 yield self.t("filerevision",
402 371 file=f,
403 372 filenode=node,
404 path=up(f),
373 path=_up(f),
405 374 text=lines(),
406 375 raw=rawtext,
407 376 mimetype=mt,
@@ -458,7 +427,7 b' class hgweb(object):'
458 427 file=f,
459 428 filenode=node,
460 429 annotate=annotate,
461 path=up(f),
430 path=_up(f),
462 431 rev=changerev,
463 432 node=hex(cn),
464 433 manifest=hex(mfn),
@@ -534,7 +503,7 b' class hgweb(object):'
534 503 rev=rev,
535 504 node=hex(node),
536 505 path=path,
537 up=up(path),
506 up=_up(path),
538 507 fentries=filelist,
539 508 dentries=dirlist,
540 509 archives=self.archivelist(hex(node)))
@@ -848,141 +817,3 b' class hgweb(object):'
848 817
849 818 else:
850 819 req.write(self.t("error"))
851
852 # This is a stopgap
853 class hgwebdir(object):
854 def __init__(self, config):
855 def cleannames(items):
856 return [(name.strip(os.sep), path) for name, path in items]
857
858 self.motd = ""
859 self.repos_sorted = ('name', False)
860 if isinstance(config, (list, tuple)):
861 self.repos = cleannames(config)
862 self.repos_sorted = ('', False)
863 elif isinstance(config, dict):
864 self.repos = cleannames(config.items())
865 self.repos.sort()
866 else:
867 cp = ConfigParser.SafeConfigParser()
868 cp.read(config)
869 self.repos = []
870 if cp.has_section('web') and cp.has_option('web', 'motd'):
871 self.motd = cp.get('web', 'motd')
872 if cp.has_section('paths'):
873 self.repos.extend(cleannames(cp.items('paths')))
874 if cp.has_section('collections'):
875 for prefix, root in cp.items('collections'):
876 for path in util.walkrepos(root):
877 repo = os.path.normpath(path)
878 name = repo
879 if name.startswith(prefix):
880 name = name[len(prefix):]
881 self.repos.append((name.lstrip(os.sep), repo))
882 self.repos.sort()
883
884 def run(self, req=hgrequest()):
885 def header(**map):
886 yield tmpl("header", **map)
887
888 def footer(**map):
889 yield tmpl("footer", motd=self.motd, **map)
890
891 m = os.path.join(templater.templatepath(), "map")
892 tmpl = templater.templater(m, templater.common_filters,
893 defaults={"header": header,
894 "footer": footer})
895
896 def archivelist(ui, nodeid, url):
897 for i in ['zip', 'gz', 'bz2']:
898 if ui.configbool("web", "allow" + i, False):
899 yield {"type" : i, "node": nodeid, "url": url}
900
901 def entries(sortcolumn="", descending=False, **map):
902 rows = []
903 parity = 0
904 for name, path in self.repos:
905 u = ui.ui()
906 try:
907 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
908 except IOError:
909 pass
910 get = u.config
911
912 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
913 .replace("//", "/"))
914
915 # update time with local timezone
916 try:
917 d = (get_mtime(path), util.makedate()[1])
918 except OSError:
919 continue
920
921 contact = (get("ui", "username") or # preferred
922 get("web", "contact") or # deprecated
923 get("web", "author", "")) # also
924 description = get("web", "description", "")
925 name = get("web", "name", name)
926 row = dict(contact=contact or "unknown",
927 contact_sort=contact.upper() or "unknown",
928 name=name,
929 name_sort=name,
930 url=url,
931 description=description or "unknown",
932 description_sort=description.upper() or "unknown",
933 lastchange=d,
934 lastchange_sort=d[1]-d[0],
935 archives=archivelist(u, "tip", url))
936 if (not sortcolumn
937 or (sortcolumn, descending) == self.repos_sorted):
938 # fast path for unsorted output
939 row['parity'] = parity
940 parity = 1 - parity
941 yield row
942 else:
943 rows.append((row["%s_sort" % sortcolumn], row))
944 if rows:
945 rows.sort()
946 if descending:
947 rows.reverse()
948 for key, row in rows:
949 row['parity'] = parity
950 parity = 1 - parity
951 yield row
952
953 virtual = req.env.get("PATH_INFO", "").strip('/')
954 if virtual:
955 real = dict(self.repos).get(virtual)
956 if real:
957 try:
958 hgweb(real).run(req)
959 except IOError, inst:
960 req.write(tmpl("error", error=inst.strerror))
961 except hg.RepoError, inst:
962 req.write(tmpl("error", error=str(inst)))
963 else:
964 req.write(tmpl("notfound", repo=virtual))
965 else:
966 if req.form.has_key('static'):
967 static = os.path.join(templater.templatepath(), "static")
968 fname = req.form['static'][0]
969 req.write(staticfile(static, fname)
970 or tmpl("error", error="%r not found" % fname))
971 else:
972 sortable = ["name", "description", "contact", "lastchange"]
973 sortcolumn, descending = self.repos_sorted
974 if req.form.has_key('sort'):
975 sortcolumn = req.form['sort'][0]
976 descending = sortcolumn.startswith('-')
977 if descending:
978 sortcolumn = sortcolumn[1:]
979 if sortcolumn not in sortable:
980 sortcolumn = ""
981
982 sort = [("sort_%s" % column,
983 "%s%s" % ((not descending and column == sortcolumn)
984 and "-" or "", column))
985 for column in sortable]
986 req.write(tmpl("index", entries=entries,
987 sortcolumn=sortcolumn, descending=descending,
988 **dict(sort)))
This diff has been collapsed as it changes many lines, (842 lines changed) Show them Hide them
@@ -6,849 +6,13 b''
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 import os, cgi, sys
10 import mimetypes
9 import os
11 10 from mercurial.demandload import demandload
12 demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
13 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
11 demandload(globals(), "ConfigParser")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
14 13 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.server:create_server")
16 from mercurial.node import *
17 14 from mercurial.i18n import gettext as _
18 15
19 def up(p):
20 if p[0] != "/":
21 p = "/" + p
22 if p[-1] == "/":
23 p = p[:-1]
24 up = os.path.dirname(p)
25 if up == "/":
26 return "/"
27 return up + "/"
28
29 def get_mtime(repo_path):
30 hg_path = os.path.join(repo_path, ".hg")
31 cl_path = os.path.join(hg_path, "00changelog.i")
32 if os.path.exists(os.path.join(cl_path)):
33 return os.stat(cl_path).st_mtime
34 else:
35 return os.stat(hg_path).st_mtime
36
37 def staticfile(directory, fname):
38 """return a file inside directory with guessed content-type header
39
40 fname always uses '/' as directory separator and isn't allowed to
41 contain unusual path components.
42 Content-type is guessed using the mimetypes module.
43 Return an empty string if fname is illegal or file not found.
44
45 """
46 parts = fname.split('/')
47 path = directory
48 for part in parts:
49 if (part in ('', os.curdir, os.pardir) or
50 os.sep in part or os.altsep is not None and os.altsep in part):
51 return ""
52 path = os.path.join(path, part)
53 try:
54 os.stat(path)
55 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 return "Content-type: %s\n\n%s" % (ct, file(path).read())
57 except (TypeError, OSError):
58 # illegal fname or unreadable file
59 return ""
60
61 class hgweb(object):
62 def __init__(self, repo, name=None):
63 if type(repo) == type(""):
64 self.repo = hg.repository(ui.ui(), repo)
65 else:
66 self.repo = repo
67
68 self.mtime = -1
69 self.reponame = name
70 self.archives = 'zip', 'gz', 'bz2'
71
72 def refresh(self):
73 mtime = get_mtime(self.repo.root)
74 if mtime != self.mtime:
75 self.mtime = mtime
76 self.repo = hg.repository(self.repo.ui, self.repo.root)
77 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
78 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
79 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
80
81 def archivelist(self, nodeid):
82 for i in self.archives:
83 if self.repo.ui.configbool("web", "allow" + i, False):
84 yield {"type" : i, "node" : nodeid, "url": ""}
85
86 def listfiles(self, files, mf):
87 for f in files[:self.maxfiles]:
88 yield self.t("filenodelink", node=hex(mf[f]), file=f)
89 if len(files) > self.maxfiles:
90 yield self.t("fileellipses")
91
92 def listfilediffs(self, files, changeset):
93 for f in files[:self.maxfiles]:
94 yield self.t("filedifflink", node=hex(changeset), file=f)
95 if len(files) > self.maxfiles:
96 yield self.t("fileellipses")
97
98 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
99 if not rev:
100 rev = lambda x: ""
101 siblings = [s for s in siblings if s != nullid]
102 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
103 return
104 for s in siblings:
105 yield dict(node=hex(s), rev=rev(s), **args)
106
107 def renamelink(self, fl, node):
108 r = fl.renamed(node)
109 if r:
110 return [dict(file=r[0], node=hex(r[1]))]
111 return []
112
113 def showtag(self, t1, node=nullid, **args):
114 for t in self.repo.nodetags(node):
115 yield self.t(t1, tag=t, **args)
116
117 def diff(self, node1, node2, files):
118 def filterfiles(filters, files):
119 l = [x for x in files if x in filters]
120
121 for t in filters:
122 if t and t[-1] != os.sep:
123 t += os.sep
124 l += [x for x in files if x.startswith(t)]
125 return l
126
127 parity = [0]
128 def diffblock(diff, f, fn):
129 yield self.t("diffblock",
130 lines=prettyprintlines(diff),
131 parity=parity[0],
132 file=f,
133 filenode=hex(fn or nullid))
134 parity[0] = 1 - parity[0]
135
136 def prettyprintlines(diff):
137 for l in diff.splitlines(1):
138 if l.startswith('+'):
139 yield self.t("difflineplus", line=l)
140 elif l.startswith('-'):
141 yield self.t("difflineminus", line=l)
142 elif l.startswith('@'):
143 yield self.t("difflineat", line=l)
144 else:
145 yield self.t("diffline", line=l)
146
147 r = self.repo
148 cl = r.changelog
149 mf = r.manifest
150 change1 = cl.read(node1)
151 change2 = cl.read(node2)
152 mmap1 = mf.read(change1[0])
153 mmap2 = mf.read(change2[0])
154 date1 = util.datestr(change1[2])
155 date2 = util.datestr(change2[2])
156
157 modified, added, removed, deleted, unknown = r.changes(node1, node2)
158 if files:
159 modified, added, removed = map(lambda x: filterfiles(files, x),
160 (modified, added, removed))
161
162 diffopts = self.repo.ui.diffopts()
163 showfunc = diffopts['showfunc']
164 ignorews = diffopts['ignorews']
165 for f in modified:
166 to = r.file(f).read(mmap1[f])
167 tn = r.file(f).read(mmap2[f])
168 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
169 showfunc=showfunc, ignorews=ignorews), f, tn)
170 for f in added:
171 to = None
172 tn = r.file(f).read(mmap2[f])
173 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
174 showfunc=showfunc, ignorews=ignorews), f, tn)
175 for f in removed:
176 to = r.file(f).read(mmap1[f])
177 tn = None
178 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
179 showfunc=showfunc, ignorews=ignorews), f, tn)
180
181 def changelog(self, pos):
182 def changenav(**map):
183 def seq(factor, maxchanges=None):
184 if maxchanges:
185 yield maxchanges
186 if maxchanges >= 20 and maxchanges <= 40:
187 yield 50
188 else:
189 yield 1 * factor
190 yield 3 * factor
191 for f in seq(factor * 10):
192 yield f
193
194 l = []
195 last = 0
196 for f in seq(1, self.maxchanges):
197 if f < self.maxchanges or f <= last:
198 continue
199 if f > count:
200 break
201 last = f
202 r = "%d" % f
203 if pos + f < count:
204 l.append(("+" + r, pos + f))
205 if pos - f >= 0:
206 l.insert(0, ("-" + r, pos - f))
207
208 yield {"rev": 0, "label": "(0)"}
209
210 for label, rev in l:
211 yield {"label": label, "rev": rev}
212
213 yield {"label": "tip", "rev": "tip"}
214
215 def changelist(**map):
216 parity = (start - end) & 1
217 cl = self.repo.changelog
218 l = [] # build a list in forward order for efficiency
219 for i in range(start, end):
220 n = cl.node(i)
221 changes = cl.read(n)
222 hn = hex(n)
223
224 l.insert(0, {"parity": parity,
225 "author": changes[1],
226 "parent": self.siblings(cl.parents(n), cl.rev,
227 cl.rev(n) - 1),
228 "child": self.siblings(cl.children(n), cl.rev,
229 cl.rev(n) + 1),
230 "changelogtag": self.showtag("changelogtag",n),
231 "manifest": hex(changes[0]),
232 "desc": changes[4],
233 "date": changes[2],
234 "files": self.listfilediffs(changes[3], n),
235 "rev": i,
236 "node": hn})
237 parity = 1 - parity
238
239 for e in l:
240 yield e
241
242 cl = self.repo.changelog
243 mf = cl.read(cl.tip())[0]
244 count = cl.count()
245 start = max(0, pos - self.maxchanges + 1)
246 end = min(count, start + self.maxchanges)
247 pos = end - 1
248
249 yield self.t('changelog',
250 changenav=changenav,
251 manifest=hex(mf),
252 rev=pos, changesets=count, entries=changelist,
253 archives=self.archivelist("tip"))
254
255 def search(self, query):
256
257 def changelist(**map):
258 cl = self.repo.changelog
259 count = 0
260 qw = query.lower().split()
261
262 def revgen():
263 for i in range(cl.count() - 1, 0, -100):
264 l = []
265 for j in range(max(0, i - 100), i):
266 n = cl.node(j)
267 changes = cl.read(n)
268 l.append((n, j, changes))
269 l.reverse()
270 for e in l:
271 yield e
272
273 for n, i, changes in revgen():
274 miss = 0
275 for q in qw:
276 if not (q in changes[1].lower() or
277 q in changes[4].lower() or
278 q in " ".join(changes[3][:20]).lower()):
279 miss = 1
280 break
281 if miss:
282 continue
283
284 count += 1
285 hn = hex(n)
286
287 yield self.t('searchentry',
288 parity=count & 1,
289 author=changes[1],
290 parent=self.siblings(cl.parents(n), cl.rev),
291 child=self.siblings(cl.children(n), cl.rev),
292 changelogtag=self.showtag("changelogtag",n),
293 manifest=hex(changes[0]),
294 desc=changes[4],
295 date=changes[2],
296 files=self.listfilediffs(changes[3], n),
297 rev=i,
298 node=hn)
299
300 if count >= self.maxchanges:
301 break
302
303 cl = self.repo.changelog
304 mf = cl.read(cl.tip())[0]
305
306 yield self.t('search',
307 query=query,
308 manifest=hex(mf),
309 entries=changelist)
310
311 def changeset(self, nodeid):
312 cl = self.repo.changelog
313 n = self.repo.lookup(nodeid)
314 nodeid = hex(n)
315 changes = cl.read(n)
316 p1 = cl.parents(n)[0]
317
318 files = []
319 mf = self.repo.manifest.read(changes[0])
320 for f in changes[3]:
321 files.append(self.t("filenodelink",
322 filenode=hex(mf.get(f, nullid)), file=f))
323
324 def diff(**map):
325 yield self.diff(p1, n, None)
326
327 yield self.t('changeset',
328 diff=diff,
329 rev=cl.rev(n),
330 node=nodeid,
331 parent=self.siblings(cl.parents(n), cl.rev),
332 child=self.siblings(cl.children(n), cl.rev),
333 changesettag=self.showtag("changesettag",n),
334 manifest=hex(changes[0]),
335 author=changes[1],
336 desc=changes[4],
337 date=changes[2],
338 files=files,
339 archives=self.archivelist(nodeid))
340
341 def filelog(self, f, filenode):
342 cl = self.repo.changelog
343 fl = self.repo.file(f)
344 filenode = hex(fl.lookup(filenode))
345 count = fl.count()
346
347 def entries(**map):
348 l = []
349 parity = (count - 1) & 1
350
351 for i in range(count):
352 n = fl.node(i)
353 lr = fl.linkrev(n)
354 cn = cl.node(lr)
355 cs = cl.read(cl.node(lr))
356
357 l.insert(0, {"parity": parity,
358 "filenode": hex(n),
359 "filerev": i,
360 "file": f,
361 "node": hex(cn),
362 "author": cs[1],
363 "date": cs[2],
364 "rename": self.renamelink(fl, n),
365 "parent": self.siblings(fl.parents(n),
366 fl.rev, file=f),
367 "child": self.siblings(fl.children(n),
368 fl.rev, file=f),
369 "desc": cs[4]})
370 parity = 1 - parity
371
372 for e in l:
373 yield e
374
375 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
376
377 def filerevision(self, f, node):
378 fl = self.repo.file(f)
379 n = fl.lookup(node)
380 node = hex(n)
381 text = fl.read(n)
382 changerev = fl.linkrev(n)
383 cl = self.repo.changelog
384 cn = cl.node(changerev)
385 cs = cl.read(cn)
386 mfn = cs[0]
387
388 mt = mimetypes.guess_type(f)[0]
389 rawtext = text
390 if util.binary(text):
391 mt = mt or 'application/octet-stream'
392 text = "(binary:%s)" % mt
393 mt = mt or 'text/plain'
394
395 def lines():
396 for l, t in enumerate(text.splitlines(1)):
397 yield {"line": t,
398 "linenumber": "% 6d" % (l + 1),
399 "parity": l & 1}
400
401 yield self.t("filerevision",
402 file=f,
403 filenode=node,
404 path=up(f),
405 text=lines(),
406 raw=rawtext,
407 mimetype=mt,
408 rev=changerev,
409 node=hex(cn),
410 manifest=hex(mfn),
411 author=cs[1],
412 date=cs[2],
413 parent=self.siblings(fl.parents(n), fl.rev, file=f),
414 child=self.siblings(fl.children(n), fl.rev, file=f),
415 rename=self.renamelink(fl, n),
416 permissions=self.repo.manifest.readflags(mfn)[f])
417
418 def fileannotate(self, f, node):
419 bcache = {}
420 ncache = {}
421 fl = self.repo.file(f)
422 n = fl.lookup(node)
423 node = hex(n)
424 changerev = fl.linkrev(n)
425
426 cl = self.repo.changelog
427 cn = cl.node(changerev)
428 cs = cl.read(cn)
429 mfn = cs[0]
430
431 def annotate(**map):
432 parity = 1
433 last = None
434 for r, l in fl.annotate(n):
435 try:
436 cnode = ncache[r]
437 except KeyError:
438 cnode = ncache[r] = self.repo.changelog.node(r)
439
440 try:
441 name = bcache[r]
442 except KeyError:
443 cl = self.repo.changelog.read(cnode)
444 bcache[r] = name = self.repo.ui.shortuser(cl[1])
445
446 if last != cnode:
447 parity = 1 - parity
448 last = cnode
449
450 yield {"parity": parity,
451 "node": hex(cnode),
452 "rev": r,
453 "author": name,
454 "file": f,
455 "line": l}
456
457 yield self.t("fileannotate",
458 file=f,
459 filenode=node,
460 annotate=annotate,
461 path=up(f),
462 rev=changerev,
463 node=hex(cn),
464 manifest=hex(mfn),
465 author=cs[1],
466 date=cs[2],
467 rename=self.renamelink(fl, n),
468 parent=self.siblings(fl.parents(n), fl.rev, file=f),
469 child=self.siblings(fl.children(n), fl.rev, file=f),
470 permissions=self.repo.manifest.readflags(mfn)[f])
471
472 def manifest(self, mnode, path):
473 man = self.repo.manifest
474 mn = man.lookup(mnode)
475 mnode = hex(mn)
476 mf = man.read(mn)
477 rev = man.rev(mn)
478 changerev = man.linkrev(mn)
479 node = self.repo.changelog.node(changerev)
480 mff = man.readflags(mn)
481
482 files = {}
483
484 p = path[1:]
485 if p and p[-1] != "/":
486 p += "/"
487 l = len(p)
488
489 for f,n in mf.items():
490 if f[:l] != p:
491 continue
492 remain = f[l:]
493 if "/" in remain:
494 short = remain[:remain.find("/") + 1] # bleah
495 files[short] = (f, None)
496 else:
497 short = os.path.basename(remain)
498 files[short] = (f, n)
499
500 def filelist(**map):
501 parity = 0
502 fl = files.keys()
503 fl.sort()
504 for f in fl:
505 full, fnode = files[f]
506 if not fnode:
507 continue
508
509 yield {"file": full,
510 "manifest": mnode,
511 "filenode": hex(fnode),
512 "parity": parity,
513 "basename": f,
514 "permissions": mff[full]}
515 parity = 1 - parity
516
517 def dirlist(**map):
518 parity = 0
519 fl = files.keys()
520 fl.sort()
521 for f in fl:
522 full, fnode = files[f]
523 if fnode:
524 continue
525
526 yield {"parity": parity,
527 "path": os.path.join(path, f),
528 "manifest": mnode,
529 "basename": f[:-1]}
530 parity = 1 - parity
531
532 yield self.t("manifest",
533 manifest=mnode,
534 rev=rev,
535 node=hex(node),
536 path=path,
537 up=up(path),
538 fentries=filelist,
539 dentries=dirlist,
540 archives=self.archivelist(hex(node)))
541
542 def tags(self):
543 cl = self.repo.changelog
544 mf = cl.read(cl.tip())[0]
545
546 i = self.repo.tagslist()
547 i.reverse()
548
549 def entries(notip=False, **map):
550 parity = 0
551 for k,n in i:
552 if notip and k == "tip": continue
553 yield {"parity": parity,
554 "tag": k,
555 "tagmanifest": hex(cl.read(n)[0]),
556 "date": cl.read(n)[2],
557 "node": hex(n)}
558 parity = 1 - parity
559
560 yield self.t("tags",
561 manifest=hex(mf),
562 entries=lambda **x: entries(False, **x),
563 entriesnotip=lambda **x: entries(True, **x))
564
565 def summary(self):
566 cl = self.repo.changelog
567 mf = cl.read(cl.tip())[0]
568
569 i = self.repo.tagslist()
570 i.reverse()
571
572 def tagentries(**map):
573 parity = 0
574 count = 0
575 for k,n in i:
576 if k == "tip": # skip tip
577 continue;
578
579 count += 1
580 if count > 10: # limit to 10 tags
581 break;
582
583 c = cl.read(n)
584 m = c[0]
585 t = c[2]
586
587 yield self.t("tagentry",
588 parity = parity,
589 tag = k,
590 node = hex(n),
591 date = t,
592 tagmanifest = hex(m))
593 parity = 1 - parity
594
595 def changelist(**map):
596 parity = 0
597 cl = self.repo.changelog
598 l = [] # build a list in forward order for efficiency
599 for i in range(start, end):
600 n = cl.node(i)
601 changes = cl.read(n)
602 hn = hex(n)
603 t = changes[2]
604
605 l.insert(0, self.t(
606 'shortlogentry',
607 parity = parity,
608 author = changes[1],
609 manifest = hex(changes[0]),
610 desc = changes[4],
611 date = t,
612 rev = i,
613 node = hn))
614 parity = 1 - parity
615
616 yield l
617
618 cl = self.repo.changelog
619 mf = cl.read(cl.tip())[0]
620 count = cl.count()
621 start = max(0, count - self.maxchanges)
622 end = min(count, start + self.maxchanges)
623 pos = end - 1
624
625 yield self.t("summary",
626 desc = self.repo.ui.config("web", "description", "unknown"),
627 owner = (self.repo.ui.config("ui", "username") or # preferred
628 self.repo.ui.config("web", "contact") or # deprecated
629 self.repo.ui.config("web", "author", "unknown")), # also
630 lastchange = (0, 0), # FIXME
631 manifest = hex(mf),
632 tags = tagentries,
633 shortlog = changelist)
634
635 def filediff(self, file, changeset):
636 cl = self.repo.changelog
637 n = self.repo.lookup(changeset)
638 changeset = hex(n)
639 p1 = cl.parents(n)[0]
640 cs = cl.read(n)
641 mf = self.repo.manifest.read(cs[0])
642
643 def diff(**map):
644 yield self.diff(p1, n, [file])
645
646 yield self.t("filediff",
647 file=file,
648 filenode=hex(mf.get(file, nullid)),
649 node=changeset,
650 rev=self.repo.changelog.rev(n),
651 parent=self.siblings(cl.parents(n), cl.rev),
652 child=self.siblings(cl.children(n), cl.rev),
653 diff=diff)
654
655 archive_specs = {
656 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
657 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
658 'zip': ('application/zip', 'zip', '.zip', None),
659 }
660
661 def archive(self, req, cnode, type):
662 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
663 name = "%s-%s" % (reponame, short(cnode))
664 mimetype, artype, extension, encoding = self.archive_specs[type]
665 headers = [('Content-type', mimetype),
666 ('Content-disposition', 'attachment; filename=%s%s' %
667 (name, extension))]
668 if encoding:
669 headers.append(('Content-encoding', encoding))
670 req.header(headers)
671 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
672
673 # add tags to things
674 # tags -> list of changesets corresponding to tags
675 # find tag, changeset, file
676
677 def run(self, req=hgrequest()):
678 def clean(path):
679 p = util.normpath(path)
680 if p[:2] == "..":
681 raise "suspicious path"
682 return p
683
684 def header(**map):
685 yield self.t("header", **map)
686
687 def footer(**map):
688 yield self.t("footer",
689 motd=self.repo.ui.config("web", "motd", ""),
690 **map)
691
692 def expand_form(form):
693 shortcuts = {
694 'cl': [('cmd', ['changelog']), ('rev', None)],
695 'cs': [('cmd', ['changeset']), ('node', None)],
696 'f': [('cmd', ['file']), ('filenode', None)],
697 'fl': [('cmd', ['filelog']), ('filenode', None)],
698 'fd': [('cmd', ['filediff']), ('node', None)],
699 'fa': [('cmd', ['annotate']), ('filenode', None)],
700 'mf': [('cmd', ['manifest']), ('manifest', None)],
701 'ca': [('cmd', ['archive']), ('node', None)],
702 'tags': [('cmd', ['tags'])],
703 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
704 'static': [('cmd', ['static']), ('file', None)]
705 }
706
707 for k in shortcuts.iterkeys():
708 if form.has_key(k):
709 for name, value in shortcuts[k]:
710 if value is None:
711 value = form[k]
712 form[name] = value
713 del form[k]
714
715 self.refresh()
716
717 expand_form(req.form)
718
719 t = self.repo.ui.config("web", "templates", templater.templatepath())
720 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
721 m = os.path.join(t, "map")
722 style = self.repo.ui.config("web", "style", "")
723 if req.form.has_key('style'):
724 style = req.form['style'][0]
725 if style:
726 b = os.path.basename("map-" + style)
727 p = os.path.join(t, b)
728 if os.path.isfile(p):
729 m = p
730
731 port = req.env["SERVER_PORT"]
732 port = port != "80" and (":" + port) or ""
733 uri = req.env["REQUEST_URI"]
734 if "?" in uri:
735 uri = uri.split("?")[0]
736 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
737 if not self.reponame:
738 self.reponame = (self.repo.ui.config("web", "name")
739 or uri.strip('/') or self.repo.root)
740
741 self.t = templater.templater(m, templater.common_filters,
742 defaults={"url": url,
743 "repo": self.reponame,
744 "header": header,
745 "footer": footer,
746 })
747
748 if not req.form.has_key('cmd'):
749 req.form['cmd'] = [self.t.cache['default'],]
750
751 cmd = req.form['cmd'][0]
752 if cmd == 'changelog':
753 hi = self.repo.changelog.count() - 1
754 if req.form.has_key('rev'):
755 hi = req.form['rev'][0]
756 try:
757 hi = self.repo.changelog.rev(self.repo.lookup(hi))
758 except hg.RepoError:
759 req.write(self.search(hi)) # XXX redirect to 404 page?
760 return
761
762 req.write(self.changelog(hi))
763
764 elif cmd == 'changeset':
765 req.write(self.changeset(req.form['node'][0]))
766
767 elif cmd == 'manifest':
768 req.write(self.manifest(req.form['manifest'][0],
769 clean(req.form['path'][0])))
770
771 elif cmd == 'tags':
772 req.write(self.tags())
773
774 elif cmd == 'summary':
775 req.write(self.summary())
776
777 elif cmd == 'filediff':
778 req.write(self.filediff(clean(req.form['file'][0]),
779 req.form['node'][0]))
780
781 elif cmd == 'file':
782 req.write(self.filerevision(clean(req.form['file'][0]),
783 req.form['filenode'][0]))
784
785 elif cmd == 'annotate':
786 req.write(self.fileannotate(clean(req.form['file'][0]),
787 req.form['filenode'][0]))
788
789 elif cmd == 'filelog':
790 req.write(self.filelog(clean(req.form['file'][0]),
791 req.form['filenode'][0]))
792
793 elif cmd == 'heads':
794 req.httphdr("application/mercurial-0.1")
795 h = self.repo.heads()
796 req.write(" ".join(map(hex, h)) + "\n")
797
798 elif cmd == 'branches':
799 req.httphdr("application/mercurial-0.1")
800 nodes = []
801 if req.form.has_key('nodes'):
802 nodes = map(bin, req.form['nodes'][0].split(" "))
803 for b in self.repo.branches(nodes):
804 req.write(" ".join(map(hex, b)) + "\n")
805
806 elif cmd == 'between':
807 req.httphdr("application/mercurial-0.1")
808 nodes = []
809 if req.form.has_key('pairs'):
810 pairs = [map(bin, p.split("-"))
811 for p in req.form['pairs'][0].split(" ")]
812 for b in self.repo.between(pairs):
813 req.write(" ".join(map(hex, b)) + "\n")
814
815 elif cmd == 'changegroup':
816 req.httphdr("application/mercurial-0.1")
817 nodes = []
818 if not self.allowpull:
819 return
820
821 if req.form.has_key('roots'):
822 nodes = map(bin, req.form['roots'][0].split(" "))
823
824 z = zlib.compressobj()
825 f = self.repo.changegroup(nodes, 'serve')
826 while 1:
827 chunk = f.read(4096)
828 if not chunk:
829 break
830 req.write(z.compress(chunk))
831
832 req.write(z.flush())
833
834 elif cmd == 'archive':
835 changeset = self.repo.lookup(req.form['node'][0])
836 type = req.form['type'][0]
837 if (type in self.archives and
838 self.repo.ui.configbool("web", "allow" + type, False)):
839 self.archive(req, changeset, type)
840 return
841
842 req.write(self.t("error"))
843
844 elif cmd == 'static':
845 fname = req.form['file'][0]
846 req.write(staticfile(static, fname)
847 or self.t("error", error="%r not found" % fname))
848
849 else:
850 req.write(self.t("error"))
851
852 16 # This is a stopgap
853 17 class hgwebdir(object):
854 18 def __init__(self, config):
General Comments 0
You need to be logged in to leave comments. Login now