Mercurial > public > mercurial-scm > hg
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) |