|
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) |