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)