comparison mercurial/hgweb/webcommands.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
children b95b2525c6e8
comparison
equal deleted inserted replaced
5590:05451f6b5f07 5591:08887121a652
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)