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