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