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