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