##// END OF EJS Templates
Make {urlbase} work in templates when https is used.
Wesley J. Landaker -
r4867:8be7ba42 default
parent child Browse files
Show More
@@ -1,1180 +1,1187 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, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 15 from common import get_mtime, staticfile, style_map, paritygen
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 type(repo) == type(""):
68 68 self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
69 69 else:
70 70 self.repo = repo
71 71
72 72 self.mtime = -1
73 73 self.reponame = name
74 74 self.archives = 'zip', 'gz', 'bz2'
75 75 self.stripecount = 1
76 76 # a repo owner may set web.templates in .hg/hgrc to get any file
77 77 # readable by the user running the CGI script
78 78 self.templatepath = self.config("web", "templates",
79 79 templater.templatepath(),
80 80 untrusted=False)
81 81
82 82 # The CGI scripts are often run by a user different from the repo owner.
83 83 # Trust the settings from the .hg/hgrc files by default.
84 84 def config(self, section, name, default=None, untrusted=True):
85 85 return self.repo.ui.config(section, name, default,
86 86 untrusted=untrusted)
87 87
88 88 def configbool(self, section, name, default=False, untrusted=True):
89 89 return self.repo.ui.configbool(section, name, default,
90 90 untrusted=untrusted)
91 91
92 92 def configlist(self, section, name, default=None, untrusted=True):
93 93 return self.repo.ui.configlist(section, name, default,
94 94 untrusted=untrusted)
95 95
96 96 def refresh(self):
97 97 mtime = get_mtime(self.repo.root)
98 98 if mtime != self.mtime:
99 99 self.mtime = mtime
100 100 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 101 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 102 self.stripecount = int(self.config("web", "stripes", 1))
103 103 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 104 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 105 self.allowpull = self.configbool("web", "allowpull", True)
106 106 self.encoding = self.config("web", "encoding", util._encoding)
107 107
108 108 def archivelist(self, nodeid):
109 109 allowed = self.configlist("web", "allow_archive")
110 110 for i, spec in self.archive_specs.iteritems():
111 111 if i in allowed or self.configbool("web", "allow" + i):
112 112 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
113 113
114 114 def listfilediffs(self, files, changeset):
115 115 for f in files[:self.maxfiles]:
116 116 yield self.t("filedifflink", node=hex(changeset), file=f)
117 117 if len(files) > self.maxfiles:
118 118 yield self.t("fileellipses")
119 119
120 120 def siblings(self, siblings=[], hiderev=None, **args):
121 121 siblings = [s for s in siblings if s.node() != nullid]
122 122 if len(siblings) == 1 and siblings[0].rev() == hiderev:
123 123 return
124 124 for s in siblings:
125 125 d = {'node': hex(s.node()), 'rev': s.rev()}
126 126 if hasattr(s, 'path'):
127 127 d['file'] = s.path()
128 128 d.update(args)
129 129 yield d
130 130
131 131 def renamelink(self, fl, node):
132 132 r = fl.renamed(node)
133 133 if r:
134 134 return [dict(file=r[0], node=hex(r[1]))]
135 135 return []
136 136
137 137 def nodetagsdict(self, node):
138 138 return [{"name": i} for i in self.repo.nodetags(node)]
139 139
140 140 def nodebranchdict(self, ctx):
141 141 branches = []
142 142 branch = ctx.branch()
143 143 if self.repo.branchtags()[branch] == ctx.node():
144 144 branches.append({"name": branch})
145 145 return branches
146 146
147 147 def showtag(self, t1, node=nullid, **args):
148 148 for t in self.repo.nodetags(node):
149 149 yield self.t(t1, tag=t, **args)
150 150
151 151 def diff(self, node1, node2, files):
152 152 def filterfiles(filters, files):
153 153 l = [x for x in files if x in filters]
154 154
155 155 for t in filters:
156 156 if t and t[-1] != os.sep:
157 157 t += os.sep
158 158 l += [x for x in files if x.startswith(t)]
159 159 return l
160 160
161 161 parity = paritygen(self.stripecount)
162 162 def diffblock(diff, f, fn):
163 163 yield self.t("diffblock",
164 164 lines=prettyprintlines(diff),
165 165 parity=parity.next(),
166 166 file=f,
167 167 filenode=hex(fn or nullid))
168 168
169 169 def prettyprintlines(diff):
170 170 for l in diff.splitlines(1):
171 171 if l.startswith('+'):
172 172 yield self.t("difflineplus", line=l)
173 173 elif l.startswith('-'):
174 174 yield self.t("difflineminus", line=l)
175 175 elif l.startswith('@'):
176 176 yield self.t("difflineat", line=l)
177 177 else:
178 178 yield self.t("diffline", line=l)
179 179
180 180 r = self.repo
181 181 c1 = r.changectx(node1)
182 182 c2 = r.changectx(node2)
183 183 date1 = util.datestr(c1.date())
184 184 date2 = util.datestr(c2.date())
185 185
186 186 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
187 187 if files:
188 188 modified, added, removed = map(lambda x: filterfiles(files, x),
189 189 (modified, added, removed))
190 190
191 191 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
192 192 for f in modified:
193 193 to = c1.filectx(f).data()
194 194 tn = c2.filectx(f).data()
195 195 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
196 196 opts=diffopts), f, tn)
197 197 for f in added:
198 198 to = None
199 199 tn = c2.filectx(f).data()
200 200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
201 201 opts=diffopts), f, tn)
202 202 for f in removed:
203 203 to = c1.filectx(f).data()
204 204 tn = None
205 205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
206 206 opts=diffopts), f, tn)
207 207
208 208 def changelog(self, ctx, shortlog=False):
209 209 def changelist(**map):
210 210 cl = self.repo.changelog
211 211 l = [] # build a list in forward order for efficiency
212 212 for i in xrange(start, end):
213 213 ctx = self.repo.changectx(i)
214 214 n = ctx.node()
215 215
216 216 l.insert(0, {"parity": parity.next(),
217 217 "author": ctx.user(),
218 218 "parent": self.siblings(ctx.parents(), i - 1),
219 219 "child": self.siblings(ctx.children(), i + 1),
220 220 "changelogtag": self.showtag("changelogtag",n),
221 221 "desc": ctx.description(),
222 222 "date": ctx.date(),
223 223 "files": self.listfilediffs(ctx.files(), n),
224 224 "rev": i,
225 225 "node": hex(n),
226 226 "tags": self.nodetagsdict(n),
227 227 "branches": self.nodebranchdict(ctx)})
228 228
229 229 for e in l:
230 230 yield e
231 231
232 232 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
233 233 cl = self.repo.changelog
234 234 count = cl.count()
235 235 pos = ctx.rev()
236 236 start = max(0, pos - maxchanges + 1)
237 237 end = min(count, start + maxchanges)
238 238 pos = end - 1
239 239 parity = paritygen(self.stripecount, offset=start-end)
240 240
241 241 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
242 242
243 243 yield self.t(shortlog and 'shortlog' or 'changelog',
244 244 changenav=changenav,
245 245 node=hex(cl.tip()),
246 246 rev=pos, changesets=count, entries=changelist,
247 247 archives=self.archivelist("tip"))
248 248
249 249 def search(self, query):
250 250
251 251 def changelist(**map):
252 252 cl = self.repo.changelog
253 253 count = 0
254 254 qw = query.lower().split()
255 255
256 256 def revgen():
257 257 for i in xrange(cl.count() - 1, 0, -100):
258 258 l = []
259 259 for j in xrange(max(0, i - 100), i):
260 260 ctx = self.repo.changectx(j)
261 261 l.append(ctx)
262 262 l.reverse()
263 263 for e in l:
264 264 yield e
265 265
266 266 for ctx in revgen():
267 267 miss = 0
268 268 for q in qw:
269 269 if not (q in ctx.user().lower() or
270 270 q in ctx.description().lower() or
271 271 q in " ".join(ctx.files()).lower()):
272 272 miss = 1
273 273 break
274 274 if miss:
275 275 continue
276 276
277 277 count += 1
278 278 n = ctx.node()
279 279
280 280 yield self.t('searchentry',
281 281 parity=parity.next(),
282 282 author=ctx.user(),
283 283 parent=self.siblings(ctx.parents()),
284 284 child=self.siblings(ctx.children()),
285 285 changelogtag=self.showtag("changelogtag",n),
286 286 desc=ctx.description(),
287 287 date=ctx.date(),
288 288 files=self.listfilediffs(ctx.files(), n),
289 289 rev=ctx.rev(),
290 290 node=hex(n),
291 291 tags=self.nodetagsdict(n),
292 292 branches=self.nodebranchdict(ctx))
293 293
294 294 if count >= self.maxchanges:
295 295 break
296 296
297 297 cl = self.repo.changelog
298 298 parity = paritygen(self.stripecount)
299 299
300 300 yield self.t('search',
301 301 query=query,
302 302 node=hex(cl.tip()),
303 303 entries=changelist,
304 304 archives=self.archivelist("tip"))
305 305
306 306 def changeset(self, ctx):
307 307 n = ctx.node()
308 308 parents = ctx.parents()
309 309 p1 = parents[0].node()
310 310
311 311 files = []
312 312 parity = paritygen(self.stripecount)
313 313 for f in ctx.files():
314 314 files.append(self.t("filenodelink",
315 315 node=hex(n), file=f,
316 316 parity=parity.next()))
317 317
318 318 def diff(**map):
319 319 yield self.diff(p1, n, None)
320 320
321 321 yield self.t('changeset',
322 322 diff=diff,
323 323 rev=ctx.rev(),
324 324 node=hex(n),
325 325 parent=self.siblings(parents),
326 326 child=self.siblings(ctx.children()),
327 327 changesettag=self.showtag("changesettag",n),
328 328 author=ctx.user(),
329 329 desc=ctx.description(),
330 330 date=ctx.date(),
331 331 files=files,
332 332 archives=self.archivelist(hex(n)),
333 333 tags=self.nodetagsdict(n),
334 334 branches=self.nodebranchdict(ctx))
335 335
336 336 def filelog(self, fctx):
337 337 f = fctx.path()
338 338 fl = fctx.filelog()
339 339 count = fl.count()
340 340 pagelen = self.maxshortchanges
341 341 pos = fctx.filerev()
342 342 start = max(0, pos - pagelen + 1)
343 343 end = min(count, start + pagelen)
344 344 pos = end - 1
345 345 parity = paritygen(self.stripecount, offset=start-end)
346 346
347 347 def entries(**map):
348 348 l = []
349 349
350 350 for i in xrange(start, end):
351 351 ctx = fctx.filectx(i)
352 352 n = fl.node(i)
353 353
354 354 l.insert(0, {"parity": parity.next(),
355 355 "filerev": i,
356 356 "file": f,
357 357 "node": hex(ctx.node()),
358 358 "author": ctx.user(),
359 359 "date": ctx.date(),
360 360 "rename": self.renamelink(fl, n),
361 361 "parent": self.siblings(fctx.parents()),
362 362 "child": self.siblings(fctx.children()),
363 363 "desc": ctx.description()})
364 364
365 365 for e in l:
366 366 yield e
367 367
368 368 nodefunc = lambda x: fctx.filectx(fileid=x)
369 369 nav = revnavgen(pos, pagelen, count, nodefunc)
370 370 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
371 371 entries=entries)
372 372
373 373 def filerevision(self, fctx):
374 374 f = fctx.path()
375 375 text = fctx.data()
376 376 fl = fctx.filelog()
377 377 n = fctx.filenode()
378 378 parity = paritygen(self.stripecount)
379 379
380 380 mt = mimetypes.guess_type(f)[0]
381 381 rawtext = text
382 382 if util.binary(text):
383 383 mt = mt or 'application/octet-stream'
384 384 text = "(binary:%s)" % mt
385 385 mt = mt or 'text/plain'
386 386
387 387 def lines():
388 388 for l, t in enumerate(text.splitlines(1)):
389 389 yield {"line": t,
390 390 "linenumber": "% 6d" % (l + 1),
391 391 "parity": parity.next()}
392 392
393 393 yield self.t("filerevision",
394 394 file=f,
395 395 path=_up(f),
396 396 text=lines(),
397 397 raw=rawtext,
398 398 mimetype=mt,
399 399 rev=fctx.rev(),
400 400 node=hex(fctx.node()),
401 401 author=fctx.user(),
402 402 date=fctx.date(),
403 403 desc=fctx.description(),
404 404 parent=self.siblings(fctx.parents()),
405 405 child=self.siblings(fctx.children()),
406 406 rename=self.renamelink(fl, n),
407 407 permissions=fctx.manifest().flags(f))
408 408
409 409 def fileannotate(self, fctx):
410 410 f = fctx.path()
411 411 n = fctx.filenode()
412 412 fl = fctx.filelog()
413 413 parity = paritygen(self.stripecount)
414 414
415 415 def annotate(**map):
416 416 last = None
417 417 for f, l in fctx.annotate(follow=True):
418 418 fnode = f.filenode()
419 419 name = self.repo.ui.shortuser(f.user())
420 420
421 421 if last != fnode:
422 422 last = fnode
423 423
424 424 yield {"parity": parity.next(),
425 425 "node": hex(f.node()),
426 426 "rev": f.rev(),
427 427 "author": name,
428 428 "file": f.path(),
429 429 "line": l}
430 430
431 431 yield self.t("fileannotate",
432 432 file=f,
433 433 annotate=annotate,
434 434 path=_up(f),
435 435 rev=fctx.rev(),
436 436 node=hex(fctx.node()),
437 437 author=fctx.user(),
438 438 date=fctx.date(),
439 439 desc=fctx.description(),
440 440 rename=self.renamelink(fl, n),
441 441 parent=self.siblings(fctx.parents()),
442 442 child=self.siblings(fctx.children()),
443 443 permissions=fctx.manifest().flags(f))
444 444
445 445 def manifest(self, ctx, path):
446 446 mf = ctx.manifest()
447 447 node = ctx.node()
448 448
449 449 files = {}
450 450 parity = paritygen(self.stripecount)
451 451
452 452 if path and path[-1] != "/":
453 453 path += "/"
454 454 l = len(path)
455 455 abspath = "/" + path
456 456
457 457 for f, n in mf.items():
458 458 if f[:l] != path:
459 459 continue
460 460 remain = f[l:]
461 461 if "/" in remain:
462 462 short = remain[:remain.index("/") + 1] # bleah
463 463 files[short] = (f, None)
464 464 else:
465 465 short = os.path.basename(remain)
466 466 files[short] = (f, n)
467 467
468 468 def filelist(**map):
469 469 fl = files.keys()
470 470 fl.sort()
471 471 for f in fl:
472 472 full, fnode = files[f]
473 473 if not fnode:
474 474 continue
475 475
476 476 yield {"file": full,
477 477 "parity": parity.next(),
478 478 "basename": f,
479 479 "size": ctx.filectx(full).size(),
480 480 "permissions": mf.flags(full)}
481 481
482 482 def dirlist(**map):
483 483 fl = files.keys()
484 484 fl.sort()
485 485 for f in fl:
486 486 full, fnode = files[f]
487 487 if fnode:
488 488 continue
489 489
490 490 yield {"parity": parity.next(),
491 491 "path": os.path.join(abspath, f),
492 492 "basename": f[:-1]}
493 493
494 494 yield self.t("manifest",
495 495 rev=ctx.rev(),
496 496 node=hex(node),
497 497 path=abspath,
498 498 up=_up(abspath),
499 499 upparity=parity.next(),
500 500 fentries=filelist,
501 501 dentries=dirlist,
502 502 archives=self.archivelist(hex(node)),
503 503 tags=self.nodetagsdict(node),
504 504 branches=self.nodebranchdict(ctx))
505 505
506 506 def tags(self):
507 507 i = self.repo.tagslist()
508 508 i.reverse()
509 509 parity = paritygen(self.stripecount)
510 510
511 511 def entries(notip=False, **map):
512 512 for k, n in i:
513 513 if notip and k == "tip":
514 514 continue
515 515 yield {"parity": parity.next(),
516 516 "tag": k,
517 517 "date": self.repo.changectx(n).date(),
518 518 "node": hex(n)}
519 519
520 520 yield self.t("tags",
521 521 node=hex(self.repo.changelog.tip()),
522 522 entries=lambda **x: entries(False, **x),
523 523 entriesnotip=lambda **x: entries(True, **x))
524 524
525 525 def summary(self):
526 526 i = self.repo.tagslist()
527 527 i.reverse()
528 528
529 529 def tagentries(**map):
530 530 parity = paritygen(self.stripecount)
531 531 count = 0
532 532 for k, n in i:
533 533 if k == "tip": # skip tip
534 534 continue;
535 535
536 536 count += 1
537 537 if count > 10: # limit to 10 tags
538 538 break;
539 539
540 540 yield self.t("tagentry",
541 541 parity=parity.next(),
542 542 tag=k,
543 543 node=hex(n),
544 544 date=self.repo.changectx(n).date())
545 545
546 546
547 547 def branches(**map):
548 548 parity = paritygen(self.stripecount)
549 549
550 550 b = self.repo.branchtags()
551 551 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
552 552 l.sort()
553 553
554 554 for r,n,t in l:
555 555 ctx = self.repo.changectx(n)
556 556
557 557 yield {'parity': parity.next(),
558 558 'branch': t,
559 559 'node': hex(n),
560 560 'date': ctx.date()}
561 561
562 562 def changelist(**map):
563 563 parity = paritygen(self.stripecount, offset=start-end)
564 564 l = [] # build a list in forward order for efficiency
565 565 for i in xrange(start, end):
566 566 ctx = self.repo.changectx(i)
567 567 n = ctx.node()
568 568 hn = hex(n)
569 569
570 570 l.insert(0, self.t(
571 571 'shortlogentry',
572 572 parity=parity.next(),
573 573 author=ctx.user(),
574 574 desc=ctx.description(),
575 575 date=ctx.date(),
576 576 rev=i,
577 577 node=hn,
578 578 tags=self.nodetagsdict(n),
579 579 branches=self.nodebranchdict(ctx)))
580 580
581 581 yield l
582 582
583 583 cl = self.repo.changelog
584 584 count = cl.count()
585 585 start = max(0, count - self.maxchanges)
586 586 end = min(count, start + self.maxchanges)
587 587
588 588 yield self.t("summary",
589 589 desc=self.config("web", "description", "unknown"),
590 590 owner=(self.config("ui", "username") or # preferred
591 591 self.config("web", "contact") or # deprecated
592 592 self.config("web", "author", "unknown")), # also
593 593 lastchange=cl.read(cl.tip())[2],
594 594 tags=tagentries,
595 595 branches=branches,
596 596 shortlog=changelist,
597 597 node=hex(cl.tip()),
598 598 archives=self.archivelist("tip"))
599 599
600 600 def filediff(self, fctx):
601 601 n = fctx.node()
602 602 path = fctx.path()
603 603 parents = fctx.parents()
604 604 p1 = parents and parents[0].node() or nullid
605 605
606 606 def diff(**map):
607 607 yield self.diff(p1, n, [path])
608 608
609 609 yield self.t("filediff",
610 610 file=path,
611 611 node=hex(n),
612 612 rev=fctx.rev(),
613 613 parent=self.siblings(parents),
614 614 child=self.siblings(fctx.children()),
615 615 diff=diff)
616 616
617 617 archive_specs = {
618 618 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
619 619 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
620 620 'zip': ('application/zip', 'zip', '.zip', None),
621 621 }
622 622
623 623 def archive(self, req, key, type_):
624 624 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
625 625 cnode = self.repo.lookup(key)
626 626 arch_version = key
627 627 if cnode == key or key == 'tip':
628 628 arch_version = short(cnode)
629 629 name = "%s-%s" % (reponame, arch_version)
630 630 mimetype, artype, extension, encoding = self.archive_specs[type_]
631 631 headers = [('Content-type', mimetype),
632 632 ('Content-disposition', 'attachment; filename=%s%s' %
633 633 (name, extension))]
634 634 if encoding:
635 635 headers.append(('Content-encoding', encoding))
636 636 req.header(headers)
637 637 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
638 638
639 639 # add tags to things
640 640 # tags -> list of changesets corresponding to tags
641 641 # find tag, changeset, file
642 642
643 643 def cleanpath(self, path):
644 644 path = path.lstrip('/')
645 645 return util.canonpath(self.repo.root, '', path)
646 646
647 647 def run(self):
648 648 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
649 649 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
650 650 import mercurial.hgweb.wsgicgi as wsgicgi
651 651 from request import wsgiapplication
652 652 def make_web_app():
653 653 return self
654 654 wsgicgi.launch(wsgiapplication(make_web_app))
655 655
656 656 def run_wsgi(self, req):
657 657 def header(**map):
658 658 header_file = cStringIO.StringIO(
659 659 ''.join(self.t("header", encoding=self.encoding, **map)))
660 660 msg = mimetools.Message(header_file, 0)
661 661 req.header(msg.items())
662 662 yield header_file.read()
663 663
664 664 def rawfileheader(**map):
665 665 req.header([('Content-type', map['mimetype']),
666 666 ('Content-disposition', 'filename=%s' % map['file']),
667 667 ('Content-length', str(len(map['raw'])))])
668 668 yield ''
669 669
670 670 def footer(**map):
671 671 yield self.t("footer", **map)
672 672
673 673 def motd(**map):
674 674 yield self.config("web", "motd", "")
675 675
676 676 def expand_form(form):
677 677 shortcuts = {
678 678 'cl': [('cmd', ['changelog']), ('rev', None)],
679 679 'sl': [('cmd', ['shortlog']), ('rev', None)],
680 680 'cs': [('cmd', ['changeset']), ('node', None)],
681 681 'f': [('cmd', ['file']), ('filenode', None)],
682 682 'fl': [('cmd', ['filelog']), ('filenode', None)],
683 683 'fd': [('cmd', ['filediff']), ('node', None)],
684 684 'fa': [('cmd', ['annotate']), ('filenode', None)],
685 685 'mf': [('cmd', ['manifest']), ('manifest', None)],
686 686 'ca': [('cmd', ['archive']), ('node', None)],
687 687 'tags': [('cmd', ['tags'])],
688 688 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
689 689 'static': [('cmd', ['static']), ('file', None)]
690 690 }
691 691
692 692 for k in shortcuts.iterkeys():
693 693 if form.has_key(k):
694 694 for name, value in shortcuts[k]:
695 695 if value is None:
696 696 value = form[k]
697 697 form[name] = value
698 698 del form[k]
699 699
700 700 def rewrite_request(req):
701 701 '''translate new web interface to traditional format'''
702 702
703 703 def spliturl(req):
704 704 def firstitem(query):
705 705 return query.split('&', 1)[0].split(';', 1)[0]
706 706
707 707 def normurl(url):
708 708 inner = '/'.join([x for x in url.split('/') if x])
709 709 tl = len(url) > 1 and url.endswith('/') and '/' or ''
710 710
711 711 return '%s%s%s' % (url.startswith('/') and '/' or '',
712 712 inner, tl)
713 713
714 714 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
715 715 pi = normurl(req.env.get('PATH_INFO', ''))
716 716 if pi:
717 717 # strip leading /
718 718 pi = pi[1:]
719 719 if pi:
720 720 root = root[:root.rfind(pi)]
721 721 if req.env.has_key('REPO_NAME'):
722 722 rn = req.env['REPO_NAME'] + '/'
723 723 root += rn
724 724 query = pi[len(rn):]
725 725 else:
726 726 query = pi
727 727 else:
728 728 root += '?'
729 729 query = firstitem(req.env['QUERY_STRING'])
730 730
731 731 return (root, query)
732 732
733 733 req.url, query = spliturl(req)
734 734
735 735 if req.form.has_key('cmd'):
736 736 # old style
737 737 return
738 738
739 739 args = query.split('/', 2)
740 740 if not args or not args[0]:
741 741 return
742 742
743 743 cmd = args.pop(0)
744 744 style = cmd.rfind('-')
745 745 if style != -1:
746 746 req.form['style'] = [cmd[:style]]
747 747 cmd = cmd[style+1:]
748 748 # avoid accepting e.g. style parameter as command
749 749 if hasattr(self, 'do_' + cmd):
750 750 req.form['cmd'] = [cmd]
751 751
752 752 if args and args[0]:
753 753 node = args.pop(0)
754 754 req.form['node'] = [node]
755 755 if args:
756 756 req.form['file'] = args
757 757
758 758 if cmd == 'static':
759 759 req.form['file'] = req.form['node']
760 760 elif cmd == 'archive':
761 761 fn = req.form['node'][0]
762 762 for type_, spec in self.archive_specs.iteritems():
763 763 ext = spec[2]
764 764 if fn.endswith(ext):
765 765 req.form['node'] = [fn[:-len(ext)]]
766 766 req.form['type'] = [type_]
767 767
768 768 def sessionvars(**map):
769 769 fields = []
770 770 if req.form.has_key('style'):
771 771 style = req.form['style'][0]
772 772 if style != self.config('web', 'style', ''):
773 773 fields.append(('style', style))
774 774
775 775 separator = req.url[-1] == '?' and ';' or '?'
776 776 for name, value in fields:
777 777 yield dict(name=name, value=value, separator=separator)
778 778 separator = ';'
779 779
780 780 self.refresh()
781 781
782 782 expand_form(req.form)
783 783 rewrite_request(req)
784 784
785 785 style = self.config("web", "style", "")
786 786 if req.form.has_key('style'):
787 787 style = req.form['style'][0]
788 788 mapfile = style_map(self.templatepath, style)
789 789
790 if req.env.get('HTTPS'):
791 proto = 'https'
792 default_port = "443"
793 else:
794 proto = 'http'
795 default_port = "80"
796
790 797 port = req.env["SERVER_PORT"]
791 port = port != "80" and (":" + port) or ""
792 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
798 port = port != default_port and (":" + port) or ""
799 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
793 800 staticurl = self.config("web", "staticurl") or req.url + 'static/'
794 801 if not staticurl.endswith('/'):
795 802 staticurl += '/'
796 803
797 804 if not self.reponame:
798 805 self.reponame = (self.config("web", "name")
799 806 or req.env.get('REPO_NAME')
800 807 or req.url.strip('/') or self.repo.root)
801 808
802 809 self.t = templater.templater(mapfile, templater.common_filters,
803 810 defaults={"url": req.url,
804 811 "staticurl": staticurl,
805 812 "urlbase": urlbase,
806 813 "repo": self.reponame,
807 814 "header": header,
808 815 "footer": footer,
809 816 "motd": motd,
810 817 "rawfileheader": rawfileheader,
811 818 "sessionvars": sessionvars
812 819 })
813 820
814 821 try:
815 822 if not req.form.has_key('cmd'):
816 823 req.form['cmd'] = [self.t.cache['default']]
817 824
818 825 cmd = req.form['cmd'][0]
819 826
820 827 method = getattr(self, 'do_' + cmd, None)
821 828 if method:
822 829 try:
823 830 method(req)
824 831 except (hg.RepoError, revlog.RevlogError), inst:
825 832 req.write(self.t("error", error=str(inst)))
826 833 else:
827 834 req.write(self.t("error", error='No such method: ' + cmd))
828 835 finally:
829 836 self.t = None
830 837
831 838 def changectx(self, req):
832 839 if req.form.has_key('node'):
833 840 changeid = req.form['node'][0]
834 841 elif req.form.has_key('manifest'):
835 842 changeid = req.form['manifest'][0]
836 843 else:
837 844 changeid = self.repo.changelog.count() - 1
838 845
839 846 try:
840 847 ctx = self.repo.changectx(changeid)
841 848 except hg.RepoError:
842 849 man = self.repo.manifest
843 850 mn = man.lookup(changeid)
844 851 ctx = self.repo.changectx(man.linkrev(mn))
845 852
846 853 return ctx
847 854
848 855 def filectx(self, req):
849 856 path = self.cleanpath(req.form['file'][0])
850 857 if req.form.has_key('node'):
851 858 changeid = req.form['node'][0]
852 859 else:
853 860 changeid = req.form['filenode'][0]
854 861 try:
855 862 ctx = self.repo.changectx(changeid)
856 863 fctx = ctx.filectx(path)
857 864 except hg.RepoError:
858 865 fctx = self.repo.filectx(path, fileid=changeid)
859 866
860 867 return fctx
861 868
862 869 def do_log(self, req):
863 870 if req.form.has_key('file') and req.form['file'][0]:
864 871 self.do_filelog(req)
865 872 else:
866 873 self.do_changelog(req)
867 874
868 875 def do_rev(self, req):
869 876 self.do_changeset(req)
870 877
871 878 def do_file(self, req):
872 879 path = self.cleanpath(req.form.get('file', [''])[0])
873 880 if path:
874 881 try:
875 882 req.write(self.filerevision(self.filectx(req)))
876 883 return
877 884 except revlog.LookupError:
878 885 pass
879 886
880 887 req.write(self.manifest(self.changectx(req), path))
881 888
882 889 def do_diff(self, req):
883 890 self.do_filediff(req)
884 891
885 892 def do_changelog(self, req, shortlog = False):
886 893 if req.form.has_key('node'):
887 894 ctx = self.changectx(req)
888 895 else:
889 896 if req.form.has_key('rev'):
890 897 hi = req.form['rev'][0]
891 898 else:
892 899 hi = self.repo.changelog.count() - 1
893 900 try:
894 901 ctx = self.repo.changectx(hi)
895 902 except hg.RepoError:
896 903 req.write(self.search(hi)) # XXX redirect to 404 page?
897 904 return
898 905
899 906 req.write(self.changelog(ctx, shortlog = shortlog))
900 907
901 908 def do_shortlog(self, req):
902 909 self.do_changelog(req, shortlog = True)
903 910
904 911 def do_changeset(self, req):
905 912 req.write(self.changeset(self.changectx(req)))
906 913
907 914 def do_manifest(self, req):
908 915 req.write(self.manifest(self.changectx(req),
909 916 self.cleanpath(req.form['path'][0])))
910 917
911 918 def do_tags(self, req):
912 919 req.write(self.tags())
913 920
914 921 def do_summary(self, req):
915 922 req.write(self.summary())
916 923
917 924 def do_filediff(self, req):
918 925 req.write(self.filediff(self.filectx(req)))
919 926
920 927 def do_annotate(self, req):
921 928 req.write(self.fileannotate(self.filectx(req)))
922 929
923 930 def do_filelog(self, req):
924 931 req.write(self.filelog(self.filectx(req)))
925 932
926 933 def do_lookup(self, req):
927 934 try:
928 935 r = hex(self.repo.lookup(req.form['key'][0]))
929 936 success = 1
930 937 except Exception,inst:
931 938 r = str(inst)
932 939 success = 0
933 940 resp = "%s %s\n" % (success, r)
934 941 req.httphdr("application/mercurial-0.1", length=len(resp))
935 942 req.write(resp)
936 943
937 944 def do_heads(self, req):
938 945 resp = " ".join(map(hex, self.repo.heads())) + "\n"
939 946 req.httphdr("application/mercurial-0.1", length=len(resp))
940 947 req.write(resp)
941 948
942 949 def do_branches(self, req):
943 950 nodes = []
944 951 if req.form.has_key('nodes'):
945 952 nodes = map(bin, req.form['nodes'][0].split(" "))
946 953 resp = cStringIO.StringIO()
947 954 for b in self.repo.branches(nodes):
948 955 resp.write(" ".join(map(hex, b)) + "\n")
949 956 resp = resp.getvalue()
950 957 req.httphdr("application/mercurial-0.1", length=len(resp))
951 958 req.write(resp)
952 959
953 960 def do_between(self, req):
954 961 if req.form.has_key('pairs'):
955 962 pairs = [map(bin, p.split("-"))
956 963 for p in req.form['pairs'][0].split(" ")]
957 964 resp = cStringIO.StringIO()
958 965 for b in self.repo.between(pairs):
959 966 resp.write(" ".join(map(hex, b)) + "\n")
960 967 resp = resp.getvalue()
961 968 req.httphdr("application/mercurial-0.1", length=len(resp))
962 969 req.write(resp)
963 970
964 971 def do_changegroup(self, req):
965 972 req.httphdr("application/mercurial-0.1")
966 973 nodes = []
967 974 if not self.allowpull:
968 975 return
969 976
970 977 if req.form.has_key('roots'):
971 978 nodes = map(bin, req.form['roots'][0].split(" "))
972 979
973 980 z = zlib.compressobj()
974 981 f = self.repo.changegroup(nodes, 'serve')
975 982 while 1:
976 983 chunk = f.read(4096)
977 984 if not chunk:
978 985 break
979 986 req.write(z.compress(chunk))
980 987
981 988 req.write(z.flush())
982 989
983 990 def do_changegroupsubset(self, req):
984 991 req.httphdr("application/mercurial-0.1")
985 992 bases = []
986 993 heads = []
987 994 if not self.allowpull:
988 995 return
989 996
990 997 if req.form.has_key('bases'):
991 998 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
992 999 if req.form.has_key('heads'):
993 1000 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
994 1001
995 1002 z = zlib.compressobj()
996 1003 f = self.repo.changegroupsubset(bases, heads, 'serve')
997 1004 while 1:
998 1005 chunk = f.read(4096)
999 1006 if not chunk:
1000 1007 break
1001 1008 req.write(z.compress(chunk))
1002 1009
1003 1010 req.write(z.flush())
1004 1011
1005 1012 def do_archive(self, req):
1006 1013 type_ = req.form['type'][0]
1007 1014 allowed = self.configlist("web", "allow_archive")
1008 1015 if (type_ in self.archives and (type_ in allowed or
1009 1016 self.configbool("web", "allow" + type_, False))):
1010 1017 self.archive(req, req.form['node'][0], type_)
1011 1018 return
1012 1019
1013 1020 req.write(self.t("error"))
1014 1021
1015 1022 def do_static(self, req):
1016 1023 fname = req.form['file'][0]
1017 1024 # a repo owner may set web.static in .hg/hgrc to get any file
1018 1025 # readable by the user running the CGI script
1019 1026 static = self.config("web", "static",
1020 1027 os.path.join(self.templatepath, "static"),
1021 1028 untrusted=False)
1022 1029 req.write(staticfile(static, fname, req)
1023 1030 or self.t("error", error="%r not found" % fname))
1024 1031
1025 1032 def do_capabilities(self, req):
1026 1033 caps = ['lookup', 'changegroupsubset']
1027 1034 if self.configbool('server', 'uncompressed'):
1028 1035 caps.append('stream=%d' % self.repo.changelog.version)
1029 1036 # XXX: make configurable and/or share code with do_unbundle:
1030 1037 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1031 1038 if unbundleversions:
1032 1039 caps.append('unbundle=%s' % ','.join(unbundleversions))
1033 1040 resp = ' '.join(caps)
1034 1041 req.httphdr("application/mercurial-0.1", length=len(resp))
1035 1042 req.write(resp)
1036 1043
1037 1044 def check_perm(self, req, op, default):
1038 1045 '''check permission for operation based on user auth.
1039 1046 return true if op allowed, else false.
1040 1047 default is policy to use if no config given.'''
1041 1048
1042 1049 user = req.env.get('REMOTE_USER')
1043 1050
1044 1051 deny = self.configlist('web', 'deny_' + op)
1045 1052 if deny and (not user or deny == ['*'] or user in deny):
1046 1053 return False
1047 1054
1048 1055 allow = self.configlist('web', 'allow_' + op)
1049 1056 return (allow and (allow == ['*'] or user in allow)) or default
1050 1057
1051 1058 def do_unbundle(self, req):
1052 1059 def bail(response, headers={}):
1053 1060 length = int(req.env['CONTENT_LENGTH'])
1054 1061 for s in util.filechunkiter(req, limit=length):
1055 1062 # drain incoming bundle, else client will not see
1056 1063 # response when run outside cgi script
1057 1064 pass
1058 1065 req.httphdr("application/mercurial-0.1", headers=headers)
1059 1066 req.write('0\n')
1060 1067 req.write(response)
1061 1068
1062 1069 # require ssl by default, auth info cannot be sniffed and
1063 1070 # replayed
1064 1071 ssl_req = self.configbool('web', 'push_ssl', True)
1065 1072 if ssl_req:
1066 1073 if not req.env.get('HTTPS'):
1067 1074 bail(_('ssl required\n'))
1068 1075 return
1069 1076 proto = 'https'
1070 1077 else:
1071 1078 proto = 'http'
1072 1079
1073 1080 # do not allow push unless explicitly allowed
1074 1081 if not self.check_perm(req, 'push', False):
1075 1082 bail(_('push not authorized\n'),
1076 1083 headers={'status': '401 Unauthorized'})
1077 1084 return
1078 1085
1079 1086 their_heads = req.form['heads'][0].split(' ')
1080 1087
1081 1088 def check_heads():
1082 1089 heads = map(hex, self.repo.heads())
1083 1090 return their_heads == [hex('force')] or their_heads == heads
1084 1091
1085 1092 # fail early if possible
1086 1093 if not check_heads():
1087 1094 bail(_('unsynced changes\n'))
1088 1095 return
1089 1096
1090 1097 req.httphdr("application/mercurial-0.1")
1091 1098
1092 1099 # do not lock repo until all changegroup data is
1093 1100 # streamed. save to temporary file.
1094 1101
1095 1102 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1096 1103 fp = os.fdopen(fd, 'wb+')
1097 1104 try:
1098 1105 length = int(req.env['CONTENT_LENGTH'])
1099 1106 for s in util.filechunkiter(req, limit=length):
1100 1107 fp.write(s)
1101 1108
1102 1109 try:
1103 1110 lock = self.repo.lock()
1104 1111 try:
1105 1112 if not check_heads():
1106 1113 req.write('0\n')
1107 1114 req.write(_('unsynced changes\n'))
1108 1115 return
1109 1116
1110 1117 fp.seek(0)
1111 1118 header = fp.read(6)
1112 1119 if not header.startswith("HG"):
1113 1120 # old client with uncompressed bundle
1114 1121 def generator(f):
1115 1122 yield header
1116 1123 for chunk in f:
1117 1124 yield chunk
1118 1125 elif not header.startswith("HG10"):
1119 1126 req.write("0\n")
1120 1127 req.write(_("unknown bundle version\n"))
1121 1128 return
1122 1129 elif header == "HG10GZ":
1123 1130 def generator(f):
1124 1131 zd = zlib.decompressobj()
1125 1132 for chunk in f:
1126 1133 yield zd.decompress(chunk)
1127 1134 elif header == "HG10BZ":
1128 1135 def generator(f):
1129 1136 zd = bz2.BZ2Decompressor()
1130 1137 zd.decompress("BZ")
1131 1138 for chunk in f:
1132 1139 yield zd.decompress(chunk)
1133 1140 elif header == "HG10UN":
1134 1141 def generator(f):
1135 1142 for chunk in f:
1136 1143 yield chunk
1137 1144 else:
1138 1145 req.write("0\n")
1139 1146 req.write(_("unknown bundle compression type\n"))
1140 1147 return
1141 1148 gen = generator(util.filechunkiter(fp, 4096))
1142 1149
1143 1150 # send addchangegroup output to client
1144 1151
1145 1152 old_stdout = sys.stdout
1146 1153 sys.stdout = cStringIO.StringIO()
1147 1154
1148 1155 try:
1149 1156 url = 'remote:%s:%s' % (proto,
1150 1157 req.env.get('REMOTE_HOST', ''))
1151 1158 try:
1152 1159 ret = self.repo.addchangegroup(
1153 1160 util.chunkbuffer(gen), 'serve', url)
1154 1161 except util.Abort, inst:
1155 1162 sys.stdout.write("abort: %s\n" % inst)
1156 1163 ret = 0
1157 1164 finally:
1158 1165 val = sys.stdout.getvalue()
1159 1166 sys.stdout = old_stdout
1160 1167 req.write('%d\n' % ret)
1161 1168 req.write(val)
1162 1169 finally:
1163 1170 lock.release()
1164 1171 except (OSError, IOError), inst:
1165 1172 req.write('0\n')
1166 1173 filename = getattr(inst, 'filename', '')
1167 1174 # Don't send our filesystem layout to the client
1168 1175 if filename.startswith(self.repo.root):
1169 1176 filename = filename[len(self.repo.root)+1:]
1170 1177 else:
1171 1178 filename = ''
1172 1179 error = getattr(inst, 'strerror', 'Unknown error')
1173 1180 req.write('%s: %s\n' % (error, filename))
1174 1181 finally:
1175 1182 fp.close()
1176 1183 os.unlink(tempname)
1177 1184
1178 1185 def do_stream_out(self, req):
1179 1186 req.httphdr("application/mercurial-0.1")
1180 1187 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now