Mercurial > public > mercurial-scm > hg
comparison mercurial/hgweb/hgweb_mod.py @ 5591:08887121a652
split out hgweb commands into a separate file, move some code around
author | Dirkjan Ochtman <dirkjan@ochtman.nl> |
---|---|
date | Sun, 02 Dec 2007 23:26:40 +0100 |
parents | e15f7db0f0ee |
children | b95b2525c6e8 |
comparison
equal
deleted
inserted
replaced
5590:05451f6b5f07 | 5591:08887121a652 |
---|---|
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
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 errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys | 9 import os, mimetypes, re, mimetools, cStringIO, sys, urllib, bz2 |
10 import tempfile, urllib, bz2 | |
11 from mercurial.node import * | 10 from mercurial.node import * |
12 from mercurial.i18n import gettext as _ | 11 from mercurial import mdiff, ui, hg, util, archival, patch |
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch | |
14 from mercurial import revlog, templater | 12 from mercurial import revlog, templater |
15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen | 13 from common import ErrorResponse, get_mtime, style_map, paritygen |
16 from request import wsgirequest | 14 from request import wsgirequest |
15 import webcommands | |
17 | 16 |
18 def _up(p): | 17 def _up(p): |
19 if p[0] != "/": | 18 if p[0] != "/": |
20 p = "/" + p | 19 p = "/" + p |
21 if p[-1] == "/": | 20 if p[-1] == "/": |
104 self.stripecount = int(self.config("web", "stripes", 1)) | 103 self.stripecount = int(self.config("web", "stripes", 1)) |
105 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60)) | 104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60)) |
106 self.maxfiles = int(self.config("web", "maxfiles", 10)) | 105 self.maxfiles = int(self.config("web", "maxfiles", 10)) |
107 self.allowpull = self.configbool("web", "allowpull", True) | 106 self.allowpull = self.configbool("web", "allowpull", True) |
108 self.encoding = self.config("web", "encoding", util._encoding) | 107 self.encoding = self.config("web", "encoding", util._encoding) |
108 | |
109 def run(self): | |
110 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): | |
111 raise RuntimeError("This function is only intended to be called while running as a CGI script.") | |
112 import mercurial.hgweb.wsgicgi as wsgicgi | |
113 wsgicgi.launch(self) | |
114 | |
115 def __call__(self, env, respond): | |
116 req = wsgirequest(env, respond) | |
117 self.run_wsgi(req) | |
118 return req | |
119 | |
120 def run_wsgi(self, req): | |
121 def header(**map): | |
122 header_file = cStringIO.StringIO( | |
123 ''.join(self.t("header", encoding=self.encoding, **map))) | |
124 msg = mimetools.Message(header_file, 0) | |
125 req.header(msg.items()) | |
126 yield header_file.read() | |
127 | |
128 def rawfileheader(**map): | |
129 req.header([('Content-type', map['mimetype']), | |
130 ('Content-disposition', 'filename=%s' % map['file']), | |
131 ('Content-length', str(len(map['raw'])))]) | |
132 yield '' | |
133 | |
134 def footer(**map): | |
135 yield self.t("footer", **map) | |
136 | |
137 def motd(**map): | |
138 yield self.config("web", "motd", "") | |
139 | |
140 def expand_form(form): | |
141 shortcuts = { | |
142 'cl': [('cmd', ['changelog']), ('rev', None)], | |
143 'sl': [('cmd', ['shortlog']), ('rev', None)], | |
144 'cs': [('cmd', ['changeset']), ('node', None)], | |
145 'f': [('cmd', ['file']), ('filenode', None)], | |
146 'fl': [('cmd', ['filelog']), ('filenode', None)], | |
147 'fd': [('cmd', ['filediff']), ('node', None)], | |
148 'fa': [('cmd', ['annotate']), ('filenode', None)], | |
149 'mf': [('cmd', ['manifest']), ('manifest', None)], | |
150 'ca': [('cmd', ['archive']), ('node', None)], | |
151 'tags': [('cmd', ['tags'])], | |
152 'tip': [('cmd', ['changeset']), ('node', ['tip'])], | |
153 'static': [('cmd', ['static']), ('file', None)] | |
154 } | |
155 | |
156 for k in shortcuts.iterkeys(): | |
157 if form.has_key(k): | |
158 for name, value in shortcuts[k]: | |
159 if value is None: | |
160 value = form[k] | |
161 form[name] = value | |
162 del form[k] | |
163 | |
164 def rewrite_request(req): | |
165 '''translate new web interface to traditional format''' | |
166 | |
167 req.url = req.env['SCRIPT_NAME'] | |
168 if not req.url.endswith('/'): | |
169 req.url += '/' | |
170 if req.env.has_key('REPO_NAME'): | |
171 req.url += req.env['REPO_NAME'] + '/' | |
172 | |
173 if req.env.get('PATH_INFO'): | |
174 parts = req.env.get('PATH_INFO').strip('/').split('/') | |
175 repo_parts = req.env.get('REPO_NAME', '').split('/') | |
176 if parts[:len(repo_parts)] == repo_parts: | |
177 parts = parts[len(repo_parts):] | |
178 query = '/'.join(parts) | |
179 else: | |
180 query = req.env['QUERY_STRING'].split('&', 1)[0] | |
181 query = query.split(';', 1)[0] | |
182 | |
183 if req.form.has_key('cmd'): | |
184 # old style | |
185 return | |
186 | |
187 args = query.split('/', 2) | |
188 if not args or not args[0]: | |
189 return | |
190 | |
191 cmd = args.pop(0) | |
192 style = cmd.rfind('-') | |
193 if style != -1: | |
194 req.form['style'] = [cmd[:style]] | |
195 cmd = cmd[style+1:] | |
196 # avoid accepting e.g. style parameter as command | |
197 if hasattr(webcommands, cmd): | |
198 req.form['cmd'] = [cmd] | |
199 | |
200 if args and args[0]: | |
201 node = args.pop(0) | |
202 req.form['node'] = [node] | |
203 if args: | |
204 req.form['file'] = args | |
205 | |
206 if cmd == 'static': | |
207 req.form['file'] = req.form['node'] | |
208 elif cmd == 'archive': | |
209 fn = req.form['node'][0] | |
210 for type_, spec in self.archive_specs.iteritems(): | |
211 ext = spec[2] | |
212 if fn.endswith(ext): | |
213 req.form['node'] = [fn[:-len(ext)]] | |
214 req.form['type'] = [type_] | |
215 | |
216 def sessionvars(**map): | |
217 fields = [] | |
218 if req.form.has_key('style'): | |
219 style = req.form['style'][0] | |
220 if style != self.config('web', 'style', ''): | |
221 fields.append(('style', style)) | |
222 | |
223 separator = req.url[-1] == '?' and ';' or '?' | |
224 for name, value in fields: | |
225 yield dict(name=name, value=value, separator=separator) | |
226 separator = ';' | |
227 | |
228 self.refresh() | |
229 | |
230 expand_form(req.form) | |
231 rewrite_request(req) | |
232 | |
233 style = self.config("web", "style", "") | |
234 if req.form.has_key('style'): | |
235 style = req.form['style'][0] | |
236 mapfile = style_map(self.templatepath, style) | |
237 | |
238 proto = req.env.get('wsgi.url_scheme') | |
239 if proto == 'https': | |
240 proto = 'https' | |
241 default_port = "443" | |
242 else: | |
243 proto = 'http' | |
244 default_port = "80" | |
245 | |
246 port = req.env["SERVER_PORT"] | |
247 port = port != default_port and (":" + port) or "" | |
248 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port) | |
249 staticurl = self.config("web", "staticurl") or req.url + 'static/' | |
250 if not staticurl.endswith('/'): | |
251 staticurl += '/' | |
252 | |
253 if not self.reponame: | |
254 self.reponame = (self.config("web", "name") | |
255 or req.env.get('REPO_NAME') | |
256 or req.url.strip('/') or self.repo.root) | |
257 | |
258 self.t = templater.templater(mapfile, templater.common_filters, | |
259 defaults={"url": req.url, | |
260 "staticurl": staticurl, | |
261 "urlbase": urlbase, | |
262 "repo": self.reponame, | |
263 "header": header, | |
264 "footer": footer, | |
265 "motd": motd, | |
266 "rawfileheader": rawfileheader, | |
267 "sessionvars": sessionvars | |
268 }) | |
269 | |
270 try: | |
271 if not req.form.has_key('cmd'): | |
272 req.form['cmd'] = [self.t.cache['default']] | |
273 | |
274 cmd = req.form['cmd'][0] | |
275 | |
276 try: | |
277 method = getattr(webcommands, cmd) | |
278 method(self, req) | |
279 except revlog.LookupError, err: | |
280 req.respond(404, self.t( | |
281 'error', error='revision not found: %s' % err.name)) | |
282 except (hg.RepoError, revlog.RevlogError), inst: | |
283 req.respond('500 Internal Server Error', | |
284 self.t('error', error=str(inst))) | |
285 except ErrorResponse, inst: | |
286 req.respond(inst.code, self.t('error', error=inst.message)) | |
287 except AttributeError: | |
288 req.respond(400, | |
289 self.t('error', error='No such method: ' + cmd)) | |
290 finally: | |
291 self.t = None | |
109 | 292 |
110 def archivelist(self, nodeid): | 293 def archivelist(self, nodeid): |
111 allowed = self.configlist("web", "allow_archive") | 294 allowed = self.configlist("web", "allow_archive") |
112 for i, spec in self.archive_specs.iteritems(): | 295 for i, spec in self.archive_specs.iteritems(): |
113 if i in allowed or self.configbool("web", "allow" + i): | 296 if i in allowed or self.configbool("web", "allow" + i): |
666 | 849 |
667 def cleanpath(self, path): | 850 def cleanpath(self, path): |
668 path = path.lstrip('/') | 851 path = path.lstrip('/') |
669 return util.canonpath(self.repo.root, '', path) | 852 return util.canonpath(self.repo.root, '', path) |
670 | 853 |
671 def run(self): | |
672 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): | |
673 raise RuntimeError("This function is only intended to be called while running as a CGI script.") | |
674 import mercurial.hgweb.wsgicgi as wsgicgi | |
675 wsgicgi.launch(self) | |
676 | |
677 def __call__(self, env, respond): | |
678 req = wsgirequest(env, respond) | |
679 self.run_wsgi(req) | |
680 return req | |
681 | |
682 def run_wsgi(self, req): | |
683 def header(**map): | |
684 header_file = cStringIO.StringIO( | |
685 ''.join(self.t("header", encoding=self.encoding, **map))) | |
686 msg = mimetools.Message(header_file, 0) | |
687 req.header(msg.items()) | |
688 yield header_file.read() | |
689 | |
690 def rawfileheader(**map): | |
691 req.header([('Content-type', map['mimetype']), | |
692 ('Content-disposition', 'filename=%s' % map['file']), | |
693 ('Content-length', str(len(map['raw'])))]) | |
694 yield '' | |
695 | |
696 def footer(**map): | |
697 yield self.t("footer", **map) | |
698 | |
699 def motd(**map): | |
700 yield self.config("web", "motd", "") | |
701 | |
702 def expand_form(form): | |
703 shortcuts = { | |
704 'cl': [('cmd', ['changelog']), ('rev', None)], | |
705 'sl': [('cmd', ['shortlog']), ('rev', None)], | |
706 'cs': [('cmd', ['changeset']), ('node', None)], | |
707 'f': [('cmd', ['file']), ('filenode', None)], | |
708 'fl': [('cmd', ['filelog']), ('filenode', None)], | |
709 'fd': [('cmd', ['filediff']), ('node', None)], | |
710 'fa': [('cmd', ['annotate']), ('filenode', None)], | |
711 'mf': [('cmd', ['manifest']), ('manifest', None)], | |
712 'ca': [('cmd', ['archive']), ('node', None)], | |
713 'tags': [('cmd', ['tags'])], | |
714 'tip': [('cmd', ['changeset']), ('node', ['tip'])], | |
715 'static': [('cmd', ['static']), ('file', None)] | |
716 } | |
717 | |
718 for k in shortcuts.iterkeys(): | |
719 if form.has_key(k): | |
720 for name, value in shortcuts[k]: | |
721 if value is None: | |
722 value = form[k] | |
723 form[name] = value | |
724 del form[k] | |
725 | |
726 def rewrite_request(req): | |
727 '''translate new web interface to traditional format''' | |
728 | |
729 req.url = req.env['SCRIPT_NAME'] | |
730 if not req.url.endswith('/'): | |
731 req.url += '/' | |
732 if req.env.has_key('REPO_NAME'): | |
733 req.url += req.env['REPO_NAME'] + '/' | |
734 | |
735 if req.env.get('PATH_INFO'): | |
736 parts = req.env.get('PATH_INFO').strip('/').split('/') | |
737 repo_parts = req.env.get('REPO_NAME', '').split('/') | |
738 if parts[:len(repo_parts)] == repo_parts: | |
739 parts = parts[len(repo_parts):] | |
740 query = '/'.join(parts) | |
741 else: | |
742 query = req.env['QUERY_STRING'].split('&', 1)[0] | |
743 query = query.split(';', 1)[0] | |
744 | |
745 if req.form.has_key('cmd'): | |
746 # old style | |
747 return | |
748 | |
749 args = query.split('/', 2) | |
750 if not args or not args[0]: | |
751 return | |
752 | |
753 cmd = args.pop(0) | |
754 style = cmd.rfind('-') | |
755 if style != -1: | |
756 req.form['style'] = [cmd[:style]] | |
757 cmd = cmd[style+1:] | |
758 # avoid accepting e.g. style parameter as command | |
759 if hasattr(self, 'do_' + cmd): | |
760 req.form['cmd'] = [cmd] | |
761 | |
762 if args and args[0]: | |
763 node = args.pop(0) | |
764 req.form['node'] = [node] | |
765 if args: | |
766 req.form['file'] = args | |
767 | |
768 if cmd == 'static': | |
769 req.form['file'] = req.form['node'] | |
770 elif cmd == 'archive': | |
771 fn = req.form['node'][0] | |
772 for type_, spec in self.archive_specs.iteritems(): | |
773 ext = spec[2] | |
774 if fn.endswith(ext): | |
775 req.form['node'] = [fn[:-len(ext)]] | |
776 req.form['type'] = [type_] | |
777 | |
778 def sessionvars(**map): | |
779 fields = [] | |
780 if req.form.has_key('style'): | |
781 style = req.form['style'][0] | |
782 if style != self.config('web', 'style', ''): | |
783 fields.append(('style', style)) | |
784 | |
785 separator = req.url[-1] == '?' and ';' or '?' | |
786 for name, value in fields: | |
787 yield dict(name=name, value=value, separator=separator) | |
788 separator = ';' | |
789 | |
790 self.refresh() | |
791 | |
792 expand_form(req.form) | |
793 rewrite_request(req) | |
794 | |
795 style = self.config("web", "style", "") | |
796 if req.form.has_key('style'): | |
797 style = req.form['style'][0] | |
798 mapfile = style_map(self.templatepath, style) | |
799 | |
800 proto = req.env.get('wsgi.url_scheme') | |
801 if proto == 'https': | |
802 proto = 'https' | |
803 default_port = "443" | |
804 else: | |
805 proto = 'http' | |
806 default_port = "80" | |
807 | |
808 port = req.env["SERVER_PORT"] | |
809 port = port != default_port and (":" + port) or "" | |
810 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port) | |
811 staticurl = self.config("web", "staticurl") or req.url + 'static/' | |
812 if not staticurl.endswith('/'): | |
813 staticurl += '/' | |
814 | |
815 if not self.reponame: | |
816 self.reponame = (self.config("web", "name") | |
817 or req.env.get('REPO_NAME') | |
818 or req.url.strip('/') or self.repo.root) | |
819 | |
820 self.t = templater.templater(mapfile, templater.common_filters, | |
821 defaults={"url": req.url, | |
822 "staticurl": staticurl, | |
823 "urlbase": urlbase, | |
824 "repo": self.reponame, | |
825 "header": header, | |
826 "footer": footer, | |
827 "motd": motd, | |
828 "rawfileheader": rawfileheader, | |
829 "sessionvars": sessionvars | |
830 }) | |
831 | |
832 try: | |
833 if not req.form.has_key('cmd'): | |
834 req.form['cmd'] = [self.t.cache['default']] | |
835 | |
836 cmd = req.form['cmd'][0] | |
837 | |
838 try: | |
839 method = getattr(self, 'do_' + cmd) | |
840 method(req) | |
841 except revlog.LookupError, err: | |
842 req.respond(404, self.t( | |
843 'error', error='revision not found: %s' % err.name)) | |
844 except (hg.RepoError, revlog.RevlogError), inst: | |
845 req.respond('500 Internal Server Error', | |
846 self.t('error', error=str(inst))) | |
847 except ErrorResponse, inst: | |
848 req.respond(inst.code, self.t('error', error=inst.message)) | |
849 except AttributeError: | |
850 req.respond(400, | |
851 self.t('error', error='No such method: ' + cmd)) | |
852 finally: | |
853 self.t = None | |
854 | |
855 def changectx(self, req): | 854 def changectx(self, req): |
856 if req.form.has_key('node'): | 855 if req.form.has_key('node'): |
857 changeid = req.form['node'][0] | 856 changeid = req.form['node'][0] |
858 elif req.form.has_key('manifest'): | 857 elif req.form.has_key('manifest'): |
859 changeid = req.form['manifest'][0] | 858 changeid = req.form['manifest'][0] |
881 except hg.RepoError: | 880 except hg.RepoError: |
882 fctx = self.repo.filectx(path, fileid=changeid) | 881 fctx = self.repo.filectx(path, fileid=changeid) |
883 | 882 |
884 return fctx | 883 return fctx |
885 | 884 |
886 def do_log(self, req): | |
887 if req.form.has_key('file') and req.form['file'][0]: | |
888 self.do_filelog(req) | |
889 else: | |
890 self.do_changelog(req) | |
891 | |
892 def do_rev(self, req): | |
893 self.do_changeset(req) | |
894 | |
895 def do_file(self, req): | |
896 path = self.cleanpath(req.form.get('file', [''])[0]) | |
897 if path: | |
898 try: | |
899 req.write(self.filerevision(self.filectx(req))) | |
900 return | |
901 except revlog.LookupError: | |
902 pass | |
903 | |
904 req.write(self.manifest(self.changectx(req), path)) | |
905 | |
906 def do_diff(self, req): | |
907 self.do_filediff(req) | |
908 | |
909 def do_changelog(self, req, shortlog = False): | |
910 if req.form.has_key('node'): | |
911 ctx = self.changectx(req) | |
912 else: | |
913 if req.form.has_key('rev'): | |
914 hi = req.form['rev'][0] | |
915 else: | |
916 hi = self.repo.changelog.count() - 1 | |
917 try: | |
918 ctx = self.repo.changectx(hi) | |
919 except hg.RepoError: | |
920 req.write(self.search(hi)) # XXX redirect to 404 page? | |
921 return | |
922 | |
923 req.write(self.changelog(ctx, shortlog = shortlog)) | |
924 | |
925 def do_shortlog(self, req): | |
926 self.do_changelog(req, shortlog = True) | |
927 | |
928 def do_changeset(self, req): | |
929 req.write(self.changeset(self.changectx(req))) | |
930 | |
931 def do_manifest(self, req): | |
932 req.write(self.manifest(self.changectx(req), | |
933 self.cleanpath(req.form['path'][0]))) | |
934 | |
935 def do_tags(self, req): | |
936 req.write(self.tags()) | |
937 | |
938 def do_summary(self, req): | |
939 req.write(self.summary()) | |
940 | |
941 def do_filediff(self, req): | |
942 req.write(self.filediff(self.filectx(req))) | |
943 | |
944 def do_annotate(self, req): | |
945 req.write(self.fileannotate(self.filectx(req))) | |
946 | |
947 def do_filelog(self, req): | |
948 req.write(self.filelog(self.filectx(req))) | |
949 | |
950 def do_lookup(self, req): | |
951 try: | |
952 r = hex(self.repo.lookup(req.form['key'][0])) | |
953 success = 1 | |
954 except Exception,inst: | |
955 r = str(inst) | |
956 success = 0 | |
957 resp = "%s %s\n" % (success, r) | |
958 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
959 req.write(resp) | |
960 | |
961 def do_heads(self, req): | |
962 resp = " ".join(map(hex, self.repo.heads())) + "\n" | |
963 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
964 req.write(resp) | |
965 | |
966 def do_branches(self, req): | |
967 nodes = [] | |
968 if req.form.has_key('nodes'): | |
969 nodes = map(bin, req.form['nodes'][0].split(" ")) | |
970 resp = cStringIO.StringIO() | |
971 for b in self.repo.branches(nodes): | |
972 resp.write(" ".join(map(hex, b)) + "\n") | |
973 resp = resp.getvalue() | |
974 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
975 req.write(resp) | |
976 | |
977 def do_between(self, req): | |
978 if req.form.has_key('pairs'): | |
979 pairs = [map(bin, p.split("-")) | |
980 for p in req.form['pairs'][0].split(" ")] | |
981 resp = cStringIO.StringIO() | |
982 for b in self.repo.between(pairs): | |
983 resp.write(" ".join(map(hex, b)) + "\n") | |
984 resp = resp.getvalue() | |
985 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
986 req.write(resp) | |
987 | |
988 def do_changegroup(self, req): | |
989 req.httphdr("application/mercurial-0.1") | |
990 nodes = [] | |
991 if not self.allowpull: | |
992 return | |
993 | |
994 if req.form.has_key('roots'): | |
995 nodes = map(bin, req.form['roots'][0].split(" ")) | |
996 | |
997 z = zlib.compressobj() | |
998 f = self.repo.changegroup(nodes, 'serve') | |
999 while 1: | |
1000 chunk = f.read(4096) | |
1001 if not chunk: | |
1002 break | |
1003 req.write(z.compress(chunk)) | |
1004 | |
1005 req.write(z.flush()) | |
1006 | |
1007 def do_changegroupsubset(self, req): | |
1008 req.httphdr("application/mercurial-0.1") | |
1009 bases = [] | |
1010 heads = [] | |
1011 if not self.allowpull: | |
1012 return | |
1013 | |
1014 if req.form.has_key('bases'): | |
1015 bases = [bin(x) for x in req.form['bases'][0].split(' ')] | |
1016 if req.form.has_key('heads'): | |
1017 heads = [bin(x) for x in req.form['heads'][0].split(' ')] | |
1018 | |
1019 z = zlib.compressobj() | |
1020 f = self.repo.changegroupsubset(bases, heads, 'serve') | |
1021 while 1: | |
1022 chunk = f.read(4096) | |
1023 if not chunk: | |
1024 break | |
1025 req.write(z.compress(chunk)) | |
1026 | |
1027 req.write(z.flush()) | |
1028 | |
1029 def do_archive(self, req): | |
1030 type_ = req.form['type'][0] | |
1031 allowed = self.configlist("web", "allow_archive") | |
1032 if (type_ in self.archives and (type_ in allowed or | |
1033 self.configbool("web", "allow" + type_, False))): | |
1034 self.archive(req, req.form['node'][0], type_) | |
1035 return | |
1036 | |
1037 req.respond(400, self.t('error', | |
1038 error='Unsupported archive type: %s' % type_)) | |
1039 | |
1040 def do_static(self, req): | |
1041 fname = req.form['file'][0] | |
1042 # a repo owner may set web.static in .hg/hgrc to get any file | |
1043 # readable by the user running the CGI script | |
1044 static = self.config("web", "static", | |
1045 os.path.join(self.templatepath, "static"), | |
1046 untrusted=False) | |
1047 req.write(staticfile(static, fname, req)) | |
1048 | |
1049 def do_capabilities(self, req): | |
1050 caps = ['lookup', 'changegroupsubset'] | |
1051 if self.configbool('server', 'uncompressed'): | |
1052 caps.append('stream=%d' % self.repo.changelog.version) | |
1053 # XXX: make configurable and/or share code with do_unbundle: | |
1054 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN'] | |
1055 if unbundleversions: | |
1056 caps.append('unbundle=%s' % ','.join(unbundleversions)) | |
1057 resp = ' '.join(caps) | |
1058 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
1059 req.write(resp) | |
1060 | |
1061 def check_perm(self, req, op, default): | 885 def check_perm(self, req, op, default): |
1062 '''check permission for operation based on user auth. | 886 '''check permission for operation based on user auth. |
1063 return true if op allowed, else false. | 887 return true if op allowed, else false. |
1064 default is policy to use if no config given.''' | 888 default is policy to use if no config given.''' |
1065 | 889 |
1069 if deny and (not user or deny == ['*'] or user in deny): | 893 if deny and (not user or deny == ['*'] or user in deny): |
1070 return False | 894 return False |
1071 | 895 |
1072 allow = self.configlist('web', 'allow_' + op) | 896 allow = self.configlist('web', 'allow_' + op) |
1073 return (allow and (allow == ['*'] or user in allow)) or default | 897 return (allow and (allow == ['*'] or user in allow)) or default |
1074 | |
1075 def do_unbundle(self, req): | |
1076 def bail(response, headers={}): | |
1077 length = int(req.env['CONTENT_LENGTH']) | |
1078 for s in util.filechunkiter(req, limit=length): | |
1079 # drain incoming bundle, else client will not see | |
1080 # response when run outside cgi script | |
1081 pass | |
1082 req.httphdr("application/mercurial-0.1", headers=headers) | |
1083 req.write('0\n') | |
1084 req.write(response) | |
1085 | |
1086 # require ssl by default, auth info cannot be sniffed and | |
1087 # replayed | |
1088 ssl_req = self.configbool('web', 'push_ssl', True) | |
1089 if ssl_req: | |
1090 if req.env.get('wsgi.url_scheme') != 'https': | |
1091 bail(_('ssl required\n')) | |
1092 return | |
1093 proto = 'https' | |
1094 else: | |
1095 proto = 'http' | |
1096 | |
1097 # do not allow push unless explicitly allowed | |
1098 if not self.check_perm(req, 'push', False): | |
1099 bail(_('push not authorized\n'), | |
1100 headers={'status': '401 Unauthorized'}) | |
1101 return | |
1102 | |
1103 their_heads = req.form['heads'][0].split(' ') | |
1104 | |
1105 def check_heads(): | |
1106 heads = map(hex, self.repo.heads()) | |
1107 return their_heads == [hex('force')] or their_heads == heads | |
1108 | |
1109 # fail early if possible | |
1110 if not check_heads(): | |
1111 bail(_('unsynced changes\n')) | |
1112 return | |
1113 | |
1114 req.httphdr("application/mercurial-0.1") | |
1115 | |
1116 # do not lock repo until all changegroup data is | |
1117 # streamed. save to temporary file. | |
1118 | |
1119 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') | |
1120 fp = os.fdopen(fd, 'wb+') | |
1121 try: | |
1122 length = int(req.env['CONTENT_LENGTH']) | |
1123 for s in util.filechunkiter(req, limit=length): | |
1124 fp.write(s) | |
1125 | |
1126 try: | |
1127 lock = self.repo.lock() | |
1128 try: | |
1129 if not check_heads(): | |
1130 req.write('0\n') | |
1131 req.write(_('unsynced changes\n')) | |
1132 return | |
1133 | |
1134 fp.seek(0) | |
1135 header = fp.read(6) | |
1136 if not header.startswith("HG"): | |
1137 # old client with uncompressed bundle | |
1138 def generator(f): | |
1139 yield header | |
1140 for chunk in f: | |
1141 yield chunk | |
1142 elif not header.startswith("HG10"): | |
1143 req.write("0\n") | |
1144 req.write(_("unknown bundle version\n")) | |
1145 return | |
1146 elif header == "HG10GZ": | |
1147 def generator(f): | |
1148 zd = zlib.decompressobj() | |
1149 for chunk in f: | |
1150 yield zd.decompress(chunk) | |
1151 elif header == "HG10BZ": | |
1152 def generator(f): | |
1153 zd = bz2.BZ2Decompressor() | |
1154 zd.decompress("BZ") | |
1155 for chunk in f: | |
1156 yield zd.decompress(chunk) | |
1157 elif header == "HG10UN": | |
1158 def generator(f): | |
1159 for chunk in f: | |
1160 yield chunk | |
1161 else: | |
1162 req.write("0\n") | |
1163 req.write(_("unknown bundle compression type\n")) | |
1164 return | |
1165 gen = generator(util.filechunkiter(fp, 4096)) | |
1166 | |
1167 # send addchangegroup output to client | |
1168 | |
1169 old_stdout = sys.stdout | |
1170 sys.stdout = cStringIO.StringIO() | |
1171 | |
1172 try: | |
1173 url = 'remote:%s:%s' % (proto, | |
1174 req.env.get('REMOTE_HOST', '')) | |
1175 try: | |
1176 ret = self.repo.addchangegroup( | |
1177 util.chunkbuffer(gen), 'serve', url) | |
1178 except util.Abort, inst: | |
1179 sys.stdout.write("abort: %s\n" % inst) | |
1180 ret = 0 | |
1181 finally: | |
1182 val = sys.stdout.getvalue() | |
1183 sys.stdout = old_stdout | |
1184 req.write('%d\n' % ret) | |
1185 req.write(val) | |
1186 finally: | |
1187 del lock | |
1188 except (OSError, IOError), inst: | |
1189 req.write('0\n') | |
1190 filename = getattr(inst, 'filename', '') | |
1191 # Don't send our filesystem layout to the client | |
1192 if filename.startswith(self.repo.root): | |
1193 filename = filename[len(self.repo.root)+1:] | |
1194 else: | |
1195 filename = '' | |
1196 error = getattr(inst, 'strerror', 'Unknown error') | |
1197 if inst.errno == errno.ENOENT: | |
1198 code = 404 | |
1199 else: | |
1200 code = 500 | |
1201 req.respond(code, '%s: %s\n' % (error, filename)) | |
1202 finally: | |
1203 fp.close() | |
1204 os.unlink(tempname) | |
1205 | |
1206 def do_stream_out(self, req): | |
1207 req.httphdr("application/mercurial-0.1") | |
1208 streamclone.stream_out(self.repo, req, untrusted=True) |