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