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