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