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