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)