comparison mercurial/hgweb/hgweb_mod.py @ 2356:2db831b33e8f

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