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