5 # |
5 # |
6 # This software may be used and distributed according to the terms |
6 # This software may be used and distributed according to the terms |
7 # of the GNU General Public License, incorporated herein by reference. |
7 # of the GNU General Public License, incorporated herein by reference. |
8 |
8 |
9 import os, cgi, sys, urllib |
9 import os, cgi, sys, urllib |
|
10 import mimetypes |
10 from demandload import demandload |
11 from demandload import demandload |
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser") |
12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser") |
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util") |
13 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util") |
13 demandload(globals(), "mimetypes") |
14 demandload(globals(), "mimetypes") |
14 from node import * |
15 from node import * |
68 cl_path = os.path.join(hg_path, "00changelog.i") |
69 cl_path = os.path.join(hg_path, "00changelog.i") |
69 if os.path.exists(os.path.join(cl_path)): |
70 if os.path.exists(os.path.join(cl_path)): |
70 return os.stat(cl_path).st_mtime |
71 return os.stat(cl_path).st_mtime |
71 else: |
72 else: |
72 return os.stat(hg_path).st_mtime |
73 return os.stat(hg_path).st_mtime |
|
74 |
|
75 def staticfile(directory, fname): |
|
76 """return a file inside directory with guessed content-type header |
|
77 |
|
78 fname always uses '/' as directory separator and isn't allowed to |
|
79 contain unusual path components. |
|
80 Content-type is guessed using the mimetypes module. |
|
81 Return an empty string if fname is illegal or file not found. |
|
82 |
|
83 """ |
|
84 parts = fname.split('/') |
|
85 path = directory |
|
86 for part in parts: |
|
87 if (part in ('', os.curdir, os.pardir) or |
|
88 os.sep in part or os.altsep is not None and os.altsep in part): |
|
89 return "" |
|
90 path = os.path.join(path, part) |
|
91 try: |
|
92 os.stat(path) |
|
93 ct = mimetypes.guess_type(path)[0] or "text/plain" |
|
94 return "Content-type: %s\n\n%s" % (ct, file(path).read()) |
|
95 except (TypeError, OSError): |
|
96 # illegal fname or unreadable file |
|
97 return "" |
73 |
98 |
74 class hgrequest(object): |
99 class hgrequest(object): |
75 def __init__(self, inp=None, out=None, env=None): |
100 def __init__(self, inp=None, out=None, env=None): |
76 self.inp = inp or sys.stdin |
101 self.inp = inp or sys.stdin |
77 self.out = out or sys.stdout |
102 self.out = out or sys.stdout |
658 mf = cl.read(cl.tip())[0] |
683 mf = cl.read(cl.tip())[0] |
659 |
684 |
660 i = self.repo.tagslist() |
685 i = self.repo.tagslist() |
661 i.reverse() |
686 i.reverse() |
662 |
687 |
663 def entries(**map): |
688 def entries(notip=False, **map): |
664 parity = 0 |
689 parity = 0 |
665 for k,n in i: |
690 for k,n in i: |
|
691 if notip and k == "tip": continue |
666 yield {"parity": parity, |
692 yield {"parity": parity, |
667 "tag": k, |
693 "tag": k, |
668 "tagmanifest": hex(cl.read(n)[0]), |
694 "tagmanifest": hex(cl.read(n)[0]), |
669 "date": cl.read(n)[2], |
695 "date": cl.read(n)[2], |
670 "node": hex(n)} |
696 "node": hex(n)} |
671 parity = 1 - parity |
697 parity = 1 - parity |
672 |
698 |
673 yield self.t("tags", |
699 yield self.t("tags", |
674 manifest=hex(mf), |
700 manifest=hex(mf), |
675 entries=entries) |
701 entries=lambda **x: entries(False, **x), |
|
702 entriesnotip=lambda **x: entries(True, **x)) |
676 |
703 |
677 def summary(self): |
704 def summary(self): |
678 cl = self.repo.changelog |
705 cl = self.repo.changelog |
679 mf = cl.read(cl.tip())[0] |
706 mf = cl.read(cl.tip())[0] |
680 |
707 |
841 'fa': [('cmd', ['annotate']), ('filenode', None)], |
868 'fa': [('cmd', ['annotate']), ('filenode', None)], |
842 'mf': [('cmd', ['manifest']), ('manifest', None)], |
869 'mf': [('cmd', ['manifest']), ('manifest', None)], |
843 'ca': [('cmd', ['archive']), ('node', None)], |
870 'ca': [('cmd', ['archive']), ('node', None)], |
844 'tags': [('cmd', ['tags'])], |
871 'tags': [('cmd', ['tags'])], |
845 'tip': [('cmd', ['changeset']), ('node', ['tip'])], |
872 'tip': [('cmd', ['changeset']), ('node', ['tip'])], |
|
873 'static': [('cmd', ['static']), ('file', None)] |
846 } |
874 } |
847 |
875 |
848 for k in shortcuts.iterkeys(): |
876 for k in shortcuts.iterkeys(): |
849 if form.has_key(k): |
877 if form.has_key(k): |
850 for name, value in shortcuts[k]: |
878 for name, value in shortcuts[k]: |
856 self.refresh() |
884 self.refresh() |
857 |
885 |
858 expand_form(req.form) |
886 expand_form(req.form) |
859 |
887 |
860 t = self.repo.ui.config("web", "templates", templatepath()) |
888 t = self.repo.ui.config("web", "templates", templatepath()) |
|
889 static = self.repo.ui.config("web", "static", os.path.join(t,"static")) |
861 m = os.path.join(t, "map") |
890 m = os.path.join(t, "map") |
862 style = self.repo.ui.config("web", "style", "") |
891 style = self.repo.ui.config("web", "style", "") |
863 if req.form.has_key('style'): |
892 if req.form.has_key('style'): |
864 style = req.form['style'][0] |
893 style = req.form['style'][0] |
865 if style: |
894 if style: |
960 |
989 |
961 if req.form.has_key('roots'): |
990 if req.form.has_key('roots'): |
962 nodes = map(bin, req.form['roots'][0].split(" ")) |
991 nodes = map(bin, req.form['roots'][0].split(" ")) |
963 |
992 |
964 z = zlib.compressobj() |
993 z = zlib.compressobj() |
965 f = self.repo.changegroup(nodes) |
994 f = self.repo.changegroup(nodes, 'serve') |
966 while 1: |
995 while 1: |
967 chunk = f.read(4096) |
996 chunk = f.read(4096) |
968 if not chunk: |
997 if not chunk: |
969 break |
998 break |
970 req.write(z.compress(chunk)) |
999 req.write(z.compress(chunk)) |
978 self.repo.ui.configbool("web", "allow" + type, False)): |
1007 self.repo.ui.configbool("web", "allow" + type, False)): |
979 self.archive(req, changeset, type) |
1008 self.archive(req, changeset, type) |
980 return |
1009 return |
981 |
1010 |
982 req.write(self.t("error")) |
1011 req.write(self.t("error")) |
|
1012 |
|
1013 elif req.form['cmd'][0] == 'static': |
|
1014 fname = req.form['file'][0] |
|
1015 req.write(staticfile(static, fname) |
|
1016 or self.t("error", error="%r not found" % fname)) |
983 |
1017 |
984 else: |
1018 else: |
985 req.write(self.t("error")) |
1019 req.write(self.t("error")) |
986 |
1020 |
987 def create_server(repo): |
1021 def create_server(repo): |
1150 except hg.RepoError, inst: |
1184 except hg.RepoError, inst: |
1151 req.write(tmpl("error", error=str(inst))) |
1185 req.write(tmpl("error", error=str(inst))) |
1152 else: |
1186 else: |
1153 req.write(tmpl("notfound", repo=virtual)) |
1187 req.write(tmpl("notfound", repo=virtual)) |
1154 else: |
1188 else: |
1155 req.write(tmpl("index", entries=entries)) |
1189 if req.form.has_key('static'): |
|
1190 static = os.path.join(templatepath(), "static") |
|
1191 fname = req.form['static'][0] |
|
1192 req.write(staticfile(static, fname) |
|
1193 or tmpl("error", error="%r not found" % fname)) |
|
1194 else: |
|
1195 req.write(tmpl("index", entries=entries)) |