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