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