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