##// END OF EJS Templates
Use application/octet-stream as the content-type of unknown binary files
Alexis S. L. Carvalho -
r2103:caccf539 default
parent child Browse files
Show More
@@ -1,1086 +1,1087 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 or 'data')
422 mt = mt or 'application/octet-stream'
423 text = "(binary:%s)" % mt
423 424 mt = mt or 'text/plain'
424 425
425 426 def lines():
426 427 for l, t in enumerate(text.splitlines(1)):
427 428 yield {"line": t,
428 429 "linenumber": "% 6d" % (l + 1),
429 430 "parity": l & 1}
430 431
431 432 yield self.t("filerevision",
432 433 file=f,
433 434 filenode=node,
434 435 path=up(f),
435 436 text=lines(),
436 437 raw=rawtext,
437 438 mimetype=mt,
438 439 rev=changerev,
439 440 node=hex(cn),
440 441 manifest=hex(mfn),
441 442 author=cs[1],
442 443 date=cs[2],
443 444 parent=self.siblings(fl.parents(n), fl.rev, file=f),
444 445 child=self.siblings(fl.children(n), fl.rev, file=f),
445 446 rename=self.renamelink(fl, n),
446 447 permissions=self.repo.manifest.readflags(mfn)[f])
447 448
448 449 def fileannotate(self, f, node):
449 450 bcache = {}
450 451 ncache = {}
451 452 fl = self.repo.file(f)
452 453 n = fl.lookup(node)
453 454 node = hex(n)
454 455 changerev = fl.linkrev(n)
455 456
456 457 cl = self.repo.changelog
457 458 cn = cl.node(changerev)
458 459 cs = cl.read(cn)
459 460 mfn = cs[0]
460 461
461 462 def annotate(**map):
462 463 parity = 1
463 464 last = None
464 465 for r, l in fl.annotate(n):
465 466 try:
466 467 cnode = ncache[r]
467 468 except KeyError:
468 469 cnode = ncache[r] = self.repo.changelog.node(r)
469 470
470 471 try:
471 472 name = bcache[r]
472 473 except KeyError:
473 474 cl = self.repo.changelog.read(cnode)
474 475 bcache[r] = name = self.repo.ui.shortuser(cl[1])
475 476
476 477 if last != cnode:
477 478 parity = 1 - parity
478 479 last = cnode
479 480
480 481 yield {"parity": parity,
481 482 "node": hex(cnode),
482 483 "rev": r,
483 484 "author": name,
484 485 "file": f,
485 486 "line": l}
486 487
487 488 yield self.t("fileannotate",
488 489 file=f,
489 490 filenode=node,
490 491 annotate=annotate,
491 492 path=up(f),
492 493 rev=changerev,
493 494 node=hex(cn),
494 495 manifest=hex(mfn),
495 496 author=cs[1],
496 497 date=cs[2],
497 498 rename=self.renamelink(fl, n),
498 499 parent=self.siblings(fl.parents(n), fl.rev, file=f),
499 500 child=self.siblings(fl.children(n), fl.rev, file=f),
500 501 permissions=self.repo.manifest.readflags(mfn)[f])
501 502
502 503 def manifest(self, mnode, path):
503 504 man = self.repo.manifest
504 505 mn = man.lookup(mnode)
505 506 mnode = hex(mn)
506 507 mf = man.read(mn)
507 508 rev = man.rev(mn)
508 509 node = self.repo.changelog.node(rev)
509 510 mff = man.readflags(mn)
510 511
511 512 files = {}
512 513
513 514 p = path[1:]
514 515 if p and p[-1] != "/":
515 516 p += "/"
516 517 l = len(p)
517 518
518 519 for f,n in mf.items():
519 520 if f[:l] != p:
520 521 continue
521 522 remain = f[l:]
522 523 if "/" in remain:
523 524 short = remain[:remain.find("/") + 1] # bleah
524 525 files[short] = (f, None)
525 526 else:
526 527 short = os.path.basename(remain)
527 528 files[short] = (f, n)
528 529
529 530 def filelist(**map):
530 531 parity = 0
531 532 fl = files.keys()
532 533 fl.sort()
533 534 for f in fl:
534 535 full, fnode = files[f]
535 536 if not fnode:
536 537 continue
537 538
538 539 yield {"file": full,
539 540 "manifest": mnode,
540 541 "filenode": hex(fnode),
541 542 "parity": parity,
542 543 "basename": f,
543 544 "permissions": mff[full]}
544 545 parity = 1 - parity
545 546
546 547 def dirlist(**map):
547 548 parity = 0
548 549 fl = files.keys()
549 550 fl.sort()
550 551 for f in fl:
551 552 full, fnode = files[f]
552 553 if fnode:
553 554 continue
554 555
555 556 yield {"parity": parity,
556 557 "path": os.path.join(path, f),
557 558 "manifest": mnode,
558 559 "basename": f[:-1]}
559 560 parity = 1 - parity
560 561
561 562 yield self.t("manifest",
562 563 manifest=mnode,
563 564 rev=rev,
564 565 node=hex(node),
565 566 path=path,
566 567 up=up(path),
567 568 fentries=filelist,
568 569 dentries=dirlist,
569 570 archives=self.archivelist(hex(node)))
570 571
571 572 def tags(self):
572 573 cl = self.repo.changelog
573 574 mf = cl.read(cl.tip())[0]
574 575
575 576 i = self.repo.tagslist()
576 577 i.reverse()
577 578
578 579 def entries(notip=False, **map):
579 580 parity = 0
580 581 for k,n in i:
581 582 if notip and k == "tip": continue
582 583 yield {"parity": parity,
583 584 "tag": k,
584 585 "tagmanifest": hex(cl.read(n)[0]),
585 586 "date": cl.read(n)[2],
586 587 "node": hex(n)}
587 588 parity = 1 - parity
588 589
589 590 yield self.t("tags",
590 591 manifest=hex(mf),
591 592 entries=lambda **x: entries(False, **x),
592 593 entriesnotip=lambda **x: entries(True, **x))
593 594
594 595 def summary(self):
595 596 cl = self.repo.changelog
596 597 mf = cl.read(cl.tip())[0]
597 598
598 599 i = self.repo.tagslist()
599 600 i.reverse()
600 601
601 602 def tagentries(**map):
602 603 parity = 0
603 604 count = 0
604 605 for k,n in i:
605 606 if k == "tip": # skip tip
606 607 continue;
607 608
608 609 count += 1
609 610 if count > 10: # limit to 10 tags
610 611 break;
611 612
612 613 c = cl.read(n)
613 614 m = c[0]
614 615 t = c[2]
615 616
616 617 yield self.t("tagentry",
617 618 parity = parity,
618 619 tag = k,
619 620 node = hex(n),
620 621 date = t,
621 622 tagmanifest = hex(m))
622 623 parity = 1 - parity
623 624
624 625 def changelist(**map):
625 626 parity = 0
626 627 cl = self.repo.changelog
627 628 l = [] # build a list in forward order for efficiency
628 629 for i in range(start, end):
629 630 n = cl.node(i)
630 631 changes = cl.read(n)
631 632 hn = hex(n)
632 633 t = changes[2]
633 634
634 635 l.insert(0, self.t(
635 636 'shortlogentry',
636 637 parity = parity,
637 638 author = changes[1],
638 639 manifest = hex(changes[0]),
639 640 desc = changes[4],
640 641 date = t,
641 642 rev = i,
642 643 node = hn))
643 644 parity = 1 - parity
644 645
645 646 yield l
646 647
647 648 cl = self.repo.changelog
648 649 mf = cl.read(cl.tip())[0]
649 650 count = cl.count()
650 651 start = max(0, count - self.maxchanges)
651 652 end = min(count, start + self.maxchanges)
652 653 pos = end - 1
653 654
654 655 yield self.t("summary",
655 656 desc = self.repo.ui.config("web", "description", "unknown"),
656 657 owner = (self.repo.ui.config("ui", "username") or # preferred
657 658 self.repo.ui.config("web", "contact") or # deprecated
658 659 self.repo.ui.config("web", "author", "unknown")), # also
659 660 lastchange = (0, 0), # FIXME
660 661 manifest = hex(mf),
661 662 tags = tagentries,
662 663 shortlog = changelist)
663 664
664 665 def filediff(self, file, changeset):
665 666 cl = self.repo.changelog
666 667 n = self.repo.lookup(changeset)
667 668 changeset = hex(n)
668 669 p1 = cl.parents(n)[0]
669 670 cs = cl.read(n)
670 671 mf = self.repo.manifest.read(cs[0])
671 672
672 673 def diff(**map):
673 674 yield self.diff(p1, n, file)
674 675
675 676 yield self.t("filediff",
676 677 file=file,
677 678 filenode=hex(mf.get(file, nullid)),
678 679 node=changeset,
679 680 rev=self.repo.changelog.rev(n),
680 681 parent=self.siblings(cl.parents(n), cl.rev),
681 682 child=self.siblings(cl.children(n), cl.rev),
682 683 diff=diff)
683 684
684 685 def archive(self, req, cnode, type):
685 686 cs = self.repo.changelog.read(cnode)
686 687 mnode = cs[0]
687 688 mf = self.repo.manifest.read(mnode)
688 689 rev = self.repo.manifest.rev(mnode)
689 690 reponame = re.sub(r"\W+", "-", self.reponame)
690 691 name = "%s-%s/" % (reponame, short(cnode))
691 692
692 693 files = mf.keys()
693 694 files.sort()
694 695
695 696 if type == 'zip':
696 697 tmp = tempfile.mkstemp()[1]
697 698 try:
698 699 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
699 700
700 701 for f in files:
701 702 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
702 703 zf.close()
703 704
704 705 f = open(tmp, 'r')
705 706 req.httphdr('application/zip', name[:-1] + '.zip',
706 707 os.path.getsize(tmp))
707 708 req.write(f.read())
708 709 f.close()
709 710 finally:
710 711 os.unlink(tmp)
711 712
712 713 else:
713 714 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
714 715 mff = self.repo.manifest.readflags(mnode)
715 716 mtime = int(time.time())
716 717
717 718 if type == "gz":
718 719 encoding = "gzip"
719 720 else:
720 721 encoding = "x-bzip2"
721 722 req.header([('Content-type', 'application/x-tar'),
722 723 ('Content-disposition', 'attachment; filename=%s%s%s' %
723 724 (name[:-1], '.tar.', type)),
724 725 ('Content-encoding', encoding)])
725 726 for fname in files:
726 727 rcont = self.repo.file(fname).read(mf[fname])
727 728 finfo = tarfile.TarInfo(name + fname)
728 729 finfo.mtime = mtime
729 730 finfo.size = len(rcont)
730 731 finfo.mode = mff[fname] and 0755 or 0644
731 732 tf.addfile(finfo, StringIO.StringIO(rcont))
732 733 tf.close()
733 734
734 735 # add tags to things
735 736 # tags -> list of changesets corresponding to tags
736 737 # find tag, changeset, file
737 738
738 739 def run(self, req=hgrequest()):
739 740 def clean(path):
740 741 p = util.normpath(path)
741 742 if p[:2] == "..":
742 743 raise "suspicious path"
743 744 return p
744 745
745 746 def header(**map):
746 747 yield self.t("header", **map)
747 748
748 749 def footer(**map):
749 750 yield self.t("footer", **map)
750 751
751 752 def expand_form(form):
752 753 shortcuts = {
753 754 'cl': [('cmd', ['changelog']), ('rev', None)],
754 755 'cs': [('cmd', ['changeset']), ('node', None)],
755 756 'f': [('cmd', ['file']), ('filenode', None)],
756 757 'fl': [('cmd', ['filelog']), ('filenode', None)],
757 758 'fd': [('cmd', ['filediff']), ('node', None)],
758 759 'fa': [('cmd', ['annotate']), ('filenode', None)],
759 760 'mf': [('cmd', ['manifest']), ('manifest', None)],
760 761 'ca': [('cmd', ['archive']), ('node', None)],
761 762 'tags': [('cmd', ['tags'])],
762 763 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
763 764 'static': [('cmd', ['static']), ('file', None)]
764 765 }
765 766
766 767 for k in shortcuts.iterkeys():
767 768 if form.has_key(k):
768 769 for name, value in shortcuts[k]:
769 770 if value is None:
770 771 value = form[k]
771 772 form[name] = value
772 773 del form[k]
773 774
774 775 self.refresh()
775 776
776 777 expand_form(req.form)
777 778
778 779 t = self.repo.ui.config("web", "templates", templater.templatepath())
779 780 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
780 781 m = os.path.join(t, "map")
781 782 style = self.repo.ui.config("web", "style", "")
782 783 if req.form.has_key('style'):
783 784 style = req.form['style'][0]
784 785 if style:
785 786 b = os.path.basename("map-" + style)
786 787 p = os.path.join(t, b)
787 788 if os.path.isfile(p):
788 789 m = p
789 790
790 791 port = req.env["SERVER_PORT"]
791 792 port = port != "80" and (":" + port) or ""
792 793 uri = req.env["REQUEST_URI"]
793 794 if "?" in uri:
794 795 uri = uri.split("?")[0]
795 796 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
796 797 if not self.reponame:
797 798 self.reponame = (self.repo.ui.config("web", "name")
798 799 or uri.strip('/') or self.repo.root)
799 800
800 801 self.t = templater.templater(m, templater.common_filters,
801 802 defaults={"url": url,
802 803 "repo": self.reponame,
803 804 "header": header,
804 805 "footer": footer,
805 806 })
806 807
807 808 if not req.form.has_key('cmd'):
808 809 req.form['cmd'] = [self.t.cache['default'],]
809 810
810 811 if req.form['cmd'][0] == 'changelog':
811 812 c = self.repo.changelog.count() - 1
812 813 hi = c
813 814 if req.form.has_key('rev'):
814 815 hi = req.form['rev'][0]
815 816 try:
816 817 hi = self.repo.changelog.rev(self.repo.lookup(hi))
817 818 except hg.RepoError:
818 819 req.write(self.search(hi))
819 820 return
820 821
821 822 req.write(self.changelog(hi))
822 823
823 824 elif req.form['cmd'][0] == 'changeset':
824 825 req.write(self.changeset(req.form['node'][0]))
825 826
826 827 elif req.form['cmd'][0] == 'manifest':
827 828 req.write(self.manifest(req.form['manifest'][0],
828 829 clean(req.form['path'][0])))
829 830
830 831 elif req.form['cmd'][0] == 'tags':
831 832 req.write(self.tags())
832 833
833 834 elif req.form['cmd'][0] == 'summary':
834 835 req.write(self.summary())
835 836
836 837 elif req.form['cmd'][0] == 'filediff':
837 838 req.write(self.filediff(clean(req.form['file'][0]),
838 839 req.form['node'][0]))
839 840
840 841 elif req.form['cmd'][0] == 'file':
841 842 req.write(self.filerevision(clean(req.form['file'][0]),
842 843 req.form['filenode'][0]))
843 844
844 845 elif req.form['cmd'][0] == 'annotate':
845 846 req.write(self.fileannotate(clean(req.form['file'][0]),
846 847 req.form['filenode'][0]))
847 848
848 849 elif req.form['cmd'][0] == 'filelog':
849 850 req.write(self.filelog(clean(req.form['file'][0]),
850 851 req.form['filenode'][0]))
851 852
852 853 elif req.form['cmd'][0] == 'heads':
853 854 req.httphdr("application/mercurial-0.1")
854 855 h = self.repo.heads()
855 856 req.write(" ".join(map(hex, h)) + "\n")
856 857
857 858 elif req.form['cmd'][0] == 'branches':
858 859 req.httphdr("application/mercurial-0.1")
859 860 nodes = []
860 861 if req.form.has_key('nodes'):
861 862 nodes = map(bin, req.form['nodes'][0].split(" "))
862 863 for b in self.repo.branches(nodes):
863 864 req.write(" ".join(map(hex, b)) + "\n")
864 865
865 866 elif req.form['cmd'][0] == 'between':
866 867 req.httphdr("application/mercurial-0.1")
867 868 nodes = []
868 869 if req.form.has_key('pairs'):
869 870 pairs = [map(bin, p.split("-"))
870 871 for p in req.form['pairs'][0].split(" ")]
871 872 for b in self.repo.between(pairs):
872 873 req.write(" ".join(map(hex, b)) + "\n")
873 874
874 875 elif req.form['cmd'][0] == 'changegroup':
875 876 req.httphdr("application/mercurial-0.1")
876 877 nodes = []
877 878 if not self.allowpull:
878 879 return
879 880
880 881 if req.form.has_key('roots'):
881 882 nodes = map(bin, req.form['roots'][0].split(" "))
882 883
883 884 z = zlib.compressobj()
884 885 f = self.repo.changegroup(nodes, 'serve')
885 886 while 1:
886 887 chunk = f.read(4096)
887 888 if not chunk:
888 889 break
889 890 req.write(z.compress(chunk))
890 891
891 892 req.write(z.flush())
892 893
893 894 elif req.form['cmd'][0] == 'archive':
894 895 changeset = self.repo.lookup(req.form['node'][0])
895 896 type = req.form['type'][0]
896 897 if (type in self.archives and
897 898 self.repo.ui.configbool("web", "allow" + type, False)):
898 899 self.archive(req, changeset, type)
899 900 return
900 901
901 902 req.write(self.t("error"))
902 903
903 904 elif req.form['cmd'][0] == 'static':
904 905 fname = req.form['file'][0]
905 906 req.write(staticfile(static, fname)
906 907 or self.t("error", error="%r not found" % fname))
907 908
908 909 else:
909 910 req.write(self.t("error"))
910 911
911 912 def create_server(repo):
912 913
913 914 def openlog(opt, default):
914 915 if opt and opt != '-':
915 916 return open(opt, 'w')
916 917 return default
917 918
918 919 address = repo.ui.config("web", "address", "")
919 920 port = int(repo.ui.config("web", "port", 8000))
920 921 use_ipv6 = repo.ui.configbool("web", "ipv6")
921 922 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
922 923 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
923 924
924 925 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
925 926 address_family = getattr(socket, 'AF_INET6', None)
926 927
927 928 def __init__(self, *args, **kwargs):
928 929 if self.address_family is None:
929 930 raise hg.RepoError(_('IPv6 not available on this system'))
930 931 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
931 932
932 933 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
933 934 def log_error(self, format, *args):
934 935 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
935 936 self.log_date_time_string(),
936 937 format % args))
937 938
938 939 def log_message(self, format, *args):
939 940 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
940 941 self.log_date_time_string(),
941 942 format % args))
942 943
943 944 def do_POST(self):
944 945 try:
945 946 self.do_hgweb()
946 947 except socket.error, inst:
947 948 if inst[0] != errno.EPIPE:
948 949 raise
949 950
950 951 def do_GET(self):
951 952 self.do_POST()
952 953
953 954 def do_hgweb(self):
954 955 query = ""
955 956 p = self.path.find("?")
956 957 if p:
957 958 query = self.path[p + 1:]
958 959 query = query.replace('+', ' ')
959 960
960 961 env = {}
961 962 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
962 963 env['REQUEST_METHOD'] = self.command
963 964 env['SERVER_NAME'] = self.server.server_name
964 965 env['SERVER_PORT'] = str(self.server.server_port)
965 966 env['REQUEST_URI'] = "/"
966 967 if query:
967 968 env['QUERY_STRING'] = query
968 969 host = self.address_string()
969 970 if host != self.client_address[0]:
970 971 env['REMOTE_HOST'] = host
971 972 env['REMOTE_ADDR'] = self.client_address[0]
972 973
973 974 if self.headers.typeheader is None:
974 975 env['CONTENT_TYPE'] = self.headers.type
975 976 else:
976 977 env['CONTENT_TYPE'] = self.headers.typeheader
977 978 length = self.headers.getheader('content-length')
978 979 if length:
979 980 env['CONTENT_LENGTH'] = length
980 981 accept = []
981 982 for line in self.headers.getallmatchingheaders('accept'):
982 983 if line[:1] in "\t\n\r ":
983 984 accept.append(line.strip())
984 985 else:
985 986 accept = accept + line[7:].split(',')
986 987 env['HTTP_ACCEPT'] = ','.join(accept)
987 988
988 989 req = hgrequest(self.rfile, self.wfile, env)
989 990 self.send_response(200, "Script output follows")
990 991 hg.run(req)
991 992
992 993 hg = hgweb(repo)
993 994 if use_ipv6:
994 995 return IPv6HTTPServer((address, port), hgwebhandler)
995 996 else:
996 997 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
997 998
998 999 # This is a stopgap
999 1000 class hgwebdir(object):
1000 1001 def __init__(self, config):
1001 1002 def cleannames(items):
1002 1003 return [(name.strip(os.sep), path) for name, path in items]
1003 1004
1004 1005 if isinstance(config, (list, tuple)):
1005 1006 self.repos = cleannames(config)
1006 1007 elif isinstance(config, dict):
1007 1008 self.repos = cleannames(config.items())
1008 1009 self.repos.sort()
1009 1010 else:
1010 1011 cp = ConfigParser.SafeConfigParser()
1011 1012 cp.read(config)
1012 1013 self.repos = []
1013 1014 if cp.has_section('paths'):
1014 1015 self.repos.extend(cleannames(cp.items('paths')))
1015 1016 if cp.has_section('collections'):
1016 1017 for prefix, root in cp.items('collections'):
1017 1018 for path in util.walkrepos(root):
1018 1019 repo = os.path.normpath(path)
1019 1020 name = repo
1020 1021 if name.startswith(prefix):
1021 1022 name = name[len(prefix):]
1022 1023 self.repos.append((name.lstrip(os.sep), repo))
1023 1024 self.repos.sort()
1024 1025
1025 1026 def run(self, req=hgrequest()):
1026 1027 def header(**map):
1027 1028 yield tmpl("header", **map)
1028 1029
1029 1030 def footer(**map):
1030 1031 yield tmpl("footer", **map)
1031 1032
1032 1033 m = os.path.join(templater.templatepath(), "map")
1033 1034 tmpl = templater.templater(m, templater.common_filters,
1034 1035 defaults={"header": header,
1035 1036 "footer": footer})
1036 1037
1037 1038 def entries(**map):
1038 1039 parity = 0
1039 1040 for name, path in self.repos:
1040 1041 u = ui.ui()
1041 1042 try:
1042 1043 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1043 1044 except IOError:
1044 1045 pass
1045 1046 get = u.config
1046 1047
1047 1048 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1048 1049 .replace("//", "/"))
1049 1050
1050 1051 # update time with local timezone
1051 1052 try:
1052 1053 d = (get_mtime(path), util.makedate()[1])
1053 1054 except OSError:
1054 1055 continue
1055 1056
1056 1057 yield dict(contact=(get("ui", "username") or # preferred
1057 1058 get("web", "contact") or # deprecated
1058 1059 get("web", "author", "unknown")), # also
1059 1060 name=get("web", "name", name),
1060 1061 url=url,
1061 1062 parity=parity,
1062 1063 shortdesc=get("web", "description", "unknown"),
1063 1064 lastupdate=d)
1064 1065
1065 1066 parity = 1 - parity
1066 1067
1067 1068 virtual = req.env.get("PATH_INFO", "").strip('/')
1068 1069 if virtual:
1069 1070 real = dict(self.repos).get(virtual)
1070 1071 if real:
1071 1072 try:
1072 1073 hgweb(real).run(req)
1073 1074 except IOError, inst:
1074 1075 req.write(tmpl("error", error=inst.strerror))
1075 1076 except hg.RepoError, inst:
1076 1077 req.write(tmpl("error", error=str(inst)))
1077 1078 else:
1078 1079 req.write(tmpl("notfound", repo=virtual))
1079 1080 else:
1080 1081 if req.form.has_key('static'):
1081 1082 static = os.path.join(templater.templatepath(), "static")
1082 1083 fname = req.form['static'][0]
1083 1084 req.write(staticfile(static, fname)
1084 1085 or tmpl("error", error="%r not found" % fname))
1085 1086 else:
1086 1087 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now