##// END OF EJS Templates
Don't send "Content-Type: none"...
Alexis S. L. Carvalho -
r2095:0bf2a9e5 default
parent child Browse files
Show More
@@ -1,1085 +1,1086 b''
1 1 # hgweb.py - web interface to a mercurial 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, cgi, sys
10 10 import mimetypes
11 11 from demandload import demandload
12 12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
13 13 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
14 14 demandload(globals(), "mimetypes templater")
15 15 from node import *
16 16 from i18n import gettext as _
17 17
18 18 def up(p):
19 19 if p[0] != "/":
20 20 p = "/" + p
21 21 if p[-1] == "/":
22 22 p = p[:-1]
23 23 up = os.path.dirname(p)
24 24 if up == "/":
25 25 return "/"
26 26 return up + "/"
27 27
28 28 def get_mtime(repo_path):
29 29 hg_path = os.path.join(repo_path, ".hg")
30 30 cl_path = os.path.join(hg_path, "00changelog.i")
31 31 if os.path.exists(os.path.join(cl_path)):
32 32 return os.stat(cl_path).st_mtime
33 33 else:
34 34 return os.stat(hg_path).st_mtime
35 35
36 36 def staticfile(directory, fname):
37 37 """return a file inside directory with guessed content-type header
38 38
39 39 fname always uses '/' as directory separator and isn't allowed to
40 40 contain unusual path components.
41 41 Content-type is guessed using the mimetypes module.
42 42 Return an empty string if fname is illegal or file not found.
43 43
44 44 """
45 45 parts = fname.split('/')
46 46 path = directory
47 47 for part in parts:
48 48 if (part in ('', os.curdir, os.pardir) or
49 49 os.sep in part or os.altsep is not None and os.altsep in part):
50 50 return ""
51 51 path = os.path.join(path, part)
52 52 try:
53 53 os.stat(path)
54 54 ct = mimetypes.guess_type(path)[0] or "text/plain"
55 55 return "Content-type: %s\n\n%s" % (ct, file(path).read())
56 56 except (TypeError, OSError):
57 57 # illegal fname or unreadable file
58 58 return ""
59 59
60 60 class hgrequest(object):
61 61 def __init__(self, inp=None, out=None, env=None):
62 62 self.inp = inp or sys.stdin
63 63 self.out = out or sys.stdout
64 64 self.env = env or os.environ
65 65 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
66 66
67 67 def write(self, *things):
68 68 for thing in things:
69 69 if hasattr(thing, "__iter__"):
70 70 for part in thing:
71 71 self.write(part)
72 72 else:
73 73 try:
74 74 self.out.write(str(thing))
75 75 except socket.error, inst:
76 76 if inst[0] != errno.ECONNRESET:
77 77 raise
78 78
79 79 def header(self, headers=[('Content-type','text/html')]):
80 80 for header in headers:
81 81 self.out.write("%s: %s\r\n" % header)
82 82 self.out.write("\r\n")
83 83
84 84 def httphdr(self, type, file="", size=0):
85 85
86 86 headers = [('Content-type', type)]
87 87 if file:
88 88 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
89 89 if size > 0:
90 90 headers.append(('Content-length', str(size)))
91 91 self.header(headers)
92 92
93 93 class hgweb(object):
94 94 def __init__(self, repo, name=None):
95 95 if type(repo) == type(""):
96 96 self.repo = hg.repository(ui.ui(), repo)
97 97 else:
98 98 self.repo = repo
99 99
100 100 self.mtime = -1
101 101 self.reponame = name
102 102 self.archives = 'zip', 'gz', 'bz2'
103 103
104 104 def refresh(self):
105 105 mtime = get_mtime(self.repo.root)
106 106 if mtime != self.mtime:
107 107 self.mtime = mtime
108 108 self.repo = hg.repository(self.repo.ui, self.repo.root)
109 109 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
110 110 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
111 111 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
112 112
113 113 def archivelist(self, nodeid):
114 114 for i in self.archives:
115 115 if self.repo.ui.configbool("web", "allow" + i, False):
116 116 yield {"type" : i, "node" : nodeid}
117 117
118 118 def listfiles(self, files, mf):
119 119 for f in files[:self.maxfiles]:
120 120 yield self.t("filenodelink", node=hex(mf[f]), file=f)
121 121 if len(files) > self.maxfiles:
122 122 yield self.t("fileellipses")
123 123
124 124 def listfilediffs(self, files, changeset):
125 125 for f in files[:self.maxfiles]:
126 126 yield self.t("filedifflink", node=hex(changeset), file=f)
127 127 if len(files) > self.maxfiles:
128 128 yield self.t("fileellipses")
129 129
130 130 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
131 131 if not rev:
132 132 rev = lambda x: ""
133 133 siblings = [s for s in siblings if s != nullid]
134 134 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
135 135 return
136 136 for s in siblings:
137 137 yield dict(node=hex(s), rev=rev(s), **args)
138 138
139 139 def renamelink(self, fl, node):
140 140 r = fl.renamed(node)
141 141 if r:
142 142 return [dict(file=r[0], node=hex(r[1]))]
143 143 return []
144 144
145 145 def showtag(self, t1, node=nullid, **args):
146 146 for t in self.repo.nodetags(node):
147 147 yield self.t(t1, tag=t, **args)
148 148
149 149 def diff(self, node1, node2, files):
150 150 def filterfiles(filters, files):
151 151 l = [x for x in files if x in filters]
152 152
153 153 for t in filters:
154 154 if t and t[-1] != os.sep:
155 155 t += os.sep
156 156 l += [x for x in files if x.startswith(t)]
157 157 return l
158 158
159 159 parity = [0]
160 160 def diffblock(diff, f, fn):
161 161 yield self.t("diffblock",
162 162 lines=prettyprintlines(diff),
163 163 parity=parity[0],
164 164 file=f,
165 165 filenode=hex(fn or nullid))
166 166 parity[0] = 1 - parity[0]
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 cl = r.changelog
181 181 mf = r.manifest
182 182 change1 = cl.read(node1)
183 183 change2 = cl.read(node2)
184 184 mmap1 = mf.read(change1[0])
185 185 mmap2 = mf.read(change2[0])
186 186 date1 = util.datestr(change1[2])
187 187 date2 = util.datestr(change2[2])
188 188
189 189 modified, added, removed, deleted, unknown = r.changes(node1, node2)
190 190 if files:
191 191 modified, added, removed = map(lambda x: filterfiles(files, x),
192 192 (modified, added, removed))
193 193
194 194 diffopts = self.repo.ui.diffopts()
195 195 showfunc = diffopts['showfunc']
196 196 ignorews = diffopts['ignorews']
197 197 for f in modified:
198 198 to = r.file(f).read(mmap1[f])
199 199 tn = r.file(f).read(mmap2[f])
200 200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
201 201 showfunc=showfunc, ignorews=ignorews), f, tn)
202 202 for f in added:
203 203 to = None
204 204 tn = r.file(f).read(mmap2[f])
205 205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
206 206 showfunc=showfunc, ignorews=ignorews), f, tn)
207 207 for f in removed:
208 208 to = r.file(f).read(mmap1[f])
209 209 tn = None
210 210 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
211 211 showfunc=showfunc, ignorews=ignorews), f, tn)
212 212
213 213 def changelog(self, pos):
214 214 def changenav(**map):
215 215 def seq(factor, maxchanges=None):
216 216 if maxchanges:
217 217 yield maxchanges
218 218 if maxchanges >= 20 and maxchanges <= 40:
219 219 yield 50
220 220 else:
221 221 yield 1 * factor
222 222 yield 3 * factor
223 223 for f in seq(factor * 10):
224 224 yield f
225 225
226 226 l = []
227 227 last = 0
228 228 for f in seq(1, self.maxchanges):
229 229 if f < self.maxchanges or f <= last:
230 230 continue
231 231 if f > count:
232 232 break
233 233 last = f
234 234 r = "%d" % f
235 235 if pos + f < count:
236 236 l.append(("+" + r, pos + f))
237 237 if pos - f >= 0:
238 238 l.insert(0, ("-" + r, pos - f))
239 239
240 240 yield {"rev": 0, "label": "(0)"}
241 241
242 242 for label, rev in l:
243 243 yield {"label": label, "rev": rev}
244 244
245 245 yield {"label": "tip", "rev": "tip"}
246 246
247 247 def changelist(**map):
248 248 parity = (start - end) & 1
249 249 cl = self.repo.changelog
250 250 l = [] # build a list in forward order for efficiency
251 251 for i in range(start, end):
252 252 n = cl.node(i)
253 253 changes = cl.read(n)
254 254 hn = hex(n)
255 255
256 256 l.insert(0, {"parity": parity,
257 257 "author": changes[1],
258 258 "parent": self.siblings(cl.parents(n), cl.rev,
259 259 cl.rev(n) - 1),
260 260 "child": self.siblings(cl.children(n), cl.rev,
261 261 cl.rev(n) + 1),
262 262 "changelogtag": self.showtag("changelogtag",n),
263 263 "manifest": hex(changes[0]),
264 264 "desc": changes[4],
265 265 "date": changes[2],
266 266 "files": self.listfilediffs(changes[3], n),
267 267 "rev": i,
268 268 "node": hn})
269 269 parity = 1 - parity
270 270
271 271 for e in l:
272 272 yield e
273 273
274 274 cl = self.repo.changelog
275 275 mf = cl.read(cl.tip())[0]
276 276 count = cl.count()
277 277 start = max(0, pos - self.maxchanges + 1)
278 278 end = min(count, start + self.maxchanges)
279 279 pos = end - 1
280 280
281 281 yield self.t('changelog',
282 282 changenav=changenav,
283 283 manifest=hex(mf),
284 284 rev=pos, changesets=count, entries=changelist)
285 285
286 286 def search(self, query):
287 287
288 288 def changelist(**map):
289 289 cl = self.repo.changelog
290 290 count = 0
291 291 qw = query.lower().split()
292 292
293 293 def revgen():
294 294 for i in range(cl.count() - 1, 0, -100):
295 295 l = []
296 296 for j in range(max(0, i - 100), i):
297 297 n = cl.node(j)
298 298 changes = cl.read(n)
299 299 l.append((n, j, changes))
300 300 l.reverse()
301 301 for e in l:
302 302 yield e
303 303
304 304 for n, i, changes in revgen():
305 305 miss = 0
306 306 for q in qw:
307 307 if not (q in changes[1].lower() or
308 308 q in changes[4].lower() or
309 309 q in " ".join(changes[3][:20]).lower()):
310 310 miss = 1
311 311 break
312 312 if miss:
313 313 continue
314 314
315 315 count += 1
316 316 hn = hex(n)
317 317
318 318 yield self.t('searchentry',
319 319 parity=count & 1,
320 320 author=changes[1],
321 321 parent=self.siblings(cl.parents(n), cl.rev),
322 322 child=self.siblings(cl.children(n), cl.rev),
323 323 changelogtag=self.showtag("changelogtag",n),
324 324 manifest=hex(changes[0]),
325 325 desc=changes[4],
326 326 date=changes[2],
327 327 files=self.listfilediffs(changes[3], n),
328 328 rev=i,
329 329 node=hn)
330 330
331 331 if count >= self.maxchanges:
332 332 break
333 333
334 334 cl = self.repo.changelog
335 335 mf = cl.read(cl.tip())[0]
336 336
337 337 yield self.t('search',
338 338 query=query,
339 339 manifest=hex(mf),
340 340 entries=changelist)
341 341
342 342 def changeset(self, nodeid):
343 343 cl = self.repo.changelog
344 344 n = self.repo.lookup(nodeid)
345 345 nodeid = hex(n)
346 346 changes = cl.read(n)
347 347 p1 = cl.parents(n)[0]
348 348
349 349 files = []
350 350 mf = self.repo.manifest.read(changes[0])
351 351 for f in changes[3]:
352 352 files.append(self.t("filenodelink",
353 353 filenode=hex(mf.get(f, nullid)), file=f))
354 354
355 355 def diff(**map):
356 356 yield self.diff(p1, n, None)
357 357
358 358 yield self.t('changeset',
359 359 diff=diff,
360 360 rev=cl.rev(n),
361 361 node=nodeid,
362 362 parent=self.siblings(cl.parents(n), cl.rev),
363 363 child=self.siblings(cl.children(n), cl.rev),
364 364 changesettag=self.showtag("changesettag",n),
365 365 manifest=hex(changes[0]),
366 366 author=changes[1],
367 367 desc=changes[4],
368 368 date=changes[2],
369 369 files=files,
370 370 archives=self.archivelist(nodeid))
371 371
372 372 def filelog(self, f, filenode):
373 373 cl = self.repo.changelog
374 374 fl = self.repo.file(f)
375 375 filenode = hex(fl.lookup(filenode))
376 376 count = fl.count()
377 377
378 378 def entries(**map):
379 379 l = []
380 380 parity = (count - 1) & 1
381 381
382 382 for i in range(count):
383 383 n = fl.node(i)
384 384 lr = fl.linkrev(n)
385 385 cn = cl.node(lr)
386 386 cs = cl.read(cl.node(lr))
387 387
388 388 l.insert(0, {"parity": parity,
389 389 "filenode": hex(n),
390 390 "filerev": i,
391 391 "file": f,
392 392 "node": hex(cn),
393 393 "author": cs[1],
394 394 "date": cs[2],
395 395 "rename": self.renamelink(fl, n),
396 396 "parent": self.siblings(fl.parents(n),
397 397 fl.rev, file=f),
398 398 "child": self.siblings(fl.children(n),
399 399 fl.rev, file=f),
400 400 "desc": cs[4]})
401 401 parity = 1 - parity
402 402
403 403 for e in l:
404 404 yield e
405 405
406 406 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
407 407
408 408 def filerevision(self, f, node):
409 409 fl = self.repo.file(f)
410 410 n = fl.lookup(node)
411 411 node = hex(n)
412 412 text = fl.read(n)
413 413 changerev = fl.linkrev(n)
414 414 cl = self.repo.changelog
415 415 cn = cl.node(changerev)
416 416 cs = cl.read(cn)
417 417 mfn = cs[0]
418 418
419 419 mt = mimetypes.guess_type(f)[0]
420 420 rawtext = text
421 421 if util.binary(text):
422 text = "(binary:%s)" % mt
422 text = "(binary:%s)" % (mt or 'data')
423 mt = mt or 'text/plain'
423 424
424 425 def lines():
425 426 for l, t in enumerate(text.splitlines(1)):
426 427 yield {"line": t,
427 428 "linenumber": "% 6d" % (l + 1),
428 429 "parity": l & 1}
429 430
430 431 yield self.t("filerevision",
431 432 file=f,
432 433 filenode=node,
433 434 path=up(f),
434 435 text=lines(),
435 436 raw=rawtext,
436 437 mimetype=mt,
437 438 rev=changerev,
438 439 node=hex(cn),
439 440 manifest=hex(mfn),
440 441 author=cs[1],
441 442 date=cs[2],
442 443 parent=self.siblings(fl.parents(n), fl.rev, file=f),
443 444 child=self.siblings(fl.children(n), fl.rev, file=f),
444 445 rename=self.renamelink(fl, n),
445 446 permissions=self.repo.manifest.readflags(mfn)[f])
446 447
447 448 def fileannotate(self, f, node):
448 449 bcache = {}
449 450 ncache = {}
450 451 fl = self.repo.file(f)
451 452 n = fl.lookup(node)
452 453 node = hex(n)
453 454 changerev = fl.linkrev(n)
454 455
455 456 cl = self.repo.changelog
456 457 cn = cl.node(changerev)
457 458 cs = cl.read(cn)
458 459 mfn = cs[0]
459 460
460 461 def annotate(**map):
461 462 parity = 1
462 463 last = None
463 464 for r, l in fl.annotate(n):
464 465 try:
465 466 cnode = ncache[r]
466 467 except KeyError:
467 468 cnode = ncache[r] = self.repo.changelog.node(r)
468 469
469 470 try:
470 471 name = bcache[r]
471 472 except KeyError:
472 473 cl = self.repo.changelog.read(cnode)
473 474 bcache[r] = name = self.repo.ui.shortuser(cl[1])
474 475
475 476 if last != cnode:
476 477 parity = 1 - parity
477 478 last = cnode
478 479
479 480 yield {"parity": parity,
480 481 "node": hex(cnode),
481 482 "rev": r,
482 483 "author": name,
483 484 "file": f,
484 485 "line": l}
485 486
486 487 yield self.t("fileannotate",
487 488 file=f,
488 489 filenode=node,
489 490 annotate=annotate,
490 491 path=up(f),
491 492 rev=changerev,
492 493 node=hex(cn),
493 494 manifest=hex(mfn),
494 495 author=cs[1],
495 496 date=cs[2],
496 497 rename=self.renamelink(fl, n),
497 498 parent=self.siblings(fl.parents(n), fl.rev, file=f),
498 499 child=self.siblings(fl.children(n), fl.rev, file=f),
499 500 permissions=self.repo.manifest.readflags(mfn)[f])
500 501
501 502 def manifest(self, mnode, path):
502 503 man = self.repo.manifest
503 504 mn = man.lookup(mnode)
504 505 mnode = hex(mn)
505 506 mf = man.read(mn)
506 507 rev = man.rev(mn)
507 508 node = self.repo.changelog.node(rev)
508 509 mff = man.readflags(mn)
509 510
510 511 files = {}
511 512
512 513 p = path[1:]
513 514 if p and p[-1] != "/":
514 515 p += "/"
515 516 l = len(p)
516 517
517 518 for f,n in mf.items():
518 519 if f[:l] != p:
519 520 continue
520 521 remain = f[l:]
521 522 if "/" in remain:
522 523 short = remain[:remain.find("/") + 1] # bleah
523 524 files[short] = (f, None)
524 525 else:
525 526 short = os.path.basename(remain)
526 527 files[short] = (f, n)
527 528
528 529 def filelist(**map):
529 530 parity = 0
530 531 fl = files.keys()
531 532 fl.sort()
532 533 for f in fl:
533 534 full, fnode = files[f]
534 535 if not fnode:
535 536 continue
536 537
537 538 yield {"file": full,
538 539 "manifest": mnode,
539 540 "filenode": hex(fnode),
540 541 "parity": parity,
541 542 "basename": f,
542 543 "permissions": mff[full]}
543 544 parity = 1 - parity
544 545
545 546 def dirlist(**map):
546 547 parity = 0
547 548 fl = files.keys()
548 549 fl.sort()
549 550 for f in fl:
550 551 full, fnode = files[f]
551 552 if fnode:
552 553 continue
553 554
554 555 yield {"parity": parity,
555 556 "path": os.path.join(path, f),
556 557 "manifest": mnode,
557 558 "basename": f[:-1]}
558 559 parity = 1 - parity
559 560
560 561 yield self.t("manifest",
561 562 manifest=mnode,
562 563 rev=rev,
563 564 node=hex(node),
564 565 path=path,
565 566 up=up(path),
566 567 fentries=filelist,
567 568 dentries=dirlist,
568 569 archives=self.archivelist(hex(node)))
569 570
570 571 def tags(self):
571 572 cl = self.repo.changelog
572 573 mf = cl.read(cl.tip())[0]
573 574
574 575 i = self.repo.tagslist()
575 576 i.reverse()
576 577
577 578 def entries(notip=False, **map):
578 579 parity = 0
579 580 for k,n in i:
580 581 if notip and k == "tip": continue
581 582 yield {"parity": parity,
582 583 "tag": k,
583 584 "tagmanifest": hex(cl.read(n)[0]),
584 585 "date": cl.read(n)[2],
585 586 "node": hex(n)}
586 587 parity = 1 - parity
587 588
588 589 yield self.t("tags",
589 590 manifest=hex(mf),
590 591 entries=lambda **x: entries(False, **x),
591 592 entriesnotip=lambda **x: entries(True, **x))
592 593
593 594 def summary(self):
594 595 cl = self.repo.changelog
595 596 mf = cl.read(cl.tip())[0]
596 597
597 598 i = self.repo.tagslist()
598 599 i.reverse()
599 600
600 601 def tagentries(**map):
601 602 parity = 0
602 603 count = 0
603 604 for k,n in i:
604 605 if k == "tip": # skip tip
605 606 continue;
606 607
607 608 count += 1
608 609 if count > 10: # limit to 10 tags
609 610 break;
610 611
611 612 c = cl.read(n)
612 613 m = c[0]
613 614 t = c[2]
614 615
615 616 yield self.t("tagentry",
616 617 parity = parity,
617 618 tag = k,
618 619 node = hex(n),
619 620 date = t,
620 621 tagmanifest = hex(m))
621 622 parity = 1 - parity
622 623
623 624 def changelist(**map):
624 625 parity = 0
625 626 cl = self.repo.changelog
626 627 l = [] # build a list in forward order for efficiency
627 628 for i in range(start, end):
628 629 n = cl.node(i)
629 630 changes = cl.read(n)
630 631 hn = hex(n)
631 632 t = changes[2]
632 633
633 634 l.insert(0, self.t(
634 635 'shortlogentry',
635 636 parity = parity,
636 637 author = changes[1],
637 638 manifest = hex(changes[0]),
638 639 desc = changes[4],
639 640 date = t,
640 641 rev = i,
641 642 node = hn))
642 643 parity = 1 - parity
643 644
644 645 yield l
645 646
646 647 cl = self.repo.changelog
647 648 mf = cl.read(cl.tip())[0]
648 649 count = cl.count()
649 650 start = max(0, count - self.maxchanges)
650 651 end = min(count, start + self.maxchanges)
651 652 pos = end - 1
652 653
653 654 yield self.t("summary",
654 655 desc = self.repo.ui.config("web", "description", "unknown"),
655 656 owner = (self.repo.ui.config("ui", "username") or # preferred
656 657 self.repo.ui.config("web", "contact") or # deprecated
657 658 self.repo.ui.config("web", "author", "unknown")), # also
658 659 lastchange = (0, 0), # FIXME
659 660 manifest = hex(mf),
660 661 tags = tagentries,
661 662 shortlog = changelist)
662 663
663 664 def filediff(self, file, changeset):
664 665 cl = self.repo.changelog
665 666 n = self.repo.lookup(changeset)
666 667 changeset = hex(n)
667 668 p1 = cl.parents(n)[0]
668 669 cs = cl.read(n)
669 670 mf = self.repo.manifest.read(cs[0])
670 671
671 672 def diff(**map):
672 673 yield self.diff(p1, n, file)
673 674
674 675 yield self.t("filediff",
675 676 file=file,
676 677 filenode=hex(mf.get(file, nullid)),
677 678 node=changeset,
678 679 rev=self.repo.changelog.rev(n),
679 680 parent=self.siblings(cl.parents(n), cl.rev),
680 681 child=self.siblings(cl.children(n), cl.rev),
681 682 diff=diff)
682 683
683 684 def archive(self, req, cnode, type):
684 685 cs = self.repo.changelog.read(cnode)
685 686 mnode = cs[0]
686 687 mf = self.repo.manifest.read(mnode)
687 688 rev = self.repo.manifest.rev(mnode)
688 689 reponame = re.sub(r"\W+", "-", self.reponame)
689 690 name = "%s-%s/" % (reponame, short(cnode))
690 691
691 692 files = mf.keys()
692 693 files.sort()
693 694
694 695 if type == 'zip':
695 696 tmp = tempfile.mkstemp()[1]
696 697 try:
697 698 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
698 699
699 700 for f in files:
700 701 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
701 702 zf.close()
702 703
703 704 f = open(tmp, 'r')
704 705 req.httphdr('application/zip', name[:-1] + '.zip',
705 706 os.path.getsize(tmp))
706 707 req.write(f.read())
707 708 f.close()
708 709 finally:
709 710 os.unlink(tmp)
710 711
711 712 else:
712 713 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
713 714 mff = self.repo.manifest.readflags(mnode)
714 715 mtime = int(time.time())
715 716
716 717 if type == "gz":
717 718 encoding = "gzip"
718 719 else:
719 720 encoding = "x-bzip2"
720 721 req.header([('Content-type', 'application/x-tar'),
721 722 ('Content-disposition', 'attachment; filename=%s%s%s' %
722 723 (name[:-1], '.tar.', type)),
723 724 ('Content-encoding', encoding)])
724 725 for fname in files:
725 726 rcont = self.repo.file(fname).read(mf[fname])
726 727 finfo = tarfile.TarInfo(name + fname)
727 728 finfo.mtime = mtime
728 729 finfo.size = len(rcont)
729 730 finfo.mode = mff[fname] and 0755 or 0644
730 731 tf.addfile(finfo, StringIO.StringIO(rcont))
731 732 tf.close()
732 733
733 734 # add tags to things
734 735 # tags -> list of changesets corresponding to tags
735 736 # find tag, changeset, file
736 737
737 738 def run(self, req=hgrequest()):
738 739 def clean(path):
739 740 p = util.normpath(path)
740 741 if p[:2] == "..":
741 742 raise "suspicious path"
742 743 return p
743 744
744 745 def header(**map):
745 746 yield self.t("header", **map)
746 747
747 748 def footer(**map):
748 749 yield self.t("footer", **map)
749 750
750 751 def expand_form(form):
751 752 shortcuts = {
752 753 'cl': [('cmd', ['changelog']), ('rev', None)],
753 754 'cs': [('cmd', ['changeset']), ('node', None)],
754 755 'f': [('cmd', ['file']), ('filenode', None)],
755 756 'fl': [('cmd', ['filelog']), ('filenode', None)],
756 757 'fd': [('cmd', ['filediff']), ('node', None)],
757 758 'fa': [('cmd', ['annotate']), ('filenode', None)],
758 759 'mf': [('cmd', ['manifest']), ('manifest', None)],
759 760 'ca': [('cmd', ['archive']), ('node', None)],
760 761 'tags': [('cmd', ['tags'])],
761 762 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
762 763 'static': [('cmd', ['static']), ('file', None)]
763 764 }
764 765
765 766 for k in shortcuts.iterkeys():
766 767 if form.has_key(k):
767 768 for name, value in shortcuts[k]:
768 769 if value is None:
769 770 value = form[k]
770 771 form[name] = value
771 772 del form[k]
772 773
773 774 self.refresh()
774 775
775 776 expand_form(req.form)
776 777
777 778 t = self.repo.ui.config("web", "templates", templater.templatepath())
778 779 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
779 780 m = os.path.join(t, "map")
780 781 style = self.repo.ui.config("web", "style", "")
781 782 if req.form.has_key('style'):
782 783 style = req.form['style'][0]
783 784 if style:
784 785 b = os.path.basename("map-" + style)
785 786 p = os.path.join(t, b)
786 787 if os.path.isfile(p):
787 788 m = p
788 789
789 790 port = req.env["SERVER_PORT"]
790 791 port = port != "80" and (":" + port) or ""
791 792 uri = req.env["REQUEST_URI"]
792 793 if "?" in uri:
793 794 uri = uri.split("?")[0]
794 795 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
795 796 if not self.reponame:
796 797 self.reponame = (self.repo.ui.config("web", "name")
797 798 or uri.strip('/') or self.repo.root)
798 799
799 800 self.t = templater.templater(m, templater.common_filters,
800 801 defaults={"url": url,
801 802 "repo": self.reponame,
802 803 "header": header,
803 804 "footer": footer,
804 805 })
805 806
806 807 if not req.form.has_key('cmd'):
807 808 req.form['cmd'] = [self.t.cache['default'],]
808 809
809 810 if req.form['cmd'][0] == 'changelog':
810 811 c = self.repo.changelog.count() - 1
811 812 hi = c
812 813 if req.form.has_key('rev'):
813 814 hi = req.form['rev'][0]
814 815 try:
815 816 hi = self.repo.changelog.rev(self.repo.lookup(hi))
816 817 except hg.RepoError:
817 818 req.write(self.search(hi))
818 819 return
819 820
820 821 req.write(self.changelog(hi))
821 822
822 823 elif req.form['cmd'][0] == 'changeset':
823 824 req.write(self.changeset(req.form['node'][0]))
824 825
825 826 elif req.form['cmd'][0] == 'manifest':
826 827 req.write(self.manifest(req.form['manifest'][0],
827 828 clean(req.form['path'][0])))
828 829
829 830 elif req.form['cmd'][0] == 'tags':
830 831 req.write(self.tags())
831 832
832 833 elif req.form['cmd'][0] == 'summary':
833 834 req.write(self.summary())
834 835
835 836 elif req.form['cmd'][0] == 'filediff':
836 837 req.write(self.filediff(clean(req.form['file'][0]),
837 838 req.form['node'][0]))
838 839
839 840 elif req.form['cmd'][0] == 'file':
840 841 req.write(self.filerevision(clean(req.form['file'][0]),
841 842 req.form['filenode'][0]))
842 843
843 844 elif req.form['cmd'][0] == 'annotate':
844 845 req.write(self.fileannotate(clean(req.form['file'][0]),
845 846 req.form['filenode'][0]))
846 847
847 848 elif req.form['cmd'][0] == 'filelog':
848 849 req.write(self.filelog(clean(req.form['file'][0]),
849 850 req.form['filenode'][0]))
850 851
851 852 elif req.form['cmd'][0] == 'heads':
852 853 req.httphdr("application/mercurial-0.1")
853 854 h = self.repo.heads()
854 855 req.write(" ".join(map(hex, h)) + "\n")
855 856
856 857 elif req.form['cmd'][0] == 'branches':
857 858 req.httphdr("application/mercurial-0.1")
858 859 nodes = []
859 860 if req.form.has_key('nodes'):
860 861 nodes = map(bin, req.form['nodes'][0].split(" "))
861 862 for b in self.repo.branches(nodes):
862 863 req.write(" ".join(map(hex, b)) + "\n")
863 864
864 865 elif req.form['cmd'][0] == 'between':
865 866 req.httphdr("application/mercurial-0.1")
866 867 nodes = []
867 868 if req.form.has_key('pairs'):
868 869 pairs = [map(bin, p.split("-"))
869 870 for p in req.form['pairs'][0].split(" ")]
870 871 for b in self.repo.between(pairs):
871 872 req.write(" ".join(map(hex, b)) + "\n")
872 873
873 874 elif req.form['cmd'][0] == 'changegroup':
874 875 req.httphdr("application/mercurial-0.1")
875 876 nodes = []
876 877 if not self.allowpull:
877 878 return
878 879
879 880 if req.form.has_key('roots'):
880 881 nodes = map(bin, req.form['roots'][0].split(" "))
881 882
882 883 z = zlib.compressobj()
883 884 f = self.repo.changegroup(nodes, 'serve')
884 885 while 1:
885 886 chunk = f.read(4096)
886 887 if not chunk:
887 888 break
888 889 req.write(z.compress(chunk))
889 890
890 891 req.write(z.flush())
891 892
892 893 elif req.form['cmd'][0] == 'archive':
893 894 changeset = self.repo.lookup(req.form['node'][0])
894 895 type = req.form['type'][0]
895 896 if (type in self.archives and
896 897 self.repo.ui.configbool("web", "allow" + type, False)):
897 898 self.archive(req, changeset, type)
898 899 return
899 900
900 901 req.write(self.t("error"))
901 902
902 903 elif req.form['cmd'][0] == 'static':
903 904 fname = req.form['file'][0]
904 905 req.write(staticfile(static, fname)
905 906 or self.t("error", error="%r not found" % fname))
906 907
907 908 else:
908 909 req.write(self.t("error"))
909 910
910 911 def create_server(repo):
911 912
912 913 def openlog(opt, default):
913 914 if opt and opt != '-':
914 915 return open(opt, 'w')
915 916 return default
916 917
917 918 address = repo.ui.config("web", "address", "")
918 919 port = int(repo.ui.config("web", "port", 8000))
919 920 use_ipv6 = repo.ui.configbool("web", "ipv6")
920 921 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
921 922 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
922 923
923 924 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
924 925 address_family = getattr(socket, 'AF_INET6', None)
925 926
926 927 def __init__(self, *args, **kwargs):
927 928 if self.address_family is None:
928 929 raise hg.RepoError(_('IPv6 not available on this system'))
929 930 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
930 931
931 932 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
932 933 def log_error(self, format, *args):
933 934 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
934 935 self.log_date_time_string(),
935 936 format % args))
936 937
937 938 def log_message(self, format, *args):
938 939 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
939 940 self.log_date_time_string(),
940 941 format % args))
941 942
942 943 def do_POST(self):
943 944 try:
944 945 self.do_hgweb()
945 946 except socket.error, inst:
946 947 if inst[0] != errno.EPIPE:
947 948 raise
948 949
949 950 def do_GET(self):
950 951 self.do_POST()
951 952
952 953 def do_hgweb(self):
953 954 query = ""
954 955 p = self.path.find("?")
955 956 if p:
956 957 query = self.path[p + 1:]
957 958 query = query.replace('+', ' ')
958 959
959 960 env = {}
960 961 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
961 962 env['REQUEST_METHOD'] = self.command
962 963 env['SERVER_NAME'] = self.server.server_name
963 964 env['SERVER_PORT'] = str(self.server.server_port)
964 965 env['REQUEST_URI'] = "/"
965 966 if query:
966 967 env['QUERY_STRING'] = query
967 968 host = self.address_string()
968 969 if host != self.client_address[0]:
969 970 env['REMOTE_HOST'] = host
970 971 env['REMOTE_ADDR'] = self.client_address[0]
971 972
972 973 if self.headers.typeheader is None:
973 974 env['CONTENT_TYPE'] = self.headers.type
974 975 else:
975 976 env['CONTENT_TYPE'] = self.headers.typeheader
976 977 length = self.headers.getheader('content-length')
977 978 if length:
978 979 env['CONTENT_LENGTH'] = length
979 980 accept = []
980 981 for line in self.headers.getallmatchingheaders('accept'):
981 982 if line[:1] in "\t\n\r ":
982 983 accept.append(line.strip())
983 984 else:
984 985 accept = accept + line[7:].split(',')
985 986 env['HTTP_ACCEPT'] = ','.join(accept)
986 987
987 988 req = hgrequest(self.rfile, self.wfile, env)
988 989 self.send_response(200, "Script output follows")
989 990 hg.run(req)
990 991
991 992 hg = hgweb(repo)
992 993 if use_ipv6:
993 994 return IPv6HTTPServer((address, port), hgwebhandler)
994 995 else:
995 996 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
996 997
997 998 # This is a stopgap
998 999 class hgwebdir(object):
999 1000 def __init__(self, config):
1000 1001 def cleannames(items):
1001 1002 return [(name.strip(os.sep), path) for name, path in items]
1002 1003
1003 1004 if isinstance(config, (list, tuple)):
1004 1005 self.repos = cleannames(config)
1005 1006 elif isinstance(config, dict):
1006 1007 self.repos = cleannames(config.items())
1007 1008 self.repos.sort()
1008 1009 else:
1009 1010 cp = ConfigParser.SafeConfigParser()
1010 1011 cp.read(config)
1011 1012 self.repos = []
1012 1013 if cp.has_section('paths'):
1013 1014 self.repos.extend(cleannames(cp.items('paths')))
1014 1015 if cp.has_section('collections'):
1015 1016 for prefix, root in cp.items('collections'):
1016 1017 for path in util.walkrepos(root):
1017 1018 repo = os.path.normpath(path)
1018 1019 name = repo
1019 1020 if name.startswith(prefix):
1020 1021 name = name[len(prefix):]
1021 1022 self.repos.append((name.lstrip(os.sep), repo))
1022 1023 self.repos.sort()
1023 1024
1024 1025 def run(self, req=hgrequest()):
1025 1026 def header(**map):
1026 1027 yield tmpl("header", **map)
1027 1028
1028 1029 def footer(**map):
1029 1030 yield tmpl("footer", **map)
1030 1031
1031 1032 m = os.path.join(templater.templatepath(), "map")
1032 1033 tmpl = templater.templater(m, templater.common_filters,
1033 1034 defaults={"header": header,
1034 1035 "footer": footer})
1035 1036
1036 1037 def entries(**map):
1037 1038 parity = 0
1038 1039 for name, path in self.repos:
1039 1040 u = ui.ui()
1040 1041 try:
1041 1042 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1042 1043 except IOError:
1043 1044 pass
1044 1045 get = u.config
1045 1046
1046 1047 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1047 1048 .replace("//", "/"))
1048 1049
1049 1050 # update time with local timezone
1050 1051 try:
1051 1052 d = (get_mtime(path), util.makedate()[1])
1052 1053 except OSError:
1053 1054 continue
1054 1055
1055 1056 yield dict(contact=(get("ui", "username") or # preferred
1056 1057 get("web", "contact") or # deprecated
1057 1058 get("web", "author", "unknown")), # also
1058 1059 name=get("web", "name", name),
1059 1060 url=url,
1060 1061 parity=parity,
1061 1062 shortdesc=get("web", "description", "unknown"),
1062 1063 lastupdate=d)
1063 1064
1064 1065 parity = 1 - parity
1065 1066
1066 1067 virtual = req.env.get("PATH_INFO", "").strip('/')
1067 1068 if virtual:
1068 1069 real = dict(self.repos).get(virtual)
1069 1070 if real:
1070 1071 try:
1071 1072 hgweb(real).run(req)
1072 1073 except IOError, inst:
1073 1074 req.write(tmpl("error", error=inst.strerror))
1074 1075 except hg.RepoError, inst:
1075 1076 req.write(tmpl("error", error=str(inst)))
1076 1077 else:
1077 1078 req.write(tmpl("notfound", repo=virtual))
1078 1079 else:
1079 1080 if req.form.has_key('static'):
1080 1081 static = os.path.join(templater.templatepath(), "static")
1081 1082 fname = req.form['static'][0]
1082 1083 req.write(staticfile(static, fname)
1083 1084 or tmpl("error", error="%r not found" % fname))
1084 1085 else:
1085 1086 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now