Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/hgweb/hgweb_mod.py @ 5620:652f57de3ccf
Merge with crew
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Fri, 07 Dec 2007 14:59:33 -0600 |
parents | 083b6e3142a2 9d900f7282e6 |
children | e9f68860d5ed |
comparison
equal
deleted
inserted
replaced
5610:2493a478f395 | 5620:652f57de3ccf |
---|---|
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
5 # | 5 # |
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 |
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, protocol | |
16 | |
17 shortcuts = { | |
18 'cl': [('cmd', ['changelog']), ('rev', None)], | |
19 'sl': [('cmd', ['shortlog']), ('rev', None)], | |
20 'cs': [('cmd', ['changeset']), ('node', None)], | |
21 'f': [('cmd', ['file']), ('filenode', None)], | |
22 'fl': [('cmd', ['filelog']), ('filenode', None)], | |
23 'fd': [('cmd', ['filediff']), ('node', None)], | |
24 'fa': [('cmd', ['annotate']), ('filenode', None)], | |
25 'mf': [('cmd', ['manifest']), ('manifest', None)], | |
26 'ca': [('cmd', ['archive']), ('node', None)], | |
27 'tags': [('cmd', ['tags'])], | |
28 'tip': [('cmd', ['changeset']), ('node', ['tip'])], | |
29 'static': [('cmd', ['static']), ('file', None)] | |
30 } | |
17 | 31 |
18 def _up(p): | 32 def _up(p): |
19 if p[0] != "/": | 33 if p[0] != "/": |
20 p = "/" + p | 34 p = "/" + p |
21 if p[-1] == "/": | 35 if p[-1] == "/": |
105 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60)) | 119 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60)) |
106 self.maxfiles = int(self.config("web", "maxfiles", 10)) | 120 self.maxfiles = int(self.config("web", "maxfiles", 10)) |
107 self.allowpull = self.configbool("web", "allowpull", True) | 121 self.allowpull = self.configbool("web", "allowpull", True) |
108 self.encoding = self.config("web", "encoding", util._encoding) | 122 self.encoding = self.config("web", "encoding", util._encoding) |
109 | 123 |
124 def run(self): | |
125 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): | |
126 raise RuntimeError("This function is only intended to be called while running as a CGI script.") | |
127 import mercurial.hgweb.wsgicgi as wsgicgi | |
128 wsgicgi.launch(self) | |
129 | |
130 def __call__(self, env, respond): | |
131 req = wsgirequest(env, respond) | |
132 self.run_wsgi(req) | |
133 return req | |
134 | |
135 def run_wsgi(self, req): | |
136 | |
137 self.refresh() | |
138 | |
139 # expand form shortcuts | |
140 | |
141 for k in shortcuts.iterkeys(): | |
142 if k in req.form: | |
143 for name, value in shortcuts[k]: | |
144 if value is None: | |
145 value = req.form[k] | |
146 req.form[name] = value | |
147 del req.form[k] | |
148 | |
149 # work with CGI variables to create coherent structure | |
150 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME | |
151 | |
152 req.url = req.env['SCRIPT_NAME'] | |
153 if not req.url.endswith('/'): | |
154 req.url += '/' | |
155 if req.env.has_key('REPO_NAME'): | |
156 req.url += req.env['REPO_NAME'] + '/' | |
157 | |
158 if req.env.get('PATH_INFO'): | |
159 parts = req.env.get('PATH_INFO').strip('/').split('/') | |
160 repo_parts = req.env.get('REPO_NAME', '').split('/') | |
161 if parts[:len(repo_parts)] == repo_parts: | |
162 parts = parts[len(repo_parts):] | |
163 query = '/'.join(parts) | |
164 else: | |
165 query = req.env['QUERY_STRING'].split('&', 1)[0] | |
166 query = query.split(';', 1)[0] | |
167 | |
168 # translate user-visible url structure to internal structure | |
169 | |
170 args = query.split('/', 2) | |
171 if 'cmd' not in req.form and args and args[0]: | |
172 | |
173 cmd = args.pop(0) | |
174 style = cmd.rfind('-') | |
175 if style != -1: | |
176 req.form['style'] = [cmd[:style]] | |
177 cmd = cmd[style+1:] | |
178 | |
179 # avoid accepting e.g. style parameter as command | |
180 if hasattr(webcommands, cmd) or hasattr(protocol, cmd): | |
181 req.form['cmd'] = [cmd] | |
182 | |
183 if args and args[0]: | |
184 node = args.pop(0) | |
185 req.form['node'] = [node] | |
186 if args: | |
187 req.form['file'] = args | |
188 | |
189 if cmd == 'static': | |
190 req.form['file'] = req.form['node'] | |
191 elif cmd == 'archive': | |
192 fn = req.form['node'][0] | |
193 for type_, spec in self.archive_specs.iteritems(): | |
194 ext = spec[2] | |
195 if fn.endswith(ext): | |
196 req.form['node'] = [fn[:-len(ext)]] | |
197 req.form['type'] = [type_] | |
198 | |
199 # actually process the request | |
200 | |
201 try: | |
202 | |
203 cmd = req.form.get('cmd', [''])[0] | |
204 if hasattr(protocol, cmd): | |
205 method = getattr(protocol, cmd) | |
206 method(self, req) | |
207 else: | |
208 tmpl = self.templater(req) | |
209 if cmd == '': | |
210 req.form['cmd'] = [tmpl.cache['default']] | |
211 cmd = req.form['cmd'][0] | |
212 method = getattr(webcommands, cmd) | |
213 method(self, req, tmpl) | |
214 del tmpl | |
215 | |
216 except revlog.LookupError, err: | |
217 req.respond(404, tmpl( | |
218 'error', error='revision not found: %s' % err.name)) | |
219 except (hg.RepoError, revlog.RevlogError), inst: | |
220 req.respond('500 Internal Server Error', | |
221 tmpl('error', error=str(inst))) | |
222 except ErrorResponse, inst: | |
223 req.respond(inst.code, tmpl('error', error=inst.message)) | |
224 except AttributeError: | |
225 req.respond(400, tmpl('error', error='No such method: ' + cmd)) | |
226 | |
227 def templater(self, req): | |
228 | |
229 # determine scheme, port and server name | |
230 # this is needed to create absolute urls | |
231 | |
232 proto = req.env.get('wsgi.url_scheme') | |
233 if proto == 'https': | |
234 proto = 'https' | |
235 default_port = "443" | |
236 else: | |
237 proto = 'http' | |
238 default_port = "80" | |
239 | |
240 port = req.env["SERVER_PORT"] | |
241 port = port != default_port and (":" + port) or "" | |
242 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port) | |
243 staticurl = self.config("web", "staticurl") or req.url + 'static/' | |
244 if not staticurl.endswith('/'): | |
245 staticurl += '/' | |
246 | |
247 # some functions for the templater | |
248 | |
249 def header(**map): | |
250 header_file = cStringIO.StringIO( | |
251 ''.join(tmpl("header", encoding=self.encoding, **map))) | |
252 msg = mimetools.Message(header_file, 0) | |
253 req.header(msg.items()) | |
254 yield header_file.read() | |
255 | |
256 def rawfileheader(**map): | |
257 req.header([('Content-type', map['mimetype']), | |
258 ('Content-disposition', 'filename=%s' % map['file']), | |
259 ('Content-length', str(len(map['raw'])))]) | |
260 yield '' | |
261 | |
262 def footer(**map): | |
263 yield tmpl("footer", **map) | |
264 | |
265 def motd(**map): | |
266 yield self.config("web", "motd", "") | |
267 | |
268 def sessionvars(**map): | |
269 fields = [] | |
270 if req.form.has_key('style'): | |
271 style = req.form['style'][0] | |
272 if style != self.config('web', 'style', ''): | |
273 fields.append(('style', style)) | |
274 | |
275 separator = req.url[-1] == '?' and ';' or '?' | |
276 for name, value in fields: | |
277 yield dict(name=name, value=value, separator=separator) | |
278 separator = ';' | |
279 | |
280 # figure out which style to use | |
281 | |
282 style = self.config("web", "style", "") | |
283 if req.form.has_key('style'): | |
284 style = req.form['style'][0] | |
285 mapfile = style_map(self.templatepath, style) | |
286 | |
287 if not self.reponame: | |
288 self.reponame = (self.config("web", "name") | |
289 or req.env.get('REPO_NAME') | |
290 or req.url.strip('/') or self.repo.root) | |
291 | |
292 # create the templater | |
293 | |
294 tmpl = templater.templater(mapfile, templater.common_filters, | |
295 defaults={"url": req.url, | |
296 "staticurl": staticurl, | |
297 "urlbase": urlbase, | |
298 "repo": self.reponame, | |
299 "header": header, | |
300 "footer": footer, | |
301 "motd": motd, | |
302 "rawfileheader": rawfileheader, | |
303 "sessionvars": sessionvars | |
304 }) | |
305 return tmpl | |
306 | |
110 def archivelist(self, nodeid): | 307 def archivelist(self, nodeid): |
111 allowed = self.configlist("web", "allow_archive") | 308 allowed = self.configlist("web", "allow_archive") |
112 for i, spec in self.archive_specs.iteritems(): | 309 for i, spec in self.archive_specs.iteritems(): |
113 if i in allowed or self.configbool("web", "allow" + i): | 310 if i in allowed or self.configbool("web", "allow" + i): |
114 yield {"type" : i, "extension" : spec[2], "node" : nodeid} | 311 yield {"type" : i, "extension" : spec[2], "node" : nodeid} |
115 | 312 |
116 def listfilediffs(self, files, changeset): | 313 def listfilediffs(self, tmpl, files, changeset): |
117 for f in files[:self.maxfiles]: | 314 for f in files[:self.maxfiles]: |
118 yield self.t("filedifflink", node=hex(changeset), file=f) | 315 yield tmpl("filedifflink", node=hex(changeset), file=f) |
119 if len(files) > self.maxfiles: | 316 if len(files) > self.maxfiles: |
120 yield self.t("fileellipses") | 317 yield tmpl("fileellipses") |
121 | 318 |
122 def siblings(self, siblings=[], hiderev=None, **args): | 319 def siblings(self, siblings=[], hiderev=None, **args): |
123 siblings = [s for s in siblings if s.node() != nullid] | 320 siblings = [s for s in siblings if s.node() != nullid] |
124 if len(siblings) == 1 and siblings[0].rev() == hiderev: | 321 if len(siblings) == 1 and siblings[0].rev() == hiderev: |
125 return | 322 return |
147 # an empty dict. Using dict.get avoids a traceback. | 344 # an empty dict. Using dict.get avoids a traceback. |
148 if self.repo.branchtags().get(branch) == ctx.node(): | 345 if self.repo.branchtags().get(branch) == ctx.node(): |
149 branches.append({"name": branch}) | 346 branches.append({"name": branch}) |
150 return branches | 347 return branches |
151 | 348 |
152 def showtag(self, t1, node=nullid, **args): | 349 def showtag(self, tmpl, t1, node=nullid, **args): |
153 for t in self.repo.nodetags(node): | 350 for t in self.repo.nodetags(node): |
154 yield self.t(t1, tag=t, **args) | 351 yield tmpl(t1, tag=t, **args) |
155 | 352 |
156 def diff(self, node1, node2, files): | 353 def diff(self, tmpl, node1, node2, files): |
157 def filterfiles(filters, files): | 354 def filterfiles(filters, files): |
158 l = [x for x in files if x in filters] | 355 l = [x for x in files if x in filters] |
159 | 356 |
160 for t in filters: | 357 for t in filters: |
161 if t and t[-1] != os.sep: | 358 if t and t[-1] != os.sep: |
163 l += [x for x in files if x.startswith(t)] | 360 l += [x for x in files if x.startswith(t)] |
164 return l | 361 return l |
165 | 362 |
166 parity = paritygen(self.stripecount) | 363 parity = paritygen(self.stripecount) |
167 def diffblock(diff, f, fn): | 364 def diffblock(diff, f, fn): |
168 yield self.t("diffblock", | 365 yield tmpl("diffblock", |
169 lines=prettyprintlines(diff), | 366 lines=prettyprintlines(diff), |
170 parity=parity.next(), | 367 parity=parity.next(), |
171 file=f, | 368 file=f, |
172 filenode=hex(fn or nullid)) | 369 filenode=hex(fn or nullid)) |
173 | 370 |
174 def prettyprintlines(diff): | 371 def prettyprintlines(diff): |
175 for l in diff.splitlines(1): | 372 for l in diff.splitlines(1): |
176 if l.startswith('+'): | 373 if l.startswith('+'): |
177 yield self.t("difflineplus", line=l) | 374 yield tmpl("difflineplus", line=l) |
178 elif l.startswith('-'): | 375 elif l.startswith('-'): |
179 yield self.t("difflineminus", line=l) | 376 yield tmpl("difflineminus", line=l) |
180 elif l.startswith('@'): | 377 elif l.startswith('@'): |
181 yield self.t("difflineat", line=l) | 378 yield tmpl("difflineat", line=l) |
182 else: | 379 else: |
183 yield self.t("diffline", line=l) | 380 yield tmpl("diffline", line=l) |
184 | 381 |
185 r = self.repo | 382 r = self.repo |
186 c1 = r.changectx(node1) | 383 c1 = r.changectx(node1) |
187 c2 = r.changectx(node2) | 384 c2 = r.changectx(node2) |
188 date1 = util.datestr(c1.date()) | 385 date1 = util.datestr(c1.date()) |
208 to = c1.filectx(f).data() | 405 to = c1.filectx(f).data() |
209 tn = None | 406 tn = None |
210 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f, | 407 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f, |
211 opts=diffopts), f, tn) | 408 opts=diffopts), f, tn) |
212 | 409 |
213 def changelog(self, ctx, shortlog=False): | 410 def changelog(self, tmpl, ctx, shortlog=False): |
214 def changelist(limit=0,**map): | 411 def changelist(limit=0,**map): |
215 cl = self.repo.changelog | 412 cl = self.repo.changelog |
216 l = [] # build a list in forward order for efficiency | 413 l = [] # build a list in forward order for efficiency |
217 for i in xrange(start, end): | 414 for i in xrange(start, end): |
218 ctx = self.repo.changectx(i) | 415 ctx = self.repo.changectx(i) |
223 "parent": self.siblings(ctx.parents(), i - 1), | 420 "parent": self.siblings(ctx.parents(), i - 1), |
224 "child": self.siblings(ctx.children(), i + 1), | 421 "child": self.siblings(ctx.children(), i + 1), |
225 "changelogtag": self.showtag("changelogtag",n), | 422 "changelogtag": self.showtag("changelogtag",n), |
226 "desc": ctx.description(), | 423 "desc": ctx.description(), |
227 "date": ctx.date(), | 424 "date": ctx.date(), |
228 "files": self.listfilediffs(ctx.files(), n), | 425 "files": self.listfilediffs(tmpl, ctx.files(), n), |
229 "rev": i, | 426 "rev": i, |
230 "node": hex(n), | 427 "node": hex(n), |
231 "tags": self.nodetagsdict(n), | 428 "tags": self.nodetagsdict(n), |
232 "branches": self.nodebranchdict(ctx)}) | 429 "branches": self.nodebranchdict(ctx)}) |
233 | 430 |
246 pos = end - 1 | 443 pos = end - 1 |
247 parity = paritygen(self.stripecount, offset=start-end) | 444 parity = paritygen(self.stripecount, offset=start-end) |
248 | 445 |
249 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx) | 446 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx) |
250 | 447 |
251 yield self.t(shortlog and 'shortlog' or 'changelog', | 448 yield tmpl(shortlog and 'shortlog' or 'changelog', |
252 changenav=changenav, | 449 changenav=changenav, |
253 node=hex(cl.tip()), | 450 node=hex(cl.tip()), |
254 rev=pos, changesets=count, | 451 rev=pos, changesets=count, |
255 entries=lambda **x: changelist(limit=0,**x), | 452 entries=lambda **x: changelist(limit=0,**x), |
256 latestentry=lambda **x: changelist(limit=1,**x), | 453 latestentry=lambda **x: changelist(limit=1,**x), |
257 archives=self.archivelist("tip")) | 454 archives=self.archivelist("tip")) |
258 | 455 |
259 def search(self, query): | 456 def search(self, tmpl, query): |
260 | 457 |
261 def changelist(**map): | 458 def changelist(**map): |
262 cl = self.repo.changelog | 459 cl = self.repo.changelog |
263 count = 0 | 460 count = 0 |
264 qw = query.lower().split() | 461 qw = query.lower().split() |
285 continue | 482 continue |
286 | 483 |
287 count += 1 | 484 count += 1 |
288 n = ctx.node() | 485 n = ctx.node() |
289 | 486 |
290 yield self.t('searchentry', | 487 yield tmpl('searchentry', |
291 parity=parity.next(), | 488 parity=parity.next(), |
292 author=ctx.user(), | 489 author=ctx.user(), |
293 parent=self.siblings(ctx.parents()), | 490 parent=self.siblings(ctx.parents()), |
294 child=self.siblings(ctx.children()), | 491 child=self.siblings(ctx.children()), |
295 changelogtag=self.showtag("changelogtag",n), | 492 changelogtag=self.showtag("changelogtag",n), |
296 desc=ctx.description(), | 493 desc=ctx.description(), |
297 date=ctx.date(), | 494 date=ctx.date(), |
298 files=self.listfilediffs(ctx.files(), n), | 495 files=self.listfilediffs(tmpl, ctx.files(), n), |
299 rev=ctx.rev(), | 496 rev=ctx.rev(), |
300 node=hex(n), | 497 node=hex(n), |
301 tags=self.nodetagsdict(n), | 498 tags=self.nodetagsdict(n), |
302 branches=self.nodebranchdict(ctx)) | 499 branches=self.nodebranchdict(ctx)) |
303 | 500 |
304 if count >= self.maxchanges: | 501 if count >= self.maxchanges: |
305 break | 502 break |
306 | 503 |
307 cl = self.repo.changelog | 504 cl = self.repo.changelog |
308 parity = paritygen(self.stripecount) | 505 parity = paritygen(self.stripecount) |
309 | 506 |
310 yield self.t('search', | 507 yield tmpl('search', |
311 query=query, | 508 query=query, |
312 node=hex(cl.tip()), | 509 node=hex(cl.tip()), |
313 entries=changelist, | 510 entries=changelist, |
314 archives=self.archivelist("tip")) | 511 archives=self.archivelist("tip")) |
315 | 512 |
316 def changeset(self, ctx): | 513 def changeset(self, tmpl, ctx): |
317 n = ctx.node() | 514 n = ctx.node() |
318 parents = ctx.parents() | 515 parents = ctx.parents() |
319 p1 = parents[0].node() | 516 p1 = parents[0].node() |
320 | 517 |
321 files = [] | 518 files = [] |
322 parity = paritygen(self.stripecount) | 519 parity = paritygen(self.stripecount) |
323 for f in ctx.files(): | 520 for f in ctx.files(): |
324 files.append(self.t("filenodelink", | 521 files.append(tmpl("filenodelink", |
325 node=hex(n), file=f, | 522 node=hex(n), file=f, |
326 parity=parity.next())) | 523 parity=parity.next())) |
327 | 524 |
328 def diff(**map): | 525 def diff(**map): |
329 yield self.diff(p1, n, None) | 526 yield self.diff(tmpl, p1, n, None) |
330 | 527 |
331 yield self.t('changeset', | 528 yield tmpl('changeset', |
332 diff=diff, | 529 diff=diff, |
333 rev=ctx.rev(), | 530 rev=ctx.rev(), |
334 node=hex(n), | 531 node=hex(n), |
335 parent=self.siblings(parents), | 532 parent=self.siblings(parents), |
336 child=self.siblings(ctx.children()), | 533 child=self.siblings(ctx.children()), |
337 changesettag=self.showtag("changesettag",n), | 534 changesettag=self.showtag("changesettag",n), |
338 author=ctx.user(), | 535 author=ctx.user(), |
339 desc=ctx.description(), | 536 desc=ctx.description(), |
340 date=ctx.date(), | 537 date=ctx.date(), |
341 files=files, | 538 files=files, |
342 archives=self.archivelist(hex(n)), | 539 archives=self.archivelist(hex(n)), |
343 tags=self.nodetagsdict(n), | 540 tags=self.nodetagsdict(n), |
344 branches=self.nodebranchdict(ctx)) | 541 branches=self.nodebranchdict(ctx)) |
345 | 542 |
346 def filelog(self, fctx): | 543 def filelog(self, tmpl, fctx): |
347 f = fctx.path() | 544 f = fctx.path() |
348 fl = fctx.filelog() | 545 fl = fctx.filelog() |
349 count = fl.count() | 546 count = fl.count() |
350 pagelen = self.maxshortchanges | 547 pagelen = self.maxshortchanges |
351 pos = fctx.filerev() | 548 pos = fctx.filerev() |
378 for e in l: | 575 for e in l: |
379 yield e | 576 yield e |
380 | 577 |
381 nodefunc = lambda x: fctx.filectx(fileid=x) | 578 nodefunc = lambda x: fctx.filectx(fileid=x) |
382 nav = revnavgen(pos, pagelen, count, nodefunc) | 579 nav = revnavgen(pos, pagelen, count, nodefunc) |
383 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav, | 580 yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav, |
384 entries=lambda **x: entries(limit=0, **x), | 581 entries=lambda **x: entries(limit=0, **x), |
385 latestentry=lambda **x: entries(limit=1, **x)) | 582 latestentry=lambda **x: entries(limit=1, **x)) |
386 | 583 |
387 def filerevision(self, fctx): | 584 def filerevision(self, tmpl, fctx): |
388 f = fctx.path() | 585 f = fctx.path() |
389 text = fctx.data() | 586 text = fctx.data() |
390 fl = fctx.filelog() | 587 fl = fctx.filelog() |
391 n = fctx.filenode() | 588 n = fctx.filenode() |
392 parity = paritygen(self.stripecount) | 589 parity = paritygen(self.stripecount) |
402 for l, t in enumerate(text.splitlines(1)): | 599 for l, t in enumerate(text.splitlines(1)): |
403 yield {"line": t, | 600 yield {"line": t, |
404 "linenumber": "% 6d" % (l + 1), | 601 "linenumber": "% 6d" % (l + 1), |
405 "parity": parity.next()} | 602 "parity": parity.next()} |
406 | 603 |
407 yield self.t("filerevision", | 604 yield tmpl("filerevision", |
408 file=f, | 605 file=f, |
409 path=_up(f), | 606 path=_up(f), |
410 text=lines(), | 607 text=lines(), |
411 raw=rawtext, | 608 raw=rawtext, |
412 mimetype=mt, | 609 mimetype=mt, |
413 rev=fctx.rev(), | 610 rev=fctx.rev(), |
414 node=hex(fctx.node()), | 611 node=hex(fctx.node()), |
415 author=fctx.user(), | 612 author=fctx.user(), |
416 date=fctx.date(), | 613 date=fctx.date(), |
417 desc=fctx.description(), | 614 desc=fctx.description(), |
418 parent=self.siblings(fctx.parents()), | 615 parent=self.siblings(fctx.parents()), |
419 child=self.siblings(fctx.children()), | 616 child=self.siblings(fctx.children()), |
420 rename=self.renamelink(fl, n), | 617 rename=self.renamelink(fl, n), |
421 permissions=fctx.manifest().flags(f)) | 618 permissions=fctx.manifest().flags(f)) |
422 | 619 |
423 def fileannotate(self, fctx): | 620 def fileannotate(self, tmpl, fctx): |
424 f = fctx.path() | 621 f = fctx.path() |
425 n = fctx.filenode() | 622 n = fctx.filenode() |
426 fl = fctx.filelog() | 623 fl = fctx.filelog() |
427 parity = paritygen(self.stripecount) | 624 parity = paritygen(self.stripecount) |
428 | 625 |
440 "rev": f.rev(), | 637 "rev": f.rev(), |
441 "author": name, | 638 "author": name, |
442 "file": f.path(), | 639 "file": f.path(), |
443 "line": l} | 640 "line": l} |
444 | 641 |
445 yield self.t("fileannotate", | 642 yield tmpl("fileannotate", |
446 file=f, | 643 file=f, |
447 annotate=annotate, | 644 annotate=annotate, |
448 path=_up(f), | 645 path=_up(f), |
449 rev=fctx.rev(), | 646 rev=fctx.rev(), |
450 node=hex(fctx.node()), | 647 node=hex(fctx.node()), |
451 author=fctx.user(), | 648 author=fctx.user(), |
452 date=fctx.date(), | 649 date=fctx.date(), |
453 desc=fctx.description(), | 650 desc=fctx.description(), |
454 rename=self.renamelink(fl, n), | 651 rename=self.renamelink(fl, n), |
455 parent=self.siblings(fctx.parents()), | 652 parent=self.siblings(fctx.parents()), |
456 child=self.siblings(fctx.children()), | 653 child=self.siblings(fctx.children()), |
457 permissions=fctx.manifest().flags(f)) | 654 permissions=fctx.manifest().flags(f)) |
458 | 655 |
459 def manifest(self, ctx, path): | 656 def manifest(self, tmpl, ctx, path): |
460 mf = ctx.manifest() | 657 mf = ctx.manifest() |
461 node = ctx.node() | 658 node = ctx.node() |
462 | 659 |
463 files = {} | 660 files = {} |
464 parity = paritygen(self.stripecount) | 661 parity = paritygen(self.stripecount) |
508 | 705 |
509 yield {"parity": parity.next(), | 706 yield {"parity": parity.next(), |
510 "path": "%s%s" % (abspath, f), | 707 "path": "%s%s" % (abspath, f), |
511 "basename": f[:-1]} | 708 "basename": f[:-1]} |
512 | 709 |
513 yield self.t("manifest", | 710 yield tmpl("manifest", |
514 rev=ctx.rev(), | 711 rev=ctx.rev(), |
515 node=hex(node), | 712 node=hex(node), |
516 path=abspath, | 713 path=abspath, |
517 up=_up(abspath), | 714 up=_up(abspath), |
518 upparity=parity.next(), | 715 upparity=parity.next(), |
519 fentries=filelist, | 716 fentries=filelist, |
520 dentries=dirlist, | 717 dentries=dirlist, |
521 archives=self.archivelist(hex(node)), | 718 archives=self.archivelist(hex(node)), |
522 tags=self.nodetagsdict(node), | 719 tags=self.nodetagsdict(node), |
523 branches=self.nodebranchdict(ctx)) | 720 branches=self.nodebranchdict(ctx)) |
524 | 721 |
525 def tags(self): | 722 def tags(self, tmpl): |
526 i = self.repo.tagslist() | 723 i = self.repo.tagslist() |
527 i.reverse() | 724 i.reverse() |
528 parity = paritygen(self.stripecount) | 725 parity = paritygen(self.stripecount) |
529 | 726 |
530 def entries(notip=False,limit=0, **map): | 727 def entries(notip=False,limit=0, **map): |
538 yield {"parity": parity.next(), | 735 yield {"parity": parity.next(), |
539 "tag": k, | 736 "tag": k, |
540 "date": self.repo.changectx(n).date(), | 737 "date": self.repo.changectx(n).date(), |
541 "node": hex(n)} | 738 "node": hex(n)} |
542 | 739 |
543 yield self.t("tags", | 740 yield tmpl("tags", |
544 node=hex(self.repo.changelog.tip()), | 741 node=hex(self.repo.changelog.tip()), |
545 entries=lambda **x: entries(False,0, **x), | 742 entries=lambda **x: entries(False,0, **x), |
546 entriesnotip=lambda **x: entries(True,0, **x), | 743 entriesnotip=lambda **x: entries(True,0, **x), |
547 latestentry=lambda **x: entries(True,1, **x)) | 744 latestentry=lambda **x: entries(True,1, **x)) |
548 | 745 |
549 def summary(self): | 746 def summary(self, tmpl): |
550 i = self.repo.tagslist() | 747 i = self.repo.tagslist() |
551 i.reverse() | 748 i.reverse() |
552 | 749 |
553 def tagentries(**map): | 750 def tagentries(**map): |
554 parity = paritygen(self.stripecount) | 751 parity = paritygen(self.stripecount) |
559 | 756 |
560 count += 1 | 757 count += 1 |
561 if count > 10: # limit to 10 tags | 758 if count > 10: # limit to 10 tags |
562 break; | 759 break; |
563 | 760 |
564 yield self.t("tagentry", | 761 yield tmpl("tagentry", |
565 parity=parity.next(), | 762 parity=parity.next(), |
566 tag=k, | 763 tag=k, |
567 node=hex(n), | 764 node=hex(n), |
568 date=self.repo.changectx(n).date()) | 765 date=self.repo.changectx(n).date()) |
569 | 766 |
570 | 767 |
571 def branches(**map): | 768 def branches(**map): |
572 parity = paritygen(self.stripecount) | 769 parity = paritygen(self.stripecount) |
573 | 770 |
589 for i in xrange(start, end): | 786 for i in xrange(start, end): |
590 ctx = self.repo.changectx(i) | 787 ctx = self.repo.changectx(i) |
591 n = ctx.node() | 788 n = ctx.node() |
592 hn = hex(n) | 789 hn = hex(n) |
593 | 790 |
594 l.insert(0, self.t( | 791 l.insert(0, tmpl( |
595 'shortlogentry', | 792 'shortlogentry', |
596 parity=parity.next(), | 793 parity=parity.next(), |
597 author=ctx.user(), | 794 author=ctx.user(), |
598 desc=ctx.description(), | 795 desc=ctx.description(), |
599 date=ctx.date(), | 796 date=ctx.date(), |
600 rev=i, | 797 rev=i, |
607 cl = self.repo.changelog | 804 cl = self.repo.changelog |
608 count = cl.count() | 805 count = cl.count() |
609 start = max(0, count - self.maxchanges) | 806 start = max(0, count - self.maxchanges) |
610 end = min(count, start + self.maxchanges) | 807 end = min(count, start + self.maxchanges) |
611 | 808 |
612 yield self.t("summary", | 809 yield tmpl("summary", |
613 desc=self.config("web", "description", "unknown"), | 810 desc=self.config("web", "description", "unknown"), |
614 owner=(self.config("ui", "username") or # preferred | 811 owner=(self.config("ui", "username") or # preferred |
615 self.config("web", "contact") or # deprecated | 812 self.config("web", "contact") or # deprecated |
616 self.config("web", "author", "unknown")), # also | 813 self.config("web", "author", "unknown")), # also |
617 lastchange=cl.read(cl.tip())[2], | 814 lastchange=cl.read(cl.tip())[2], |
618 tags=tagentries, | 815 tags=tagentries, |
619 branches=branches, | 816 branches=branches, |
620 shortlog=changelist, | 817 shortlog=changelist, |
621 node=hex(cl.tip()), | 818 node=hex(cl.tip()), |
622 archives=self.archivelist("tip")) | 819 archives=self.archivelist("tip")) |
623 | 820 |
624 def filediff(self, fctx): | 821 def filediff(self, tmpl, fctx): |
625 n = fctx.node() | 822 n = fctx.node() |
626 path = fctx.path() | 823 path = fctx.path() |
627 parents = fctx.parents() | 824 parents = fctx.parents() |
628 p1 = parents and parents[0].node() or nullid | 825 p1 = parents and parents[0].node() or nullid |
629 | 826 |
630 def diff(**map): | 827 def diff(**map): |
631 yield self.diff(p1, n, [path]) | 828 yield self.diff(tmpl, p1, n, [path]) |
632 | 829 |
633 yield self.t("filediff", | 830 yield tmpl("filediff", |
634 file=path, | 831 file=path, |
635 node=hex(n), | 832 node=hex(n), |
636 rev=fctx.rev(), | 833 rev=fctx.rev(), |
637 parent=self.siblings(parents), | 834 parent=self.siblings(parents), |
638 child=self.siblings(fctx.children()), | 835 child=self.siblings(fctx.children()), |
639 diff=diff) | 836 diff=diff) |
640 | 837 |
641 archive_specs = { | 838 archive_specs = { |
642 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), | 839 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), |
643 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), | 840 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), |
644 'zip': ('application/zip', 'zip', '.zip', None), | 841 'zip': ('application/zip', 'zip', '.zip', None), |
645 } | 842 } |
646 | 843 |
647 def archive(self, req, key, type_): | 844 def archive(self, tmpl, req, key, type_): |
648 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) | 845 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) |
649 cnode = self.repo.lookup(key) | 846 cnode = self.repo.lookup(key) |
650 arch_version = key | 847 arch_version = key |
651 if cnode == key or key == 'tip': | 848 if cnode == key or key == 'tip': |
652 arch_version = short(cnode) | 849 arch_version = short(cnode) |
666 | 863 |
667 def cleanpath(self, path): | 864 def cleanpath(self, path): |
668 path = path.lstrip('/') | 865 path = path.lstrip('/') |
669 return util.canonpath(self.repo.root, '', path) | 866 return util.canonpath(self.repo.root, '', path) |
670 | 867 |
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('/') | |
819 or os.path.basename(self.repo.root)) | |
820 | |
821 self.t = templater.templater(mapfile, templater.common_filters, | |
822 defaults={"url": req.url, | |
823 "staticurl": staticurl, | |
824 "urlbase": urlbase, | |
825 "repo": self.reponame, | |
826 "header": header, | |
827 "footer": footer, | |
828 "motd": motd, | |
829 "rawfileheader": rawfileheader, | |
830 "sessionvars": sessionvars | |
831 }) | |
832 | |
833 try: | |
834 if not req.form.has_key('cmd'): | |
835 req.form['cmd'] = [self.t.cache['default']] | |
836 | |
837 cmd = req.form['cmd'][0] | |
838 | |
839 try: | |
840 method = getattr(self, 'do_' + cmd) | |
841 method(req) | |
842 except revlog.LookupError, err: | |
843 req.respond(404, self.t( | |
844 'error', error='revision not found: %s' % err.name)) | |
845 except (hg.RepoError, revlog.RevlogError), inst: | |
846 req.respond('500 Internal Server Error', | |
847 self.t('error', error=str(inst))) | |
848 except ErrorResponse, inst: | |
849 req.respond(inst.code, self.t('error', error=inst.message)) | |
850 except AttributeError: | |
851 req.respond(400, | |
852 self.t('error', error='No such method: ' + cmd)) | |
853 finally: | |
854 self.t = None | |
855 | |
856 def changectx(self, req): | 868 def changectx(self, req): |
857 if req.form.has_key('node'): | 869 if req.form.has_key('node'): |
858 changeid = req.form['node'][0] | 870 changeid = req.form['node'][0] |
859 elif req.form.has_key('manifest'): | 871 elif req.form.has_key('manifest'): |
860 changeid = req.form['manifest'][0] | 872 changeid = req.form['manifest'][0] |
882 except hg.RepoError: | 894 except hg.RepoError: |
883 fctx = self.repo.filectx(path, fileid=changeid) | 895 fctx = self.repo.filectx(path, fileid=changeid) |
884 | 896 |
885 return fctx | 897 return fctx |
886 | 898 |
887 def do_log(self, req): | |
888 if req.form.has_key('file') and req.form['file'][0]: | |
889 self.do_filelog(req) | |
890 else: | |
891 self.do_changelog(req) | |
892 | |
893 def do_rev(self, req): | |
894 self.do_changeset(req) | |
895 | |
896 def do_file(self, req): | |
897 path = self.cleanpath(req.form.get('file', [''])[0]) | |
898 if path: | |
899 try: | |
900 req.write(self.filerevision(self.filectx(req))) | |
901 return | |
902 except revlog.LookupError: | |
903 pass | |
904 | |
905 req.write(self.manifest(self.changectx(req), path)) | |
906 | |
907 def do_diff(self, req): | |
908 self.do_filediff(req) | |
909 | |
910 def do_changelog(self, req, shortlog = False): | |
911 if req.form.has_key('node'): | |
912 ctx = self.changectx(req) | |
913 else: | |
914 if req.form.has_key('rev'): | |
915 hi = req.form['rev'][0] | |
916 else: | |
917 hi = self.repo.changelog.count() - 1 | |
918 try: | |
919 ctx = self.repo.changectx(hi) | |
920 except hg.RepoError: | |
921 req.write(self.search(hi)) # XXX redirect to 404 page? | |
922 return | |
923 | |
924 req.write(self.changelog(ctx, shortlog = shortlog)) | |
925 | |
926 def do_shortlog(self, req): | |
927 self.do_changelog(req, shortlog = True) | |
928 | |
929 def do_changeset(self, req): | |
930 req.write(self.changeset(self.changectx(req))) | |
931 | |
932 def do_manifest(self, req): | |
933 req.write(self.manifest(self.changectx(req), | |
934 self.cleanpath(req.form['path'][0]))) | |
935 | |
936 def do_tags(self, req): | |
937 req.write(self.tags()) | |
938 | |
939 def do_summary(self, req): | |
940 req.write(self.summary()) | |
941 | |
942 def do_filediff(self, req): | |
943 req.write(self.filediff(self.filectx(req))) | |
944 | |
945 def do_annotate(self, req): | |
946 req.write(self.fileannotate(self.filectx(req))) | |
947 | |
948 def do_filelog(self, req): | |
949 req.write(self.filelog(self.filectx(req))) | |
950 | |
951 def do_lookup(self, req): | |
952 try: | |
953 r = hex(self.repo.lookup(req.form['key'][0])) | |
954 success = 1 | |
955 except Exception,inst: | |
956 r = str(inst) | |
957 success = 0 | |
958 resp = "%s %s\n" % (success, r) | |
959 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
960 req.write(resp) | |
961 | |
962 def do_heads(self, req): | |
963 resp = " ".join(map(hex, self.repo.heads())) + "\n" | |
964 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
965 req.write(resp) | |
966 | |
967 def do_branches(self, req): | |
968 nodes = [] | |
969 if req.form.has_key('nodes'): | |
970 nodes = map(bin, req.form['nodes'][0].split(" ")) | |
971 resp = cStringIO.StringIO() | |
972 for b in self.repo.branches(nodes): | |
973 resp.write(" ".join(map(hex, b)) + "\n") | |
974 resp = resp.getvalue() | |
975 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
976 req.write(resp) | |
977 | |
978 def do_between(self, req): | |
979 if req.form.has_key('pairs'): | |
980 pairs = [map(bin, p.split("-")) | |
981 for p in req.form['pairs'][0].split(" ")] | |
982 resp = cStringIO.StringIO() | |
983 for b in self.repo.between(pairs): | |
984 resp.write(" ".join(map(hex, b)) + "\n") | |
985 resp = resp.getvalue() | |
986 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
987 req.write(resp) | |
988 | |
989 def do_changegroup(self, req): | |
990 req.httphdr("application/mercurial-0.1") | |
991 nodes = [] | |
992 if not self.allowpull: | |
993 return | |
994 | |
995 if req.form.has_key('roots'): | |
996 nodes = map(bin, req.form['roots'][0].split(" ")) | |
997 | |
998 z = zlib.compressobj() | |
999 f = self.repo.changegroup(nodes, 'serve') | |
1000 while 1: | |
1001 chunk = f.read(4096) | |
1002 if not chunk: | |
1003 break | |
1004 req.write(z.compress(chunk)) | |
1005 | |
1006 req.write(z.flush()) | |
1007 | |
1008 def do_changegroupsubset(self, req): | |
1009 req.httphdr("application/mercurial-0.1") | |
1010 bases = [] | |
1011 heads = [] | |
1012 if not self.allowpull: | |
1013 return | |
1014 | |
1015 if req.form.has_key('bases'): | |
1016 bases = [bin(x) for x in req.form['bases'][0].split(' ')] | |
1017 if req.form.has_key('heads'): | |
1018 heads = [bin(x) for x in req.form['heads'][0].split(' ')] | |
1019 | |
1020 z = zlib.compressobj() | |
1021 f = self.repo.changegroupsubset(bases, heads, 'serve') | |
1022 while 1: | |
1023 chunk = f.read(4096) | |
1024 if not chunk: | |
1025 break | |
1026 req.write(z.compress(chunk)) | |
1027 | |
1028 req.write(z.flush()) | |
1029 | |
1030 def do_archive(self, req): | |
1031 type_ = req.form['type'][0] | |
1032 allowed = self.configlist("web", "allow_archive") | |
1033 if (type_ in self.archives and (type_ in allowed or | |
1034 self.configbool("web", "allow" + type_, False))): | |
1035 self.archive(req, req.form['node'][0], type_) | |
1036 return | |
1037 | |
1038 req.respond(400, self.t('error', | |
1039 error='Unsupported archive type: %s' % type_)) | |
1040 | |
1041 def do_static(self, req): | |
1042 fname = req.form['file'][0] | |
1043 # a repo owner may set web.static in .hg/hgrc to get any file | |
1044 # readable by the user running the CGI script | |
1045 static = self.config("web", "static", | |
1046 os.path.join(self.templatepath, "static"), | |
1047 untrusted=False) | |
1048 req.write(staticfile(static, fname, req)) | |
1049 | |
1050 def do_capabilities(self, req): | |
1051 caps = ['lookup', 'changegroupsubset'] | |
1052 if self.configbool('server', 'uncompressed'): | |
1053 caps.append('stream=%d' % self.repo.changelog.version) | |
1054 # XXX: make configurable and/or share code with do_unbundle: | |
1055 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN'] | |
1056 if unbundleversions: | |
1057 caps.append('unbundle=%s' % ','.join(unbundleversions)) | |
1058 resp = ' '.join(caps) | |
1059 req.httphdr("application/mercurial-0.1", length=len(resp)) | |
1060 req.write(resp) | |
1061 | |
1062 def check_perm(self, req, op, default): | 899 def check_perm(self, req, op, default): |
1063 '''check permission for operation based on user auth. | 900 '''check permission for operation based on user auth. |
1064 return true if op allowed, else false. | 901 return true if op allowed, else false. |
1065 default is policy to use if no config given.''' | 902 default is policy to use if no config given.''' |
1066 | 903 |
1070 if deny and (not user or deny == ['*'] or user in deny): | 907 if deny and (not user or deny == ['*'] or user in deny): |
1071 return False | 908 return False |
1072 | 909 |
1073 allow = self.configlist('web', 'allow_' + op) | 910 allow = self.configlist('web', 'allow_' + op) |
1074 return (allow and (allow == ['*'] or user in allow)) or default | 911 return (allow and (allow == ['*'] or user in allow)) or default |
1075 | |
1076 def do_unbundle(self, req): | |
1077 def bail(response, headers={}): | |
1078 length = int(req.env['CONTENT_LENGTH']) | |
1079 for s in util.filechunkiter(req, limit=length): | |
1080 # drain incoming bundle, else client will not see | |
1081 # response when run outside cgi script | |
1082 pass | |
1083 req.httphdr("application/mercurial-0.1", headers=headers) | |
1084 req.write('0\n') | |
1085 req.write(response) | |
1086 | |
1087 # require ssl by default, auth info cannot be sniffed and | |
1088 # replayed | |
1089 ssl_req = self.configbool('web', 'push_ssl', True) | |
1090 if ssl_req: | |
1091 if req.env.get('wsgi.url_scheme') != 'https': | |
1092 bail(_('ssl required\n')) | |
1093 return | |
1094 proto = 'https' | |
1095 else: | |
1096 proto = 'http' | |
1097 | |
1098 # do not allow push unless explicitly allowed | |
1099 if not self.check_perm(req, 'push', False): | |
1100 bail(_('push not authorized\n'), | |
1101 headers={'status': '401 Unauthorized'}) | |
1102 return | |
1103 | |
1104 their_heads = req.form['heads'][0].split(' ') | |
1105 | |
1106 def check_heads(): | |
1107 heads = map(hex, self.repo.heads()) | |
1108 return their_heads == [hex('force')] or their_heads == heads | |
1109 | |
1110 # fail early if possible | |
1111 if not check_heads(): | |
1112 bail(_('unsynced changes\n')) | |
1113 return | |
1114 | |
1115 req.httphdr("application/mercurial-0.1") | |
1116 | |
1117 # do not lock repo until all changegroup data is | |
1118 # streamed. save to temporary file. | |
1119 | |
1120 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') | |
1121 fp = os.fdopen(fd, 'wb+') | |
1122 try: | |
1123 length = int(req.env['CONTENT_LENGTH']) | |
1124 for s in util.filechunkiter(req, limit=length): | |
1125 fp.write(s) | |
1126 | |
1127 try: | |
1128 lock = self.repo.lock() | |
1129 try: | |
1130 if not check_heads(): | |
1131 req.write('0\n') | |
1132 req.write(_('unsynced changes\n')) | |
1133 return | |
1134 | |
1135 fp.seek(0) | |
1136 header = fp.read(6) | |
1137 if not header.startswith("HG"): | |
1138 # old client with uncompressed bundle | |
1139 def generator(f): | |
1140 yield header | |
1141 for chunk in f: | |
1142 yield chunk | |
1143 elif not header.startswith("HG10"): | |
1144 req.write("0\n") | |
1145 req.write(_("unknown bundle version\n")) | |
1146 return | |
1147 elif header == "HG10GZ": | |
1148 def generator(f): | |
1149 zd = zlib.decompressobj() | |
1150 for chunk in f: | |
1151 yield zd.decompress(chunk) | |
1152 elif header == "HG10BZ": | |
1153 def generator(f): | |
1154 zd = bz2.BZ2Decompressor() | |
1155 zd.decompress("BZ") | |
1156 for chunk in f: | |
1157 yield zd.decompress(chunk) | |
1158 elif header == "HG10UN": | |
1159 def generator(f): | |
1160 for chunk in f: | |
1161 yield chunk | |
1162 else: | |
1163 req.write("0\n") | |
1164 req.write(_("unknown bundle compression type\n")) | |
1165 return | |
1166 gen = generator(util.filechunkiter(fp, 4096)) | |
1167 | |
1168 # send addchangegroup output to client | |
1169 | |
1170 old_stdout = sys.stdout | |
1171 sys.stdout = cStringIO.StringIO() | |
1172 | |
1173 try: | |
1174 url = 'remote:%s:%s' % (proto, | |
1175 req.env.get('REMOTE_HOST', '')) | |
1176 try: | |
1177 ret = self.repo.addchangegroup( | |
1178 util.chunkbuffer(gen), 'serve', url) | |
1179 except util.Abort, inst: | |
1180 sys.stdout.write("abort: %s\n" % inst) | |
1181 ret = 0 | |
1182 finally: | |
1183 val = sys.stdout.getvalue() | |
1184 sys.stdout = old_stdout | |
1185 req.write('%d\n' % ret) | |
1186 req.write(val) | |
1187 finally: | |
1188 del lock | |
1189 except (OSError, IOError), inst: | |
1190 req.write('0\n') | |
1191 filename = getattr(inst, 'filename', '') | |
1192 # Don't send our filesystem layout to the client | |
1193 if filename.startswith(self.repo.root): | |
1194 filename = filename[len(self.repo.root)+1:] | |
1195 else: | |
1196 filename = '' | |
1197 error = getattr(inst, 'strerror', 'Unknown error') | |
1198 if inst.errno == errno.ENOENT: | |
1199 code = 404 | |
1200 else: | |
1201 code = 500 | |
1202 req.respond(code, '%s: %s\n' % (error, filename)) | |
1203 finally: | |
1204 fp.close() | |
1205 os.unlink(tempname) | |
1206 | |
1207 def do_stream_out(self, req): | |
1208 req.httphdr("application/mercurial-0.1") | |
1209 streamclone.stream_out(self.repo, req, untrusted=True) |