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