##// END OF EJS Templates
http server: support persistent connections....
Vadim Gelfer -
r2434:a2df85ad default
parent child Browse files
Show More
@@ -1,821 +1,828 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser")
13 demandload(globals(), "re zlib ConfigParser cStringIO")
14 14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
16 16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 17 from mercurial.node import *
18 18 from mercurial.i18n import gettext as _
19 19
20 20 def _up(p):
21 21 if p[0] != "/":
22 22 p = "/" + p
23 23 if p[-1] == "/":
24 24 p = p[:-1]
25 25 up = os.path.dirname(p)
26 26 if up == "/":
27 27 return "/"
28 28 return up + "/"
29 29
30 30 class hgweb(object):
31 31 def __init__(self, repo, name=None):
32 32 if type(repo) == type(""):
33 33 self.repo = hg.repository(ui.ui(), repo)
34 34 else:
35 35 self.repo = repo
36 36
37 37 self.mtime = -1
38 38 self.reponame = name
39 39 self.archives = 'zip', 'gz', 'bz2'
40 40
41 41 def refresh(self):
42 42 mtime = get_mtime(self.repo.root)
43 43 if mtime != self.mtime:
44 44 self.mtime = mtime
45 45 self.repo = hg.repository(self.repo.ui, self.repo.root)
46 46 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
47 47 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
48 48 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
49 49
50 50 def archivelist(self, nodeid):
51 51 allowed = (self.repo.ui.config("web", "allow_archive", "")
52 52 .replace(",", " ").split())
53 53 for i in self.archives:
54 54 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
55 55 yield {"type" : i, "node" : nodeid, "url": ""}
56 56
57 57 def listfiles(self, files, mf):
58 58 for f in files[:self.maxfiles]:
59 59 yield self.t("filenodelink", node=hex(mf[f]), file=f)
60 60 if len(files) > self.maxfiles:
61 61 yield self.t("fileellipses")
62 62
63 63 def listfilediffs(self, files, changeset):
64 64 for f in files[:self.maxfiles]:
65 65 yield self.t("filedifflink", node=hex(changeset), file=f)
66 66 if len(files) > self.maxfiles:
67 67 yield self.t("fileellipses")
68 68
69 69 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
70 70 if not rev:
71 71 rev = lambda x: ""
72 72 siblings = [s for s in siblings if s != nullid]
73 73 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
74 74 return
75 75 for s in siblings:
76 76 yield dict(node=hex(s), rev=rev(s), **args)
77 77
78 78 def renamelink(self, fl, node):
79 79 r = fl.renamed(node)
80 80 if r:
81 81 return [dict(file=r[0], node=hex(r[1]))]
82 82 return []
83 83
84 84 def showtag(self, t1, node=nullid, **args):
85 85 for t in self.repo.nodetags(node):
86 86 yield self.t(t1, tag=t, **args)
87 87
88 88 def diff(self, node1, node2, files):
89 89 def filterfiles(filters, files):
90 90 l = [x for x in files if x in filters]
91 91
92 92 for t in filters:
93 93 if t and t[-1] != os.sep:
94 94 t += os.sep
95 95 l += [x for x in files if x.startswith(t)]
96 96 return l
97 97
98 98 parity = [0]
99 99 def diffblock(diff, f, fn):
100 100 yield self.t("diffblock",
101 101 lines=prettyprintlines(diff),
102 102 parity=parity[0],
103 103 file=f,
104 104 filenode=hex(fn or nullid))
105 105 parity[0] = 1 - parity[0]
106 106
107 107 def prettyprintlines(diff):
108 108 for l in diff.splitlines(1):
109 109 if l.startswith('+'):
110 110 yield self.t("difflineplus", line=l)
111 111 elif l.startswith('-'):
112 112 yield self.t("difflineminus", line=l)
113 113 elif l.startswith('@'):
114 114 yield self.t("difflineat", line=l)
115 115 else:
116 116 yield self.t("diffline", line=l)
117 117
118 118 r = self.repo
119 119 cl = r.changelog
120 120 mf = r.manifest
121 121 change1 = cl.read(node1)
122 122 change2 = cl.read(node2)
123 123 mmap1 = mf.read(change1[0])
124 124 mmap2 = mf.read(change2[0])
125 125 date1 = util.datestr(change1[2])
126 126 date2 = util.datestr(change2[2])
127 127
128 128 modified, added, removed, deleted, unknown = r.changes(node1, node2)
129 129 if files:
130 130 modified, added, removed = map(lambda x: filterfiles(files, x),
131 131 (modified, added, removed))
132 132
133 133 diffopts = self.repo.ui.diffopts()
134 134 showfunc = diffopts['showfunc']
135 135 ignorews = diffopts['ignorews']
136 136 for f in modified:
137 137 to = r.file(f).read(mmap1[f])
138 138 tn = r.file(f).read(mmap2[f])
139 139 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
140 140 showfunc=showfunc, ignorews=ignorews), f, tn)
141 141 for f in added:
142 142 to = None
143 143 tn = r.file(f).read(mmap2[f])
144 144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
145 145 showfunc=showfunc, ignorews=ignorews), f, tn)
146 146 for f in removed:
147 147 to = r.file(f).read(mmap1[f])
148 148 tn = None
149 149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
150 150 showfunc=showfunc, ignorews=ignorews), f, tn)
151 151
152 152 def changelog(self, pos):
153 153 def changenav(**map):
154 154 def seq(factor, maxchanges=None):
155 155 if maxchanges:
156 156 yield maxchanges
157 157 if maxchanges >= 20 and maxchanges <= 40:
158 158 yield 50
159 159 else:
160 160 yield 1 * factor
161 161 yield 3 * factor
162 162 for f in seq(factor * 10):
163 163 yield f
164 164
165 165 l = []
166 166 last = 0
167 167 for f in seq(1, self.maxchanges):
168 168 if f < self.maxchanges or f <= last:
169 169 continue
170 170 if f > count:
171 171 break
172 172 last = f
173 173 r = "%d" % f
174 174 if pos + f < count:
175 175 l.append(("+" + r, pos + f))
176 176 if pos - f >= 0:
177 177 l.insert(0, ("-" + r, pos - f))
178 178
179 179 yield {"rev": 0, "label": "(0)"}
180 180
181 181 for label, rev in l:
182 182 yield {"label": label, "rev": rev}
183 183
184 184 yield {"label": "tip", "rev": "tip"}
185 185
186 186 def changelist(**map):
187 187 parity = (start - end) & 1
188 188 cl = self.repo.changelog
189 189 l = [] # build a list in forward order for efficiency
190 190 for i in range(start, end):
191 191 n = cl.node(i)
192 192 changes = cl.read(n)
193 193 hn = hex(n)
194 194
195 195 l.insert(0, {"parity": parity,
196 196 "author": changes[1],
197 197 "parent": self.siblings(cl.parents(n), cl.rev,
198 198 cl.rev(n) - 1),
199 199 "child": self.siblings(cl.children(n), cl.rev,
200 200 cl.rev(n) + 1),
201 201 "changelogtag": self.showtag("changelogtag",n),
202 202 "manifest": hex(changes[0]),
203 203 "desc": changes[4],
204 204 "date": changes[2],
205 205 "files": self.listfilediffs(changes[3], n),
206 206 "rev": i,
207 207 "node": hn})
208 208 parity = 1 - parity
209 209
210 210 for e in l:
211 211 yield e
212 212
213 213 cl = self.repo.changelog
214 214 mf = cl.read(cl.tip())[0]
215 215 count = cl.count()
216 216 start = max(0, pos - self.maxchanges + 1)
217 217 end = min(count, start + self.maxchanges)
218 218 pos = end - 1
219 219
220 220 yield self.t('changelog',
221 221 changenav=changenav,
222 222 manifest=hex(mf),
223 223 rev=pos, changesets=count, entries=changelist,
224 224 archives=self.archivelist("tip"))
225 225
226 226 def search(self, query):
227 227
228 228 def changelist(**map):
229 229 cl = self.repo.changelog
230 230 count = 0
231 231 qw = query.lower().split()
232 232
233 233 def revgen():
234 234 for i in range(cl.count() - 1, 0, -100):
235 235 l = []
236 236 for j in range(max(0, i - 100), i):
237 237 n = cl.node(j)
238 238 changes = cl.read(n)
239 239 l.append((n, j, changes))
240 240 l.reverse()
241 241 for e in l:
242 242 yield e
243 243
244 244 for n, i, changes in revgen():
245 245 miss = 0
246 246 for q in qw:
247 247 if not (q in changes[1].lower() or
248 248 q in changes[4].lower() or
249 249 q in " ".join(changes[3][:20]).lower()):
250 250 miss = 1
251 251 break
252 252 if miss:
253 253 continue
254 254
255 255 count += 1
256 256 hn = hex(n)
257 257
258 258 yield self.t('searchentry',
259 259 parity=count & 1,
260 260 author=changes[1],
261 261 parent=self.siblings(cl.parents(n), cl.rev),
262 262 child=self.siblings(cl.children(n), cl.rev),
263 263 changelogtag=self.showtag("changelogtag",n),
264 264 manifest=hex(changes[0]),
265 265 desc=changes[4],
266 266 date=changes[2],
267 267 files=self.listfilediffs(changes[3], n),
268 268 rev=i,
269 269 node=hn)
270 270
271 271 if count >= self.maxchanges:
272 272 break
273 273
274 274 cl = self.repo.changelog
275 275 mf = cl.read(cl.tip())[0]
276 276
277 277 yield self.t('search',
278 278 query=query,
279 279 manifest=hex(mf),
280 280 entries=changelist)
281 281
282 282 def changeset(self, nodeid):
283 283 cl = self.repo.changelog
284 284 n = self.repo.lookup(nodeid)
285 285 nodeid = hex(n)
286 286 changes = cl.read(n)
287 287 p1 = cl.parents(n)[0]
288 288
289 289 files = []
290 290 mf = self.repo.manifest.read(changes[0])
291 291 for f in changes[3]:
292 292 files.append(self.t("filenodelink",
293 293 filenode=hex(mf.get(f, nullid)), file=f))
294 294
295 295 def diff(**map):
296 296 yield self.diff(p1, n, None)
297 297
298 298 yield self.t('changeset',
299 299 diff=diff,
300 300 rev=cl.rev(n),
301 301 node=nodeid,
302 302 parent=self.siblings(cl.parents(n), cl.rev),
303 303 child=self.siblings(cl.children(n), cl.rev),
304 304 changesettag=self.showtag("changesettag",n),
305 305 manifest=hex(changes[0]),
306 306 author=changes[1],
307 307 desc=changes[4],
308 308 date=changes[2],
309 309 files=files,
310 310 archives=self.archivelist(nodeid))
311 311
312 312 def filelog(self, f, filenode):
313 313 cl = self.repo.changelog
314 314 fl = self.repo.file(f)
315 315 filenode = hex(fl.lookup(filenode))
316 316 count = fl.count()
317 317
318 318 def entries(**map):
319 319 l = []
320 320 parity = (count - 1) & 1
321 321
322 322 for i in range(count):
323 323 n = fl.node(i)
324 324 lr = fl.linkrev(n)
325 325 cn = cl.node(lr)
326 326 cs = cl.read(cl.node(lr))
327 327
328 328 l.insert(0, {"parity": parity,
329 329 "filenode": hex(n),
330 330 "filerev": i,
331 331 "file": f,
332 332 "node": hex(cn),
333 333 "author": cs[1],
334 334 "date": cs[2],
335 335 "rename": self.renamelink(fl, n),
336 336 "parent": self.siblings(fl.parents(n),
337 337 fl.rev, file=f),
338 338 "child": self.siblings(fl.children(n),
339 339 fl.rev, file=f),
340 340 "desc": cs[4]})
341 341 parity = 1 - parity
342 342
343 343 for e in l:
344 344 yield e
345 345
346 346 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
347 347
348 348 def filerevision(self, f, node):
349 349 fl = self.repo.file(f)
350 350 n = fl.lookup(node)
351 351 node = hex(n)
352 352 text = fl.read(n)
353 353 changerev = fl.linkrev(n)
354 354 cl = self.repo.changelog
355 355 cn = cl.node(changerev)
356 356 cs = cl.read(cn)
357 357 mfn = cs[0]
358 358
359 359 mt = mimetypes.guess_type(f)[0]
360 360 rawtext = text
361 361 if util.binary(text):
362 362 mt = mt or 'application/octet-stream'
363 363 text = "(binary:%s)" % mt
364 364 mt = mt or 'text/plain'
365 365
366 366 def lines():
367 367 for l, t in enumerate(text.splitlines(1)):
368 368 yield {"line": t,
369 369 "linenumber": "% 6d" % (l + 1),
370 370 "parity": l & 1}
371 371
372 372 yield self.t("filerevision",
373 373 file=f,
374 374 filenode=node,
375 375 path=_up(f),
376 376 text=lines(),
377 377 raw=rawtext,
378 378 mimetype=mt,
379 379 rev=changerev,
380 380 node=hex(cn),
381 381 manifest=hex(mfn),
382 382 author=cs[1],
383 383 date=cs[2],
384 384 parent=self.siblings(fl.parents(n), fl.rev, file=f),
385 385 child=self.siblings(fl.children(n), fl.rev, file=f),
386 386 rename=self.renamelink(fl, n),
387 387 permissions=self.repo.manifest.readflags(mfn)[f])
388 388
389 389 def fileannotate(self, f, node):
390 390 bcache = {}
391 391 ncache = {}
392 392 fl = self.repo.file(f)
393 393 n = fl.lookup(node)
394 394 node = hex(n)
395 395 changerev = fl.linkrev(n)
396 396
397 397 cl = self.repo.changelog
398 398 cn = cl.node(changerev)
399 399 cs = cl.read(cn)
400 400 mfn = cs[0]
401 401
402 402 def annotate(**map):
403 403 parity = 1
404 404 last = None
405 405 for r, l in fl.annotate(n):
406 406 try:
407 407 cnode = ncache[r]
408 408 except KeyError:
409 409 cnode = ncache[r] = self.repo.changelog.node(r)
410 410
411 411 try:
412 412 name = bcache[r]
413 413 except KeyError:
414 414 cl = self.repo.changelog.read(cnode)
415 415 bcache[r] = name = self.repo.ui.shortuser(cl[1])
416 416
417 417 if last != cnode:
418 418 parity = 1 - parity
419 419 last = cnode
420 420
421 421 yield {"parity": parity,
422 422 "node": hex(cnode),
423 423 "rev": r,
424 424 "author": name,
425 425 "file": f,
426 426 "line": l}
427 427
428 428 yield self.t("fileannotate",
429 429 file=f,
430 430 filenode=node,
431 431 annotate=annotate,
432 432 path=_up(f),
433 433 rev=changerev,
434 434 node=hex(cn),
435 435 manifest=hex(mfn),
436 436 author=cs[1],
437 437 date=cs[2],
438 438 rename=self.renamelink(fl, n),
439 439 parent=self.siblings(fl.parents(n), fl.rev, file=f),
440 440 child=self.siblings(fl.children(n), fl.rev, file=f),
441 441 permissions=self.repo.manifest.readflags(mfn)[f])
442 442
443 443 def manifest(self, mnode, path):
444 444 man = self.repo.manifest
445 445 mn = man.lookup(mnode)
446 446 mnode = hex(mn)
447 447 mf = man.read(mn)
448 448 rev = man.rev(mn)
449 449 changerev = man.linkrev(mn)
450 450 node = self.repo.changelog.node(changerev)
451 451 mff = man.readflags(mn)
452 452
453 453 files = {}
454 454
455 455 p = path[1:]
456 456 if p and p[-1] != "/":
457 457 p += "/"
458 458 l = len(p)
459 459
460 460 for f,n in mf.items():
461 461 if f[:l] != p:
462 462 continue
463 463 remain = f[l:]
464 464 if "/" in remain:
465 465 short = remain[:remain.find("/") + 1] # bleah
466 466 files[short] = (f, None)
467 467 else:
468 468 short = os.path.basename(remain)
469 469 files[short] = (f, n)
470 470
471 471 def filelist(**map):
472 472 parity = 0
473 473 fl = files.keys()
474 474 fl.sort()
475 475 for f in fl:
476 476 full, fnode = files[f]
477 477 if not fnode:
478 478 continue
479 479
480 480 yield {"file": full,
481 481 "manifest": mnode,
482 482 "filenode": hex(fnode),
483 483 "parity": parity,
484 484 "basename": f,
485 485 "permissions": mff[full]}
486 486 parity = 1 - parity
487 487
488 488 def dirlist(**map):
489 489 parity = 0
490 490 fl = files.keys()
491 491 fl.sort()
492 492 for f in fl:
493 493 full, fnode = files[f]
494 494 if fnode:
495 495 continue
496 496
497 497 yield {"parity": parity,
498 498 "path": os.path.join(path, f),
499 499 "manifest": mnode,
500 500 "basename": f[:-1]}
501 501 parity = 1 - parity
502 502
503 503 yield self.t("manifest",
504 504 manifest=mnode,
505 505 rev=rev,
506 506 node=hex(node),
507 507 path=path,
508 508 up=_up(path),
509 509 fentries=filelist,
510 510 dentries=dirlist,
511 511 archives=self.archivelist(hex(node)))
512 512
513 513 def tags(self):
514 514 cl = self.repo.changelog
515 515 mf = cl.read(cl.tip())[0]
516 516
517 517 i = self.repo.tagslist()
518 518 i.reverse()
519 519
520 520 def entries(notip=False, **map):
521 521 parity = 0
522 522 for k,n in i:
523 523 if notip and k == "tip": continue
524 524 yield {"parity": parity,
525 525 "tag": k,
526 526 "tagmanifest": hex(cl.read(n)[0]),
527 527 "date": cl.read(n)[2],
528 528 "node": hex(n)}
529 529 parity = 1 - parity
530 530
531 531 yield self.t("tags",
532 532 manifest=hex(mf),
533 533 entries=lambda **x: entries(False, **x),
534 534 entriesnotip=lambda **x: entries(True, **x))
535 535
536 536 def summary(self):
537 537 cl = self.repo.changelog
538 538 mf = cl.read(cl.tip())[0]
539 539
540 540 i = self.repo.tagslist()
541 541 i.reverse()
542 542
543 543 def tagentries(**map):
544 544 parity = 0
545 545 count = 0
546 546 for k,n in i:
547 547 if k == "tip": # skip tip
548 548 continue;
549 549
550 550 count += 1
551 551 if count > 10: # limit to 10 tags
552 552 break;
553 553
554 554 c = cl.read(n)
555 555 m = c[0]
556 556 t = c[2]
557 557
558 558 yield self.t("tagentry",
559 559 parity = parity,
560 560 tag = k,
561 561 node = hex(n),
562 562 date = t,
563 563 tagmanifest = hex(m))
564 564 parity = 1 - parity
565 565
566 566 def changelist(**map):
567 567 parity = 0
568 568 cl = self.repo.changelog
569 569 l = [] # build a list in forward order for efficiency
570 570 for i in range(start, end):
571 571 n = cl.node(i)
572 572 changes = cl.read(n)
573 573 hn = hex(n)
574 574 t = changes[2]
575 575
576 576 l.insert(0, self.t(
577 577 'shortlogentry',
578 578 parity = parity,
579 579 author = changes[1],
580 580 manifest = hex(changes[0]),
581 581 desc = changes[4],
582 582 date = t,
583 583 rev = i,
584 584 node = hn))
585 585 parity = 1 - parity
586 586
587 587 yield l
588 588
589 589 cl = self.repo.changelog
590 590 mf = cl.read(cl.tip())[0]
591 591 count = cl.count()
592 592 start = max(0, count - self.maxchanges)
593 593 end = min(count, start + self.maxchanges)
594 594
595 595 yield self.t("summary",
596 596 desc = self.repo.ui.config("web", "description", "unknown"),
597 597 owner = (self.repo.ui.config("ui", "username") or # preferred
598 598 self.repo.ui.config("web", "contact") or # deprecated
599 599 self.repo.ui.config("web", "author", "unknown")), # also
600 600 lastchange = (0, 0), # FIXME
601 601 manifest = hex(mf),
602 602 tags = tagentries,
603 603 shortlog = changelist)
604 604
605 605 def filediff(self, file, changeset):
606 606 cl = self.repo.changelog
607 607 n = self.repo.lookup(changeset)
608 608 changeset = hex(n)
609 609 p1 = cl.parents(n)[0]
610 610 cs = cl.read(n)
611 611 mf = self.repo.manifest.read(cs[0])
612 612
613 613 def diff(**map):
614 614 yield self.diff(p1, n, [file])
615 615
616 616 yield self.t("filediff",
617 617 file=file,
618 618 filenode=hex(mf.get(file, nullid)),
619 619 node=changeset,
620 620 rev=self.repo.changelog.rev(n),
621 621 parent=self.siblings(cl.parents(n), cl.rev),
622 622 child=self.siblings(cl.children(n), cl.rev),
623 623 diff=diff)
624 624
625 625 archive_specs = {
626 626 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
627 627 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
628 628 'zip': ('application/zip', 'zip', '.zip', None),
629 629 }
630 630
631 631 def archive(self, req, cnode, type_):
632 632 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
633 633 name = "%s-%s" % (reponame, short(cnode))
634 634 mimetype, artype, extension, encoding = self.archive_specs[type_]
635 635 headers = [('Content-type', mimetype),
636 636 ('Content-disposition', 'attachment; filename=%s%s' %
637 637 (name, extension))]
638 638 if encoding:
639 639 headers.append(('Content-encoding', encoding))
640 640 req.header(headers)
641 641 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
642 642
643 643 # add tags to things
644 644 # tags -> list of changesets corresponding to tags
645 645 # find tag, changeset, file
646 646
647 647 def run(self, req=hgrequest()):
648 648 def clean(path):
649 649 p = util.normpath(path)
650 650 if p[:2] == "..":
651 651 raise Exception("suspicious path")
652 652 return p
653 653
654 654 def header(**map):
655 655 yield self.t("header", **map)
656 656
657 657 def footer(**map):
658 658 yield self.t("footer",
659 659 motd=self.repo.ui.config("web", "motd", ""),
660 660 **map)
661 661
662 662 def expand_form(form):
663 663 shortcuts = {
664 664 'cl': [('cmd', ['changelog']), ('rev', None)],
665 665 'cs': [('cmd', ['changeset']), ('node', None)],
666 666 'f': [('cmd', ['file']), ('filenode', None)],
667 667 'fl': [('cmd', ['filelog']), ('filenode', None)],
668 668 'fd': [('cmd', ['filediff']), ('node', None)],
669 669 'fa': [('cmd', ['annotate']), ('filenode', None)],
670 670 'mf': [('cmd', ['manifest']), ('manifest', None)],
671 671 'ca': [('cmd', ['archive']), ('node', None)],
672 672 'tags': [('cmd', ['tags'])],
673 673 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
674 674 'static': [('cmd', ['static']), ('file', None)]
675 675 }
676 676
677 677 for k in shortcuts.iterkeys():
678 678 if form.has_key(k):
679 679 for name, value in shortcuts[k]:
680 680 if value is None:
681 681 value = form[k]
682 682 form[name] = value
683 683 del form[k]
684 684
685 685 self.refresh()
686 686
687 687 expand_form(req.form)
688 688
689 689 t = self.repo.ui.config("web", "templates", templater.templatepath())
690 690 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
691 691 m = os.path.join(t, "map")
692 692 style = self.repo.ui.config("web", "style", "")
693 693 if req.form.has_key('style'):
694 694 style = req.form['style'][0]
695 695 if style:
696 696 b = os.path.basename("map-" + style)
697 697 p = os.path.join(t, b)
698 698 if os.path.isfile(p):
699 699 m = p
700 700
701 701 port = req.env["SERVER_PORT"]
702 702 port = port != "80" and (":" + port) or ""
703 703 uri = req.env["REQUEST_URI"]
704 704 if "?" in uri:
705 705 uri = uri.split("?")[0]
706 706 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
707 707 if not self.reponame:
708 708 self.reponame = (self.repo.ui.config("web", "name")
709 709 or uri.strip('/') or self.repo.root)
710 710
711 711 self.t = templater.templater(m, templater.common_filters,
712 712 defaults={"url": url,
713 713 "repo": self.reponame,
714 714 "header": header,
715 715 "footer": footer,
716 716 })
717 717
718 718 if not req.form.has_key('cmd'):
719 719 req.form['cmd'] = [self.t.cache['default'],]
720 720
721 721 cmd = req.form['cmd'][0]
722 722 if cmd == 'changelog':
723 723 hi = self.repo.changelog.count() - 1
724 724 if req.form.has_key('rev'):
725 725 hi = req.form['rev'][0]
726 726 try:
727 727 hi = self.repo.changelog.rev(self.repo.lookup(hi))
728 728 except hg.RepoError:
729 729 req.write(self.search(hi)) # XXX redirect to 404 page?
730 730 return
731 731
732 732 req.write(self.changelog(hi))
733 733
734 734 elif cmd == 'changeset':
735 735 req.write(self.changeset(req.form['node'][0]))
736 736
737 737 elif cmd == 'manifest':
738 738 req.write(self.manifest(req.form['manifest'][0],
739 739 clean(req.form['path'][0])))
740 740
741 741 elif cmd == 'tags':
742 742 req.write(self.tags())
743 743
744 744 elif cmd == 'summary':
745 745 req.write(self.summary())
746 746
747 747 elif cmd == 'filediff':
748 748 req.write(self.filediff(clean(req.form['file'][0]),
749 749 req.form['node'][0]))
750 750
751 751 elif cmd == 'file':
752 752 req.write(self.filerevision(clean(req.form['file'][0]),
753 753 req.form['filenode'][0]))
754 754
755 755 elif cmd == 'annotate':
756 756 req.write(self.fileannotate(clean(req.form['file'][0]),
757 757 req.form['filenode'][0]))
758 758
759 759 elif cmd == 'filelog':
760 760 req.write(self.filelog(clean(req.form['file'][0]),
761 761 req.form['filenode'][0]))
762 762
763 763 elif cmd == 'heads':
764 req.httphdr("application/mercurial-0.1")
765 h = self.repo.heads()
766 req.write(" ".join(map(hex, h)) + "\n")
764 resp = " ".join(map(hex, self.repo.heads())) + "\n"
765 req.httphdr("application/mercurial-0.1", length=len(resp))
766 req.write(resp)
767 767
768 768 elif cmd == 'branches':
769 req.httphdr("application/mercurial-0.1")
770 769 nodes = []
771 770 if req.form.has_key('nodes'):
772 771 nodes = map(bin, req.form['nodes'][0].split(" "))
772 resp = cStringIO.StringIO()
773 773 for b in self.repo.branches(nodes):
774 req.write(" ".join(map(hex, b)) + "\n")
774 resp.write(" ".join(map(hex, b)) + "\n")
775 resp = resp.getvalue()
776 req.httphdr("application/mercurial-0.1", length=len(resp))
777 req.write(resp)
775 778
776 779 elif cmd == 'between':
777 req.httphdr("application/mercurial-0.1")
778 780 nodes = []
779 781 if req.form.has_key('pairs'):
780 782 pairs = [map(bin, p.split("-"))
781 783 for p in req.form['pairs'][0].split(" ")]
784 resp = cStringIO.StringIO()
782 785 for b in self.repo.between(pairs):
783 req.write(" ".join(map(hex, b)) + "\n")
786 resp.write(" ".join(map(hex, b)) + "\n")
787 resp = resp.getvalue()
788 req.httphdr("application/mercurial-0.1", length=len(resp))
789 req.write(resp)
784 790
785 791 elif cmd == 'changegroup':
786 792 req.httphdr("application/mercurial-0.1")
787 793 nodes = []
788 794 if not self.allowpull:
789 795 return
790 796
791 797 if req.form.has_key('roots'):
792 798 nodes = map(bin, req.form['roots'][0].split(" "))
793 799
794 800 z = zlib.compressobj()
795 801 f = self.repo.changegroup(nodes, 'serve')
796 802 while 1:
797 803 chunk = f.read(4096)
798 804 if not chunk:
799 805 break
800 806 req.write(z.compress(chunk))
801 807
802 808 req.write(z.flush())
803 809
804 810 elif cmd == 'archive':
805 811 changeset = self.repo.lookup(req.form['node'][0])
806 812 type_ = req.form['type'][0]
807 813 allowed = self.repo.ui.config("web", "allow_archive", "").split()
808 814 if (type_ in self.archives and (type_ in allowed or
809 815 self.repo.ui.configbool("web", "allow" + type_, False))):
810 816 self.archive(req, changeset, type_)
811 817 return
812 818
813 819 req.write(self.t("error"))
814 820
815 821 elif cmd == 'static':
816 822 fname = req.form['file'][0]
817 823 req.write(staticfile(static, fname)
818 824 or self.t("error", error="%r not found" % fname))
819 825
820 826 else:
821 827 req.write(self.t("error"))
828 req.done()
@@ -1,44 +1,59 b''
1 1 # hgweb/request.py - An http request from either CGI or the standalone server.
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 from mercurial.demandload import demandload
10 10 demandload(globals(), "socket sys cgi os errno")
11 11 from mercurial.i18n import gettext as _
12 12
13 13 class hgrequest(object):
14 14 def __init__(self, inp=None, out=None, env=None):
15 15 self.inp = inp or sys.stdin
16 16 self.out = out or sys.stdout
17 17 self.env = env or os.environ
18 18 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
19 self.will_close = True
19 20
20 21 def write(self, *things):
21 22 for thing in things:
22 23 if hasattr(thing, "__iter__"):
23 24 for part in thing:
24 25 self.write(part)
25 26 else:
26 27 try:
27 28 self.out.write(str(thing))
28 29 except socket.error, inst:
29 30 if inst[0] != errno.ECONNRESET:
30 31 raise
31 32
33 def done(self):
34 if self.will_close:
35 self.inp.close()
36 self.out.close()
37 else:
38 self.out.flush()
39
32 40 def header(self, headers=[('Content-type','text/html')]):
33 41 for header in headers:
34 42 self.out.write("%s: %s\r\n" % header)
35 43 self.out.write("\r\n")
36 44
37 def httphdr(self, type, file="", size=0):
45 def httphdr(self, type, filename=None, length=0):
38 46
39 47 headers = [('Content-type', type)]
40 if file:
41 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
42 if size > 0:
43 headers.append(('Content-length', str(size)))
48 if filename:
49 headers.append(('Content-disposition', 'attachment; filename=%s' %
50 filename))
51 # we do not yet support http 1.1 chunked transfer, so we have
52 # to force connection to close if content-length not known
53 if length:
54 headers.append(('Content-length', str(length)))
55 self.will_close = False
56 else:
57 headers.append(('Connection', 'close'))
58 self.will_close = True
44 59 self.header(headers)
@@ -1,150 +1,152 b''
1 1 # hgweb/server.py - The standalone hg web server.
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 from mercurial.demandload import demandload
10 10 import os, sys, errno
11 11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer")
12 12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:hgrequest")
14 14 from mercurial.i18n import gettext as _
15 15
16 16 def _splitURI(uri):
17 17 """ Return path and query splited from uri
18 18
19 19 Just like CGI environment, the path is unquoted, the query is
20 20 not.
21 21 """
22 22 if '?' in uri:
23 23 path, query = uri.split('?', 1)
24 24 else:
25 25 path, query = uri, ''
26 26 return urllib.unquote(path), query
27 27
28 28 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
29 29 def __init__(self, *args, **kargs):
30 self.protocol_version = 'HTTP/1.1'
30 31 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
31 32
32 33 def log_error(self, format, *args):
33 34 errorlog = self.server.errorlog
34 35 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
35 36 self.log_date_time_string(),
36 37 format % args))
37 38
38 39 def log_message(self, format, *args):
39 40 accesslog = self.server.accesslog
40 41 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
41 42 self.log_date_time_string(),
42 43 format % args))
43 44
44 45 def do_POST(self):
45 46 try:
46 47 self.do_hgweb()
47 48 except socket.error, inst:
48 49 if inst[0] != errno.EPIPE:
49 50 raise
50 51
51 52 def do_GET(self):
52 53 self.do_POST()
53 54
54 55 def do_hgweb(self):
55 56 path_info, query = _splitURI(self.path)
56 57
57 58 env = {}
58 59 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
59 60 env['REQUEST_METHOD'] = self.command
60 61 env['SERVER_NAME'] = self.server.server_name
61 62 env['SERVER_PORT'] = str(self.server.server_port)
62 63 env['REQUEST_URI'] = "/"
63 64 env['PATH_INFO'] = path_info
64 65 if query:
65 66 env['QUERY_STRING'] = query
66 67 host = self.address_string()
67 68 if host != self.client_address[0]:
68 69 env['REMOTE_HOST'] = host
69 70 env['REMOTE_ADDR'] = self.client_address[0]
70 71
71 72 if self.headers.typeheader is None:
72 73 env['CONTENT_TYPE'] = self.headers.type
73 74 else:
74 75 env['CONTENT_TYPE'] = self.headers.typeheader
75 76 length = self.headers.getheader('content-length')
76 77 if length:
77 78 env['CONTENT_LENGTH'] = length
78 79 accept = []
79 80 for line in self.headers.getallmatchingheaders('accept'):
80 81 if line[:1] in "\t\n\r ":
81 82 accept.append(line.strip())
82 83 else:
83 84 accept = accept + line[7:].split(',')
84 85 env['HTTP_ACCEPT'] = ','.join(accept)
85 86
86 87 req = hgrequest(self.rfile, self.wfile, env)
87 88 self.send_response(200, "Script output follows")
88 self.server.make_and_run_handler(req)
89 self.close_connection = self.server.make_and_run_handler(req)
89 90
90 91 def create_server(ui, repo):
91 92 use_threads = True
92 93
93 94 def openlog(opt, default):
94 95 if opt and opt != '-':
95 96 return open(opt, 'w')
96 97 return default
97 98
98 99 address = ui.config("web", "address", "")
99 100 port = int(ui.config("web", "port", 8000))
100 101 use_ipv6 = ui.configbool("web", "ipv6")
101 102 webdir_conf = ui.config("web", "webdir_conf")
102 103 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
103 104 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
104 105
105 106 if use_threads:
106 107 try:
107 108 from threading import activeCount
108 109 except ImportError:
109 110 use_threads = False
110 111
111 112 if use_threads:
112 113 _mixin = SocketServer.ThreadingMixIn
113 114 else:
114 115 if hasattr(os, "fork"):
115 116 _mixin = SocketServer.ForkingMixIn
116 117 else:
117 118 class _mixin: pass
118 119
119 120 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
120 121 def __init__(self, *args, **kargs):
121 122 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
122 123 self.accesslog = accesslog
123 124 self.errorlog = errorlog
124 125 self.repo = repo
125 126 self.webdir_conf = webdir_conf
126 127 self.webdirmaker = hgwebdir
127 128 self.repoviewmaker = hgweb
128 129
129 130 def make_and_run_handler(self, req):
130 131 if self.webdir_conf:
131 132 hgwebobj = self.webdirmaker(self.webdir_conf)
132 133 elif self.repo is not None:
133 134 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
134 135 repo.origroot))
135 136 else:
136 137 raise hg.RepoError(_('no repo found'))
137 138 hgwebobj.run(req)
139 return req.will_close
138 140
139 141 class IPv6HTTPServer(MercurialHTTPServer):
140 142 address_family = getattr(socket, 'AF_INET6', None)
141 143
142 144 def __init__(self, *args, **kwargs):
143 145 if self.address_family is None:
144 146 raise hg.RepoError(_('IPv6 not available on this system'))
145 147 super(IPv6HTTPServer, self).__init__(*args, **kargs)
146 148
147 149 if use_ipv6:
148 150 return IPv6HTTPServer((address, port), _hgwebhandler)
149 151 else:
150 152 return MercurialHTTPServer((address, port), _hgwebhandler)
General Comments 0
You need to be logged in to leave comments. Login now