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