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