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