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