##// END OF EJS Templates
split out hgweb commands into a separate file, move some code around
Dirkjan Ochtman -
r5591:08887121 default
parent child Browse files
Show More
@@ -0,0 +1,320 b''
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 import cStringIO, zlib, tempfile, errno, os, sys
9 from mercurial import revlog, util, streamclone
10 from mercurial.i18n import gettext as _
11 from mercurial.node import *
12 from common import staticfile
13
14 def log(web, req):
15 if req.form.has_key('file') and req.form['file'][0]:
16 filelog(web, req)
17 else:
18 changelog(web, req)
19
20 def file(web, req):
21 path = web.cleanpath(req.form.get('file', [''])[0])
22 if path:
23 try:
24 req.write(web.filerevision(web.filectx(req)))
25 return
26 except revlog.LookupError:
27 pass
28
29 req.write(web.manifest(web.changectx(req), path))
30
31 def changelog(web, req, shortlog = False):
32 if req.form.has_key('node'):
33 ctx = web.changectx(req)
34 else:
35 if req.form.has_key('rev'):
36 hi = req.form['rev'][0]
37 else:
38 hi = web.repo.changelog.count() - 1
39 try:
40 ctx = web.repo.changectx(hi)
41 except hg.RepoError:
42 req.write(web.search(hi)) # XXX redirect to 404 page?
43 return
44
45 req.write(web.changelog(ctx, shortlog = shortlog))
46
47 def shortlog(web, req):
48 changelog(web, req, shortlog = True)
49
50 def changeset(web, req):
51 req.write(web.changeset(web.changectx(req)))
52
53 rev = changeset
54
55 def manifest(web, req):
56 req.write(web.manifest(web.changectx(req),
57 web.cleanpath(req.form['path'][0])))
58
59 def tags(web, req):
60 req.write(web.tags())
61
62 def summary(web, req):
63 req.write(web.summary())
64
65 def filediff(web, req):
66 req.write(web.filediff(web.filectx(req)))
67
68 diff = filediff
69
70 def annotate(web, req):
71 req.write(web.fileannotate(web.filectx(req)))
72
73 def filelog(web, req):
74 req.write(web.filelog(web.filectx(req)))
75
76 def lookup(web, req):
77 try:
78 r = hex(web.repo.lookup(req.form['key'][0]))
79 success = 1
80 except Exception,inst:
81 r = str(inst)
82 success = 0
83 resp = "%s %s\n" % (success, r)
84 req.httphdr("application/mercurial-0.1", length=len(resp))
85 req.write(resp)
86
87 def heads(web, req):
88 resp = " ".join(map(hex, web.repo.heads())) + "\n"
89 req.httphdr("application/mercurial-0.1", length=len(resp))
90 req.write(resp)
91
92 def branches(web, req):
93 nodes = []
94 if req.form.has_key('nodes'):
95 nodes = map(bin, req.form['nodes'][0].split(" "))
96 resp = cStringIO.StringIO()
97 for b in web.repo.branches(nodes):
98 resp.write(" ".join(map(hex, b)) + "\n")
99 resp = resp.getvalue()
100 req.httphdr("application/mercurial-0.1", length=len(resp))
101 req.write(resp)
102
103 def between(web, req):
104 if req.form.has_key('pairs'):
105 pairs = [map(bin, p.split("-"))
106 for p in req.form['pairs'][0].split(" ")]
107 resp = cStringIO.StringIO()
108 for b in web.repo.between(pairs):
109 resp.write(" ".join(map(hex, b)) + "\n")
110 resp = resp.getvalue()
111 req.httphdr("application/mercurial-0.1", length=len(resp))
112 req.write(resp)
113
114 def changegroup(web, req):
115 req.httphdr("application/mercurial-0.1")
116 nodes = []
117 if not web.allowpull:
118 return
119
120 if req.form.has_key('roots'):
121 nodes = map(bin, req.form['roots'][0].split(" "))
122
123 z = zlib.compressobj()
124 f = web.repo.changegroup(nodes, 'serve')
125 while 1:
126 chunk = f.read(4096)
127 if not chunk:
128 break
129 req.write(z.compress(chunk))
130
131 req.write(z.flush())
132
133 def changegroupsubset(web, req):
134 req.httphdr("application/mercurial-0.1")
135 bases = []
136 heads = []
137 if not web.allowpull:
138 return
139
140 if req.form.has_key('bases'):
141 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
142 if req.form.has_key('heads'):
143 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
144
145 z = zlib.compressobj()
146 f = web.repo.changegroupsubset(bases, heads, 'serve')
147 while 1:
148 chunk = f.read(4096)
149 if not chunk:
150 break
151 req.write(z.compress(chunk))
152
153 req.write(z.flush())
154
155 def archive(web, req):
156 type_ = req.form['type'][0]
157 allowed = web.configlist("web", "allow_archive")
158 if (type_ in web.archives and (type_ in allowed or
159 web.configbool("web", "allow" + type_, False))):
160 web.archive(req, req.form['node'][0], type_)
161 return
162
163 req.respond(400, web.t('error',
164 error='Unsupported archive type: %s' % type_))
165
166 def static(web, req):
167 fname = req.form['file'][0]
168 # a repo owner may set web.static in .hg/hgrc to get any file
169 # readable by the user running the CGI script
170 static = web.config("web", "static",
171 os.path.join(web.templatepath, "static"),
172 untrusted=False)
173 req.write(staticfile(static, fname, req))
174
175 def capabilities(web, req):
176 caps = ['lookup', 'changegroupsubset']
177 if web.configbool('server', 'uncompressed'):
178 caps.append('stream=%d' % web.repo.changelog.version)
179 # XXX: make configurable and/or share code with do_unbundle:
180 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
181 if unbundleversions:
182 caps.append('unbundle=%s' % ','.join(unbundleversions))
183 resp = ' '.join(caps)
184 req.httphdr("application/mercurial-0.1", length=len(resp))
185 req.write(resp)
186
187 def unbundle(web, req):
188 def bail(response, headers={}):
189 length = int(req.env['CONTENT_LENGTH'])
190 for s in util.filechunkiter(req, limit=length):
191 # drain incoming bundle, else client will not see
192 # response when run outside cgi script
193 pass
194 req.httphdr("application/mercurial-0.1", headers=headers)
195 req.write('0\n')
196 req.write(response)
197
198 # require ssl by default, auth info cannot be sniffed and
199 # replayed
200 ssl_req = web.configbool('web', 'push_ssl', True)
201 if ssl_req:
202 if req.env.get('wsgi.url_scheme') != 'https':
203 bail(_('ssl required\n'))
204 return
205 proto = 'https'
206 else:
207 proto = 'http'
208
209 # do not allow push unless explicitly allowed
210 if not web.check_perm(req, 'push', False):
211 bail(_('push not authorized\n'),
212 headers={'status': '401 Unauthorized'})
213 return
214
215 their_heads = req.form['heads'][0].split(' ')
216
217 def check_heads():
218 heads = map(hex, web.repo.heads())
219 return their_heads == [hex('force')] or their_heads == heads
220
221 # fail early if possible
222 if not check_heads():
223 bail(_('unsynced changes\n'))
224 return
225
226 req.httphdr("application/mercurial-0.1")
227
228 # do not lock repo until all changegroup data is
229 # streamed. save to temporary file.
230
231 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
232 fp = os.fdopen(fd, 'wb+')
233 try:
234 length = int(req.env['CONTENT_LENGTH'])
235 for s in util.filechunkiter(req, limit=length):
236 fp.write(s)
237
238 try:
239 lock = web.repo.lock()
240 try:
241 if not check_heads():
242 req.write('0\n')
243 req.write(_('unsynced changes\n'))
244 return
245
246 fp.seek(0)
247 header = fp.read(6)
248 if not header.startswith("HG"):
249 # old client with uncompressed bundle
250 def generator(f):
251 yield header
252 for chunk in f:
253 yield chunk
254 elif not header.startswith("HG10"):
255 req.write("0\n")
256 req.write(_("unknown bundle version\n"))
257 return
258 elif header == "HG10GZ":
259 def generator(f):
260 zd = zlib.decompressobj()
261 for chunk in f:
262 yield zd.decompress(chunk)
263 elif header == "HG10BZ":
264 def generator(f):
265 zd = bz2.BZ2Decompressor()
266 zd.decompress("BZ")
267 for chunk in f:
268 yield zd.decompress(chunk)
269 elif header == "HG10UN":
270 def generator(f):
271 for chunk in f:
272 yield chunk
273 else:
274 req.write("0\n")
275 req.write(_("unknown bundle compression type\n"))
276 return
277 gen = generator(util.filechunkiter(fp, 4096))
278
279 # send addchangegroup output to client
280
281 old_stdout = sys.stdout
282 sys.stdout = cStringIO.StringIO()
283
284 try:
285 url = 'remote:%s:%s' % (proto,
286 req.env.get('REMOTE_HOST', ''))
287 try:
288 ret = web.repo.addchangegroup(
289 util.chunkbuffer(gen), 'serve', url)
290 except util.Abort, inst:
291 sys.stdout.write("abort: %s\n" % inst)
292 ret = 0
293 finally:
294 val = sys.stdout.getvalue()
295 sys.stdout = old_stdout
296 req.write('%d\n' % ret)
297 req.write(val)
298 finally:
299 del lock
300 except (OSError, IOError), inst:
301 req.write('0\n')
302 filename = getattr(inst, 'filename', '')
303 # Don't send our filesystem layout to the client
304 if filename.startswith(web.repo.root):
305 filename = filename[len(web.repo.root)+1:]
306 else:
307 filename = ''
308 error = getattr(inst, 'strerror', 'Unknown error')
309 if inst.errno == errno.ENOENT:
310 code = 404
311 else:
312 code = 500
313 req.respond(code, '%s: %s\n' % (error, filename))
314 finally:
315 fp.close()
316 os.unlink(tempname)
317
318 def stream_out(web, req):
319 req.httphdr("application/mercurial-0.1")
320 streamclone.stream_out(web.repo, req, untrusted=True)
This diff has been collapsed as it changes many lines, (687 lines changed) Show them Hide them
@@ -6,14 +6,13 b''
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] != "/":
@@ -107,6 +106,190 b' class hgweb(object):'
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)
109
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
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():
@@ -668,190 +851,6 b' class hgweb(object):'
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]
@@ -883,181 +882,6 b' class hgweb(object):'
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.
@@ -1071,138 +895,3 b' class hgweb(object):'
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)
General Comments 0
You need to be logged in to leave comments. Login now