##// END OF EJS Templates
hgweb: fix breaking tests on Python < 2.5
Bryan O'Sullivan -
r5563:d61fea13 default
parent child Browse files
Show More
@@ -1,92 +1,99
1 1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import errno, mimetypes, os
10 10
11 11 class ErrorResponse(Exception):
12 12 def __init__(self, code, message=None):
13 13 Exception.__init__(self)
14 14 self.code = code
15 if message is None:
16 from httplib import responses
17 self.message = responses.get(code, 'Error')
15 if message:
16 self.message = message
18 17 else:
19 self.message = message
18 self.message = _statusmessage(code)
19
20 def _statusmessage(code):
21 from BaseHTTPServer import BaseHTTPRequestHandler
22 responses = BaseHTTPRequestHandler.responses
23 return responses.get(code, ('Error', 'Unknown error'))[0]
24
25 def statusmessage(code):
26 return '%d %s' % (code, _statusmessage(code))
20 27
21 28 def get_mtime(repo_path):
22 29 store_path = os.path.join(repo_path, ".hg")
23 30 if not os.path.isdir(os.path.join(store_path, "data")):
24 31 store_path = os.path.join(store_path, "store")
25 32 cl_path = os.path.join(store_path, "00changelog.i")
26 33 if os.path.exists(cl_path):
27 34 return os.stat(cl_path).st_mtime
28 35 else:
29 36 return os.stat(store_path).st_mtime
30 37
31 38 def staticfile(directory, fname, req):
32 39 """return a file inside directory with guessed content-type header
33 40
34 41 fname always uses '/' as directory separator and isn't allowed to
35 42 contain unusual path components.
36 43 Content-type is guessed using the mimetypes module.
37 44 Return an empty string if fname is illegal or file not found.
38 45
39 46 """
40 47 parts = fname.split('/')
41 48 path = directory
42 49 for part in parts:
43 50 if (part in ('', os.curdir, os.pardir) or
44 51 os.sep in part or os.altsep is not None and os.altsep in part):
45 52 return ""
46 53 path = os.path.join(path, part)
47 54 try:
48 55 os.stat(path)
49 56 ct = mimetypes.guess_type(path)[0] or "text/plain"
50 57 req.header([('Content-type', ct),
51 58 ('Content-length', str(os.path.getsize(path)))])
52 59 return file(path, 'rb').read()
53 60 except TypeError:
54 61 raise ErrorResponse(500, 'illegal file name')
55 62 except OSError, err:
56 63 if err.errno == errno.ENOENT:
57 64 raise ErrorResponse(404)
58 65 else:
59 66 raise ErrorResponse(500, err.strerror)
60 67
61 68 def style_map(templatepath, style):
62 69 """Return path to mapfile for a given style.
63 70
64 71 Searches mapfile in the following locations:
65 72 1. templatepath/style/map
66 73 2. templatepath/map-style
67 74 3. templatepath/map
68 75 """
69 76 locations = style and [os.path.join(style, "map"), "map-"+style] or []
70 77 locations.append("map")
71 78 for location in locations:
72 79 mapfile = os.path.join(templatepath, location)
73 80 if os.path.isfile(mapfile):
74 81 return mapfile
75 82 raise RuntimeError("No hgweb templates found in %r" % templatepath)
76 83
77 84 def paritygen(stripecount, offset=0):
78 85 """count parity of horizontal stripes for easier reading"""
79 86 if stripecount and offset:
80 87 # account for offset, e.g. due to building the list in reverse
81 88 count = (stripecount + offset) % stripecount
82 89 parity = (stripecount + offset) / stripecount & 1
83 90 else:
84 91 count = 0
85 92 parity = 0
86 93 while True:
87 94 yield parity
88 95 count += 1
89 96 if stripecount and count >= stripecount:
90 97 parity = 1 - parity
91 98 count = 0
92 99
@@ -1,1221 +1,1222
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
16 from common import statusmsg
16 17
17 18 def _up(p):
18 19 if p[0] != "/":
19 20 p = "/" + p
20 21 if p[-1] == "/":
21 22 p = p[:-1]
22 23 up = os.path.dirname(p)
23 24 if up == "/":
24 25 return "/"
25 26 return up + "/"
26 27
27 28 def revnavgen(pos, pagelen, limit, nodefunc):
28 29 def seq(factor, limit=None):
29 30 if limit:
30 31 yield limit
31 32 if limit >= 20 and limit <= 40:
32 33 yield 50
33 34 else:
34 35 yield 1 * factor
35 36 yield 3 * factor
36 37 for f in seq(factor * 10):
37 38 yield f
38 39
39 40 def nav(**map):
40 41 l = []
41 42 last = 0
42 43 for f in seq(1, pagelen):
43 44 if f < pagelen or f <= last:
44 45 continue
45 46 if f > limit:
46 47 break
47 48 last = f
48 49 if pos + f < limit:
49 50 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 51 if pos - f >= 0:
51 52 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 53
53 54 try:
54 55 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 56
56 57 for label, node in l:
57 58 yield {"label": label, "node": node}
58 59
59 60 yield {"label": "tip", "node": "tip"}
60 61 except hg.RepoError:
61 62 pass
62 63
63 64 return nav
64 65
65 66 class hgweb(object):
66 67 def __init__(self, repo, name=None):
67 68 if isinstance(repo, str):
68 69 parentui = ui.ui(report_untrusted=False, interactive=False)
69 70 self.repo = hg.repository(parentui, repo)
70 71 else:
71 72 self.repo = repo
72 73
73 74 self.mtime = -1
74 75 self.reponame = name
75 76 self.archives = 'zip', 'gz', 'bz2'
76 77 self.stripecount = 1
77 78 # a repo owner may set web.templates in .hg/hgrc to get any file
78 79 # readable by the user running the CGI script
79 80 self.templatepath = self.config("web", "templates",
80 81 templater.templatepath(),
81 82 untrusted=False)
82 83
83 84 # The CGI scripts are often run by a user different from the repo owner.
84 85 # Trust the settings from the .hg/hgrc files by default.
85 86 def config(self, section, name, default=None, untrusted=True):
86 87 return self.repo.ui.config(section, name, default,
87 88 untrusted=untrusted)
88 89
89 90 def configbool(self, section, name, default=False, untrusted=True):
90 91 return self.repo.ui.configbool(section, name, default,
91 92 untrusted=untrusted)
92 93
93 94 def configlist(self, section, name, default=None, untrusted=True):
94 95 return self.repo.ui.configlist(section, name, default,
95 96 untrusted=untrusted)
96 97
97 98 def refresh(self):
98 99 mtime = get_mtime(self.repo.root)
99 100 if mtime != self.mtime:
100 101 self.mtime = mtime
101 102 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 103 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 104 self.stripecount = int(self.config("web", "stripes", 1))
104 105 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 106 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 107 self.allowpull = self.configbool("web", "allowpull", True)
107 108 self.encoding = self.config("web", "encoding", util._encoding)
108 109
109 110 def archivelist(self, nodeid):
110 111 allowed = self.configlist("web", "allow_archive")
111 112 for i, spec in self.archive_specs.iteritems():
112 113 if i in allowed or self.configbool("web", "allow" + i):
113 114 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114 115
115 116 def listfilediffs(self, files, changeset):
116 117 for f in files[:self.maxfiles]:
117 118 yield self.t("filedifflink", node=hex(changeset), file=f)
118 119 if len(files) > self.maxfiles:
119 120 yield self.t("fileellipses")
120 121
121 122 def siblings(self, siblings=[], hiderev=None, **args):
122 123 siblings = [s for s in siblings if s.node() != nullid]
123 124 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 125 return
125 126 for s in siblings:
126 127 d = {'node': hex(s.node()), 'rev': s.rev()}
127 128 if hasattr(s, 'path'):
128 129 d['file'] = s.path()
129 130 d.update(args)
130 131 yield d
131 132
132 133 def renamelink(self, fl, node):
133 134 r = fl.renamed(node)
134 135 if r:
135 136 return [dict(file=r[0], node=hex(r[1]))]
136 137 return []
137 138
138 139 def nodetagsdict(self, node):
139 140 return [{"name": i} for i in self.repo.nodetags(node)]
140 141
141 142 def nodebranchdict(self, ctx):
142 143 branches = []
143 144 branch = ctx.branch()
144 145 # If this is an empty repo, ctx.node() == nullid,
145 146 # ctx.branch() == 'default', but branchtags() is
146 147 # an empty dict. Using dict.get avoids a traceback.
147 148 if self.repo.branchtags().get(branch) == ctx.node():
148 149 branches.append({"name": branch})
149 150 return branches
150 151
151 152 def showtag(self, t1, node=nullid, **args):
152 153 for t in self.repo.nodetags(node):
153 154 yield self.t(t1, tag=t, **args)
154 155
155 156 def diff(self, node1, node2, files):
156 157 def filterfiles(filters, files):
157 158 l = [x for x in files if x in filters]
158 159
159 160 for t in filters:
160 161 if t and t[-1] != os.sep:
161 162 t += os.sep
162 163 l += [x for x in files if x.startswith(t)]
163 164 return l
164 165
165 166 parity = paritygen(self.stripecount)
166 167 def diffblock(diff, f, fn):
167 168 yield self.t("diffblock",
168 169 lines=prettyprintlines(diff),
169 170 parity=parity.next(),
170 171 file=f,
171 172 filenode=hex(fn or nullid))
172 173
173 174 def prettyprintlines(diff):
174 175 for l in diff.splitlines(1):
175 176 if l.startswith('+'):
176 177 yield self.t("difflineplus", line=l)
177 178 elif l.startswith('-'):
178 179 yield self.t("difflineminus", line=l)
179 180 elif l.startswith('@'):
180 181 yield self.t("difflineat", line=l)
181 182 else:
182 183 yield self.t("diffline", line=l)
183 184
184 185 r = self.repo
185 186 c1 = r.changectx(node1)
186 187 c2 = r.changectx(node2)
187 188 date1 = util.datestr(c1.date())
188 189 date2 = util.datestr(c2.date())
189 190
190 191 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
191 192 if files:
192 193 modified, added, removed = map(lambda x: filterfiles(files, x),
193 194 (modified, added, removed))
194 195
195 196 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
196 197 for f in modified:
197 198 to = c1.filectx(f).data()
198 199 tn = c2.filectx(f).data()
199 200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
200 201 opts=diffopts), f, tn)
201 202 for f in added:
202 203 to = None
203 204 tn = c2.filectx(f).data()
204 205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
205 206 opts=diffopts), f, tn)
206 207 for f in removed:
207 208 to = c1.filectx(f).data()
208 209 tn = None
209 210 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
210 211 opts=diffopts), f, tn)
211 212
212 213 def changelog(self, ctx, shortlog=False):
213 214 def changelist(limit=0,**map):
214 215 cl = self.repo.changelog
215 216 l = [] # build a list in forward order for efficiency
216 217 for i in xrange(start, end):
217 218 ctx = self.repo.changectx(i)
218 219 n = ctx.node()
219 220
220 221 l.insert(0, {"parity": parity.next(),
221 222 "author": ctx.user(),
222 223 "parent": self.siblings(ctx.parents(), i - 1),
223 224 "child": self.siblings(ctx.children(), i + 1),
224 225 "changelogtag": self.showtag("changelogtag",n),
225 226 "desc": ctx.description(),
226 227 "date": ctx.date(),
227 228 "files": self.listfilediffs(ctx.files(), n),
228 229 "rev": i,
229 230 "node": hex(n),
230 231 "tags": self.nodetagsdict(n),
231 232 "branches": self.nodebranchdict(ctx)})
232 233
233 234 if limit > 0:
234 235 l = l[:limit]
235 236
236 237 for e in l:
237 238 yield e
238 239
239 240 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
240 241 cl = self.repo.changelog
241 242 count = cl.count()
242 243 pos = ctx.rev()
243 244 start = max(0, pos - maxchanges + 1)
244 245 end = min(count, start + maxchanges)
245 246 pos = end - 1
246 247 parity = paritygen(self.stripecount, offset=start-end)
247 248
248 249 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
249 250
250 251 yield self.t(shortlog and 'shortlog' or 'changelog',
251 252 changenav=changenav,
252 253 node=hex(cl.tip()),
253 254 rev=pos, changesets=count,
254 255 entries=lambda **x: changelist(limit=0,**x),
255 256 latestentry=lambda **x: changelist(limit=1,**x),
256 257 archives=self.archivelist("tip"))
257 258
258 259 def search(self, query):
259 260
260 261 def changelist(**map):
261 262 cl = self.repo.changelog
262 263 count = 0
263 264 qw = query.lower().split()
264 265
265 266 def revgen():
266 267 for i in xrange(cl.count() - 1, 0, -100):
267 268 l = []
268 269 for j in xrange(max(0, i - 100), i):
269 270 ctx = self.repo.changectx(j)
270 271 l.append(ctx)
271 272 l.reverse()
272 273 for e in l:
273 274 yield e
274 275
275 276 for ctx in revgen():
276 277 miss = 0
277 278 for q in qw:
278 279 if not (q in ctx.user().lower() or
279 280 q in ctx.description().lower() or
280 281 q in " ".join(ctx.files()).lower()):
281 282 miss = 1
282 283 break
283 284 if miss:
284 285 continue
285 286
286 287 count += 1
287 288 n = ctx.node()
288 289
289 290 yield self.t('searchentry',
290 291 parity=parity.next(),
291 292 author=ctx.user(),
292 293 parent=self.siblings(ctx.parents()),
293 294 child=self.siblings(ctx.children()),
294 295 changelogtag=self.showtag("changelogtag",n),
295 296 desc=ctx.description(),
296 297 date=ctx.date(),
297 298 files=self.listfilediffs(ctx.files(), n),
298 299 rev=ctx.rev(),
299 300 node=hex(n),
300 301 tags=self.nodetagsdict(n),
301 302 branches=self.nodebranchdict(ctx))
302 303
303 304 if count >= self.maxchanges:
304 305 break
305 306
306 307 cl = self.repo.changelog
307 308 parity = paritygen(self.stripecount)
308 309
309 310 yield self.t('search',
310 311 query=query,
311 312 node=hex(cl.tip()),
312 313 entries=changelist,
313 314 archives=self.archivelist("tip"))
314 315
315 316 def changeset(self, ctx):
316 317 n = ctx.node()
317 318 parents = ctx.parents()
318 319 p1 = parents[0].node()
319 320
320 321 files = []
321 322 parity = paritygen(self.stripecount)
322 323 for f in ctx.files():
323 324 files.append(self.t("filenodelink",
324 325 node=hex(n), file=f,
325 326 parity=parity.next()))
326 327
327 328 def diff(**map):
328 329 yield self.diff(p1, n, None)
329 330
330 331 yield self.t('changeset',
331 332 diff=diff,
332 333 rev=ctx.rev(),
333 334 node=hex(n),
334 335 parent=self.siblings(parents),
335 336 child=self.siblings(ctx.children()),
336 337 changesettag=self.showtag("changesettag",n),
337 338 author=ctx.user(),
338 339 desc=ctx.description(),
339 340 date=ctx.date(),
340 341 files=files,
341 342 archives=self.archivelist(hex(n)),
342 343 tags=self.nodetagsdict(n),
343 344 branches=self.nodebranchdict(ctx))
344 345
345 346 def filelog(self, fctx):
346 347 f = fctx.path()
347 348 fl = fctx.filelog()
348 349 count = fl.count()
349 350 pagelen = self.maxshortchanges
350 351 pos = fctx.filerev()
351 352 start = max(0, pos - pagelen + 1)
352 353 end = min(count, start + pagelen)
353 354 pos = end - 1
354 355 parity = paritygen(self.stripecount, offset=start-end)
355 356
356 357 def entries(limit=0, **map):
357 358 l = []
358 359
359 360 for i in xrange(start, end):
360 361 ctx = fctx.filectx(i)
361 362 n = fl.node(i)
362 363
363 364 l.insert(0, {"parity": parity.next(),
364 365 "filerev": i,
365 366 "file": f,
366 367 "node": hex(ctx.node()),
367 368 "author": ctx.user(),
368 369 "date": ctx.date(),
369 370 "rename": self.renamelink(fl, n),
370 371 "parent": self.siblings(fctx.parents()),
371 372 "child": self.siblings(fctx.children()),
372 373 "desc": ctx.description()})
373 374
374 375 if limit > 0:
375 376 l = l[:limit]
376 377
377 378 for e in l:
378 379 yield e
379 380
380 381 nodefunc = lambda x: fctx.filectx(fileid=x)
381 382 nav = revnavgen(pos, pagelen, count, nodefunc)
382 383 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
383 384 entries=lambda **x: entries(limit=0, **x),
384 385 latestentry=lambda **x: entries(limit=1, **x))
385 386
386 387 def filerevision(self, fctx):
387 388 f = fctx.path()
388 389 text = fctx.data()
389 390 fl = fctx.filelog()
390 391 n = fctx.filenode()
391 392 parity = paritygen(self.stripecount)
392 393
393 394 mt = mimetypes.guess_type(f)[0]
394 395 rawtext = text
395 396 if util.binary(text):
396 397 mt = mt or 'application/octet-stream'
397 398 text = "(binary:%s)" % mt
398 399 mt = mt or 'text/plain'
399 400
400 401 def lines():
401 402 for l, t in enumerate(text.splitlines(1)):
402 403 yield {"line": t,
403 404 "linenumber": "% 6d" % (l + 1),
404 405 "parity": parity.next()}
405 406
406 407 yield self.t("filerevision",
407 408 file=f,
408 409 path=_up(f),
409 410 text=lines(),
410 411 raw=rawtext,
411 412 mimetype=mt,
412 413 rev=fctx.rev(),
413 414 node=hex(fctx.node()),
414 415 author=fctx.user(),
415 416 date=fctx.date(),
416 417 desc=fctx.description(),
417 418 parent=self.siblings(fctx.parents()),
418 419 child=self.siblings(fctx.children()),
419 420 rename=self.renamelink(fl, n),
420 421 permissions=fctx.manifest().flags(f))
421 422
422 423 def fileannotate(self, fctx):
423 424 f = fctx.path()
424 425 n = fctx.filenode()
425 426 fl = fctx.filelog()
426 427 parity = paritygen(self.stripecount)
427 428
428 429 def annotate(**map):
429 430 last = None
430 431 for f, l in fctx.annotate(follow=True):
431 432 fnode = f.filenode()
432 433 name = self.repo.ui.shortuser(f.user())
433 434
434 435 if last != fnode:
435 436 last = fnode
436 437
437 438 yield {"parity": parity.next(),
438 439 "node": hex(f.node()),
439 440 "rev": f.rev(),
440 441 "author": name,
441 442 "file": f.path(),
442 443 "line": l}
443 444
444 445 yield self.t("fileannotate",
445 446 file=f,
446 447 annotate=annotate,
447 448 path=_up(f),
448 449 rev=fctx.rev(),
449 450 node=hex(fctx.node()),
450 451 author=fctx.user(),
451 452 date=fctx.date(),
452 453 desc=fctx.description(),
453 454 rename=self.renamelink(fl, n),
454 455 parent=self.siblings(fctx.parents()),
455 456 child=self.siblings(fctx.children()),
456 457 permissions=fctx.manifest().flags(f))
457 458
458 459 def manifest(self, ctx, path):
459 460 mf = ctx.manifest()
460 461 node = ctx.node()
461 462
462 463 files = {}
463 464 parity = paritygen(self.stripecount)
464 465
465 466 if path and path[-1] != "/":
466 467 path += "/"
467 468 l = len(path)
468 469 abspath = "/" + path
469 470
470 471 for f, n in mf.items():
471 472 if f[:l] != path:
472 473 continue
473 474 remain = f[l:]
474 475 if "/" in remain:
475 476 short = remain[:remain.index("/") + 1] # bleah
476 477 files[short] = (f, None)
477 478 else:
478 479 short = os.path.basename(remain)
479 480 files[short] = (f, n)
480 481
481 482 if not files:
482 483 raise ErrorResponse(404, 'Path not found: ' + path)
483 484
484 485 def filelist(**map):
485 486 fl = files.keys()
486 487 fl.sort()
487 488 for f in fl:
488 489 full, fnode = files[f]
489 490 if not fnode:
490 491 continue
491 492
492 493 fctx = ctx.filectx(full)
493 494 yield {"file": full,
494 495 "parity": parity.next(),
495 496 "basename": f,
496 497 "date": fctx.changectx().date(),
497 498 "size": fctx.size(),
498 499 "permissions": mf.flags(full)}
499 500
500 501 def dirlist(**map):
501 502 fl = files.keys()
502 503 fl.sort()
503 504 for f in fl:
504 505 full, fnode = files[f]
505 506 if fnode:
506 507 continue
507 508
508 509 yield {"parity": parity.next(),
509 510 "path": "%s%s" % (abspath, f),
510 511 "basename": f[:-1]}
511 512
512 513 yield self.t("manifest",
513 514 rev=ctx.rev(),
514 515 node=hex(node),
515 516 path=abspath,
516 517 up=_up(abspath),
517 518 upparity=parity.next(),
518 519 fentries=filelist,
519 520 dentries=dirlist,
520 521 archives=self.archivelist(hex(node)),
521 522 tags=self.nodetagsdict(node),
522 523 branches=self.nodebranchdict(ctx))
523 524
524 525 def tags(self):
525 526 i = self.repo.tagslist()
526 527 i.reverse()
527 528 parity = paritygen(self.stripecount)
528 529
529 530 def entries(notip=False,limit=0, **map):
530 531 count = 0
531 532 for k, n in i:
532 533 if notip and k == "tip":
533 534 continue
534 535 if limit > 0 and count >= limit:
535 536 continue
536 537 count = count + 1
537 538 yield {"parity": parity.next(),
538 539 "tag": k,
539 540 "date": self.repo.changectx(n).date(),
540 541 "node": hex(n)}
541 542
542 543 yield self.t("tags",
543 544 node=hex(self.repo.changelog.tip()),
544 545 entries=lambda **x: entries(False,0, **x),
545 546 entriesnotip=lambda **x: entries(True,0, **x),
546 547 latestentry=lambda **x: entries(True,1, **x))
547 548
548 549 def summary(self):
549 550 i = self.repo.tagslist()
550 551 i.reverse()
551 552
552 553 def tagentries(**map):
553 554 parity = paritygen(self.stripecount)
554 555 count = 0
555 556 for k, n in i:
556 557 if k == "tip": # skip tip
557 558 continue;
558 559
559 560 count += 1
560 561 if count > 10: # limit to 10 tags
561 562 break;
562 563
563 564 yield self.t("tagentry",
564 565 parity=parity.next(),
565 566 tag=k,
566 567 node=hex(n),
567 568 date=self.repo.changectx(n).date())
568 569
569 570
570 571 def branches(**map):
571 572 parity = paritygen(self.stripecount)
572 573
573 574 b = self.repo.branchtags()
574 575 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
575 576 l.sort()
576 577
577 578 for r,n,t in l:
578 579 ctx = self.repo.changectx(n)
579 580
580 581 yield {'parity': parity.next(),
581 582 'branch': t,
582 583 'node': hex(n),
583 584 'date': ctx.date()}
584 585
585 586 def changelist(**map):
586 587 parity = paritygen(self.stripecount, offset=start-end)
587 588 l = [] # build a list in forward order for efficiency
588 589 for i in xrange(start, end):
589 590 ctx = self.repo.changectx(i)
590 591 n = ctx.node()
591 592 hn = hex(n)
592 593
593 594 l.insert(0, self.t(
594 595 'shortlogentry',
595 596 parity=parity.next(),
596 597 author=ctx.user(),
597 598 desc=ctx.description(),
598 599 date=ctx.date(),
599 600 rev=i,
600 601 node=hn,
601 602 tags=self.nodetagsdict(n),
602 603 branches=self.nodebranchdict(ctx)))
603 604
604 605 yield l
605 606
606 607 cl = self.repo.changelog
607 608 count = cl.count()
608 609 start = max(0, count - self.maxchanges)
609 610 end = min(count, start + self.maxchanges)
610 611
611 612 yield self.t("summary",
612 613 desc=self.config("web", "description", "unknown"),
613 614 owner=(self.config("ui", "username") or # preferred
614 615 self.config("web", "contact") or # deprecated
615 616 self.config("web", "author", "unknown")), # also
616 617 lastchange=cl.read(cl.tip())[2],
617 618 tags=tagentries,
618 619 branches=branches,
619 620 shortlog=changelist,
620 621 node=hex(cl.tip()),
621 622 archives=self.archivelist("tip"))
622 623
623 624 def filediff(self, fctx):
624 625 n = fctx.node()
625 626 path = fctx.path()
626 627 parents = fctx.parents()
627 628 p1 = parents and parents[0].node() or nullid
628 629
629 630 def diff(**map):
630 631 yield self.diff(p1, n, [path])
631 632
632 633 yield self.t("filediff",
633 634 file=path,
634 635 node=hex(n),
635 636 rev=fctx.rev(),
636 637 parent=self.siblings(parents),
637 638 child=self.siblings(fctx.children()),
638 639 diff=diff)
639 640
640 641 archive_specs = {
641 642 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
642 643 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
643 644 'zip': ('application/zip', 'zip', '.zip', None),
644 645 }
645 646
646 647 def archive(self, req, key, type_):
647 648 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
648 649 cnode = self.repo.lookup(key)
649 650 arch_version = key
650 651 if cnode == key or key == 'tip':
651 652 arch_version = short(cnode)
652 653 name = "%s-%s" % (reponame, arch_version)
653 654 mimetype, artype, extension, encoding = self.archive_specs[type_]
654 655 headers = [('Content-type', mimetype),
655 656 ('Content-disposition', 'attachment; filename=%s%s' %
656 657 (name, extension))]
657 658 if encoding:
658 659 headers.append(('Content-encoding', encoding))
659 660 req.header(headers)
660 661 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
661 662
662 663 # add tags to things
663 664 # tags -> list of changesets corresponding to tags
664 665 # find tag, changeset, file
665 666
666 667 def cleanpath(self, path):
667 668 path = path.lstrip('/')
668 669 return util.canonpath(self.repo.root, '', path)
669 670
670 671 def run(self):
671 672 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
672 673 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
673 674 import mercurial.hgweb.wsgicgi as wsgicgi
674 675 from request import wsgiapplication
675 676 def make_web_app():
676 677 return self
677 678 wsgicgi.launch(wsgiapplication(make_web_app))
678 679
679 680 def run_wsgi(self, req):
680 681 def header(**map):
681 682 header_file = cStringIO.StringIO(
682 683 ''.join(self.t("header", encoding=self.encoding, **map)))
683 684 msg = mimetools.Message(header_file, 0)
684 685 req.header(msg.items())
685 686 yield header_file.read()
686 687
687 688 def rawfileheader(**map):
688 689 req.header([('Content-type', map['mimetype']),
689 690 ('Content-disposition', 'filename=%s' % map['file']),
690 691 ('Content-length', str(len(map['raw'])))])
691 692 yield ''
692 693
693 694 def footer(**map):
694 695 yield self.t("footer", **map)
695 696
696 697 def motd(**map):
697 698 yield self.config("web", "motd", "")
698 699
699 700 def expand_form(form):
700 701 shortcuts = {
701 702 'cl': [('cmd', ['changelog']), ('rev', None)],
702 703 'sl': [('cmd', ['shortlog']), ('rev', None)],
703 704 'cs': [('cmd', ['changeset']), ('node', None)],
704 705 'f': [('cmd', ['file']), ('filenode', None)],
705 706 'fl': [('cmd', ['filelog']), ('filenode', None)],
706 707 'fd': [('cmd', ['filediff']), ('node', None)],
707 708 'fa': [('cmd', ['annotate']), ('filenode', None)],
708 709 'mf': [('cmd', ['manifest']), ('manifest', None)],
709 710 'ca': [('cmd', ['archive']), ('node', None)],
710 711 'tags': [('cmd', ['tags'])],
711 712 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
712 713 'static': [('cmd', ['static']), ('file', None)]
713 714 }
714 715
715 716 for k in shortcuts.iterkeys():
716 717 if form.has_key(k):
717 718 for name, value in shortcuts[k]:
718 719 if value is None:
719 720 value = form[k]
720 721 form[name] = value
721 722 del form[k]
722 723
723 724 def rewrite_request(req):
724 725 '''translate new web interface to traditional format'''
725 726
726 727 def spliturl(req):
727 728 def firstitem(query):
728 729 return query.split('&', 1)[0].split(';', 1)[0]
729 730
730 731 def normurl(url):
731 732 inner = '/'.join([x for x in url.split('/') if x])
732 733 tl = len(url) > 1 and url.endswith('/') and '/' or ''
733 734
734 735 return '%s%s%s' % (url.startswith('/') and '/' or '',
735 736 inner, tl)
736 737
737 738 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
738 739 pi = normurl(req.env.get('PATH_INFO', ''))
739 740 if pi:
740 741 # strip leading /
741 742 pi = pi[1:]
742 743 if pi:
743 744 root = root[:root.rfind(pi)]
744 745 if req.env.has_key('REPO_NAME'):
745 746 rn = req.env['REPO_NAME'] + '/'
746 747 root += rn
747 748 query = pi[len(rn):]
748 749 else:
749 750 query = pi
750 751 else:
751 752 root += '?'
752 753 query = firstitem(req.env['QUERY_STRING'])
753 754
754 755 return (root, query)
755 756
756 757 req.url, query = spliturl(req)
757 758
758 759 if req.form.has_key('cmd'):
759 760 # old style
760 761 return
761 762
762 763 args = query.split('/', 2)
763 764 if not args or not args[0]:
764 765 return
765 766
766 767 cmd = args.pop(0)
767 768 style = cmd.rfind('-')
768 769 if style != -1:
769 770 req.form['style'] = [cmd[:style]]
770 771 cmd = cmd[style+1:]
771 772 # avoid accepting e.g. style parameter as command
772 773 if hasattr(self, 'do_' + cmd):
773 774 req.form['cmd'] = [cmd]
774 775
775 776 if args and args[0]:
776 777 node = args.pop(0)
777 778 req.form['node'] = [node]
778 779 if args:
779 780 req.form['file'] = args
780 781
781 782 if cmd == 'static':
782 783 req.form['file'] = req.form['node']
783 784 elif cmd == 'archive':
784 785 fn = req.form['node'][0]
785 786 for type_, spec in self.archive_specs.iteritems():
786 787 ext = spec[2]
787 788 if fn.endswith(ext):
788 789 req.form['node'] = [fn[:-len(ext)]]
789 790 req.form['type'] = [type_]
790 791
791 792 def sessionvars(**map):
792 793 fields = []
793 794 if req.form.has_key('style'):
794 795 style = req.form['style'][0]
795 796 if style != self.config('web', 'style', ''):
796 797 fields.append(('style', style))
797 798
798 799 separator = req.url[-1] == '?' and ';' or '?'
799 800 for name, value in fields:
800 801 yield dict(name=name, value=value, separator=separator)
801 802 separator = ';'
802 803
803 804 self.refresh()
804 805
805 806 expand_form(req.form)
806 807 rewrite_request(req)
807 808
808 809 style = self.config("web", "style", "")
809 810 if req.form.has_key('style'):
810 811 style = req.form['style'][0]
811 812 mapfile = style_map(self.templatepath, style)
812 813
813 814 proto = req.env.get('wsgi.url_scheme')
814 815 if proto == 'https':
815 816 proto = 'https'
816 817 default_port = "443"
817 818 else:
818 819 proto = 'http'
819 820 default_port = "80"
820 821
821 822 port = req.env["SERVER_PORT"]
822 823 port = port != default_port and (":" + port) or ""
823 824 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
824 825 staticurl = self.config("web", "staticurl") or req.url + 'static/'
825 826 if not staticurl.endswith('/'):
826 827 staticurl += '/'
827 828
828 829 if not self.reponame:
829 830 self.reponame = (self.config("web", "name")
830 831 or req.env.get('REPO_NAME')
831 832 or req.url.strip('/') or self.repo.root)
832 833
833 834 self.t = templater.templater(mapfile, templater.common_filters,
834 835 defaults={"url": req.url,
835 836 "staticurl": staticurl,
836 837 "urlbase": urlbase,
837 838 "repo": self.reponame,
838 839 "header": header,
839 840 "footer": footer,
840 841 "motd": motd,
841 842 "rawfileheader": rawfileheader,
842 843 "sessionvars": sessionvars
843 844 })
844 845
845 846 try:
846 847 if not req.form.has_key('cmd'):
847 848 req.form['cmd'] = [self.t.cache['default']]
848 849
849 850 cmd = req.form['cmd'][0]
850 851
851 852 try:
852 853 method = getattr(self, 'do_' + cmd)
853 854 method(req)
854 855 except revlog.LookupError, err:
855 856 req.respond(404, self.t(
856 857 'error', error='revision not found: %s' % err.name))
857 858 except (hg.RepoError, revlog.RevlogError), inst:
858 859 req.respond('500 Internal Server Error',
859 860 self.t('error', error=str(inst)))
860 861 except ErrorResponse, inst:
861 862 req.respond(inst.code, self.t('error', error=inst.message))
862 863 except AttributeError:
863 864 req.respond(400,
864 865 self.t('error', error='No such method: ' + cmd))
865 866 finally:
866 867 self.t = None
867 868
868 869 def changectx(self, req):
869 870 if req.form.has_key('node'):
870 871 changeid = req.form['node'][0]
871 872 elif req.form.has_key('manifest'):
872 873 changeid = req.form['manifest'][0]
873 874 else:
874 875 changeid = self.repo.changelog.count() - 1
875 876
876 877 try:
877 878 ctx = self.repo.changectx(changeid)
878 879 except hg.RepoError:
879 880 man = self.repo.manifest
880 881 mn = man.lookup(changeid)
881 882 ctx = self.repo.changectx(man.linkrev(mn))
882 883
883 884 return ctx
884 885
885 886 def filectx(self, req):
886 887 path = self.cleanpath(req.form['file'][0])
887 888 if req.form.has_key('node'):
888 889 changeid = req.form['node'][0]
889 890 else:
890 891 changeid = req.form['filenode'][0]
891 892 try:
892 893 ctx = self.repo.changectx(changeid)
893 894 fctx = ctx.filectx(path)
894 895 except hg.RepoError:
895 896 fctx = self.repo.filectx(path, fileid=changeid)
896 897
897 898 return fctx
898 899
899 900 def do_log(self, req):
900 901 if req.form.has_key('file') and req.form['file'][0]:
901 902 self.do_filelog(req)
902 903 else:
903 904 self.do_changelog(req)
904 905
905 906 def do_rev(self, req):
906 907 self.do_changeset(req)
907 908
908 909 def do_file(self, req):
909 910 path = self.cleanpath(req.form.get('file', [''])[0])
910 911 if path:
911 912 try:
912 913 req.write(self.filerevision(self.filectx(req)))
913 914 return
914 915 except revlog.LookupError:
915 916 pass
916 917
917 918 req.write(self.manifest(self.changectx(req), path))
918 919
919 920 def do_diff(self, req):
920 921 self.do_filediff(req)
921 922
922 923 def do_changelog(self, req, shortlog = False):
923 924 if req.form.has_key('node'):
924 925 ctx = self.changectx(req)
925 926 else:
926 927 if req.form.has_key('rev'):
927 928 hi = req.form['rev'][0]
928 929 else:
929 930 hi = self.repo.changelog.count() - 1
930 931 try:
931 932 ctx = self.repo.changectx(hi)
932 933 except hg.RepoError:
933 934 req.write(self.search(hi)) # XXX redirect to 404 page?
934 935 return
935 936
936 937 req.write(self.changelog(ctx, shortlog = shortlog))
937 938
938 939 def do_shortlog(self, req):
939 940 self.do_changelog(req, shortlog = True)
940 941
941 942 def do_changeset(self, req):
942 943 req.write(self.changeset(self.changectx(req)))
943 944
944 945 def do_manifest(self, req):
945 946 req.write(self.manifest(self.changectx(req),
946 947 self.cleanpath(req.form['path'][0])))
947 948
948 949 def do_tags(self, req):
949 950 req.write(self.tags())
950 951
951 952 def do_summary(self, req):
952 953 req.write(self.summary())
953 954
954 955 def do_filediff(self, req):
955 956 req.write(self.filediff(self.filectx(req)))
956 957
957 958 def do_annotate(self, req):
958 959 req.write(self.fileannotate(self.filectx(req)))
959 960
960 961 def do_filelog(self, req):
961 962 req.write(self.filelog(self.filectx(req)))
962 963
963 964 def do_lookup(self, req):
964 965 try:
965 966 r = hex(self.repo.lookup(req.form['key'][0]))
966 967 success = 1
967 968 except Exception,inst:
968 969 r = str(inst)
969 970 success = 0
970 971 resp = "%s %s\n" % (success, r)
971 972 req.httphdr("application/mercurial-0.1", length=len(resp))
972 973 req.write(resp)
973 974
974 975 def do_heads(self, req):
975 976 resp = " ".join(map(hex, self.repo.heads())) + "\n"
976 977 req.httphdr("application/mercurial-0.1", length=len(resp))
977 978 req.write(resp)
978 979
979 980 def do_branches(self, req):
980 981 nodes = []
981 982 if req.form.has_key('nodes'):
982 983 nodes = map(bin, req.form['nodes'][0].split(" "))
983 984 resp = cStringIO.StringIO()
984 985 for b in self.repo.branches(nodes):
985 986 resp.write(" ".join(map(hex, b)) + "\n")
986 987 resp = resp.getvalue()
987 988 req.httphdr("application/mercurial-0.1", length=len(resp))
988 989 req.write(resp)
989 990
990 991 def do_between(self, req):
991 992 if req.form.has_key('pairs'):
992 993 pairs = [map(bin, p.split("-"))
993 994 for p in req.form['pairs'][0].split(" ")]
994 995 resp = cStringIO.StringIO()
995 996 for b in self.repo.between(pairs):
996 997 resp.write(" ".join(map(hex, b)) + "\n")
997 998 resp = resp.getvalue()
998 999 req.httphdr("application/mercurial-0.1", length=len(resp))
999 1000 req.write(resp)
1000 1001
1001 1002 def do_changegroup(self, req):
1002 1003 req.httphdr("application/mercurial-0.1")
1003 1004 nodes = []
1004 1005 if not self.allowpull:
1005 1006 return
1006 1007
1007 1008 if req.form.has_key('roots'):
1008 1009 nodes = map(bin, req.form['roots'][0].split(" "))
1009 1010
1010 1011 z = zlib.compressobj()
1011 1012 f = self.repo.changegroup(nodes, 'serve')
1012 1013 while 1:
1013 1014 chunk = f.read(4096)
1014 1015 if not chunk:
1015 1016 break
1016 1017 req.write(z.compress(chunk))
1017 1018
1018 1019 req.write(z.flush())
1019 1020
1020 1021 def do_changegroupsubset(self, req):
1021 1022 req.httphdr("application/mercurial-0.1")
1022 1023 bases = []
1023 1024 heads = []
1024 1025 if not self.allowpull:
1025 1026 return
1026 1027
1027 1028 if req.form.has_key('bases'):
1028 1029 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1029 1030 if req.form.has_key('heads'):
1030 1031 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1031 1032
1032 1033 z = zlib.compressobj()
1033 1034 f = self.repo.changegroupsubset(bases, heads, 'serve')
1034 1035 while 1:
1035 1036 chunk = f.read(4096)
1036 1037 if not chunk:
1037 1038 break
1038 1039 req.write(z.compress(chunk))
1039 1040
1040 1041 req.write(z.flush())
1041 1042
1042 1043 def do_archive(self, req):
1043 1044 type_ = req.form['type'][0]
1044 1045 allowed = self.configlist("web", "allow_archive")
1045 1046 if (type_ in self.archives and (type_ in allowed or
1046 1047 self.configbool("web", "allow" + type_, False))):
1047 1048 self.archive(req, req.form['node'][0], type_)
1048 1049 return
1049 1050
1050 1051 req.respond(400, self.t('error',
1051 1052 error='Unsupported archive type: %s' % type_))
1052 1053
1053 1054 def do_static(self, req):
1054 1055 fname = req.form['file'][0]
1055 1056 # a repo owner may set web.static in .hg/hgrc to get any file
1056 1057 # readable by the user running the CGI script
1057 1058 static = self.config("web", "static",
1058 1059 os.path.join(self.templatepath, "static"),
1059 1060 untrusted=False)
1060 1061 req.write(staticfile(static, fname, req))
1061 1062
1062 1063 def do_capabilities(self, req):
1063 1064 caps = ['lookup', 'changegroupsubset']
1064 1065 if self.configbool('server', 'uncompressed'):
1065 1066 caps.append('stream=%d' % self.repo.changelog.version)
1066 1067 # XXX: make configurable and/or share code with do_unbundle:
1067 1068 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1068 1069 if unbundleversions:
1069 1070 caps.append('unbundle=%s' % ','.join(unbundleversions))
1070 1071 resp = ' '.join(caps)
1071 1072 req.httphdr("application/mercurial-0.1", length=len(resp))
1072 1073 req.write(resp)
1073 1074
1074 1075 def check_perm(self, req, op, default):
1075 1076 '''check permission for operation based on user auth.
1076 1077 return true if op allowed, else false.
1077 1078 default is policy to use if no config given.'''
1078 1079
1079 1080 user = req.env.get('REMOTE_USER')
1080 1081
1081 1082 deny = self.configlist('web', 'deny_' + op)
1082 1083 if deny and (not user or deny == ['*'] or user in deny):
1083 1084 return False
1084 1085
1085 1086 allow = self.configlist('web', 'allow_' + op)
1086 1087 return (allow and (allow == ['*'] or user in allow)) or default
1087 1088
1088 1089 def do_unbundle(self, req):
1089 1090 def bail(response, headers={}):
1090 1091 length = int(req.env['CONTENT_LENGTH'])
1091 1092 for s in util.filechunkiter(req, limit=length):
1092 1093 # drain incoming bundle, else client will not see
1093 1094 # response when run outside cgi script
1094 1095 pass
1095 1096 req.httphdr("application/mercurial-0.1", headers=headers)
1096 1097 req.write('0\n')
1097 1098 req.write(response)
1098 1099
1099 1100 # require ssl by default, auth info cannot be sniffed and
1100 1101 # replayed
1101 1102 ssl_req = self.configbool('web', 'push_ssl', True)
1102 1103 if ssl_req:
1103 1104 if req.env.get('wsgi.url_scheme') != 'https':
1104 1105 bail(_('ssl required\n'))
1105 1106 return
1106 1107 proto = 'https'
1107 1108 else:
1108 1109 proto = 'http'
1109 1110
1110 1111 # do not allow push unless explicitly allowed
1111 1112 if not self.check_perm(req, 'push', False):
1112 1113 bail(_('push not authorized\n'),
1113 1114 headers={'status': '401 Unauthorized'})
1114 1115 return
1115 1116
1116 1117 their_heads = req.form['heads'][0].split(' ')
1117 1118
1118 1119 def check_heads():
1119 1120 heads = map(hex, self.repo.heads())
1120 1121 return their_heads == [hex('force')] or their_heads == heads
1121 1122
1122 1123 # fail early if possible
1123 1124 if not check_heads():
1124 1125 bail(_('unsynced changes\n'))
1125 1126 return
1126 1127
1127 1128 req.httphdr("application/mercurial-0.1")
1128 1129
1129 1130 # do not lock repo until all changegroup data is
1130 1131 # streamed. save to temporary file.
1131 1132
1132 1133 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1133 1134 fp = os.fdopen(fd, 'wb+')
1134 1135 try:
1135 1136 length = int(req.env['CONTENT_LENGTH'])
1136 1137 for s in util.filechunkiter(req, limit=length):
1137 1138 fp.write(s)
1138 1139
1139 1140 try:
1140 1141 lock = self.repo.lock()
1141 1142 try:
1142 1143 if not check_heads():
1143 1144 req.write('0\n')
1144 1145 req.write(_('unsynced changes\n'))
1145 1146 return
1146 1147
1147 1148 fp.seek(0)
1148 1149 header = fp.read(6)
1149 1150 if not header.startswith("HG"):
1150 1151 # old client with uncompressed bundle
1151 1152 def generator(f):
1152 1153 yield header
1153 1154 for chunk in f:
1154 1155 yield chunk
1155 1156 elif not header.startswith("HG10"):
1156 1157 req.write("0\n")
1157 1158 req.write(_("unknown bundle version\n"))
1158 1159 return
1159 1160 elif header == "HG10GZ":
1160 1161 def generator(f):
1161 1162 zd = zlib.decompressobj()
1162 1163 for chunk in f:
1163 1164 yield zd.decompress(chunk)
1164 1165 elif header == "HG10BZ":
1165 1166 def generator(f):
1166 1167 zd = bz2.BZ2Decompressor()
1167 1168 zd.decompress("BZ")
1168 1169 for chunk in f:
1169 1170 yield zd.decompress(chunk)
1170 1171 elif header == "HG10UN":
1171 1172 def generator(f):
1172 1173 for chunk in f:
1173 1174 yield chunk
1174 1175 else:
1175 1176 req.write("0\n")
1176 1177 req.write(_("unknown bundle compression type\n"))
1177 1178 return
1178 1179 gen = generator(util.filechunkiter(fp, 4096))
1179 1180
1180 1181 # send addchangegroup output to client
1181 1182
1182 1183 old_stdout = sys.stdout
1183 1184 sys.stdout = cStringIO.StringIO()
1184 1185
1185 1186 try:
1186 1187 url = 'remote:%s:%s' % (proto,
1187 1188 req.env.get('REMOTE_HOST', ''))
1188 1189 try:
1189 1190 ret = self.repo.addchangegroup(
1190 1191 util.chunkbuffer(gen), 'serve', url)
1191 1192 except util.Abort, inst:
1192 1193 sys.stdout.write("abort: %s\n" % inst)
1193 1194 ret = 0
1194 1195 finally:
1195 1196 val = sys.stdout.getvalue()
1196 1197 sys.stdout = old_stdout
1197 1198 req.write('%d\n' % ret)
1198 1199 req.write(val)
1199 1200 finally:
1200 1201 del lock
1201 1202 except (OSError, IOError), inst:
1202 1203 req.write('0\n')
1203 1204 filename = getattr(inst, 'filename', '')
1204 1205 # Don't send our filesystem layout to the client
1205 1206 if filename.startswith(self.repo.root):
1206 1207 filename = filename[len(self.repo.root)+1:]
1207 1208 else:
1208 1209 filename = ''
1209 1210 error = getattr(inst, 'strerror', 'Unknown error')
1210 1211 if inst.errno == errno.ENOENT:
1211 1212 code = 404
1212 1213 else:
1213 1214 code = 500
1214 1215 req.respond(code, '%s: %s\n' % (error, filename))
1215 1216 finally:
1216 1217 fp.close()
1217 1218 os.unlink(tempname)
1218 1219
1219 1220 def do_stream_out(self, req):
1220 1221 req.httphdr("application/mercurial-0.1")
1221 1222 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,99 +1,94
1 1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import socket, cgi, errno
10 10 from mercurial.i18n import gettext as _
11 from common import ErrorResponse
11 from common import ErrorResponse, statusmessage
12 12
13 13 class wsgiapplication(object):
14 14 def __init__(self, destmaker):
15 15 self.destmaker = destmaker
16 16
17 17 def __call__(self, wsgienv, start_response):
18 18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
19 19
20 20 class _wsgirequest(object):
21 21 def __init__(self, destination, wsgienv, start_response):
22 22 version = wsgienv['wsgi.version']
23 23 if (version < (1, 0)) or (version >= (2, 0)):
24 24 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
25 25 % version)
26 26 self.inp = wsgienv['wsgi.input']
27 27 self.server_write = None
28 28 self.err = wsgienv['wsgi.errors']
29 29 self.threaded = wsgienv['wsgi.multithread']
30 30 self.multiprocess = wsgienv['wsgi.multiprocess']
31 31 self.run_once = wsgienv['wsgi.run_once']
32 32 self.env = wsgienv
33 33 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
34 34 self.start_response = start_response
35 35 self.headers = []
36 36 destination.run_wsgi(self)
37 37
38 38 out = property(lambda self: self)
39 39
40 40 def __iter__(self):
41 41 return iter([])
42 42
43 43 def read(self, count=-1):
44 44 return self.inp.read(count)
45 45
46 46 def respond(self, status, *things):
47 47 for thing in things:
48 48 if hasattr(thing, "__iter__"):
49 49 for part in thing:
50 50 self.respond(status, part)
51 51 else:
52 52 thing = str(thing)
53 53 if self.server_write is None:
54 54 if not self.headers:
55 55 raise RuntimeError("request.write called before headers sent (%s)." % thing)
56 code = None
57 56 if isinstance(status, ErrorResponse):
58 code = status.code
57 status = statusmessage(status.code)
59 58 elif isinstance(status, int):
60 code = status
61 if code:
62 from httplib import responses
63 status = '%d %s' % (
64 code, responses.get(code, 'Error'))
59 status = statusmessage(status)
65 60 self.server_write = self.start_response(status,
66 61 self.headers)
67 62 self.start_response = None
68 63 self.headers = []
69 64 try:
70 65 self.server_write(thing)
71 66 except socket.error, inst:
72 67 if inst[0] != errno.ECONNRESET:
73 68 raise
74 69
75 70 def write(self, *things):
76 71 self.respond('200 Script output follows', *things)
77 72
78 73 def writelines(self, lines):
79 74 for line in lines:
80 75 self.write(line)
81 76
82 77 def flush(self):
83 78 return None
84 79
85 80 def close(self):
86 81 return None
87 82
88 83 def header(self, headers=[('Content-type','text/html')]):
89 84 self.headers.extend(headers)
90 85
91 86 def httphdr(self, type, filename=None, length=0, headers={}):
92 87 headers = headers.items()
93 88 headers.append(('Content-type', type))
94 89 if filename:
95 90 headers.append(('Content-disposition', 'attachment; filename=%s' %
96 91 filename))
97 92 if length:
98 93 headers.append(('Content-length', str(length)))
99 94 self.header(headers)
General Comments 0
You need to be logged in to leave comments. Login now