##// END OF EJS Templates
hgweb: remove some legacy code
Dirkjan Ochtman -
r5886:dd1998dd default
parent child Browse files
Show More
@@ -1,910 +1,910
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 os, mimetypes, re, mimetools, cStringIO
10 10 from mercurial.node import *
11 11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 12 from mercurial import revlog, templater
13 13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
14 14 from request import wsgirequest
15 15 import webcommands, protocol
16 16
17 17 shortcuts = {
18 18 'cl': [('cmd', ['changelog']), ('rev', None)],
19 19 'sl': [('cmd', ['shortlog']), ('rev', None)],
20 20 'cs': [('cmd', ['changeset']), ('node', None)],
21 21 'f': [('cmd', ['file']), ('filenode', None)],
22 22 'fl': [('cmd', ['filelog']), ('filenode', None)],
23 23 'fd': [('cmd', ['filediff']), ('node', None)],
24 24 'fa': [('cmd', ['annotate']), ('filenode', None)],
25 25 'mf': [('cmd', ['manifest']), ('manifest', None)],
26 26 'ca': [('cmd', ['archive']), ('node', None)],
27 27 'tags': [('cmd', ['tags'])],
28 28 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
29 29 'static': [('cmd', ['static']), ('file', None)]
30 30 }
31 31
32 32 def _up(p):
33 33 if p[0] != "/":
34 34 p = "/" + p
35 35 if p[-1] == "/":
36 36 p = p[:-1]
37 37 up = os.path.dirname(p)
38 38 if up == "/":
39 39 return "/"
40 40 return up + "/"
41 41
42 42 def revnavgen(pos, pagelen, limit, nodefunc):
43 43 def seq(factor, limit=None):
44 44 if limit:
45 45 yield limit
46 46 if limit >= 20 and limit <= 40:
47 47 yield 50
48 48 else:
49 49 yield 1 * factor
50 50 yield 3 * factor
51 51 for f in seq(factor * 10):
52 52 yield f
53 53
54 54 def nav(**map):
55 55 l = []
56 56 last = 0
57 57 for f in seq(1, pagelen):
58 58 if f < pagelen or f <= last:
59 59 continue
60 60 if f > limit:
61 61 break
62 62 last = f
63 63 if pos + f < limit:
64 64 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
65 65 if pos - f >= 0:
66 66 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
67 67
68 68 try:
69 69 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
70 70
71 71 for label, node in l:
72 72 yield {"label": label, "node": node}
73 73
74 74 yield {"label": "tip", "node": "tip"}
75 75 except hg.RepoError:
76 76 pass
77 77
78 78 return nav
79 79
80 80 class hgweb(object):
81 81 def __init__(self, repo, name=None):
82 82 if isinstance(repo, str):
83 83 parentui = ui.ui(report_untrusted=False, interactive=False)
84 84 self.repo = hg.repository(parentui, repo)
85 85 else:
86 86 self.repo = repo
87 87
88 88 hook.redirect(True)
89 89 self.mtime = -1
90 90 self.reponame = name
91 91 self.archives = 'zip', 'gz', 'bz2'
92 92 self.stripecount = 1
93 93 # a repo owner may set web.templates in .hg/hgrc to get any file
94 94 # readable by the user running the CGI script
95 95 self.templatepath = self.config("web", "templates",
96 96 templater.templatepath(),
97 97 untrusted=False)
98 98
99 99 # The CGI scripts are often run by a user different from the repo owner.
100 100 # Trust the settings from the .hg/hgrc files by default.
101 101 def config(self, section, name, default=None, untrusted=True):
102 102 return self.repo.ui.config(section, name, default,
103 103 untrusted=untrusted)
104 104
105 105 def configbool(self, section, name, default=False, untrusted=True):
106 106 return self.repo.ui.configbool(section, name, default,
107 107 untrusted=untrusted)
108 108
109 109 def configlist(self, section, name, default=None, untrusted=True):
110 110 return self.repo.ui.configlist(section, name, default,
111 111 untrusted=untrusted)
112 112
113 113 def refresh(self):
114 114 mtime = get_mtime(self.repo.root)
115 115 if mtime != self.mtime:
116 116 self.mtime = mtime
117 117 self.repo = hg.repository(self.repo.ui, self.repo.root)
118 118 self.maxchanges = int(self.config("web", "maxchanges", 10))
119 119 self.stripecount = int(self.config("web", "stripes", 1))
120 120 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
121 121 self.maxfiles = int(self.config("web", "maxfiles", 10))
122 122 self.allowpull = self.configbool("web", "allowpull", True)
123 123 self.encoding = self.config("web", "encoding", util._encoding)
124 124
125 125 def run(self):
126 126 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
127 127 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
128 128 import mercurial.hgweb.wsgicgi as wsgicgi
129 129 wsgicgi.launch(self)
130 130
131 131 def __call__(self, env, respond):
132 132 req = wsgirequest(env, respond)
133 133 self.run_wsgi(req)
134 134 return req
135 135
136 136 def run_wsgi(self, req):
137 137
138 138 self.refresh()
139 139
140 140 # expand form shortcuts
141 141
142 142 for k in shortcuts.iterkeys():
143 143 if k in req.form:
144 144 for name, value in shortcuts[k]:
145 145 if value is None:
146 146 value = req.form[k]
147 147 req.form[name] = value
148 148 del req.form[k]
149 149
150 150 # work with CGI variables to create coherent structure
151 151 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
152 152
153 153 req.url = req.env['SCRIPT_NAME']
154 154 if not req.url.endswith('/'):
155 155 req.url += '/'
156 156 if req.env.has_key('REPO_NAME'):
157 157 req.url += req.env['REPO_NAME'] + '/'
158 158
159 159 if req.env.get('PATH_INFO'):
160 160 parts = req.env.get('PATH_INFO').strip('/').split('/')
161 161 repo_parts = req.env.get('REPO_NAME', '').split('/')
162 162 if parts[:len(repo_parts)] == repo_parts:
163 163 parts = parts[len(repo_parts):]
164 164 query = '/'.join(parts)
165 165 else:
166 166 query = req.env['QUERY_STRING'].split('&', 1)[0]
167 167 query = query.split(';', 1)[0]
168 168
169 169 # translate user-visible url structure to internal structure
170 170
171 171 args = query.split('/', 2)
172 172 if 'cmd' not in req.form and args and args[0]:
173 173
174 174 cmd = args.pop(0)
175 175 style = cmd.rfind('-')
176 176 if style != -1:
177 177 req.form['style'] = [cmd[:style]]
178 178 cmd = cmd[style+1:]
179 179
180 180 # avoid accepting e.g. style parameter as command
181 181 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
182 182 req.form['cmd'] = [cmd]
183 183
184 184 if args and args[0]:
185 185 node = args.pop(0)
186 186 req.form['node'] = [node]
187 187 if args:
188 188 req.form['file'] = args
189 189
190 190 if cmd == 'static':
191 191 req.form['file'] = req.form['node']
192 192 elif cmd == 'archive':
193 193 fn = req.form['node'][0]
194 194 for type_, spec in self.archive_specs.iteritems():
195 195 ext = spec[2]
196 196 if fn.endswith(ext):
197 197 req.form['node'] = [fn[:-len(ext)]]
198 198 req.form['type'] = [type_]
199 199
200 200 # actually process the request
201 201
202 202 try:
203 203
204 204 cmd = req.form.get('cmd', [''])[0]
205 205 if hasattr(protocol, cmd):
206 206 method = getattr(protocol, cmd)
207 207 method(self, req)
208 208 else:
209 209 tmpl = self.templater(req)
210 210 if cmd == '':
211 211 req.form['cmd'] = [tmpl.cache['default']]
212 212 cmd = req.form['cmd'][0]
213 213 method = getattr(webcommands, cmd)
214 214 method(self, req, tmpl)
215 215 del tmpl
216 216
217 217 except revlog.LookupError, err:
218 218 req.respond(404, tmpl(
219 219 'error', error='revision not found: %s' % err.name))
220 220 except (hg.RepoError, revlog.RevlogError), inst:
221 221 req.respond('500 Internal Server Error',
222 222 tmpl('error', error=str(inst)))
223 223 except ErrorResponse, inst:
224 224 req.respond(inst.code, tmpl('error', error=inst.message))
225 225 except AttributeError:
226 226 req.respond(400, tmpl('error', error='No such method: ' + cmd))
227 227
228 228 def templater(self, req):
229 229
230 230 # determine scheme, port and server name
231 231 # this is needed to create absolute urls
232 232
233 233 proto = req.env.get('wsgi.url_scheme')
234 234 if proto == 'https':
235 235 proto = 'https'
236 236 default_port = "443"
237 237 else:
238 238 proto = 'http'
239 239 default_port = "80"
240 240
241 241 port = req.env["SERVER_PORT"]
242 242 port = port != default_port and (":" + port) or ""
243 243 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
244 244 staticurl = self.config("web", "staticurl") or req.url + 'static/'
245 245 if not staticurl.endswith('/'):
246 246 staticurl += '/'
247 247
248 248 # some functions for the templater
249 249
250 250 def header(**map):
251 251 header_file = cStringIO.StringIO(
252 252 ''.join(tmpl("header", encoding=self.encoding, **map)))
253 253 msg = mimetools.Message(header_file, 0)
254 254 req.header(msg.items())
255 255 yield header_file.read()
256 256
257 257 def rawfileheader(**map):
258 258 req.header([('Content-type', map['mimetype']),
259 259 ('Content-disposition', 'filename=%s' % map['file']),
260 260 ('Content-length', str(len(map['raw'])))])
261 261 yield ''
262 262
263 263 def footer(**map):
264 264 yield tmpl("footer", **map)
265 265
266 266 def motd(**map):
267 267 yield self.config("web", "motd", "")
268 268
269 269 def sessionvars(**map):
270 270 fields = []
271 271 if req.form.has_key('style'):
272 272 style = req.form['style'][0]
273 273 if style != self.config('web', 'style', ''):
274 274 fields.append(('style', style))
275 275
276 276 separator = req.url[-1] == '?' and ';' or '?'
277 277 for name, value in fields:
278 278 yield dict(name=name, value=value, separator=separator)
279 279 separator = ';'
280 280
281 281 # figure out which style to use
282 282
283 283 style = self.config("web", "style", "")
284 284 if req.form.has_key('style'):
285 285 style = req.form['style'][0]
286 286 mapfile = style_map(self.templatepath, style)
287 287
288 288 if not self.reponame:
289 289 self.reponame = (self.config("web", "name")
290 290 or req.env.get('REPO_NAME')
291 291 or req.url.strip('/') or self.repo.root)
292 292
293 293 # create the templater
294 294
295 295 tmpl = templater.templater(mapfile, templater.common_filters,
296 296 defaults={"url": req.url,
297 297 "staticurl": staticurl,
298 298 "urlbase": urlbase,
299 299 "repo": self.reponame,
300 300 "header": header,
301 301 "footer": footer,
302 302 "motd": motd,
303 303 "rawfileheader": rawfileheader,
304 304 "sessionvars": sessionvars
305 305 })
306 306 return tmpl
307 307
308 308 def archivelist(self, nodeid):
309 309 allowed = self.configlist("web", "allow_archive")
310 310 for i, spec in self.archive_specs.iteritems():
311 311 if i in allowed or self.configbool("web", "allow" + i):
312 312 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
313 313
314 314 def listfilediffs(self, tmpl, files, changeset):
315 315 for f in files[:self.maxfiles]:
316 316 yield tmpl("filedifflink", node=hex(changeset), file=f)
317 317 if len(files) > self.maxfiles:
318 318 yield tmpl("fileellipses")
319 319
320 320 def siblings(self, siblings=[], hiderev=None, **args):
321 321 siblings = [s for s in siblings if s.node() != nullid]
322 322 if len(siblings) == 1 and siblings[0].rev() == hiderev:
323 323 return
324 324 for s in siblings:
325 325 d = {'node': hex(s.node()), 'rev': s.rev()}
326 326 if hasattr(s, 'path'):
327 327 d['file'] = s.path()
328 328 d.update(args)
329 329 yield d
330 330
331 331 def renamelink(self, fl, node):
332 332 r = fl.renamed(node)
333 333 if r:
334 334 return [dict(file=r[0], node=hex(r[1]))]
335 335 return []
336 336
337 337 def nodetagsdict(self, node):
338 338 return [{"name": i} for i in self.repo.nodetags(node)]
339 339
340 340 def nodebranchdict(self, ctx):
341 341 branches = []
342 342 branch = ctx.branch()
343 343 # If this is an empty repo, ctx.node() == nullid,
344 344 # ctx.branch() == 'default', but branchtags() is
345 345 # an empty dict. Using dict.get avoids a traceback.
346 346 if self.repo.branchtags().get(branch) == ctx.node():
347 347 branches.append({"name": branch})
348 348 return branches
349 349
350 350 def showtag(self, tmpl, t1, node=nullid, **args):
351 351 for t in self.repo.nodetags(node):
352 352 yield tmpl(t1, tag=t, **args)
353 353
354 354 def diff(self, tmpl, node1, node2, files):
355 355 def filterfiles(filters, files):
356 356 l = [x for x in files if x in filters]
357 357
358 358 for t in filters:
359 359 if t and t[-1] != os.sep:
360 360 t += os.sep
361 361 l += [x for x in files if x.startswith(t)]
362 362 return l
363 363
364 364 parity = paritygen(self.stripecount)
365 365 def diffblock(diff, f, fn):
366 366 yield tmpl("diffblock",
367 367 lines=prettyprintlines(diff),
368 368 parity=parity.next(),
369 369 file=f,
370 370 filenode=hex(fn or nullid))
371 371
372 372 def prettyprintlines(diff):
373 373 for l in diff.splitlines(1):
374 374 if l.startswith('+'):
375 375 yield tmpl("difflineplus", line=l)
376 376 elif l.startswith('-'):
377 377 yield tmpl("difflineminus", line=l)
378 378 elif l.startswith('@'):
379 379 yield tmpl("difflineat", line=l)
380 380 else:
381 381 yield tmpl("diffline", line=l)
382 382
383 383 r = self.repo
384 384 c1 = r.changectx(node1)
385 385 c2 = r.changectx(node2)
386 386 date1 = util.datestr(c1.date())
387 387 date2 = util.datestr(c2.date())
388 388
389 389 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
390 390 if files:
391 391 modified, added, removed = map(lambda x: filterfiles(files, x),
392 392 (modified, added, removed))
393 393
394 394 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
395 395 for f in modified:
396 396 to = c1.filectx(f).data()
397 397 tn = c2.filectx(f).data()
398 398 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
399 399 opts=diffopts), f, tn)
400 400 for f in added:
401 401 to = None
402 402 tn = c2.filectx(f).data()
403 403 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
404 404 opts=diffopts), f, tn)
405 405 for f in removed:
406 406 to = c1.filectx(f).data()
407 407 tn = None
408 408 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
409 409 opts=diffopts), f, tn)
410 410
411 411 def changelog(self, tmpl, ctx, shortlog=False):
412 412 def changelist(limit=0,**map):
413 413 cl = self.repo.changelog
414 414 l = [] # build a list in forward order for efficiency
415 415 for i in xrange(start, end):
416 416 ctx = self.repo.changectx(i)
417 417 n = ctx.node()
418 418
419 419 l.insert(0, {"parity": parity.next(),
420 420 "author": ctx.user(),
421 421 "parent": self.siblings(ctx.parents(), i - 1),
422 422 "child": self.siblings(ctx.children(), i + 1),
423 423 "changelogtag": self.showtag("changelogtag",n),
424 424 "desc": ctx.description(),
425 425 "date": ctx.date(),
426 426 "files": self.listfilediffs(tmpl, ctx.files(), n),
427 427 "rev": i,
428 428 "node": hex(n),
429 429 "tags": self.nodetagsdict(n),
430 430 "branches": self.nodebranchdict(ctx)})
431 431
432 432 if limit > 0:
433 433 l = l[:limit]
434 434
435 435 for e in l:
436 436 yield e
437 437
438 438 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
439 439 cl = self.repo.changelog
440 440 count = cl.count()
441 441 pos = ctx.rev()
442 442 start = max(0, pos - maxchanges + 1)
443 443 end = min(count, start + maxchanges)
444 444 pos = end - 1
445 445 parity = paritygen(self.stripecount, offset=start-end)
446 446
447 447 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
448 448
449 449 yield tmpl(shortlog and 'shortlog' or 'changelog',
450 450 changenav=changenav,
451 451 node=hex(cl.tip()),
452 452 rev=pos, changesets=count,
453 453 entries=lambda **x: changelist(limit=0,**x),
454 454 latestentry=lambda **x: changelist(limit=1,**x),
455 455 archives=self.archivelist("tip"))
456 456
457 457 def search(self, tmpl, query):
458 458
459 459 def changelist(**map):
460 460 cl = self.repo.changelog
461 461 count = 0
462 462 qw = query.lower().split()
463 463
464 464 def revgen():
465 465 for i in xrange(cl.count() - 1, 0, -100):
466 466 l = []
467 467 for j in xrange(max(0, i - 100), i):
468 468 ctx = self.repo.changectx(j)
469 469 l.append(ctx)
470 470 l.reverse()
471 471 for e in l:
472 472 yield e
473 473
474 474 for ctx in revgen():
475 475 miss = 0
476 476 for q in qw:
477 477 if not (q in ctx.user().lower() or
478 478 q in ctx.description().lower() or
479 479 q in " ".join(ctx.files()).lower()):
480 480 miss = 1
481 481 break
482 482 if miss:
483 483 continue
484 484
485 485 count += 1
486 486 n = ctx.node()
487 487
488 488 yield tmpl('searchentry',
489 489 parity=parity.next(),
490 490 author=ctx.user(),
491 491 parent=self.siblings(ctx.parents()),
492 492 child=self.siblings(ctx.children()),
493 493 changelogtag=self.showtag("changelogtag",n),
494 494 desc=ctx.description(),
495 495 date=ctx.date(),
496 496 files=self.listfilediffs(tmpl, ctx.files(), n),
497 497 rev=ctx.rev(),
498 498 node=hex(n),
499 499 tags=self.nodetagsdict(n),
500 500 branches=self.nodebranchdict(ctx))
501 501
502 502 if count >= self.maxchanges:
503 503 break
504 504
505 505 cl = self.repo.changelog
506 506 parity = paritygen(self.stripecount)
507 507
508 508 yield tmpl('search',
509 509 query=query,
510 510 node=hex(cl.tip()),
511 511 entries=changelist,
512 512 archives=self.archivelist("tip"))
513 513
514 514 def changeset(self, tmpl, ctx):
515 515 n = ctx.node()
516 516 parents = ctx.parents()
517 517 p1 = parents[0].node()
518 518
519 519 files = []
520 520 parity = paritygen(self.stripecount)
521 521 for f in ctx.files():
522 522 files.append(tmpl("filenodelink",
523 523 node=hex(n), file=f,
524 524 parity=parity.next()))
525 525
526 526 def diff(**map):
527 527 yield self.diff(tmpl, p1, n, None)
528 528
529 529 yield tmpl('changeset',
530 530 diff=diff,
531 531 rev=ctx.rev(),
532 532 node=hex(n),
533 533 parent=self.siblings(parents),
534 534 child=self.siblings(ctx.children()),
535 535 changesettag=self.showtag("changesettag",n),
536 536 author=ctx.user(),
537 537 desc=ctx.description(),
538 538 date=ctx.date(),
539 539 files=files,
540 540 archives=self.archivelist(hex(n)),
541 541 tags=self.nodetagsdict(n),
542 542 branches=self.nodebranchdict(ctx))
543 543
544 544 def filelog(self, tmpl, fctx):
545 545 f = fctx.path()
546 546 fl = fctx.filelog()
547 547 count = fl.count()
548 548 pagelen = self.maxshortchanges
549 549 pos = fctx.filerev()
550 550 start = max(0, pos - pagelen + 1)
551 551 end = min(count, start + pagelen)
552 552 pos = end - 1
553 553 parity = paritygen(self.stripecount, offset=start-end)
554 554
555 555 def entries(limit=0, **map):
556 556 l = []
557 557
558 558 for i in xrange(start, end):
559 559 ctx = fctx.filectx(i)
560 560 n = fl.node(i)
561 561
562 562 l.insert(0, {"parity": parity.next(),
563 563 "filerev": i,
564 564 "file": f,
565 565 "node": hex(ctx.node()),
566 566 "author": ctx.user(),
567 567 "date": ctx.date(),
568 568 "rename": self.renamelink(fl, n),
569 569 "parent": self.siblings(fctx.parents()),
570 570 "child": self.siblings(fctx.children()),
571 571 "desc": ctx.description()})
572 572
573 573 if limit > 0:
574 574 l = l[:limit]
575 575
576 576 for e in l:
577 577 yield e
578 578
579 579 nodefunc = lambda x: fctx.filectx(fileid=x)
580 580 nav = revnavgen(pos, pagelen, count, nodefunc)
581 581 yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
582 582 entries=lambda **x: entries(limit=0, **x),
583 583 latestentry=lambda **x: entries(limit=1, **x))
584 584
585 585 def filerevision(self, tmpl, fctx):
586 586 f = fctx.path()
587 587 text = fctx.data()
588 588 fl = fctx.filelog()
589 589 n = fctx.filenode()
590 590 parity = paritygen(self.stripecount)
591 591
592 592 mt = mimetypes.guess_type(f)[0]
593 593 rawtext = text
594 594 if util.binary(text):
595 595 mt = mt or 'application/octet-stream'
596 596 text = "(binary:%s)" % mt
597 597 mt = mt or 'text/plain'
598 598
599 599 def lines():
600 600 for l, t in enumerate(text.splitlines(1)):
601 601 yield {"line": t,
602 602 "linenumber": "% 6d" % (l + 1),
603 603 "parity": parity.next()}
604 604
605 605 yield tmpl("filerevision",
606 606 file=f,
607 607 path=_up(f),
608 608 text=lines(),
609 609 raw=rawtext,
610 610 mimetype=mt,
611 611 rev=fctx.rev(),
612 612 node=hex(fctx.node()),
613 613 author=fctx.user(),
614 614 date=fctx.date(),
615 615 desc=fctx.description(),
616 616 parent=self.siblings(fctx.parents()),
617 617 child=self.siblings(fctx.children()),
618 618 rename=self.renamelink(fl, n),
619 619 permissions=fctx.manifest().flags(f))
620 620
621 621 def fileannotate(self, tmpl, fctx):
622 622 f = fctx.path()
623 623 n = fctx.filenode()
624 624 fl = fctx.filelog()
625 625 parity = paritygen(self.stripecount)
626 626
627 627 def annotate(**map):
628 628 last = None
629 629 for f, l in fctx.annotate(follow=True):
630 630 fnode = f.filenode()
631 631 name = self.repo.ui.shortuser(f.user())
632 632
633 633 if last != fnode:
634 634 last = fnode
635 635
636 636 yield {"parity": parity.next(),
637 637 "node": hex(f.node()),
638 638 "rev": f.rev(),
639 639 "author": name,
640 640 "file": f.path(),
641 641 "line": l}
642 642
643 643 yield tmpl("fileannotate",
644 644 file=f,
645 645 annotate=annotate,
646 646 path=_up(f),
647 647 rev=fctx.rev(),
648 648 node=hex(fctx.node()),
649 649 author=fctx.user(),
650 650 date=fctx.date(),
651 651 desc=fctx.description(),
652 652 rename=self.renamelink(fl, n),
653 653 parent=self.siblings(fctx.parents()),
654 654 child=self.siblings(fctx.children()),
655 655 permissions=fctx.manifest().flags(f))
656 656
657 657 def manifest(self, tmpl, ctx, path):
658 658 mf = ctx.manifest()
659 659 node = ctx.node()
660 660
661 661 files = {}
662 662 parity = paritygen(self.stripecount)
663 663
664 664 if path and path[-1] != "/":
665 665 path += "/"
666 666 l = len(path)
667 667 abspath = "/" + path
668 668
669 669 for f, n in mf.items():
670 670 if f[:l] != path:
671 671 continue
672 672 remain = f[l:]
673 673 if "/" in remain:
674 674 short = remain[:remain.index("/") + 1] # bleah
675 675 files[short] = (f, None)
676 676 else:
677 677 short = os.path.basename(remain)
678 678 files[short] = (f, n)
679 679
680 680 if not files:
681 681 raise ErrorResponse(404, 'Path not found: ' + path)
682 682
683 683 def filelist(**map):
684 684 fl = files.keys()
685 685 fl.sort()
686 686 for f in fl:
687 687 full, fnode = files[f]
688 688 if not fnode:
689 689 continue
690 690
691 691 fctx = ctx.filectx(full)
692 692 yield {"file": full,
693 693 "parity": parity.next(),
694 694 "basename": f,
695 695 "date": fctx.changectx().date(),
696 696 "size": fctx.size(),
697 697 "permissions": mf.flags(full)}
698 698
699 699 def dirlist(**map):
700 700 fl = files.keys()
701 701 fl.sort()
702 702 for f in fl:
703 703 full, fnode = files[f]
704 704 if fnode:
705 705 continue
706 706
707 707 yield {"parity": parity.next(),
708 708 "path": "%s%s" % (abspath, f),
709 709 "basename": f[:-1]}
710 710
711 711 yield tmpl("manifest",
712 712 rev=ctx.rev(),
713 713 node=hex(node),
714 714 path=abspath,
715 715 up=_up(abspath),
716 716 upparity=parity.next(),
717 717 fentries=filelist,
718 718 dentries=dirlist,
719 719 archives=self.archivelist(hex(node)),
720 720 tags=self.nodetagsdict(node),
721 721 branches=self.nodebranchdict(ctx))
722 722
723 723 def tags(self, tmpl):
724 724 i = self.repo.tagslist()
725 725 i.reverse()
726 726 parity = paritygen(self.stripecount)
727 727
728 728 def entries(notip=False,limit=0, **map):
729 729 count = 0
730 730 for k, n in i:
731 731 if notip and k == "tip":
732 732 continue
733 733 if limit > 0 and count >= limit:
734 734 continue
735 735 count = count + 1
736 736 yield {"parity": parity.next(),
737 737 "tag": k,
738 738 "date": self.repo.changectx(n).date(),
739 739 "node": hex(n)}
740 740
741 741 yield tmpl("tags",
742 742 node=hex(self.repo.changelog.tip()),
743 743 entries=lambda **x: entries(False,0, **x),
744 744 entriesnotip=lambda **x: entries(True,0, **x),
745 745 latestentry=lambda **x: entries(True,1, **x))
746 746
747 747 def summary(self, tmpl):
748 748 i = self.repo.tagslist()
749 749 i.reverse()
750 750
751 751 def tagentries(**map):
752 752 parity = paritygen(self.stripecount)
753 753 count = 0
754 754 for k, n in i:
755 755 if k == "tip": # skip tip
756 756 continue;
757 757
758 758 count += 1
759 759 if count > 10: # limit to 10 tags
760 760 break;
761 761
762 762 yield tmpl("tagentry",
763 763 parity=parity.next(),
764 764 tag=k,
765 765 node=hex(n),
766 766 date=self.repo.changectx(n).date())
767 767
768 768
769 769 def branches(**map):
770 770 parity = paritygen(self.stripecount)
771 771
772 772 b = self.repo.branchtags()
773 773 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
774 774 l.sort()
775 775
776 776 for r,n,t in l:
777 777 ctx = self.repo.changectx(n)
778 778
779 779 yield {'parity': parity.next(),
780 780 'branch': t,
781 781 'node': hex(n),
782 782 'date': ctx.date()}
783 783
784 784 def changelist(**map):
785 785 parity = paritygen(self.stripecount, offset=start-end)
786 786 l = [] # build a list in forward order for efficiency
787 787 for i in xrange(start, end):
788 788 ctx = self.repo.changectx(i)
789 789 n = ctx.node()
790 790 hn = hex(n)
791 791
792 792 l.insert(0, tmpl(
793 793 'shortlogentry',
794 794 parity=parity.next(),
795 795 author=ctx.user(),
796 796 desc=ctx.description(),
797 797 date=ctx.date(),
798 798 rev=i,
799 799 node=hn,
800 800 tags=self.nodetagsdict(n),
801 801 branches=self.nodebranchdict(ctx)))
802 802
803 803 yield l
804 804
805 805 cl = self.repo.changelog
806 806 count = cl.count()
807 807 start = max(0, count - self.maxchanges)
808 808 end = min(count, start + self.maxchanges)
809 809
810 810 yield tmpl("summary",
811 811 desc=self.config("web", "description", "unknown"),
812 812 owner=get_contact(self.config) or "unknown",
813 813 lastchange=cl.read(cl.tip())[2],
814 814 tags=tagentries,
815 815 branches=branches,
816 816 shortlog=changelist,
817 817 node=hex(cl.tip()),
818 818 archives=self.archivelist("tip"))
819 819
820 820 def filediff(self, tmpl, fctx):
821 821 n = fctx.node()
822 822 path = fctx.path()
823 823 parents = fctx.parents()
824 824 p1 = parents and parents[0].node() or nullid
825 825
826 826 def diff(**map):
827 827 yield self.diff(tmpl, p1, n, [path])
828 828
829 829 yield tmpl("filediff",
830 830 file=path,
831 831 node=hex(n),
832 832 rev=fctx.rev(),
833 833 parent=self.siblings(parents),
834 834 child=self.siblings(fctx.children()),
835 835 diff=diff)
836 836
837 837 archive_specs = {
838 838 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
839 839 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
840 840 'zip': ('application/zip', 'zip', '.zip', None),
841 841 }
842 842
843 843 def archive(self, tmpl, req, key, type_):
844 844 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
845 845 cnode = self.repo.lookup(key)
846 846 arch_version = key
847 847 if cnode == key or key == 'tip':
848 848 arch_version = short(cnode)
849 849 name = "%s-%s" % (reponame, arch_version)
850 850 mimetype, artype, extension, encoding = self.archive_specs[type_]
851 851 headers = [('Content-type', mimetype),
852 852 ('Content-disposition', 'attachment; filename=%s%s' %
853 853 (name, extension))]
854 854 if encoding:
855 855 headers.append(('Content-encoding', encoding))
856 856 req.header(headers)
857 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
857 archival.archive(self.repo, req, cnode, artype, prefix=name)
858 858
859 859 # add tags to things
860 860 # tags -> list of changesets corresponding to tags
861 861 # find tag, changeset, file
862 862
863 863 def cleanpath(self, path):
864 864 path = path.lstrip('/')
865 865 return util.canonpath(self.repo.root, '', path)
866 866
867 867 def changectx(self, req):
868 868 if req.form.has_key('node'):
869 869 changeid = req.form['node'][0]
870 870 elif req.form.has_key('manifest'):
871 871 changeid = req.form['manifest'][0]
872 872 else:
873 873 changeid = self.repo.changelog.count() - 1
874 874
875 875 try:
876 876 ctx = self.repo.changectx(changeid)
877 877 except hg.RepoError:
878 878 man = self.repo.manifest
879 879 mn = man.lookup(changeid)
880 880 ctx = self.repo.changectx(man.linkrev(mn))
881 881
882 882 return ctx
883 883
884 884 def filectx(self, req):
885 885 path = self.cleanpath(req.form['file'][0])
886 886 if req.form.has_key('node'):
887 887 changeid = req.form['node'][0]
888 888 else:
889 889 changeid = req.form['filenode'][0]
890 890 try:
891 891 ctx = self.repo.changectx(changeid)
892 892 fctx = ctx.filectx(path)
893 893 except hg.RepoError:
894 894 fctx = self.repo.filectx(path, fileid=changeid)
895 895
896 896 return fctx
897 897
898 898 def check_perm(self, req, op, default):
899 899 '''check permission for operation based on user auth.
900 900 return true if op allowed, else false.
901 901 default is policy to use if no config given.'''
902 902
903 903 user = req.env.get('REMOTE_USER')
904 904
905 905 deny = self.configlist('web', 'deny_' + op)
906 906 if deny and (not user or deny == ['*'] or user in deny):
907 907 return False
908 908
909 909 allow = self.configlist('web', 'allow_' + op)
910 910 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,92 +1,90
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 11 from common import ErrorResponse, statusmessage
12 12
13 13 class wsgirequest(object):
14 14 def __init__(self, wsgienv, start_response):
15 15 version = wsgienv['wsgi.version']
16 16 if (version < (1, 0)) or (version >= (2, 0)):
17 17 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
18 18 % version)
19 19 self.inp = wsgienv['wsgi.input']
20 20 self.server_write = None
21 21 self.err = wsgienv['wsgi.errors']
22 22 self.threaded = wsgienv['wsgi.multithread']
23 23 self.multiprocess = wsgienv['wsgi.multiprocess']
24 24 self.run_once = wsgienv['wsgi.run_once']
25 25 self.env = wsgienv
26 26 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
27 27 self.start_response = start_response
28 28 self.headers = []
29 29
30 out = property(lambda self: self)
31
32 30 def __iter__(self):
33 31 return iter([])
34 32
35 33 def read(self, count=-1):
36 34 return self.inp.read(count)
37 35
38 36 def respond(self, status, *things):
39 37 for thing in things:
40 38 if hasattr(thing, "__iter__"):
41 39 for part in thing:
42 40 self.respond(status, part)
43 41 else:
44 42 thing = str(thing)
45 43 if self.server_write is None:
46 44 if not self.headers:
47 45 raise RuntimeError("request.write called before headers sent (%s)." % thing)
48 46 if isinstance(status, ErrorResponse):
49 47 status = statusmessage(status.code)
50 48 elif isinstance(status, int):
51 49 status = statusmessage(status)
52 50 self.server_write = self.start_response(status,
53 51 self.headers)
54 52 self.start_response = None
55 53 self.headers = []
56 54 try:
57 55 self.server_write(thing)
58 56 except socket.error, inst:
59 57 if inst[0] != errno.ECONNRESET:
60 58 raise
61 59
62 60 def write(self, *things):
63 61 self.respond('200 Script output follows', *things)
64 62
65 63 def writelines(self, lines):
66 64 for line in lines:
67 65 self.write(line)
68 66
69 67 def flush(self):
70 68 return None
71 69
72 70 def close(self):
73 71 return None
74 72
75 73 def header(self, headers=[('Content-type','text/html')]):
76 74 self.headers.extend(headers)
77 75
78 76 def httphdr(self, type, filename=None, length=0, headers={}):
79 77 headers = headers.items()
80 78 headers.append(('Content-type', type))
81 79 if filename:
82 80 headers.append(('Content-disposition', 'attachment; filename=%s' %
83 81 filename))
84 82 if length:
85 83 headers.append(('Content-length', str(length)))
86 84 self.header(headers)
87 85
88 86 def wsgiapplication(app_maker):
89 87 application = app_maker()
90 88 def run_wsgi(env, respond):
91 89 application(env, respond)
92 90 return run_wsgi
General Comments 0
You need to be logged in to leave comments. Login now