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