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