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