comparison mercurial/hgweb/protocol.py @ 5598:d534ba1c4eb4

separate the wire protocol commands from the user interface commands
author Dirkjan Ochtman <dirkjan@ochtman.nl>
date Mon, 03 Dec 2007 12:27:11 +0100
parents
children d0576d065993
comparison
equal deleted inserted replaced
5597:e7f99a3ed008 5598:d534ba1c4eb4
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, bz2, tempfile, errno, os, sys
9 from mercurial import util, streamclone
10 from mercurial.i18n import gettext as _
11 from mercurial.node import *
12
13 def lookup(web, req):
14 try:
15 r = hex(web.repo.lookup(req.form['key'][0]))
16 success = 1
17 except Exception,inst:
18 r = str(inst)
19 success = 0
20 resp = "%s %s\n" % (success, r)
21 req.httphdr("application/mercurial-0.1", length=len(resp))
22 req.write(resp)
23
24 def heads(web, req):
25 resp = " ".join(map(hex, web.repo.heads())) + "\n"
26 req.httphdr("application/mercurial-0.1", length=len(resp))
27 req.write(resp)
28
29 def branches(web, req):
30 nodes = []
31 if req.form.has_key('nodes'):
32 nodes = map(bin, req.form['nodes'][0].split(" "))
33 resp = cStringIO.StringIO()
34 for b in web.repo.branches(nodes):
35 resp.write(" ".join(map(hex, b)) + "\n")
36 resp = resp.getvalue()
37 req.httphdr("application/mercurial-0.1", length=len(resp))
38 req.write(resp)
39
40 def between(web, req):
41 if req.form.has_key('pairs'):
42 pairs = [map(bin, p.split("-"))
43 for p in req.form['pairs'][0].split(" ")]
44 resp = cStringIO.StringIO()
45 for b in web.repo.between(pairs):
46 resp.write(" ".join(map(hex, b)) + "\n")
47 resp = resp.getvalue()
48 req.httphdr("application/mercurial-0.1", length=len(resp))
49 req.write(resp)
50
51 def changegroup(web, req):
52 req.httphdr("application/mercurial-0.1")
53 nodes = []
54 if not web.allowpull:
55 return
56
57 if req.form.has_key('roots'):
58 nodes = map(bin, req.form['roots'][0].split(" "))
59
60 z = zlib.compressobj()
61 f = web.repo.changegroup(nodes, 'serve')
62 while 1:
63 chunk = f.read(4096)
64 if not chunk:
65 break
66 req.write(z.compress(chunk))
67
68 req.write(z.flush())
69
70 def changegroupsubset(web, req):
71 req.httphdr("application/mercurial-0.1")
72 bases = []
73 heads = []
74 if not web.allowpull:
75 return
76
77 if req.form.has_key('bases'):
78 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
79 if req.form.has_key('heads'):
80 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
81
82 z = zlib.compressobj()
83 f = web.repo.changegroupsubset(bases, heads, 'serve')
84 while 1:
85 chunk = f.read(4096)
86 if not chunk:
87 break
88 req.write(z.compress(chunk))
89
90 req.write(z.flush())
91
92 def capabilities(web, req):
93 caps = ['lookup', 'changegroupsubset']
94 if web.configbool('server', 'uncompressed'):
95 caps.append('stream=%d' % web.repo.changelog.version)
96 # XXX: make configurable and/or share code with do_unbundle:
97 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
98 if unbundleversions:
99 caps.append('unbundle=%s' % ','.join(unbundleversions))
100 resp = ' '.join(caps)
101 req.httphdr("application/mercurial-0.1", length=len(resp))
102 req.write(resp)
103
104 def unbundle(web, req):
105 def bail(response, headers={}):
106 length = int(req.env['CONTENT_LENGTH'])
107 for s in util.filechunkiter(req, limit=length):
108 # drain incoming bundle, else client will not see
109 # response when run outside cgi script
110 pass
111 req.httphdr("application/mercurial-0.1", headers=headers)
112 req.write('0\n')
113 req.write(response)
114
115 # require ssl by default, auth info cannot be sniffed and
116 # replayed
117 ssl_req = web.configbool('web', 'push_ssl', True)
118 if ssl_req:
119 if req.env.get('wsgi.url_scheme') != 'https':
120 bail(_('ssl required\n'))
121 return
122 proto = 'https'
123 else:
124 proto = 'http'
125
126 # do not allow push unless explicitly allowed
127 if not web.check_perm(req, 'push', False):
128 bail(_('push not authorized\n'),
129 headers={'status': '401 Unauthorized'})
130 return
131
132 their_heads = req.form['heads'][0].split(' ')
133
134 def check_heads():
135 heads = map(hex, web.repo.heads())
136 return their_heads == [hex('force')] or their_heads == heads
137
138 # fail early if possible
139 if not check_heads():
140 bail(_('unsynced changes\n'))
141 return
142
143 req.httphdr("application/mercurial-0.1")
144
145 # do not lock repo until all changegroup data is
146 # streamed. save to temporary file.
147
148 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
149 fp = os.fdopen(fd, 'wb+')
150 try:
151 length = int(req.env['CONTENT_LENGTH'])
152 for s in util.filechunkiter(req, limit=length):
153 fp.write(s)
154
155 try:
156 lock = web.repo.lock()
157 try:
158 if not check_heads():
159 req.write('0\n')
160 req.write(_('unsynced changes\n'))
161 return
162
163 fp.seek(0)
164 header = fp.read(6)
165 if not header.startswith("HG"):
166 # old client with uncompressed bundle
167 def generator(f):
168 yield header
169 for chunk in f:
170 yield chunk
171 elif not header.startswith("HG10"):
172 req.write("0\n")
173 req.write(_("unknown bundle version\n"))
174 return
175 elif header == "HG10GZ":
176 def generator(f):
177 zd = zlib.decompressobj()
178 for chunk in f:
179 yield zd.decompress(chunk)
180 elif header == "HG10BZ":
181 def generator(f):
182 zd = bz2.BZ2Decompressor()
183 zd.decompress("BZ")
184 for chunk in f:
185 yield zd.decompress(chunk)
186 elif header == "HG10UN":
187 def generator(f):
188 for chunk in f:
189 yield chunk
190 else:
191 req.write("0\n")
192 req.write(_("unknown bundle compression type\n"))
193 return
194 gen = generator(util.filechunkiter(fp, 4096))
195
196 # send addchangegroup output to client
197
198 old_stdout = sys.stdout
199 sys.stdout = cStringIO.StringIO()
200
201 try:
202 url = 'remote:%s:%s' % (proto,
203 req.env.get('REMOTE_HOST', ''))
204 try:
205 ret = web.repo.addchangegroup(
206 util.chunkbuffer(gen), 'serve', url)
207 except util.Abort, inst:
208 sys.stdout.write("abort: %s\n" % inst)
209 ret = 0
210 finally:
211 val = sys.stdout.getvalue()
212 sys.stdout = old_stdout
213 req.write('%d\n' % ret)
214 req.write(val)
215 finally:
216 del lock
217 except (OSError, IOError), inst:
218 req.write('0\n')
219 filename = getattr(inst, 'filename', '')
220 # Don't send our filesystem layout to the client
221 if filename.startswith(web.repo.root):
222 filename = filename[len(web.repo.root)+1:]
223 else:
224 filename = ''
225 error = getattr(inst, 'strerror', 'Unknown error')
226 if inst.errno == errno.ENOENT:
227 code = 404
228 else:
229 code = 500
230 req.respond(code, '%s: %s\n' % (error, filename))
231 finally:
232 fp.close()
233 os.unlink(tempname)
234
235 def stream_out(web, req):
236 req.httphdr("application/mercurial-0.1")
237 streamclone.stream_out(web.repo, req, untrusted=True)