##// END OF EJS Templates
Use application/octet-stream as the content-type of unknown binary files
Alexis S. L. Carvalho -
r2103:caccf539 default
parent child Browse files
Show More
@@ -1,1086 +1,1087 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 or 'data')
422 mt = mt or 'application/octet-stream'
423 text = "(binary:%s)" % mt
423 mt = mt or 'text/plain'
424 mt = mt or 'text/plain'
424
425
425 def lines():
426 def lines():
426 for l, t in enumerate(text.splitlines(1)):
427 for l, t in enumerate(text.splitlines(1)):
427 yield {"line": t,
428 yield {"line": t,
428 "linenumber": "% 6d" % (l + 1),
429 "linenumber": "% 6d" % (l + 1),
429 "parity": l & 1}
430 "parity": l & 1}
430
431
431 yield self.t("filerevision",
432 yield self.t("filerevision",
432 file=f,
433 file=f,
433 filenode=node,
434 filenode=node,
434 path=up(f),
435 path=up(f),
435 text=lines(),
436 text=lines(),
436 raw=rawtext,
437 raw=rawtext,
437 mimetype=mt,
438 mimetype=mt,
438 rev=changerev,
439 rev=changerev,
439 node=hex(cn),
440 node=hex(cn),
440 manifest=hex(mfn),
441 manifest=hex(mfn),
441 author=cs[1],
442 author=cs[1],
442 date=cs[2],
443 date=cs[2],
443 parent=self.siblings(fl.parents(n), fl.rev, file=f),
444 parent=self.siblings(fl.parents(n), fl.rev, file=f),
444 child=self.siblings(fl.children(n), fl.rev, file=f),
445 child=self.siblings(fl.children(n), fl.rev, file=f),
445 rename=self.renamelink(fl, n),
446 rename=self.renamelink(fl, n),
446 permissions=self.repo.manifest.readflags(mfn)[f])
447 permissions=self.repo.manifest.readflags(mfn)[f])
447
448
448 def fileannotate(self, f, node):
449 def fileannotate(self, f, node):
449 bcache = {}
450 bcache = {}
450 ncache = {}
451 ncache = {}
451 fl = self.repo.file(f)
452 fl = self.repo.file(f)
452 n = fl.lookup(node)
453 n = fl.lookup(node)
453 node = hex(n)
454 node = hex(n)
454 changerev = fl.linkrev(n)
455 changerev = fl.linkrev(n)
455
456
456 cl = self.repo.changelog
457 cl = self.repo.changelog
457 cn = cl.node(changerev)
458 cn = cl.node(changerev)
458 cs = cl.read(cn)
459 cs = cl.read(cn)
459 mfn = cs[0]
460 mfn = cs[0]
460
461
461 def annotate(**map):
462 def annotate(**map):
462 parity = 1
463 parity = 1
463 last = None
464 last = None
464 for r, l in fl.annotate(n):
465 for r, l in fl.annotate(n):
465 try:
466 try:
466 cnode = ncache[r]
467 cnode = ncache[r]
467 except KeyError:
468 except KeyError:
468 cnode = ncache[r] = self.repo.changelog.node(r)
469 cnode = ncache[r] = self.repo.changelog.node(r)
469
470
470 try:
471 try:
471 name = bcache[r]
472 name = bcache[r]
472 except KeyError:
473 except KeyError:
473 cl = self.repo.changelog.read(cnode)
474 cl = self.repo.changelog.read(cnode)
474 bcache[r] = name = self.repo.ui.shortuser(cl[1])
475 bcache[r] = name = self.repo.ui.shortuser(cl[1])
475
476
476 if last != cnode:
477 if last != cnode:
477 parity = 1 - parity
478 parity = 1 - parity
478 last = cnode
479 last = cnode
479
480
480 yield {"parity": parity,
481 yield {"parity": parity,
481 "node": hex(cnode),
482 "node": hex(cnode),
482 "rev": r,
483 "rev": r,
483 "author": name,
484 "author": name,
484 "file": f,
485 "file": f,
485 "line": l}
486 "line": l}
486
487
487 yield self.t("fileannotate",
488 yield self.t("fileannotate",
488 file=f,
489 file=f,
489 filenode=node,
490 filenode=node,
490 annotate=annotate,
491 annotate=annotate,
491 path=up(f),
492 path=up(f),
492 rev=changerev,
493 rev=changerev,
493 node=hex(cn),
494 node=hex(cn),
494 manifest=hex(mfn),
495 manifest=hex(mfn),
495 author=cs[1],
496 author=cs[1],
496 date=cs[2],
497 date=cs[2],
497 rename=self.renamelink(fl, n),
498 rename=self.renamelink(fl, n),
498 parent=self.siblings(fl.parents(n), fl.rev, file=f),
499 parent=self.siblings(fl.parents(n), fl.rev, file=f),
499 child=self.siblings(fl.children(n), fl.rev, file=f),
500 child=self.siblings(fl.children(n), fl.rev, file=f),
500 permissions=self.repo.manifest.readflags(mfn)[f])
501 permissions=self.repo.manifest.readflags(mfn)[f])
501
502
502 def manifest(self, mnode, path):
503 def manifest(self, mnode, path):
503 man = self.repo.manifest
504 man = self.repo.manifest
504 mn = man.lookup(mnode)
505 mn = man.lookup(mnode)
505 mnode = hex(mn)
506 mnode = hex(mn)
506 mf = man.read(mn)
507 mf = man.read(mn)
507 rev = man.rev(mn)
508 rev = man.rev(mn)
508 node = self.repo.changelog.node(rev)
509 node = self.repo.changelog.node(rev)
509 mff = man.readflags(mn)
510 mff = man.readflags(mn)
510
511
511 files = {}
512 files = {}
512
513
513 p = path[1:]
514 p = path[1:]
514 if p and p[-1] != "/":
515 if p and p[-1] != "/":
515 p += "/"
516 p += "/"
516 l = len(p)
517 l = len(p)
517
518
518 for f,n in mf.items():
519 for f,n in mf.items():
519 if f[:l] != p:
520 if f[:l] != p:
520 continue
521 continue
521 remain = f[l:]
522 remain = f[l:]
522 if "/" in remain:
523 if "/" in remain:
523 short = remain[:remain.find("/") + 1] # bleah
524 short = remain[:remain.find("/") + 1] # bleah
524 files[short] = (f, None)
525 files[short] = (f, None)
525 else:
526 else:
526 short = os.path.basename(remain)
527 short = os.path.basename(remain)
527 files[short] = (f, n)
528 files[short] = (f, n)
528
529
529 def filelist(**map):
530 def filelist(**map):
530 parity = 0
531 parity = 0
531 fl = files.keys()
532 fl = files.keys()
532 fl.sort()
533 fl.sort()
533 for f in fl:
534 for f in fl:
534 full, fnode = files[f]
535 full, fnode = files[f]
535 if not fnode:
536 if not fnode:
536 continue
537 continue
537
538
538 yield {"file": full,
539 yield {"file": full,
539 "manifest": mnode,
540 "manifest": mnode,
540 "filenode": hex(fnode),
541 "filenode": hex(fnode),
541 "parity": parity,
542 "parity": parity,
542 "basename": f,
543 "basename": f,
543 "permissions": mff[full]}
544 "permissions": mff[full]}
544 parity = 1 - parity
545 parity = 1 - parity
545
546
546 def dirlist(**map):
547 def dirlist(**map):
547 parity = 0
548 parity = 0
548 fl = files.keys()
549 fl = files.keys()
549 fl.sort()
550 fl.sort()
550 for f in fl:
551 for f in fl:
551 full, fnode = files[f]
552 full, fnode = files[f]
552 if fnode:
553 if fnode:
553 continue
554 continue
554
555
555 yield {"parity": parity,
556 yield {"parity": parity,
556 "path": os.path.join(path, f),
557 "path": os.path.join(path, f),
557 "manifest": mnode,
558 "manifest": mnode,
558 "basename": f[:-1]}
559 "basename": f[:-1]}
559 parity = 1 - parity
560 parity = 1 - parity
560
561
561 yield self.t("manifest",
562 yield self.t("manifest",
562 manifest=mnode,
563 manifest=mnode,
563 rev=rev,
564 rev=rev,
564 node=hex(node),
565 node=hex(node),
565 path=path,
566 path=path,
566 up=up(path),
567 up=up(path),
567 fentries=filelist,
568 fentries=filelist,
568 dentries=dirlist,
569 dentries=dirlist,
569 archives=self.archivelist(hex(node)))
570 archives=self.archivelist(hex(node)))
570
571
571 def tags(self):
572 def tags(self):
572 cl = self.repo.changelog
573 cl = self.repo.changelog
573 mf = cl.read(cl.tip())[0]
574 mf = cl.read(cl.tip())[0]
574
575
575 i = self.repo.tagslist()
576 i = self.repo.tagslist()
576 i.reverse()
577 i.reverse()
577
578
578 def entries(notip=False, **map):
579 def entries(notip=False, **map):
579 parity = 0
580 parity = 0
580 for k,n in i:
581 for k,n in i:
581 if notip and k == "tip": continue
582 if notip and k == "tip": continue
582 yield {"parity": parity,
583 yield {"parity": parity,
583 "tag": k,
584 "tag": k,
584 "tagmanifest": hex(cl.read(n)[0]),
585 "tagmanifest": hex(cl.read(n)[0]),
585 "date": cl.read(n)[2],
586 "date": cl.read(n)[2],
586 "node": hex(n)}
587 "node": hex(n)}
587 parity = 1 - parity
588 parity = 1 - parity
588
589
589 yield self.t("tags",
590 yield self.t("tags",
590 manifest=hex(mf),
591 manifest=hex(mf),
591 entries=lambda **x: entries(False, **x),
592 entries=lambda **x: entries(False, **x),
592 entriesnotip=lambda **x: entries(True, **x))
593 entriesnotip=lambda **x: entries(True, **x))
593
594
594 def summary(self):
595 def summary(self):
595 cl = self.repo.changelog
596 cl = self.repo.changelog
596 mf = cl.read(cl.tip())[0]
597 mf = cl.read(cl.tip())[0]
597
598
598 i = self.repo.tagslist()
599 i = self.repo.tagslist()
599 i.reverse()
600 i.reverse()
600
601
601 def tagentries(**map):
602 def tagentries(**map):
602 parity = 0
603 parity = 0
603 count = 0
604 count = 0
604 for k,n in i:
605 for k,n in i:
605 if k == "tip": # skip tip
606 if k == "tip": # skip tip
606 continue;
607 continue;
607
608
608 count += 1
609 count += 1
609 if count > 10: # limit to 10 tags
610 if count > 10: # limit to 10 tags
610 break;
611 break;
611
612
612 c = cl.read(n)
613 c = cl.read(n)
613 m = c[0]
614 m = c[0]
614 t = c[2]
615 t = c[2]
615
616
616 yield self.t("tagentry",
617 yield self.t("tagentry",
617 parity = parity,
618 parity = parity,
618 tag = k,
619 tag = k,
619 node = hex(n),
620 node = hex(n),
620 date = t,
621 date = t,
621 tagmanifest = hex(m))
622 tagmanifest = hex(m))
622 parity = 1 - parity
623 parity = 1 - parity
623
624
624 def changelist(**map):
625 def changelist(**map):
625 parity = 0
626 parity = 0
626 cl = self.repo.changelog
627 cl = self.repo.changelog
627 l = [] # build a list in forward order for efficiency
628 l = [] # build a list in forward order for efficiency
628 for i in range(start, end):
629 for i in range(start, end):
629 n = cl.node(i)
630 n = cl.node(i)
630 changes = cl.read(n)
631 changes = cl.read(n)
631 hn = hex(n)
632 hn = hex(n)
632 t = changes[2]
633 t = changes[2]
633
634
634 l.insert(0, self.t(
635 l.insert(0, self.t(
635 'shortlogentry',
636 'shortlogentry',
636 parity = parity,
637 parity = parity,
637 author = changes[1],
638 author = changes[1],
638 manifest = hex(changes[0]),
639 manifest = hex(changes[0]),
639 desc = changes[4],
640 desc = changes[4],
640 date = t,
641 date = t,
641 rev = i,
642 rev = i,
642 node = hn))
643 node = hn))
643 parity = 1 - parity
644 parity = 1 - parity
644
645
645 yield l
646 yield l
646
647
647 cl = self.repo.changelog
648 cl = self.repo.changelog
648 mf = cl.read(cl.tip())[0]
649 mf = cl.read(cl.tip())[0]
649 count = cl.count()
650 count = cl.count()
650 start = max(0, count - self.maxchanges)
651 start = max(0, count - self.maxchanges)
651 end = min(count, start + self.maxchanges)
652 end = min(count, start + self.maxchanges)
652 pos = end - 1
653 pos = end - 1
653
654
654 yield self.t("summary",
655 yield self.t("summary",
655 desc = self.repo.ui.config("web", "description", "unknown"),
656 desc = self.repo.ui.config("web", "description", "unknown"),
656 owner = (self.repo.ui.config("ui", "username") or # preferred
657 owner = (self.repo.ui.config("ui", "username") or # preferred
657 self.repo.ui.config("web", "contact") or # deprecated
658 self.repo.ui.config("web", "contact") or # deprecated
658 self.repo.ui.config("web", "author", "unknown")), # also
659 self.repo.ui.config("web", "author", "unknown")), # also
659 lastchange = (0, 0), # FIXME
660 lastchange = (0, 0), # FIXME
660 manifest = hex(mf),
661 manifest = hex(mf),
661 tags = tagentries,
662 tags = tagentries,
662 shortlog = changelist)
663 shortlog = changelist)
663
664
664 def filediff(self, file, changeset):
665 def filediff(self, file, changeset):
665 cl = self.repo.changelog
666 cl = self.repo.changelog
666 n = self.repo.lookup(changeset)
667 n = self.repo.lookup(changeset)
667 changeset = hex(n)
668 changeset = hex(n)
668 p1 = cl.parents(n)[0]
669 p1 = cl.parents(n)[0]
669 cs = cl.read(n)
670 cs = cl.read(n)
670 mf = self.repo.manifest.read(cs[0])
671 mf = self.repo.manifest.read(cs[0])
671
672
672 def diff(**map):
673 def diff(**map):
673 yield self.diff(p1, n, file)
674 yield self.diff(p1, n, file)
674
675
675 yield self.t("filediff",
676 yield self.t("filediff",
676 file=file,
677 file=file,
677 filenode=hex(mf.get(file, nullid)),
678 filenode=hex(mf.get(file, nullid)),
678 node=changeset,
679 node=changeset,
679 rev=self.repo.changelog.rev(n),
680 rev=self.repo.changelog.rev(n),
680 parent=self.siblings(cl.parents(n), cl.rev),
681 parent=self.siblings(cl.parents(n), cl.rev),
681 child=self.siblings(cl.children(n), cl.rev),
682 child=self.siblings(cl.children(n), cl.rev),
682 diff=diff)
683 diff=diff)
683
684
684 def archive(self, req, cnode, type):
685 def archive(self, req, cnode, type):
685 cs = self.repo.changelog.read(cnode)
686 cs = self.repo.changelog.read(cnode)
686 mnode = cs[0]
687 mnode = cs[0]
687 mf = self.repo.manifest.read(mnode)
688 mf = self.repo.manifest.read(mnode)
688 rev = self.repo.manifest.rev(mnode)
689 rev = self.repo.manifest.rev(mnode)
689 reponame = re.sub(r"\W+", "-", self.reponame)
690 reponame = re.sub(r"\W+", "-", self.reponame)
690 name = "%s-%s/" % (reponame, short(cnode))
691 name = "%s-%s/" % (reponame, short(cnode))
691
692
692 files = mf.keys()
693 files = mf.keys()
693 files.sort()
694 files.sort()
694
695
695 if type == 'zip':
696 if type == 'zip':
696 tmp = tempfile.mkstemp()[1]
697 tmp = tempfile.mkstemp()[1]
697 try:
698 try:
698 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
699 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
699
700
700 for f in files:
701 for f in files:
701 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
702 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
702 zf.close()
703 zf.close()
703
704
704 f = open(tmp, 'r')
705 f = open(tmp, 'r')
705 req.httphdr('application/zip', name[:-1] + '.zip',
706 req.httphdr('application/zip', name[:-1] + '.zip',
706 os.path.getsize(tmp))
707 os.path.getsize(tmp))
707 req.write(f.read())
708 req.write(f.read())
708 f.close()
709 f.close()
709 finally:
710 finally:
710 os.unlink(tmp)
711 os.unlink(tmp)
711
712
712 else:
713 else:
713 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
714 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
714 mff = self.repo.manifest.readflags(mnode)
715 mff = self.repo.manifest.readflags(mnode)
715 mtime = int(time.time())
716 mtime = int(time.time())
716
717
717 if type == "gz":
718 if type == "gz":
718 encoding = "gzip"
719 encoding = "gzip"
719 else:
720 else:
720 encoding = "x-bzip2"
721 encoding = "x-bzip2"
721 req.header([('Content-type', 'application/x-tar'),
722 req.header([('Content-type', 'application/x-tar'),
722 ('Content-disposition', 'attachment; filename=%s%s%s' %
723 ('Content-disposition', 'attachment; filename=%s%s%s' %
723 (name[:-1], '.tar.', type)),
724 (name[:-1], '.tar.', type)),
724 ('Content-encoding', encoding)])
725 ('Content-encoding', encoding)])
725 for fname in files:
726 for fname in files:
726 rcont = self.repo.file(fname).read(mf[fname])
727 rcont = self.repo.file(fname).read(mf[fname])
727 finfo = tarfile.TarInfo(name + fname)
728 finfo = tarfile.TarInfo(name + fname)
728 finfo.mtime = mtime
729 finfo.mtime = mtime
729 finfo.size = len(rcont)
730 finfo.size = len(rcont)
730 finfo.mode = mff[fname] and 0755 or 0644
731 finfo.mode = mff[fname] and 0755 or 0644
731 tf.addfile(finfo, StringIO.StringIO(rcont))
732 tf.addfile(finfo, StringIO.StringIO(rcont))
732 tf.close()
733 tf.close()
733
734
734 # add tags to things
735 # add tags to things
735 # tags -> list of changesets corresponding to tags
736 # tags -> list of changesets corresponding to tags
736 # find tag, changeset, file
737 # find tag, changeset, file
737
738
738 def run(self, req=hgrequest()):
739 def run(self, req=hgrequest()):
739 def clean(path):
740 def clean(path):
740 p = util.normpath(path)
741 p = util.normpath(path)
741 if p[:2] == "..":
742 if p[:2] == "..":
742 raise "suspicious path"
743 raise "suspicious path"
743 return p
744 return p
744
745
745 def header(**map):
746 def header(**map):
746 yield self.t("header", **map)
747 yield self.t("header", **map)
747
748
748 def footer(**map):
749 def footer(**map):
749 yield self.t("footer", **map)
750 yield self.t("footer", **map)
750
751
751 def expand_form(form):
752 def expand_form(form):
752 shortcuts = {
753 shortcuts = {
753 'cl': [('cmd', ['changelog']), ('rev', None)],
754 'cl': [('cmd', ['changelog']), ('rev', None)],
754 'cs': [('cmd', ['changeset']), ('node', None)],
755 'cs': [('cmd', ['changeset']), ('node', None)],
755 'f': [('cmd', ['file']), ('filenode', None)],
756 'f': [('cmd', ['file']), ('filenode', None)],
756 'fl': [('cmd', ['filelog']), ('filenode', None)],
757 'fl': [('cmd', ['filelog']), ('filenode', None)],
757 'fd': [('cmd', ['filediff']), ('node', None)],
758 'fd': [('cmd', ['filediff']), ('node', None)],
758 'fa': [('cmd', ['annotate']), ('filenode', None)],
759 'fa': [('cmd', ['annotate']), ('filenode', None)],
759 'mf': [('cmd', ['manifest']), ('manifest', None)],
760 'mf': [('cmd', ['manifest']), ('manifest', None)],
760 'ca': [('cmd', ['archive']), ('node', None)],
761 'ca': [('cmd', ['archive']), ('node', None)],
761 'tags': [('cmd', ['tags'])],
762 'tags': [('cmd', ['tags'])],
762 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
763 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
763 'static': [('cmd', ['static']), ('file', None)]
764 'static': [('cmd', ['static']), ('file', None)]
764 }
765 }
765
766
766 for k in shortcuts.iterkeys():
767 for k in shortcuts.iterkeys():
767 if form.has_key(k):
768 if form.has_key(k):
768 for name, value in shortcuts[k]:
769 for name, value in shortcuts[k]:
769 if value is None:
770 if value is None:
770 value = form[k]
771 value = form[k]
771 form[name] = value
772 form[name] = value
772 del form[k]
773 del form[k]
773
774
774 self.refresh()
775 self.refresh()
775
776
776 expand_form(req.form)
777 expand_form(req.form)
777
778
778 t = self.repo.ui.config("web", "templates", templater.templatepath())
779 t = self.repo.ui.config("web", "templates", templater.templatepath())
779 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
780 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
780 m = os.path.join(t, "map")
781 m = os.path.join(t, "map")
781 style = self.repo.ui.config("web", "style", "")
782 style = self.repo.ui.config("web", "style", "")
782 if req.form.has_key('style'):
783 if req.form.has_key('style'):
783 style = req.form['style'][0]
784 style = req.form['style'][0]
784 if style:
785 if style:
785 b = os.path.basename("map-" + style)
786 b = os.path.basename("map-" + style)
786 p = os.path.join(t, b)
787 p = os.path.join(t, b)
787 if os.path.isfile(p):
788 if os.path.isfile(p):
788 m = p
789 m = p
789
790
790 port = req.env["SERVER_PORT"]
791 port = req.env["SERVER_PORT"]
791 port = port != "80" and (":" + port) or ""
792 port = port != "80" and (":" + port) or ""
792 uri = req.env["REQUEST_URI"]
793 uri = req.env["REQUEST_URI"]
793 if "?" in uri:
794 if "?" in uri:
794 uri = uri.split("?")[0]
795 uri = uri.split("?")[0]
795 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
796 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
796 if not self.reponame:
797 if not self.reponame:
797 self.reponame = (self.repo.ui.config("web", "name")
798 self.reponame = (self.repo.ui.config("web", "name")
798 or uri.strip('/') or self.repo.root)
799 or uri.strip('/') or self.repo.root)
799
800
800 self.t = templater.templater(m, templater.common_filters,
801 self.t = templater.templater(m, templater.common_filters,
801 defaults={"url": url,
802 defaults={"url": url,
802 "repo": self.reponame,
803 "repo": self.reponame,
803 "header": header,
804 "header": header,
804 "footer": footer,
805 "footer": footer,
805 })
806 })
806
807
807 if not req.form.has_key('cmd'):
808 if not req.form.has_key('cmd'):
808 req.form['cmd'] = [self.t.cache['default'],]
809 req.form['cmd'] = [self.t.cache['default'],]
809
810
810 if req.form['cmd'][0] == 'changelog':
811 if req.form['cmd'][0] == 'changelog':
811 c = self.repo.changelog.count() - 1
812 c = self.repo.changelog.count() - 1
812 hi = c
813 hi = c
813 if req.form.has_key('rev'):
814 if req.form.has_key('rev'):
814 hi = req.form['rev'][0]
815 hi = req.form['rev'][0]
815 try:
816 try:
816 hi = self.repo.changelog.rev(self.repo.lookup(hi))
817 hi = self.repo.changelog.rev(self.repo.lookup(hi))
817 except hg.RepoError:
818 except hg.RepoError:
818 req.write(self.search(hi))
819 req.write(self.search(hi))
819 return
820 return
820
821
821 req.write(self.changelog(hi))
822 req.write(self.changelog(hi))
822
823
823 elif req.form['cmd'][0] == 'changeset':
824 elif req.form['cmd'][0] == 'changeset':
824 req.write(self.changeset(req.form['node'][0]))
825 req.write(self.changeset(req.form['node'][0]))
825
826
826 elif req.form['cmd'][0] == 'manifest':
827 elif req.form['cmd'][0] == 'manifest':
827 req.write(self.manifest(req.form['manifest'][0],
828 req.write(self.manifest(req.form['manifest'][0],
828 clean(req.form['path'][0])))
829 clean(req.form['path'][0])))
829
830
830 elif req.form['cmd'][0] == 'tags':
831 elif req.form['cmd'][0] == 'tags':
831 req.write(self.tags())
832 req.write(self.tags())
832
833
833 elif req.form['cmd'][0] == 'summary':
834 elif req.form['cmd'][0] == 'summary':
834 req.write(self.summary())
835 req.write(self.summary())
835
836
836 elif req.form['cmd'][0] == 'filediff':
837 elif req.form['cmd'][0] == 'filediff':
837 req.write(self.filediff(clean(req.form['file'][0]),
838 req.write(self.filediff(clean(req.form['file'][0]),
838 req.form['node'][0]))
839 req.form['node'][0]))
839
840
840 elif req.form['cmd'][0] == 'file':
841 elif req.form['cmd'][0] == 'file':
841 req.write(self.filerevision(clean(req.form['file'][0]),
842 req.write(self.filerevision(clean(req.form['file'][0]),
842 req.form['filenode'][0]))
843 req.form['filenode'][0]))
843
844
844 elif req.form['cmd'][0] == 'annotate':
845 elif req.form['cmd'][0] == 'annotate':
845 req.write(self.fileannotate(clean(req.form['file'][0]),
846 req.write(self.fileannotate(clean(req.form['file'][0]),
846 req.form['filenode'][0]))
847 req.form['filenode'][0]))
847
848
848 elif req.form['cmd'][0] == 'filelog':
849 elif req.form['cmd'][0] == 'filelog':
849 req.write(self.filelog(clean(req.form['file'][0]),
850 req.write(self.filelog(clean(req.form['file'][0]),
850 req.form['filenode'][0]))
851 req.form['filenode'][0]))
851
852
852 elif req.form['cmd'][0] == 'heads':
853 elif req.form['cmd'][0] == 'heads':
853 req.httphdr("application/mercurial-0.1")
854 req.httphdr("application/mercurial-0.1")
854 h = self.repo.heads()
855 h = self.repo.heads()
855 req.write(" ".join(map(hex, h)) + "\n")
856 req.write(" ".join(map(hex, h)) + "\n")
856
857
857 elif req.form['cmd'][0] == 'branches':
858 elif req.form['cmd'][0] == 'branches':
858 req.httphdr("application/mercurial-0.1")
859 req.httphdr("application/mercurial-0.1")
859 nodes = []
860 nodes = []
860 if req.form.has_key('nodes'):
861 if req.form.has_key('nodes'):
861 nodes = map(bin, req.form['nodes'][0].split(" "))
862 nodes = map(bin, req.form['nodes'][0].split(" "))
862 for b in self.repo.branches(nodes):
863 for b in self.repo.branches(nodes):
863 req.write(" ".join(map(hex, b)) + "\n")
864 req.write(" ".join(map(hex, b)) + "\n")
864
865
865 elif req.form['cmd'][0] == 'between':
866 elif req.form['cmd'][0] == 'between':
866 req.httphdr("application/mercurial-0.1")
867 req.httphdr("application/mercurial-0.1")
867 nodes = []
868 nodes = []
868 if req.form.has_key('pairs'):
869 if req.form.has_key('pairs'):
869 pairs = [map(bin, p.split("-"))
870 pairs = [map(bin, p.split("-"))
870 for p in req.form['pairs'][0].split(" ")]
871 for p in req.form['pairs'][0].split(" ")]
871 for b in self.repo.between(pairs):
872 for b in self.repo.between(pairs):
872 req.write(" ".join(map(hex, b)) + "\n")
873 req.write(" ".join(map(hex, b)) + "\n")
873
874
874 elif req.form['cmd'][0] == 'changegroup':
875 elif req.form['cmd'][0] == 'changegroup':
875 req.httphdr("application/mercurial-0.1")
876 req.httphdr("application/mercurial-0.1")
876 nodes = []
877 nodes = []
877 if not self.allowpull:
878 if not self.allowpull:
878 return
879 return
879
880
880 if req.form.has_key('roots'):
881 if req.form.has_key('roots'):
881 nodes = map(bin, req.form['roots'][0].split(" "))
882 nodes = map(bin, req.form['roots'][0].split(" "))
882
883
883 z = zlib.compressobj()
884 z = zlib.compressobj()
884 f = self.repo.changegroup(nodes, 'serve')
885 f = self.repo.changegroup(nodes, 'serve')
885 while 1:
886 while 1:
886 chunk = f.read(4096)
887 chunk = f.read(4096)
887 if not chunk:
888 if not chunk:
888 break
889 break
889 req.write(z.compress(chunk))
890 req.write(z.compress(chunk))
890
891
891 req.write(z.flush())
892 req.write(z.flush())
892
893
893 elif req.form['cmd'][0] == 'archive':
894 elif req.form['cmd'][0] == 'archive':
894 changeset = self.repo.lookup(req.form['node'][0])
895 changeset = self.repo.lookup(req.form['node'][0])
895 type = req.form['type'][0]
896 type = req.form['type'][0]
896 if (type in self.archives and
897 if (type in self.archives and
897 self.repo.ui.configbool("web", "allow" + type, False)):
898 self.repo.ui.configbool("web", "allow" + type, False)):
898 self.archive(req, changeset, type)
899 self.archive(req, changeset, type)
899 return
900 return
900
901
901 req.write(self.t("error"))
902 req.write(self.t("error"))
902
903
903 elif req.form['cmd'][0] == 'static':
904 elif req.form['cmd'][0] == 'static':
904 fname = req.form['file'][0]
905 fname = req.form['file'][0]
905 req.write(staticfile(static, fname)
906 req.write(staticfile(static, fname)
906 or self.t("error", error="%r not found" % fname))
907 or self.t("error", error="%r not found" % fname))
907
908
908 else:
909 else:
909 req.write(self.t("error"))
910 req.write(self.t("error"))
910
911
911 def create_server(repo):
912 def create_server(repo):
912
913
913 def openlog(opt, default):
914 def openlog(opt, default):
914 if opt and opt != '-':
915 if opt and opt != '-':
915 return open(opt, 'w')
916 return open(opt, 'w')
916 return default
917 return default
917
918
918 address = repo.ui.config("web", "address", "")
919 address = repo.ui.config("web", "address", "")
919 port = int(repo.ui.config("web", "port", 8000))
920 port = int(repo.ui.config("web", "port", 8000))
920 use_ipv6 = repo.ui.configbool("web", "ipv6")
921 use_ipv6 = repo.ui.configbool("web", "ipv6")
921 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
922 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
922 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
923 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
923
924
924 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
925 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
925 address_family = getattr(socket, 'AF_INET6', None)
926 address_family = getattr(socket, 'AF_INET6', None)
926
927
927 def __init__(self, *args, **kwargs):
928 def __init__(self, *args, **kwargs):
928 if self.address_family is None:
929 if self.address_family is None:
929 raise hg.RepoError(_('IPv6 not available on this system'))
930 raise hg.RepoError(_('IPv6 not available on this system'))
930 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
931 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
931
932
932 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
933 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
933 def log_error(self, format, *args):
934 def log_error(self, format, *args):
934 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
935 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
935 self.log_date_time_string(),
936 self.log_date_time_string(),
936 format % args))
937 format % args))
937
938
938 def log_message(self, format, *args):
939 def log_message(self, format, *args):
939 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
940 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
940 self.log_date_time_string(),
941 self.log_date_time_string(),
941 format % args))
942 format % args))
942
943
943 def do_POST(self):
944 def do_POST(self):
944 try:
945 try:
945 self.do_hgweb()
946 self.do_hgweb()
946 except socket.error, inst:
947 except socket.error, inst:
947 if inst[0] != errno.EPIPE:
948 if inst[0] != errno.EPIPE:
948 raise
949 raise
949
950
950 def do_GET(self):
951 def do_GET(self):
951 self.do_POST()
952 self.do_POST()
952
953
953 def do_hgweb(self):
954 def do_hgweb(self):
954 query = ""
955 query = ""
955 p = self.path.find("?")
956 p = self.path.find("?")
956 if p:
957 if p:
957 query = self.path[p + 1:]
958 query = self.path[p + 1:]
958 query = query.replace('+', ' ')
959 query = query.replace('+', ' ')
959
960
960 env = {}
961 env = {}
961 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
962 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
962 env['REQUEST_METHOD'] = self.command
963 env['REQUEST_METHOD'] = self.command
963 env['SERVER_NAME'] = self.server.server_name
964 env['SERVER_NAME'] = self.server.server_name
964 env['SERVER_PORT'] = str(self.server.server_port)
965 env['SERVER_PORT'] = str(self.server.server_port)
965 env['REQUEST_URI'] = "/"
966 env['REQUEST_URI'] = "/"
966 if query:
967 if query:
967 env['QUERY_STRING'] = query
968 env['QUERY_STRING'] = query
968 host = self.address_string()
969 host = self.address_string()
969 if host != self.client_address[0]:
970 if host != self.client_address[0]:
970 env['REMOTE_HOST'] = host
971 env['REMOTE_HOST'] = host
971 env['REMOTE_ADDR'] = self.client_address[0]
972 env['REMOTE_ADDR'] = self.client_address[0]
972
973
973 if self.headers.typeheader is None:
974 if self.headers.typeheader is None:
974 env['CONTENT_TYPE'] = self.headers.type
975 env['CONTENT_TYPE'] = self.headers.type
975 else:
976 else:
976 env['CONTENT_TYPE'] = self.headers.typeheader
977 env['CONTENT_TYPE'] = self.headers.typeheader
977 length = self.headers.getheader('content-length')
978 length = self.headers.getheader('content-length')
978 if length:
979 if length:
979 env['CONTENT_LENGTH'] = length
980 env['CONTENT_LENGTH'] = length
980 accept = []
981 accept = []
981 for line in self.headers.getallmatchingheaders('accept'):
982 for line in self.headers.getallmatchingheaders('accept'):
982 if line[:1] in "\t\n\r ":
983 if line[:1] in "\t\n\r ":
983 accept.append(line.strip())
984 accept.append(line.strip())
984 else:
985 else:
985 accept = accept + line[7:].split(',')
986 accept = accept + line[7:].split(',')
986 env['HTTP_ACCEPT'] = ','.join(accept)
987 env['HTTP_ACCEPT'] = ','.join(accept)
987
988
988 req = hgrequest(self.rfile, self.wfile, env)
989 req = hgrequest(self.rfile, self.wfile, env)
989 self.send_response(200, "Script output follows")
990 self.send_response(200, "Script output follows")
990 hg.run(req)
991 hg.run(req)
991
992
992 hg = hgweb(repo)
993 hg = hgweb(repo)
993 if use_ipv6:
994 if use_ipv6:
994 return IPv6HTTPServer((address, port), hgwebhandler)
995 return IPv6HTTPServer((address, port), hgwebhandler)
995 else:
996 else:
996 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
997 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
997
998
998 # This is a stopgap
999 # This is a stopgap
999 class hgwebdir(object):
1000 class hgwebdir(object):
1000 def __init__(self, config):
1001 def __init__(self, config):
1001 def cleannames(items):
1002 def cleannames(items):
1002 return [(name.strip(os.sep), path) for name, path in items]
1003 return [(name.strip(os.sep), path) for name, path in items]
1003
1004
1004 if isinstance(config, (list, tuple)):
1005 if isinstance(config, (list, tuple)):
1005 self.repos = cleannames(config)
1006 self.repos = cleannames(config)
1006 elif isinstance(config, dict):
1007 elif isinstance(config, dict):
1007 self.repos = cleannames(config.items())
1008 self.repos = cleannames(config.items())
1008 self.repos.sort()
1009 self.repos.sort()
1009 else:
1010 else:
1010 cp = ConfigParser.SafeConfigParser()
1011 cp = ConfigParser.SafeConfigParser()
1011 cp.read(config)
1012 cp.read(config)
1012 self.repos = []
1013 self.repos = []
1013 if cp.has_section('paths'):
1014 if cp.has_section('paths'):
1014 self.repos.extend(cleannames(cp.items('paths')))
1015 self.repos.extend(cleannames(cp.items('paths')))
1015 if cp.has_section('collections'):
1016 if cp.has_section('collections'):
1016 for prefix, root in cp.items('collections'):
1017 for prefix, root in cp.items('collections'):
1017 for path in util.walkrepos(root):
1018 for path in util.walkrepos(root):
1018 repo = os.path.normpath(path)
1019 repo = os.path.normpath(path)
1019 name = repo
1020 name = repo
1020 if name.startswith(prefix):
1021 if name.startswith(prefix):
1021 name = name[len(prefix):]
1022 name = name[len(prefix):]
1022 self.repos.append((name.lstrip(os.sep), repo))
1023 self.repos.append((name.lstrip(os.sep), repo))
1023 self.repos.sort()
1024 self.repos.sort()
1024
1025
1025 def run(self, req=hgrequest()):
1026 def run(self, req=hgrequest()):
1026 def header(**map):
1027 def header(**map):
1027 yield tmpl("header", **map)
1028 yield tmpl("header", **map)
1028
1029
1029 def footer(**map):
1030 def footer(**map):
1030 yield tmpl("footer", **map)
1031 yield tmpl("footer", **map)
1031
1032
1032 m = os.path.join(templater.templatepath(), "map")
1033 m = os.path.join(templater.templatepath(), "map")
1033 tmpl = templater.templater(m, templater.common_filters,
1034 tmpl = templater.templater(m, templater.common_filters,
1034 defaults={"header": header,
1035 defaults={"header": header,
1035 "footer": footer})
1036 "footer": footer})
1036
1037
1037 def entries(**map):
1038 def entries(**map):
1038 parity = 0
1039 parity = 0
1039 for name, path in self.repos:
1040 for name, path in self.repos:
1040 u = ui.ui()
1041 u = ui.ui()
1041 try:
1042 try:
1042 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1043 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1043 except IOError:
1044 except IOError:
1044 pass
1045 pass
1045 get = u.config
1046 get = u.config
1046
1047
1047 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1048 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1048 .replace("//", "/"))
1049 .replace("//", "/"))
1049
1050
1050 # update time with local timezone
1051 # update time with local timezone
1051 try:
1052 try:
1052 d = (get_mtime(path), util.makedate()[1])
1053 d = (get_mtime(path), util.makedate()[1])
1053 except OSError:
1054 except OSError:
1054 continue
1055 continue
1055
1056
1056 yield dict(contact=(get("ui", "username") or # preferred
1057 yield dict(contact=(get("ui", "username") or # preferred
1057 get("web", "contact") or # deprecated
1058 get("web", "contact") or # deprecated
1058 get("web", "author", "unknown")), # also
1059 get("web", "author", "unknown")), # also
1059 name=get("web", "name", name),
1060 name=get("web", "name", name),
1060 url=url,
1061 url=url,
1061 parity=parity,
1062 parity=parity,
1062 shortdesc=get("web", "description", "unknown"),
1063 shortdesc=get("web", "description", "unknown"),
1063 lastupdate=d)
1064 lastupdate=d)
1064
1065
1065 parity = 1 - parity
1066 parity = 1 - parity
1066
1067
1067 virtual = req.env.get("PATH_INFO", "").strip('/')
1068 virtual = req.env.get("PATH_INFO", "").strip('/')
1068 if virtual:
1069 if virtual:
1069 real = dict(self.repos).get(virtual)
1070 real = dict(self.repos).get(virtual)
1070 if real:
1071 if real:
1071 try:
1072 try:
1072 hgweb(real).run(req)
1073 hgweb(real).run(req)
1073 except IOError, inst:
1074 except IOError, inst:
1074 req.write(tmpl("error", error=inst.strerror))
1075 req.write(tmpl("error", error=inst.strerror))
1075 except hg.RepoError, inst:
1076 except hg.RepoError, inst:
1076 req.write(tmpl("error", error=str(inst)))
1077 req.write(tmpl("error", error=str(inst)))
1077 else:
1078 else:
1078 req.write(tmpl("notfound", repo=virtual))
1079 req.write(tmpl("notfound", repo=virtual))
1079 else:
1080 else:
1080 if req.form.has_key('static'):
1081 if req.form.has_key('static'):
1081 static = os.path.join(templater.templatepath(), "static")
1082 static = os.path.join(templater.templatepath(), "static")
1082 fname = req.form['static'][0]
1083 fname = req.form['static'][0]
1083 req.write(staticfile(static, fname)
1084 req.write(staticfile(static, fname)
1084 or tmpl("error", error="%r not found" % fname))
1085 or tmpl("error", error="%r not found" % fname))
1085 else:
1086 else:
1086 req.write(tmpl("index", entries=entries))
1087 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now