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