##// END OF EJS Templates
hgweb: make #motd# available for all templates
Alexis S. L. Carvalho -
r3488:8f022236 default
parent child Browse files
Show More
@@ -1,1088 +1,1090 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a 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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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 mimetools cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 demandload(globals(), 'urllib')
14 demandload(globals(), 'urllib')
15 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
15 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
16 demandload(globals(), "mercurial:revlog,templater")
16 demandload(globals(), "mercurial:revlog,templater")
17 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
17 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
18 from mercurial.node import *
18 from mercurial.node import *
19 from mercurial.i18n import gettext as _
19 from mercurial.i18n import gettext as _
20
20
21 def _up(p):
21 def _up(p):
22 if p[0] != "/":
22 if p[0] != "/":
23 p = "/" + p
23 p = "/" + p
24 if p[-1] == "/":
24 if p[-1] == "/":
25 p = p[:-1]
25 p = p[:-1]
26 up = os.path.dirname(p)
26 up = os.path.dirname(p)
27 if up == "/":
27 if up == "/":
28 return "/"
28 return "/"
29 return up + "/"
29 return up + "/"
30
30
31 def revnavgen(pos, pagelen, limit, nodefunc):
31 def revnavgen(pos, pagelen, limit, nodefunc):
32 def seq(factor, limit=None):
32 def seq(factor, limit=None):
33 if limit:
33 if limit:
34 yield limit
34 yield limit
35 if limit >= 20 and limit <= 40:
35 if limit >= 20 and limit <= 40:
36 yield 50
36 yield 50
37 else:
37 else:
38 yield 1 * factor
38 yield 1 * factor
39 yield 3 * factor
39 yield 3 * factor
40 for f in seq(factor * 10):
40 for f in seq(factor * 10):
41 yield f
41 yield f
42
42
43 def nav(**map):
43 def nav(**map):
44 l = []
44 l = []
45 last = 0
45 last = 0
46 for f in seq(1, pagelen):
46 for f in seq(1, pagelen):
47 if f < pagelen or f <= last:
47 if f < pagelen or f <= last:
48 continue
48 continue
49 if f > limit:
49 if f > limit:
50 break
50 break
51 last = f
51 last = f
52 if pos + f < limit:
52 if pos + f < limit:
53 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
53 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
54 if pos - f >= 0:
54 if pos - f >= 0:
55 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
55 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
56
56
57 try:
57 try:
58 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
58 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
59
59
60 for label, node in l:
60 for label, node in l:
61 yield {"label": label, "node": node}
61 yield {"label": label, "node": node}
62
62
63 yield {"label": "tip", "node": "tip"}
63 yield {"label": "tip", "node": "tip"}
64 except hg.RepoError:
64 except hg.RepoError:
65 pass
65 pass
66
66
67 return nav
67 return nav
68
68
69 class hgweb(object):
69 class hgweb(object):
70 def __init__(self, repo, name=None):
70 def __init__(self, repo, name=None):
71 if type(repo) == type(""):
71 if type(repo) == type(""):
72 self.repo = hg.repository(ui.ui(), repo)
72 self.repo = hg.repository(ui.ui(), repo)
73 else:
73 else:
74 self.repo = repo
74 self.repo = repo
75
75
76 self.mtime = -1
76 self.mtime = -1
77 self.reponame = name
77 self.reponame = name
78 self.archives = 'zip', 'gz', 'bz2'
78 self.archives = 'zip', 'gz', 'bz2'
79 self.stripecount = 1
79 self.stripecount = 1
80 self.templatepath = self.repo.ui.config("web", "templates",
80 self.templatepath = self.repo.ui.config("web", "templates",
81 templater.templatepath())
81 templater.templatepath())
82
82
83 def refresh(self):
83 def refresh(self):
84 mtime = get_mtime(self.repo.root)
84 mtime = get_mtime(self.repo.root)
85 if mtime != self.mtime:
85 if mtime != self.mtime:
86 self.mtime = mtime
86 self.mtime = mtime
87 self.repo = hg.repository(self.repo.ui, self.repo.root)
87 self.repo = hg.repository(self.repo.ui, self.repo.root)
88 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
88 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
89 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
89 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
90 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
90 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
91 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
91 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
92 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
92 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
93
93
94 def archivelist(self, nodeid):
94 def archivelist(self, nodeid):
95 allowed = self.repo.ui.configlist("web", "allow_archive")
95 allowed = self.repo.ui.configlist("web", "allow_archive")
96 for i, spec in self.archive_specs.iteritems():
96 for i, spec in self.archive_specs.iteritems():
97 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
97 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
98 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
98 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
99
99
100 def listfilediffs(self, files, changeset):
100 def listfilediffs(self, files, changeset):
101 for f in files[:self.maxfiles]:
101 for f in files[:self.maxfiles]:
102 yield self.t("filedifflink", node=hex(changeset), file=f)
102 yield self.t("filedifflink", node=hex(changeset), file=f)
103 if len(files) > self.maxfiles:
103 if len(files) > self.maxfiles:
104 yield self.t("fileellipses")
104 yield self.t("fileellipses")
105
105
106 def siblings(self, siblings=[], hiderev=None, **args):
106 def siblings(self, siblings=[], hiderev=None, **args):
107 siblings = [s for s in siblings if s.node() != nullid]
107 siblings = [s for s in siblings if s.node() != nullid]
108 if len(siblings) == 1 and siblings[0].rev() == hiderev:
108 if len(siblings) == 1 and siblings[0].rev() == hiderev:
109 return
109 return
110 for s in siblings:
110 for s in siblings:
111 d = {'node': hex(s.node()), 'rev': s.rev()}
111 d = {'node': hex(s.node()), 'rev': s.rev()}
112 if hasattr(s, 'path'):
112 if hasattr(s, 'path'):
113 d['file'] = s.path()
113 d['file'] = s.path()
114 d.update(args)
114 d.update(args)
115 yield d
115 yield d
116
116
117 def renamelink(self, fl, node):
117 def renamelink(self, fl, node):
118 r = fl.renamed(node)
118 r = fl.renamed(node)
119 if r:
119 if r:
120 return [dict(file=r[0], node=hex(r[1]))]
120 return [dict(file=r[0], node=hex(r[1]))]
121 return []
121 return []
122
122
123 def showtag(self, t1, node=nullid, **args):
123 def showtag(self, t1, node=nullid, **args):
124 for t in self.repo.nodetags(node):
124 for t in self.repo.nodetags(node):
125 yield self.t(t1, tag=t, **args)
125 yield self.t(t1, tag=t, **args)
126
126
127 def diff(self, node1, node2, files):
127 def diff(self, node1, node2, files):
128 def filterfiles(filters, files):
128 def filterfiles(filters, files):
129 l = [x for x in files if x in filters]
129 l = [x for x in files if x in filters]
130
130
131 for t in filters:
131 for t in filters:
132 if t and t[-1] != os.sep:
132 if t and t[-1] != os.sep:
133 t += os.sep
133 t += os.sep
134 l += [x for x in files if x.startswith(t)]
134 l += [x for x in files if x.startswith(t)]
135 return l
135 return l
136
136
137 parity = [0]
137 parity = [0]
138 def diffblock(diff, f, fn):
138 def diffblock(diff, f, fn):
139 yield self.t("diffblock",
139 yield self.t("diffblock",
140 lines=prettyprintlines(diff),
140 lines=prettyprintlines(diff),
141 parity=parity[0],
141 parity=parity[0],
142 file=f,
142 file=f,
143 filenode=hex(fn or nullid))
143 filenode=hex(fn or nullid))
144 parity[0] = 1 - parity[0]
144 parity[0] = 1 - parity[0]
145
145
146 def prettyprintlines(diff):
146 def prettyprintlines(diff):
147 for l in diff.splitlines(1):
147 for l in diff.splitlines(1):
148 if l.startswith('+'):
148 if l.startswith('+'):
149 yield self.t("difflineplus", line=l)
149 yield self.t("difflineplus", line=l)
150 elif l.startswith('-'):
150 elif l.startswith('-'):
151 yield self.t("difflineminus", line=l)
151 yield self.t("difflineminus", line=l)
152 elif l.startswith('@'):
152 elif l.startswith('@'):
153 yield self.t("difflineat", line=l)
153 yield self.t("difflineat", line=l)
154 else:
154 else:
155 yield self.t("diffline", line=l)
155 yield self.t("diffline", line=l)
156
156
157 r = self.repo
157 r = self.repo
158 cl = r.changelog
158 cl = r.changelog
159 mf = r.manifest
159 mf = r.manifest
160 change1 = cl.read(node1)
160 change1 = cl.read(node1)
161 change2 = cl.read(node2)
161 change2 = cl.read(node2)
162 mmap1 = mf.read(change1[0])
162 mmap1 = mf.read(change1[0])
163 mmap2 = mf.read(change2[0])
163 mmap2 = mf.read(change2[0])
164 date1 = util.datestr(change1[2])
164 date1 = util.datestr(change1[2])
165 date2 = util.datestr(change2[2])
165 date2 = util.datestr(change2[2])
166
166
167 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
167 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
168 if files:
168 if files:
169 modified, added, removed = map(lambda x: filterfiles(files, x),
169 modified, added, removed = map(lambda x: filterfiles(files, x),
170 (modified, added, removed))
170 (modified, added, removed))
171
171
172 diffopts = patch.diffopts(self.repo.ui)
172 diffopts = patch.diffopts(self.repo.ui)
173 for f in modified:
173 for f in modified:
174 to = r.file(f).read(mmap1[f])
174 to = r.file(f).read(mmap1[f])
175 tn = r.file(f).read(mmap2[f])
175 tn = r.file(f).read(mmap2[f])
176 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
176 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
177 opts=diffopts), f, tn)
177 opts=diffopts), f, tn)
178 for f in added:
178 for f in added:
179 to = None
179 to = None
180 tn = r.file(f).read(mmap2[f])
180 tn = r.file(f).read(mmap2[f])
181 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
181 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
182 opts=diffopts), f, tn)
182 opts=diffopts), f, tn)
183 for f in removed:
183 for f in removed:
184 to = r.file(f).read(mmap1[f])
184 to = r.file(f).read(mmap1[f])
185 tn = None
185 tn = None
186 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
186 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
187 opts=diffopts), f, tn)
187 opts=diffopts), f, tn)
188
188
189 def changelog(self, ctx, shortlog=False):
189 def changelog(self, ctx, shortlog=False):
190 def changelist(**map):
190 def changelist(**map):
191 parity = (start - end) & 1
191 parity = (start - end) & 1
192 cl = self.repo.changelog
192 cl = self.repo.changelog
193 l = [] # build a list in forward order for efficiency
193 l = [] # build a list in forward order for efficiency
194 for i in xrange(start, end):
194 for i in xrange(start, end):
195 ctx = self.repo.changectx(i)
195 ctx = self.repo.changectx(i)
196 n = ctx.node()
196 n = ctx.node()
197
197
198 l.insert(0, {"parity": parity,
198 l.insert(0, {"parity": parity,
199 "author": ctx.user(),
199 "author": ctx.user(),
200 "parent": self.siblings(ctx.parents(), i - 1),
200 "parent": self.siblings(ctx.parents(), i - 1),
201 "child": self.siblings(ctx.children(), i + 1),
201 "child": self.siblings(ctx.children(), i + 1),
202 "changelogtag": self.showtag("changelogtag",n),
202 "changelogtag": self.showtag("changelogtag",n),
203 "desc": ctx.description(),
203 "desc": ctx.description(),
204 "date": ctx.date(),
204 "date": ctx.date(),
205 "files": self.listfilediffs(ctx.files(), n),
205 "files": self.listfilediffs(ctx.files(), n),
206 "rev": i,
206 "rev": i,
207 "node": hex(n)})
207 "node": hex(n)})
208 parity = 1 - parity
208 parity = 1 - parity
209
209
210 for e in l:
210 for e in l:
211 yield e
211 yield e
212
212
213 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
213 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
214 cl = self.repo.changelog
214 cl = self.repo.changelog
215 count = cl.count()
215 count = cl.count()
216 pos = ctx.rev()
216 pos = ctx.rev()
217 start = max(0, pos - maxchanges + 1)
217 start = max(0, pos - maxchanges + 1)
218 end = min(count, start + maxchanges)
218 end = min(count, start + maxchanges)
219 pos = end - 1
219 pos = end - 1
220
220
221 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
221 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
222
222
223 yield self.t(shortlog and 'shortlog' or 'changelog',
223 yield self.t(shortlog and 'shortlog' or 'changelog',
224 changenav=changenav,
224 changenav=changenav,
225 node=hex(cl.tip()),
225 node=hex(cl.tip()),
226 rev=pos, changesets=count, entries=changelist,
226 rev=pos, changesets=count, entries=changelist,
227 archives=self.archivelist("tip"))
227 archives=self.archivelist("tip"))
228
228
229 def search(self, query):
229 def search(self, query):
230
230
231 def changelist(**map):
231 def changelist(**map):
232 cl = self.repo.changelog
232 cl = self.repo.changelog
233 count = 0
233 count = 0
234 qw = query.lower().split()
234 qw = query.lower().split()
235
235
236 def revgen():
236 def revgen():
237 for i in xrange(cl.count() - 1, 0, -100):
237 for i in xrange(cl.count() - 1, 0, -100):
238 l = []
238 l = []
239 for j in xrange(max(0, i - 100), i):
239 for j in xrange(max(0, i - 100), i):
240 ctx = self.repo.changectx(j)
240 ctx = self.repo.changectx(j)
241 l.append(ctx)
241 l.append(ctx)
242 l.reverse()
242 l.reverse()
243 for e in l:
243 for e in l:
244 yield e
244 yield e
245
245
246 for ctx in revgen():
246 for ctx in revgen():
247 miss = 0
247 miss = 0
248 for q in qw:
248 for q in qw:
249 if not (q in ctx.user().lower() or
249 if not (q in ctx.user().lower() or
250 q in ctx.description().lower() or
250 q in ctx.description().lower() or
251 q in " ".join(ctx.files()[:20]).lower()):
251 q in " ".join(ctx.files()[:20]).lower()):
252 miss = 1
252 miss = 1
253 break
253 break
254 if miss:
254 if miss:
255 continue
255 continue
256
256
257 count += 1
257 count += 1
258 n = ctx.node()
258 n = ctx.node()
259
259
260 yield self.t('searchentry',
260 yield self.t('searchentry',
261 parity=self.stripes(count),
261 parity=self.stripes(count),
262 author=ctx.user(),
262 author=ctx.user(),
263 parent=self.siblings(ctx.parents()),
263 parent=self.siblings(ctx.parents()),
264 child=self.siblings(ctx.children()),
264 child=self.siblings(ctx.children()),
265 changelogtag=self.showtag("changelogtag",n),
265 changelogtag=self.showtag("changelogtag",n),
266 desc=ctx.description(),
266 desc=ctx.description(),
267 date=ctx.date(),
267 date=ctx.date(),
268 files=self.listfilediffs(ctx.files(), n),
268 files=self.listfilediffs(ctx.files(), n),
269 rev=ctx.rev(),
269 rev=ctx.rev(),
270 node=hex(n))
270 node=hex(n))
271
271
272 if count >= self.maxchanges:
272 if count >= self.maxchanges:
273 break
273 break
274
274
275 cl = self.repo.changelog
275 cl = self.repo.changelog
276
276
277 yield self.t('search',
277 yield self.t('search',
278 query=query,
278 query=query,
279 node=hex(cl.tip()),
279 node=hex(cl.tip()),
280 entries=changelist)
280 entries=changelist)
281
281
282 def changeset(self, ctx):
282 def changeset(self, ctx):
283 n = ctx.node()
283 n = ctx.node()
284 parents = ctx.parents()
284 parents = ctx.parents()
285 p1 = parents[0].node()
285 p1 = parents[0].node()
286
286
287 files = []
287 files = []
288 parity = 0
288 parity = 0
289 for f in ctx.files():
289 for f in ctx.files():
290 files.append(self.t("filenodelink",
290 files.append(self.t("filenodelink",
291 node=hex(n), file=f,
291 node=hex(n), file=f,
292 parity=parity))
292 parity=parity))
293 parity = 1 - parity
293 parity = 1 - parity
294
294
295 def diff(**map):
295 def diff(**map):
296 yield self.diff(p1, n, None)
296 yield self.diff(p1, n, None)
297
297
298 yield self.t('changeset',
298 yield self.t('changeset',
299 diff=diff,
299 diff=diff,
300 rev=ctx.rev(),
300 rev=ctx.rev(),
301 node=hex(n),
301 node=hex(n),
302 parent=self.siblings(parents),
302 parent=self.siblings(parents),
303 child=self.siblings(ctx.children()),
303 child=self.siblings(ctx.children()),
304 changesettag=self.showtag("changesettag",n),
304 changesettag=self.showtag("changesettag",n),
305 author=ctx.user(),
305 author=ctx.user(),
306 desc=ctx.description(),
306 desc=ctx.description(),
307 date=ctx.date(),
307 date=ctx.date(),
308 files=files,
308 files=files,
309 archives=self.archivelist(hex(n)))
309 archives=self.archivelist(hex(n)))
310
310
311 def filelog(self, fctx):
311 def filelog(self, fctx):
312 f = fctx.path()
312 f = fctx.path()
313 fl = fctx.filelog()
313 fl = fctx.filelog()
314 count = fl.count()
314 count = fl.count()
315 pagelen = self.maxshortchanges
315 pagelen = self.maxshortchanges
316 pos = fctx.filerev()
316 pos = fctx.filerev()
317 start = max(0, pos - pagelen + 1)
317 start = max(0, pos - pagelen + 1)
318 end = min(count, start + pagelen)
318 end = min(count, start + pagelen)
319 pos = end - 1
319 pos = end - 1
320
320
321 def entries(**map):
321 def entries(**map):
322 l = []
322 l = []
323 parity = (count - 1) & 1
323 parity = (count - 1) & 1
324
324
325 for i in xrange(start, end):
325 for i in xrange(start, end):
326 ctx = fctx.filectx(i)
326 ctx = fctx.filectx(i)
327 n = fl.node(i)
327 n = fl.node(i)
328
328
329 l.insert(0, {"parity": parity,
329 l.insert(0, {"parity": parity,
330 "filerev": i,
330 "filerev": i,
331 "file": f,
331 "file": f,
332 "node": hex(ctx.node()),
332 "node": hex(ctx.node()),
333 "author": ctx.user(),
333 "author": ctx.user(),
334 "date": ctx.date(),
334 "date": ctx.date(),
335 "rename": self.renamelink(fl, n),
335 "rename": self.renamelink(fl, n),
336 "parent": self.siblings(fctx.parents()),
336 "parent": self.siblings(fctx.parents()),
337 "child": self.siblings(fctx.children()),
337 "child": self.siblings(fctx.children()),
338 "desc": ctx.description()})
338 "desc": ctx.description()})
339 parity = 1 - parity
339 parity = 1 - parity
340
340
341 for e in l:
341 for e in l:
342 yield e
342 yield e
343
343
344 nodefunc = lambda x: fctx.filectx(fileid=x)
344 nodefunc = lambda x: fctx.filectx(fileid=x)
345 nav = revnavgen(pos, pagelen, count, nodefunc)
345 nav = revnavgen(pos, pagelen, count, nodefunc)
346 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
346 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
347 entries=entries)
347 entries=entries)
348
348
349 def filerevision(self, fctx):
349 def filerevision(self, fctx):
350 f = fctx.path()
350 f = fctx.path()
351 text = fctx.data()
351 text = fctx.data()
352 fl = fctx.filelog()
352 fl = fctx.filelog()
353 n = fctx.filenode()
353 n = fctx.filenode()
354
354
355 mt = mimetypes.guess_type(f)[0]
355 mt = mimetypes.guess_type(f)[0]
356 rawtext = text
356 rawtext = text
357 if util.binary(text):
357 if util.binary(text):
358 mt = mt or 'application/octet-stream'
358 mt = mt or 'application/octet-stream'
359 text = "(binary:%s)" % mt
359 text = "(binary:%s)" % mt
360 mt = mt or 'text/plain'
360 mt = mt or 'text/plain'
361
361
362 def lines():
362 def lines():
363 for l, t in enumerate(text.splitlines(1)):
363 for l, t in enumerate(text.splitlines(1)):
364 yield {"line": t,
364 yield {"line": t,
365 "linenumber": "% 6d" % (l + 1),
365 "linenumber": "% 6d" % (l + 1),
366 "parity": self.stripes(l)}
366 "parity": self.stripes(l)}
367
367
368 yield self.t("filerevision",
368 yield self.t("filerevision",
369 file=f,
369 file=f,
370 path=_up(f),
370 path=_up(f),
371 text=lines(),
371 text=lines(),
372 raw=rawtext,
372 raw=rawtext,
373 mimetype=mt,
373 mimetype=mt,
374 rev=fctx.rev(),
374 rev=fctx.rev(),
375 node=hex(fctx.node()),
375 node=hex(fctx.node()),
376 author=fctx.user(),
376 author=fctx.user(),
377 date=fctx.date(),
377 date=fctx.date(),
378 desc=fctx.description(),
378 desc=fctx.description(),
379 parent=self.siblings(fctx.parents()),
379 parent=self.siblings(fctx.parents()),
380 child=self.siblings(fctx.children()),
380 child=self.siblings(fctx.children()),
381 rename=self.renamelink(fl, n),
381 rename=self.renamelink(fl, n),
382 permissions=fctx.manifest().execf(f))
382 permissions=fctx.manifest().execf(f))
383
383
384 def fileannotate(self, fctx):
384 def fileannotate(self, fctx):
385 f = fctx.path()
385 f = fctx.path()
386 n = fctx.filenode()
386 n = fctx.filenode()
387 fl = fctx.filelog()
387 fl = fctx.filelog()
388
388
389 def annotate(**map):
389 def annotate(**map):
390 parity = 0
390 parity = 0
391 last = None
391 last = None
392 for f, l in fctx.annotate(follow=True):
392 for f, l in fctx.annotate(follow=True):
393 fnode = f.filenode()
393 fnode = f.filenode()
394 name = self.repo.ui.shortuser(f.user())
394 name = self.repo.ui.shortuser(f.user())
395
395
396 if last != fnode:
396 if last != fnode:
397 parity = 1 - parity
397 parity = 1 - parity
398 last = fnode
398 last = fnode
399
399
400 yield {"parity": parity,
400 yield {"parity": parity,
401 "node": hex(f.node()),
401 "node": hex(f.node()),
402 "rev": f.rev(),
402 "rev": f.rev(),
403 "author": name,
403 "author": name,
404 "file": f.path(),
404 "file": f.path(),
405 "line": l}
405 "line": l}
406
406
407 yield self.t("fileannotate",
407 yield self.t("fileannotate",
408 file=f,
408 file=f,
409 annotate=annotate,
409 annotate=annotate,
410 path=_up(f),
410 path=_up(f),
411 rev=fctx.rev(),
411 rev=fctx.rev(),
412 node=hex(fctx.node()),
412 node=hex(fctx.node()),
413 author=fctx.user(),
413 author=fctx.user(),
414 date=fctx.date(),
414 date=fctx.date(),
415 desc=fctx.description(),
415 desc=fctx.description(),
416 rename=self.renamelink(fl, n),
416 rename=self.renamelink(fl, n),
417 parent=self.siblings(fctx.parents()),
417 parent=self.siblings(fctx.parents()),
418 child=self.siblings(fctx.children()),
418 child=self.siblings(fctx.children()),
419 permissions=fctx.manifest().execf(f))
419 permissions=fctx.manifest().execf(f))
420
420
421 def manifest(self, ctx, path):
421 def manifest(self, ctx, path):
422 mf = ctx.manifest()
422 mf = ctx.manifest()
423 node = ctx.node()
423 node = ctx.node()
424
424
425 files = {}
425 files = {}
426
426
427 p = path[1:]
427 p = path[1:]
428 if p and p[-1] != "/":
428 if p and p[-1] != "/":
429 p += "/"
429 p += "/"
430 l = len(p)
430 l = len(p)
431
431
432 for f,n in mf.items():
432 for f,n in mf.items():
433 if f[:l] != p:
433 if f[:l] != p:
434 continue
434 continue
435 remain = f[l:]
435 remain = f[l:]
436 if "/" in remain:
436 if "/" in remain:
437 short = remain[:remain.index("/") + 1] # bleah
437 short = remain[:remain.index("/") + 1] # bleah
438 files[short] = (f, None)
438 files[short] = (f, None)
439 else:
439 else:
440 short = os.path.basename(remain)
440 short = os.path.basename(remain)
441 files[short] = (f, n)
441 files[short] = (f, n)
442
442
443 def filelist(**map):
443 def filelist(**map):
444 parity = 0
444 parity = 0
445 fl = files.keys()
445 fl = files.keys()
446 fl.sort()
446 fl.sort()
447 for f in fl:
447 for f in fl:
448 full, fnode = files[f]
448 full, fnode = files[f]
449 if not fnode:
449 if not fnode:
450 continue
450 continue
451
451
452 yield {"file": full,
452 yield {"file": full,
453 "parity": self.stripes(parity),
453 "parity": self.stripes(parity),
454 "basename": f,
454 "basename": f,
455 "size": ctx.filectx(full).size(),
455 "size": ctx.filectx(full).size(),
456 "permissions": mf.execf(full)}
456 "permissions": mf.execf(full)}
457 parity += 1
457 parity += 1
458
458
459 def dirlist(**map):
459 def dirlist(**map):
460 parity = 0
460 parity = 0
461 fl = files.keys()
461 fl = files.keys()
462 fl.sort()
462 fl.sort()
463 for f in fl:
463 for f in fl:
464 full, fnode = files[f]
464 full, fnode = files[f]
465 if fnode:
465 if fnode:
466 continue
466 continue
467
467
468 yield {"parity": self.stripes(parity),
468 yield {"parity": self.stripes(parity),
469 "path": os.path.join(path, f),
469 "path": os.path.join(path, f),
470 "basename": f[:-1]}
470 "basename": f[:-1]}
471 parity += 1
471 parity += 1
472
472
473 yield self.t("manifest",
473 yield self.t("manifest",
474 rev=ctx.rev(),
474 rev=ctx.rev(),
475 node=hex(node),
475 node=hex(node),
476 path=path,
476 path=path,
477 up=_up(path),
477 up=_up(path),
478 fentries=filelist,
478 fentries=filelist,
479 dentries=dirlist,
479 dentries=dirlist,
480 archives=self.archivelist(hex(node)))
480 archives=self.archivelist(hex(node)))
481
481
482 def tags(self):
482 def tags(self):
483 cl = self.repo.changelog
483 cl = self.repo.changelog
484
484
485 i = self.repo.tagslist()
485 i = self.repo.tagslist()
486 i.reverse()
486 i.reverse()
487
487
488 def entries(notip=False, **map):
488 def entries(notip=False, **map):
489 parity = 0
489 parity = 0
490 for k,n in i:
490 for k,n in i:
491 if notip and k == "tip": continue
491 if notip and k == "tip": continue
492 yield {"parity": self.stripes(parity),
492 yield {"parity": self.stripes(parity),
493 "tag": k,
493 "tag": k,
494 "date": cl.read(n)[2],
494 "date": cl.read(n)[2],
495 "node": hex(n)}
495 "node": hex(n)}
496 parity += 1
496 parity += 1
497
497
498 yield self.t("tags",
498 yield self.t("tags",
499 node=hex(self.repo.changelog.tip()),
499 node=hex(self.repo.changelog.tip()),
500 entries=lambda **x: entries(False, **x),
500 entries=lambda **x: entries(False, **x),
501 entriesnotip=lambda **x: entries(True, **x))
501 entriesnotip=lambda **x: entries(True, **x))
502
502
503 def summary(self):
503 def summary(self):
504 cl = self.repo.changelog
504 cl = self.repo.changelog
505
505
506 i = self.repo.tagslist()
506 i = self.repo.tagslist()
507 i.reverse()
507 i.reverse()
508
508
509 def tagentries(**map):
509 def tagentries(**map):
510 parity = 0
510 parity = 0
511 count = 0
511 count = 0
512 for k,n in i:
512 for k,n in i:
513 if k == "tip": # skip tip
513 if k == "tip": # skip tip
514 continue;
514 continue;
515
515
516 count += 1
516 count += 1
517 if count > 10: # limit to 10 tags
517 if count > 10: # limit to 10 tags
518 break;
518 break;
519
519
520 c = cl.read(n)
520 c = cl.read(n)
521 t = c[2]
521 t = c[2]
522
522
523 yield self.t("tagentry",
523 yield self.t("tagentry",
524 parity = self.stripes(parity),
524 parity = self.stripes(parity),
525 tag = k,
525 tag = k,
526 node = hex(n),
526 node = hex(n),
527 date = t)
527 date = t)
528 parity += 1
528 parity += 1
529
529
530 def changelist(**map):
530 def changelist(**map):
531 parity = 0
531 parity = 0
532 cl = self.repo.changelog
532 cl = self.repo.changelog
533 l = [] # build a list in forward order for efficiency
533 l = [] # build a list in forward order for efficiency
534 for i in xrange(start, end):
534 for i in xrange(start, end):
535 n = cl.node(i)
535 n = cl.node(i)
536 changes = cl.read(n)
536 changes = cl.read(n)
537 hn = hex(n)
537 hn = hex(n)
538 t = changes[2]
538 t = changes[2]
539
539
540 l.insert(0, self.t(
540 l.insert(0, self.t(
541 'shortlogentry',
541 'shortlogentry',
542 parity = parity,
542 parity = parity,
543 author = changes[1],
543 author = changes[1],
544 desc = changes[4],
544 desc = changes[4],
545 date = t,
545 date = t,
546 rev = i,
546 rev = i,
547 node = hn))
547 node = hn))
548 parity = 1 - parity
548 parity = 1 - parity
549
549
550 yield l
550 yield l
551
551
552 count = cl.count()
552 count = cl.count()
553 start = max(0, count - self.maxchanges)
553 start = max(0, count - self.maxchanges)
554 end = min(count, start + self.maxchanges)
554 end = min(count, start + self.maxchanges)
555
555
556 yield self.t("summary",
556 yield self.t("summary",
557 desc = self.repo.ui.config("web", "description", "unknown"),
557 desc = self.repo.ui.config("web", "description", "unknown"),
558 owner = (self.repo.ui.config("ui", "username") or # preferred
558 owner = (self.repo.ui.config("ui", "username") or # preferred
559 self.repo.ui.config("web", "contact") or # deprecated
559 self.repo.ui.config("web", "contact") or # deprecated
560 self.repo.ui.config("web", "author", "unknown")), # also
560 self.repo.ui.config("web", "author", "unknown")), # also
561 lastchange = cl.read(cl.tip())[2],
561 lastchange = cl.read(cl.tip())[2],
562 tags = tagentries,
562 tags = tagentries,
563 shortlog = changelist,
563 shortlog = changelist,
564 node = hex(cl.tip()),
564 node = hex(cl.tip()),
565 archives=self.archivelist("tip"))
565 archives=self.archivelist("tip"))
566
566
567 def filediff(self, fctx):
567 def filediff(self, fctx):
568 n = fctx.node()
568 n = fctx.node()
569 path = fctx.path()
569 path = fctx.path()
570 parents = fctx.parents()
570 parents = fctx.parents()
571 p1 = parents and parents[0].node() or nullid
571 p1 = parents and parents[0].node() or nullid
572
572
573 def diff(**map):
573 def diff(**map):
574 yield self.diff(p1, n, [path])
574 yield self.diff(p1, n, [path])
575
575
576 yield self.t("filediff",
576 yield self.t("filediff",
577 file=path,
577 file=path,
578 node=hex(n),
578 node=hex(n),
579 rev=fctx.rev(),
579 rev=fctx.rev(),
580 parent=self.siblings(parents),
580 parent=self.siblings(parents),
581 child=self.siblings(fctx.children()),
581 child=self.siblings(fctx.children()),
582 diff=diff)
582 diff=diff)
583
583
584 archive_specs = {
584 archive_specs = {
585 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
585 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
586 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
586 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
587 'zip': ('application/zip', 'zip', '.zip', None),
587 'zip': ('application/zip', 'zip', '.zip', None),
588 }
588 }
589
589
590 def archive(self, req, cnode, type_):
590 def archive(self, req, cnode, type_):
591 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
591 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
592 name = "%s-%s" % (reponame, short(cnode))
592 name = "%s-%s" % (reponame, short(cnode))
593 mimetype, artype, extension, encoding = self.archive_specs[type_]
593 mimetype, artype, extension, encoding = self.archive_specs[type_]
594 headers = [('Content-type', mimetype),
594 headers = [('Content-type', mimetype),
595 ('Content-disposition', 'attachment; filename=%s%s' %
595 ('Content-disposition', 'attachment; filename=%s%s' %
596 (name, extension))]
596 (name, extension))]
597 if encoding:
597 if encoding:
598 headers.append(('Content-encoding', encoding))
598 headers.append(('Content-encoding', encoding))
599 req.header(headers)
599 req.header(headers)
600 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
600 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
601
601
602 # add tags to things
602 # add tags to things
603 # tags -> list of changesets corresponding to tags
603 # tags -> list of changesets corresponding to tags
604 # find tag, changeset, file
604 # find tag, changeset, file
605
605
606 def cleanpath(self, path):
606 def cleanpath(self, path):
607 return util.canonpath(self.repo.root, '', path)
607 return util.canonpath(self.repo.root, '', path)
608
608
609 def run(self):
609 def run(self):
610 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
610 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
611 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
611 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
612 import mercurial.hgweb.wsgicgi as wsgicgi
612 import mercurial.hgweb.wsgicgi as wsgicgi
613 from request import wsgiapplication
613 from request import wsgiapplication
614 def make_web_app():
614 def make_web_app():
615 return self
615 return self
616 wsgicgi.launch(wsgiapplication(make_web_app))
616 wsgicgi.launch(wsgiapplication(make_web_app))
617
617
618 def run_wsgi(self, req):
618 def run_wsgi(self, req):
619 def header(**map):
619 def header(**map):
620 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
620 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
621 msg = mimetools.Message(header_file, 0)
621 msg = mimetools.Message(header_file, 0)
622 req.header(msg.items())
622 req.header(msg.items())
623 yield header_file.read()
623 yield header_file.read()
624
624
625 def rawfileheader(**map):
625 def rawfileheader(**map):
626 req.header([('Content-type', map['mimetype']),
626 req.header([('Content-type', map['mimetype']),
627 ('Content-disposition', 'filename=%s' % map['file']),
627 ('Content-disposition', 'filename=%s' % map['file']),
628 ('Content-length', str(len(map['raw'])))])
628 ('Content-length', str(len(map['raw'])))])
629 yield ''
629 yield ''
630
630
631 def footer(**map):
631 def footer(**map):
632 yield self.t("footer",
632 yield self.t("footer", **map)
633 motd=self.repo.ui.config("web", "motd", ""),
633
634 **map)
634 def motd(**map):
635 yield self.repo.ui.config("web", "motd", "")
635
636
636 def expand_form(form):
637 def expand_form(form):
637 shortcuts = {
638 shortcuts = {
638 'cl': [('cmd', ['changelog']), ('rev', None)],
639 'cl': [('cmd', ['changelog']), ('rev', None)],
639 'sl': [('cmd', ['shortlog']), ('rev', None)],
640 'sl': [('cmd', ['shortlog']), ('rev', None)],
640 'cs': [('cmd', ['changeset']), ('node', None)],
641 'cs': [('cmd', ['changeset']), ('node', None)],
641 'f': [('cmd', ['file']), ('filenode', None)],
642 'f': [('cmd', ['file']), ('filenode', None)],
642 'fl': [('cmd', ['filelog']), ('filenode', None)],
643 'fl': [('cmd', ['filelog']), ('filenode', None)],
643 'fd': [('cmd', ['filediff']), ('node', None)],
644 'fd': [('cmd', ['filediff']), ('node', None)],
644 'fa': [('cmd', ['annotate']), ('filenode', None)],
645 'fa': [('cmd', ['annotate']), ('filenode', None)],
645 'mf': [('cmd', ['manifest']), ('manifest', None)],
646 'mf': [('cmd', ['manifest']), ('manifest', None)],
646 'ca': [('cmd', ['archive']), ('node', None)],
647 'ca': [('cmd', ['archive']), ('node', None)],
647 'tags': [('cmd', ['tags'])],
648 'tags': [('cmd', ['tags'])],
648 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
649 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
649 'static': [('cmd', ['static']), ('file', None)]
650 'static': [('cmd', ['static']), ('file', None)]
650 }
651 }
651
652
652 for k in shortcuts.iterkeys():
653 for k in shortcuts.iterkeys():
653 if form.has_key(k):
654 if form.has_key(k):
654 for name, value in shortcuts[k]:
655 for name, value in shortcuts[k]:
655 if value is None:
656 if value is None:
656 value = form[k]
657 value = form[k]
657 form[name] = value
658 form[name] = value
658 del form[k]
659 del form[k]
659
660
660 def rewrite_request(req):
661 def rewrite_request(req):
661 '''translate new web interface to traditional format'''
662 '''translate new web interface to traditional format'''
662
663
663 def spliturl(req):
664 def spliturl(req):
664 def firstitem(query):
665 def firstitem(query):
665 return query.split('&', 1)[0].split(';', 1)[0]
666 return query.split('&', 1)[0].split(';', 1)[0]
666
667
667 def normurl(url):
668 def normurl(url):
668 inner = '/'.join([x for x in url.split('/') if x])
669 inner = '/'.join([x for x in url.split('/') if x])
669 tl = len(url) > 1 and url.endswith('/') and '/' or ''
670 tl = len(url) > 1 and url.endswith('/') and '/' or ''
670
671
671 return '%s%s%s' % (url.startswith('/') and '/' or '',
672 return '%s%s%s' % (url.startswith('/') and '/' or '',
672 inner, tl)
673 inner, tl)
673
674
674 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
675 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
675 pi = normurl(req.env.get('PATH_INFO', ''))
676 pi = normurl(req.env.get('PATH_INFO', ''))
676 if pi:
677 if pi:
677 # strip leading /
678 # strip leading /
678 pi = pi[1:]
679 pi = pi[1:]
679 if pi:
680 if pi:
680 root = root[:-len(pi)]
681 root = root[:-len(pi)]
681 if req.env.has_key('REPO_NAME'):
682 if req.env.has_key('REPO_NAME'):
682 rn = req.env['REPO_NAME'] + '/'
683 rn = req.env['REPO_NAME'] + '/'
683 root += rn
684 root += rn
684 query = pi[len(rn):]
685 query = pi[len(rn):]
685 else:
686 else:
686 query = pi
687 query = pi
687 else:
688 else:
688 root += '?'
689 root += '?'
689 query = firstitem(req.env['QUERY_STRING'])
690 query = firstitem(req.env['QUERY_STRING'])
690
691
691 return (root, query)
692 return (root, query)
692
693
693 req.url, query = spliturl(req)
694 req.url, query = spliturl(req)
694
695
695 if req.form.has_key('cmd'):
696 if req.form.has_key('cmd'):
696 # old style
697 # old style
697 return
698 return
698
699
699 args = query.split('/', 2)
700 args = query.split('/', 2)
700 if not args or not args[0]:
701 if not args or not args[0]:
701 return
702 return
702
703
703 cmd = args.pop(0)
704 cmd = args.pop(0)
704 style = cmd.rfind('-')
705 style = cmd.rfind('-')
705 if style != -1:
706 if style != -1:
706 req.form['style'] = [cmd[:style]]
707 req.form['style'] = [cmd[:style]]
707 cmd = cmd[style+1:]
708 cmd = cmd[style+1:]
708 # avoid accepting e.g. style parameter as command
709 # avoid accepting e.g. style parameter as command
709 if hasattr(self, 'do_' + cmd):
710 if hasattr(self, 'do_' + cmd):
710 req.form['cmd'] = [cmd]
711 req.form['cmd'] = [cmd]
711
712
712 if args and args[0]:
713 if args and args[0]:
713 node = args.pop(0)
714 node = args.pop(0)
714 req.form['node'] = [node]
715 req.form['node'] = [node]
715 if args:
716 if args:
716 req.form['file'] = args
717 req.form['file'] = args
717
718
718 if cmd == 'static':
719 if cmd == 'static':
719 req.form['file'] = req.form['node']
720 req.form['file'] = req.form['node']
720 elif cmd == 'archive':
721 elif cmd == 'archive':
721 fn = req.form['node'][0]
722 fn = req.form['node'][0]
722 for type_, spec in self.archive_specs.iteritems():
723 for type_, spec in self.archive_specs.iteritems():
723 ext = spec[2]
724 ext = spec[2]
724 if fn.endswith(ext):
725 if fn.endswith(ext):
725 req.form['node'] = [fn[:-len(ext)]]
726 req.form['node'] = [fn[:-len(ext)]]
726 req.form['type'] = [type_]
727 req.form['type'] = [type_]
727
728
728 def sessionvars(**map):
729 def sessionvars(**map):
729 fields = []
730 fields = []
730 if req.form.has_key('style'):
731 if req.form.has_key('style'):
731 style = req.form['style'][0]
732 style = req.form['style'][0]
732 if style != self.repo.ui.config('web', 'style', ''):
733 if style != self.repo.ui.config('web', 'style', ''):
733 fields.append(('style', style))
734 fields.append(('style', style))
734
735
735 separator = req.url[-1] == '?' and ';' or '?'
736 separator = req.url[-1] == '?' and ';' or '?'
736 for name, value in fields:
737 for name, value in fields:
737 yield dict(name=name, value=value, separator=separator)
738 yield dict(name=name, value=value, separator=separator)
738 separator = ';'
739 separator = ';'
739
740
740 self.refresh()
741 self.refresh()
741
742
742 expand_form(req.form)
743 expand_form(req.form)
743 rewrite_request(req)
744 rewrite_request(req)
744
745
745 style = self.repo.ui.config("web", "style", "")
746 style = self.repo.ui.config("web", "style", "")
746 if req.form.has_key('style'):
747 if req.form.has_key('style'):
747 style = req.form['style'][0]
748 style = req.form['style'][0]
748 mapfile = style_map(self.templatepath, style)
749 mapfile = style_map(self.templatepath, style)
749
750
750 port = req.env["SERVER_PORT"]
751 port = req.env["SERVER_PORT"]
751 port = port != "80" and (":" + port) or ""
752 port = port != "80" and (":" + port) or ""
752 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
753 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
753
754
754 if not self.reponame:
755 if not self.reponame:
755 self.reponame = (self.repo.ui.config("web", "name")
756 self.reponame = (self.repo.ui.config("web", "name")
756 or req.env.get('REPO_NAME')
757 or req.env.get('REPO_NAME')
757 or req.url.strip('/') or self.repo.root)
758 or req.url.strip('/') or self.repo.root)
758
759
759 self.t = templater.templater(mapfile, templater.common_filters,
760 self.t = templater.templater(mapfile, templater.common_filters,
760 defaults={"url": req.url,
761 defaults={"url": req.url,
761 "urlbase": urlbase,
762 "urlbase": urlbase,
762 "repo": self.reponame,
763 "repo": self.reponame,
763 "header": header,
764 "header": header,
764 "footer": footer,
765 "footer": footer,
766 "motd": motd,
765 "rawfileheader": rawfileheader,
767 "rawfileheader": rawfileheader,
766 "sessionvars": sessionvars
768 "sessionvars": sessionvars
767 })
769 })
768
770
769 if not req.form.has_key('cmd'):
771 if not req.form.has_key('cmd'):
770 req.form['cmd'] = [self.t.cache['default'],]
772 req.form['cmd'] = [self.t.cache['default'],]
771
773
772 cmd = req.form['cmd'][0]
774 cmd = req.form['cmd'][0]
773
775
774 method = getattr(self, 'do_' + cmd, None)
776 method = getattr(self, 'do_' + cmd, None)
775 if method:
777 if method:
776 try:
778 try:
777 method(req)
779 method(req)
778 except (hg.RepoError, revlog.RevlogError), inst:
780 except (hg.RepoError, revlog.RevlogError), inst:
779 req.write(self.t("error", error=str(inst)))
781 req.write(self.t("error", error=str(inst)))
780 else:
782 else:
781 req.write(self.t("error", error='No such method: ' + cmd))
783 req.write(self.t("error", error='No such method: ' + cmd))
782
784
783 def changectx(self, req):
785 def changectx(self, req):
784 if req.form.has_key('node'):
786 if req.form.has_key('node'):
785 changeid = req.form['node'][0]
787 changeid = req.form['node'][0]
786 elif req.form.has_key('manifest'):
788 elif req.form.has_key('manifest'):
787 changeid = req.form['manifest'][0]
789 changeid = req.form['manifest'][0]
788 else:
790 else:
789 changeid = self.repo.changelog.count() - 1
791 changeid = self.repo.changelog.count() - 1
790
792
791 try:
793 try:
792 ctx = self.repo.changectx(changeid)
794 ctx = self.repo.changectx(changeid)
793 except hg.RepoError:
795 except hg.RepoError:
794 man = self.repo.manifest
796 man = self.repo.manifest
795 mn = man.lookup(changeid)
797 mn = man.lookup(changeid)
796 ctx = self.repo.changectx(man.linkrev(mn))
798 ctx = self.repo.changectx(man.linkrev(mn))
797
799
798 return ctx
800 return ctx
799
801
800 def filectx(self, req):
802 def filectx(self, req):
801 path = self.cleanpath(req.form['file'][0])
803 path = self.cleanpath(req.form['file'][0])
802 if req.form.has_key('node'):
804 if req.form.has_key('node'):
803 changeid = req.form['node'][0]
805 changeid = req.form['node'][0]
804 else:
806 else:
805 changeid = req.form['filenode'][0]
807 changeid = req.form['filenode'][0]
806 try:
808 try:
807 ctx = self.repo.changectx(changeid)
809 ctx = self.repo.changectx(changeid)
808 fctx = ctx.filectx(path)
810 fctx = ctx.filectx(path)
809 except hg.RepoError:
811 except hg.RepoError:
810 fctx = self.repo.filectx(path, fileid=changeid)
812 fctx = self.repo.filectx(path, fileid=changeid)
811
813
812 return fctx
814 return fctx
813
815
814 def stripes(self, parity):
816 def stripes(self, parity):
815 "make horizontal stripes for easier reading"
817 "make horizontal stripes for easier reading"
816 if self.stripecount:
818 if self.stripecount:
817 return (1 + parity / self.stripecount) & 1
819 return (1 + parity / self.stripecount) & 1
818 else:
820 else:
819 return 0
821 return 0
820
822
821 def do_log(self, req):
823 def do_log(self, req):
822 if req.form.has_key('file') and req.form['file'][0]:
824 if req.form.has_key('file') and req.form['file'][0]:
823 self.do_filelog(req)
825 self.do_filelog(req)
824 else:
826 else:
825 self.do_changelog(req)
827 self.do_changelog(req)
826
828
827 def do_rev(self, req):
829 def do_rev(self, req):
828 self.do_changeset(req)
830 self.do_changeset(req)
829
831
830 def do_file(self, req):
832 def do_file(self, req):
831 path = req.form.get('file', [''])[0]
833 path = req.form.get('file', [''])[0]
832 if path:
834 if path:
833 try:
835 try:
834 req.write(self.filerevision(self.filectx(req)))
836 req.write(self.filerevision(self.filectx(req)))
835 return
837 return
836 except hg.RepoError:
838 except hg.RepoError:
837 pass
839 pass
838 path = self.cleanpath(path)
840 path = self.cleanpath(path)
839
841
840 req.write(self.manifest(self.changectx(req), '/' + path))
842 req.write(self.manifest(self.changectx(req), '/' + path))
841
843
842 def do_diff(self, req):
844 def do_diff(self, req):
843 self.do_filediff(req)
845 self.do_filediff(req)
844
846
845 def do_changelog(self, req, shortlog = False):
847 def do_changelog(self, req, shortlog = False):
846 if req.form.has_key('node'):
848 if req.form.has_key('node'):
847 ctx = self.changectx(req)
849 ctx = self.changectx(req)
848 else:
850 else:
849 if req.form.has_key('rev'):
851 if req.form.has_key('rev'):
850 hi = req.form['rev'][0]
852 hi = req.form['rev'][0]
851 else:
853 else:
852 hi = self.repo.changelog.count() - 1
854 hi = self.repo.changelog.count() - 1
853 try:
855 try:
854 ctx = self.repo.changectx(hi)
856 ctx = self.repo.changectx(hi)
855 except hg.RepoError:
857 except hg.RepoError:
856 req.write(self.search(hi)) # XXX redirect to 404 page?
858 req.write(self.search(hi)) # XXX redirect to 404 page?
857 return
859 return
858
860
859 req.write(self.changelog(ctx, shortlog = shortlog))
861 req.write(self.changelog(ctx, shortlog = shortlog))
860
862
861 def do_shortlog(self, req):
863 def do_shortlog(self, req):
862 self.do_changelog(req, shortlog = True)
864 self.do_changelog(req, shortlog = True)
863
865
864 def do_changeset(self, req):
866 def do_changeset(self, req):
865 req.write(self.changeset(self.changectx(req)))
867 req.write(self.changeset(self.changectx(req)))
866
868
867 def do_manifest(self, req):
869 def do_manifest(self, req):
868 req.write(self.manifest(self.changectx(req),
870 req.write(self.manifest(self.changectx(req),
869 self.cleanpath(req.form['path'][0])))
871 self.cleanpath(req.form['path'][0])))
870
872
871 def do_tags(self, req):
873 def do_tags(self, req):
872 req.write(self.tags())
874 req.write(self.tags())
873
875
874 def do_summary(self, req):
876 def do_summary(self, req):
875 req.write(self.summary())
877 req.write(self.summary())
876
878
877 def do_filediff(self, req):
879 def do_filediff(self, req):
878 req.write(self.filediff(self.filectx(req)))
880 req.write(self.filediff(self.filectx(req)))
879
881
880 def do_annotate(self, req):
882 def do_annotate(self, req):
881 req.write(self.fileannotate(self.filectx(req)))
883 req.write(self.fileannotate(self.filectx(req)))
882
884
883 def do_filelog(self, req):
885 def do_filelog(self, req):
884 req.write(self.filelog(self.filectx(req)))
886 req.write(self.filelog(self.filectx(req)))
885
887
886 def do_lookup(self, req):
888 def do_lookup(self, req):
887 try:
889 try:
888 r = hex(self.repo.lookup(req.form['key'][0]))
890 r = hex(self.repo.lookup(req.form['key'][0]))
889 success = 1
891 success = 1
890 except Exception,inst:
892 except Exception,inst:
891 r = str(inst)
893 r = str(inst)
892 success = 0
894 success = 0
893 resp = "%s %s\n" % (success, r)
895 resp = "%s %s\n" % (success, r)
894 req.httphdr("application/mercurial-0.1", length=len(resp))
896 req.httphdr("application/mercurial-0.1", length=len(resp))
895 req.write(resp)
897 req.write(resp)
896
898
897 def do_heads(self, req):
899 def do_heads(self, req):
898 resp = " ".join(map(hex, self.repo.heads())) + "\n"
900 resp = " ".join(map(hex, self.repo.heads())) + "\n"
899 req.httphdr("application/mercurial-0.1", length=len(resp))
901 req.httphdr("application/mercurial-0.1", length=len(resp))
900 req.write(resp)
902 req.write(resp)
901
903
902 def do_branches(self, req):
904 def do_branches(self, req):
903 nodes = []
905 nodes = []
904 if req.form.has_key('nodes'):
906 if req.form.has_key('nodes'):
905 nodes = map(bin, req.form['nodes'][0].split(" "))
907 nodes = map(bin, req.form['nodes'][0].split(" "))
906 resp = cStringIO.StringIO()
908 resp = cStringIO.StringIO()
907 for b in self.repo.branches(nodes):
909 for b in self.repo.branches(nodes):
908 resp.write(" ".join(map(hex, b)) + "\n")
910 resp.write(" ".join(map(hex, b)) + "\n")
909 resp = resp.getvalue()
911 resp = resp.getvalue()
910 req.httphdr("application/mercurial-0.1", length=len(resp))
912 req.httphdr("application/mercurial-0.1", length=len(resp))
911 req.write(resp)
913 req.write(resp)
912
914
913 def do_between(self, req):
915 def do_between(self, req):
914 if req.form.has_key('pairs'):
916 if req.form.has_key('pairs'):
915 pairs = [map(bin, p.split("-"))
917 pairs = [map(bin, p.split("-"))
916 for p in req.form['pairs'][0].split(" ")]
918 for p in req.form['pairs'][0].split(" ")]
917 resp = cStringIO.StringIO()
919 resp = cStringIO.StringIO()
918 for b in self.repo.between(pairs):
920 for b in self.repo.between(pairs):
919 resp.write(" ".join(map(hex, b)) + "\n")
921 resp.write(" ".join(map(hex, b)) + "\n")
920 resp = resp.getvalue()
922 resp = resp.getvalue()
921 req.httphdr("application/mercurial-0.1", length=len(resp))
923 req.httphdr("application/mercurial-0.1", length=len(resp))
922 req.write(resp)
924 req.write(resp)
923
925
924 def do_changegroup(self, req):
926 def do_changegroup(self, req):
925 req.httphdr("application/mercurial-0.1")
927 req.httphdr("application/mercurial-0.1")
926 nodes = []
928 nodes = []
927 if not self.allowpull:
929 if not self.allowpull:
928 return
930 return
929
931
930 if req.form.has_key('roots'):
932 if req.form.has_key('roots'):
931 nodes = map(bin, req.form['roots'][0].split(" "))
933 nodes = map(bin, req.form['roots'][0].split(" "))
932
934
933 z = zlib.compressobj()
935 z = zlib.compressobj()
934 f = self.repo.changegroup(nodes, 'serve')
936 f = self.repo.changegroup(nodes, 'serve')
935 while 1:
937 while 1:
936 chunk = f.read(4096)
938 chunk = f.read(4096)
937 if not chunk:
939 if not chunk:
938 break
940 break
939 req.write(z.compress(chunk))
941 req.write(z.compress(chunk))
940
942
941 req.write(z.flush())
943 req.write(z.flush())
942
944
943 def do_changegroupsubset(self, req):
945 def do_changegroupsubset(self, req):
944 req.httphdr("application/mercurial-0.1")
946 req.httphdr("application/mercurial-0.1")
945 bases = []
947 bases = []
946 heads = []
948 heads = []
947 if not self.allowpull:
949 if not self.allowpull:
948 return
950 return
949
951
950 if req.form.has_key('bases'):
952 if req.form.has_key('bases'):
951 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
953 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
952 if req.form.has_key('heads'):
954 if req.form.has_key('heads'):
953 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
955 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
954
956
955 z = zlib.compressobj()
957 z = zlib.compressobj()
956 f = self.repo.changegroupsubset(bases, heads, 'serve')
958 f = self.repo.changegroupsubset(bases, heads, 'serve')
957 while 1:
959 while 1:
958 chunk = f.read(4096)
960 chunk = f.read(4096)
959 if not chunk:
961 if not chunk:
960 break
962 break
961 req.write(z.compress(chunk))
963 req.write(z.compress(chunk))
962
964
963 req.write(z.flush())
965 req.write(z.flush())
964
966
965 def do_archive(self, req):
967 def do_archive(self, req):
966 changeset = self.repo.lookup(req.form['node'][0])
968 changeset = self.repo.lookup(req.form['node'][0])
967 type_ = req.form['type'][0]
969 type_ = req.form['type'][0]
968 allowed = self.repo.ui.configlist("web", "allow_archive")
970 allowed = self.repo.ui.configlist("web", "allow_archive")
969 if (type_ in self.archives and (type_ in allowed or
971 if (type_ in self.archives and (type_ in allowed or
970 self.repo.ui.configbool("web", "allow" + type_, False))):
972 self.repo.ui.configbool("web", "allow" + type_, False))):
971 self.archive(req, changeset, type_)
973 self.archive(req, changeset, type_)
972 return
974 return
973
975
974 req.write(self.t("error"))
976 req.write(self.t("error"))
975
977
976 def do_static(self, req):
978 def do_static(self, req):
977 fname = req.form['file'][0]
979 fname = req.form['file'][0]
978 static = self.repo.ui.config("web", "static",
980 static = self.repo.ui.config("web", "static",
979 os.path.join(self.templatepath,
981 os.path.join(self.templatepath,
980 "static"))
982 "static"))
981 req.write(staticfile(static, fname, req)
983 req.write(staticfile(static, fname, req)
982 or self.t("error", error="%r not found" % fname))
984 or self.t("error", error="%r not found" % fname))
983
985
984 def do_capabilities(self, req):
986 def do_capabilities(self, req):
985 caps = ['unbundle', 'lookup', 'changegroupsubset']
987 caps = ['unbundle', 'lookup', 'changegroupsubset']
986 if self.repo.ui.configbool('server', 'uncompressed'):
988 if self.repo.ui.configbool('server', 'uncompressed'):
987 caps.append('stream=%d' % self.repo.revlogversion)
989 caps.append('stream=%d' % self.repo.revlogversion)
988 resp = ' '.join(caps)
990 resp = ' '.join(caps)
989 req.httphdr("application/mercurial-0.1", length=len(resp))
991 req.httphdr("application/mercurial-0.1", length=len(resp))
990 req.write(resp)
992 req.write(resp)
991
993
992 def check_perm(self, req, op, default):
994 def check_perm(self, req, op, default):
993 '''check permission for operation based on user auth.
995 '''check permission for operation based on user auth.
994 return true if op allowed, else false.
996 return true if op allowed, else false.
995 default is policy to use if no config given.'''
997 default is policy to use if no config given.'''
996
998
997 user = req.env.get('REMOTE_USER')
999 user = req.env.get('REMOTE_USER')
998
1000
999 deny = self.repo.ui.configlist('web', 'deny_' + op)
1001 deny = self.repo.ui.configlist('web', 'deny_' + op)
1000 if deny and (not user or deny == ['*'] or user in deny):
1002 if deny and (not user or deny == ['*'] or user in deny):
1001 return False
1003 return False
1002
1004
1003 allow = self.repo.ui.configlist('web', 'allow_' + op)
1005 allow = self.repo.ui.configlist('web', 'allow_' + op)
1004 return (allow and (allow == ['*'] or user in allow)) or default
1006 return (allow and (allow == ['*'] or user in allow)) or default
1005
1007
1006 def do_unbundle(self, req):
1008 def do_unbundle(self, req):
1007 def bail(response, headers={}):
1009 def bail(response, headers={}):
1008 length = int(req.env['CONTENT_LENGTH'])
1010 length = int(req.env['CONTENT_LENGTH'])
1009 for s in util.filechunkiter(req, limit=length):
1011 for s in util.filechunkiter(req, limit=length):
1010 # drain incoming bundle, else client will not see
1012 # drain incoming bundle, else client will not see
1011 # response when run outside cgi script
1013 # response when run outside cgi script
1012 pass
1014 pass
1013 req.httphdr("application/mercurial-0.1", headers=headers)
1015 req.httphdr("application/mercurial-0.1", headers=headers)
1014 req.write('0\n')
1016 req.write('0\n')
1015 req.write(response)
1017 req.write(response)
1016
1018
1017 # require ssl by default, auth info cannot be sniffed and
1019 # require ssl by default, auth info cannot be sniffed and
1018 # replayed
1020 # replayed
1019 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
1021 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
1020 if ssl_req:
1022 if ssl_req:
1021 if not req.env.get('HTTPS'):
1023 if not req.env.get('HTTPS'):
1022 bail(_('ssl required\n'))
1024 bail(_('ssl required\n'))
1023 return
1025 return
1024 proto = 'https'
1026 proto = 'https'
1025 else:
1027 else:
1026 proto = 'http'
1028 proto = 'http'
1027
1029
1028 # do not allow push unless explicitly allowed
1030 # do not allow push unless explicitly allowed
1029 if not self.check_perm(req, 'push', False):
1031 if not self.check_perm(req, 'push', False):
1030 bail(_('push not authorized\n'),
1032 bail(_('push not authorized\n'),
1031 headers={'status': '401 Unauthorized'})
1033 headers={'status': '401 Unauthorized'})
1032 return
1034 return
1033
1035
1034 req.httphdr("application/mercurial-0.1")
1036 req.httphdr("application/mercurial-0.1")
1035
1037
1036 their_heads = req.form['heads'][0].split(' ')
1038 their_heads = req.form['heads'][0].split(' ')
1037
1039
1038 def check_heads():
1040 def check_heads():
1039 heads = map(hex, self.repo.heads())
1041 heads = map(hex, self.repo.heads())
1040 return their_heads == [hex('force')] or their_heads == heads
1042 return their_heads == [hex('force')] or their_heads == heads
1041
1043
1042 # fail early if possible
1044 # fail early if possible
1043 if not check_heads():
1045 if not check_heads():
1044 bail(_('unsynced changes\n'))
1046 bail(_('unsynced changes\n'))
1045 return
1047 return
1046
1048
1047 # do not lock repo until all changegroup data is
1049 # do not lock repo until all changegroup data is
1048 # streamed. save to temporary file.
1050 # streamed. save to temporary file.
1049
1051
1050 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1052 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1051 fp = os.fdopen(fd, 'wb+')
1053 fp = os.fdopen(fd, 'wb+')
1052 try:
1054 try:
1053 length = int(req.env['CONTENT_LENGTH'])
1055 length = int(req.env['CONTENT_LENGTH'])
1054 for s in util.filechunkiter(req, limit=length):
1056 for s in util.filechunkiter(req, limit=length):
1055 fp.write(s)
1057 fp.write(s)
1056
1058
1057 lock = self.repo.lock()
1059 lock = self.repo.lock()
1058 try:
1060 try:
1059 if not check_heads():
1061 if not check_heads():
1060 req.write('0\n')
1062 req.write('0\n')
1061 req.write(_('unsynced changes\n'))
1063 req.write(_('unsynced changes\n'))
1062 return
1064 return
1063
1065
1064 fp.seek(0)
1066 fp.seek(0)
1065
1067
1066 # send addchangegroup output to client
1068 # send addchangegroup output to client
1067
1069
1068 old_stdout = sys.stdout
1070 old_stdout = sys.stdout
1069 sys.stdout = cStringIO.StringIO()
1071 sys.stdout = cStringIO.StringIO()
1070
1072
1071 try:
1073 try:
1072 url = 'remote:%s:%s' % (proto,
1074 url = 'remote:%s:%s' % (proto,
1073 req.env.get('REMOTE_HOST', ''))
1075 req.env.get('REMOTE_HOST', ''))
1074 ret = self.repo.addchangegroup(fp, 'serve', url)
1076 ret = self.repo.addchangegroup(fp, 'serve', url)
1075 finally:
1077 finally:
1076 val = sys.stdout.getvalue()
1078 val = sys.stdout.getvalue()
1077 sys.stdout = old_stdout
1079 sys.stdout = old_stdout
1078 req.write('%d\n' % ret)
1080 req.write('%d\n' % ret)
1079 req.write(val)
1081 req.write(val)
1080 finally:
1082 finally:
1081 lock.release()
1083 lock.release()
1082 finally:
1084 finally:
1083 fp.close()
1085 fp.close()
1084 os.unlink(tempname)
1086 os.unlink(tempname)
1085
1087
1086 def do_stream_out(self, req):
1088 def do_stream_out(self, req):
1087 req.httphdr("application/mercurial-0.1")
1089 req.httphdr("application/mercurial-0.1")
1088 streamclone.stream_out(self.repo, req)
1090 streamclone.stream_out(self.repo, req)
@@ -1,206 +1,209 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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(), "mimetools cStringIO")
11 demandload(globals(), "mimetools cStringIO")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
15 from mercurial.i18n import gettext as _
15 from mercurial.i18n import gettext as _
16
16
17 # This is a stopgap
17 # This is a stopgap
18 class hgwebdir(object):
18 class hgwebdir(object):
19 def __init__(self, config):
19 def __init__(self, config):
20 def cleannames(items):
20 def cleannames(items):
21 return [(name.strip(os.sep), path) for name, path in items]
21 return [(name.strip(os.sep), path) for name, path in items]
22
22
23 self.motd = ""
23 self.motd = ""
24 self.style = ""
24 self.style = ""
25 self.repos_sorted = ('name', False)
25 self.repos_sorted = ('name', False)
26 if isinstance(config, (list, tuple)):
26 if isinstance(config, (list, tuple)):
27 self.repos = cleannames(config)
27 self.repos = cleannames(config)
28 self.repos_sorted = ('', False)
28 self.repos_sorted = ('', False)
29 elif isinstance(config, dict):
29 elif isinstance(config, dict):
30 self.repos = cleannames(config.items())
30 self.repos = cleannames(config.items())
31 self.repos.sort()
31 self.repos.sort()
32 else:
32 else:
33 cp = util.configparser()
33 cp = util.configparser()
34 cp.read(config)
34 cp.read(config)
35 self.repos = []
35 self.repos = []
36 if cp.has_section('web'):
36 if cp.has_section('web'):
37 if cp.has_option('web', 'motd'):
37 if cp.has_option('web', 'motd'):
38 self.motd = cp.get('web', 'motd')
38 self.motd = cp.get('web', 'motd')
39 if cp.has_option('web', 'style'):
39 if cp.has_option('web', 'style'):
40 self.style = cp.get('web', 'style')
40 self.style = cp.get('web', 'style')
41 if cp.has_section('paths'):
41 if cp.has_section('paths'):
42 self.repos.extend(cleannames(cp.items('paths')))
42 self.repos.extend(cleannames(cp.items('paths')))
43 if cp.has_section('collections'):
43 if cp.has_section('collections'):
44 for prefix, root in cp.items('collections'):
44 for prefix, root in cp.items('collections'):
45 for path in util.walkrepos(root):
45 for path in util.walkrepos(root):
46 repo = os.path.normpath(path)
46 repo = os.path.normpath(path)
47 name = repo
47 name = repo
48 if name.startswith(prefix):
48 if name.startswith(prefix):
49 name = name[len(prefix):]
49 name = name[len(prefix):]
50 self.repos.append((name.lstrip(os.sep), repo))
50 self.repos.append((name.lstrip(os.sep), repo))
51 self.repos.sort()
51 self.repos.sort()
52
52
53 def run(self):
53 def run(self):
54 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
54 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
55 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
55 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
56 import mercurial.hgweb.wsgicgi as wsgicgi
56 import mercurial.hgweb.wsgicgi as wsgicgi
57 from request import wsgiapplication
57 from request import wsgiapplication
58 def make_web_app():
58 def make_web_app():
59 return self
59 return self
60 wsgicgi.launch(wsgiapplication(make_web_app))
60 wsgicgi.launch(wsgiapplication(make_web_app))
61
61
62 def run_wsgi(self, req):
62 def run_wsgi(self, req):
63 def header(**map):
63 def header(**map):
64 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
64 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
65 msg = mimetools.Message(header_file, 0)
65 msg = mimetools.Message(header_file, 0)
66 req.header(msg.items())
66 req.header(msg.items())
67 yield header_file.read()
67 yield header_file.read()
68
68
69 def footer(**map):
69 def footer(**map):
70 yield tmpl("footer", **map)
70 yield tmpl("footer", **map)
71
71
72 def motd(**map):
73 yield self.motd
74
72 url = req.env['REQUEST_URI'].split('?')[0]
75 url = req.env['REQUEST_URI'].split('?')[0]
73 if not url.endswith('/'):
76 if not url.endswith('/'):
74 url += '/'
77 url += '/'
75
78
76 style = self.style
79 style = self.style
77 if req.form.has_key('style'):
80 if req.form.has_key('style'):
78 style = req.form['style'][0]
81 style = req.form['style'][0]
79 mapfile = style_map(templater.templatepath(), style)
82 mapfile = style_map(templater.templatepath(), style)
80 tmpl = templater.templater(mapfile, templater.common_filters,
83 tmpl = templater.templater(mapfile, templater.common_filters,
81 defaults={"header": header,
84 defaults={"header": header,
82 "footer": footer,
85 "footer": footer,
86 "motd": motd,
83 "url": url})
87 "url": url})
84
88
85 def archivelist(ui, nodeid, url):
89 def archivelist(ui, nodeid, url):
86 allowed = ui.configlist("web", "allow_archive")
90 allowed = ui.configlist("web", "allow_archive")
87 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
91 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
88 if i[0] in allowed or ui.configbool("web", "allow" + i[0]):
92 if i[0] in allowed or ui.configbool("web", "allow" + i[0]):
89 yield {"type" : i[0], "extension": i[1],
93 yield {"type" : i[0], "extension": i[1],
90 "node": nodeid, "url": url}
94 "node": nodeid, "url": url}
91
95
92 def entries(sortcolumn="", descending=False, **map):
96 def entries(sortcolumn="", descending=False, **map):
93 def sessionvars(**map):
97 def sessionvars(**map):
94 fields = []
98 fields = []
95 if req.form.has_key('style'):
99 if req.form.has_key('style'):
96 style = req.form['style'][0]
100 style = req.form['style'][0]
97 if style != get('web', 'style', ''):
101 if style != get('web', 'style', ''):
98 fields.append(('style', style))
102 fields.append(('style', style))
99
103
100 separator = url[-1] == '?' and ';' or '?'
104 separator = url[-1] == '?' and ';' or '?'
101 for name, value in fields:
105 for name, value in fields:
102 yield dict(name=name, value=value, separator=separator)
106 yield dict(name=name, value=value, separator=separator)
103 separator = ';'
107 separator = ';'
104
108
105 rows = []
109 rows = []
106 parity = 0
110 parity = 0
107 for name, path in self.repos:
111 for name, path in self.repos:
108 u = ui.ui()
112 u = ui.ui()
109 try:
113 try:
110 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
114 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
111 except IOError:
115 except IOError:
112 pass
116 pass
113 get = u.config
117 get = u.config
114
118
115 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
119 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
116 .replace("//", "/")) + '/'
120 .replace("//", "/")) + '/'
117
121
118 # update time with local timezone
122 # update time with local timezone
119 try:
123 try:
120 d = (get_mtime(path), util.makedate()[1])
124 d = (get_mtime(path), util.makedate()[1])
121 except OSError:
125 except OSError:
122 continue
126 continue
123
127
124 contact = (get("ui", "username") or # preferred
128 contact = (get("ui", "username") or # preferred
125 get("web", "contact") or # deprecated
129 get("web", "contact") or # deprecated
126 get("web", "author", "")) # also
130 get("web", "author", "")) # also
127 description = get("web", "description", "")
131 description = get("web", "description", "")
128 name = get("web", "name", name)
132 name = get("web", "name", name)
129 row = dict(contact=contact or "unknown",
133 row = dict(contact=contact or "unknown",
130 contact_sort=contact.upper() or "unknown",
134 contact_sort=contact.upper() or "unknown",
131 name=name,
135 name=name,
132 name_sort=name,
136 name_sort=name,
133 url=url,
137 url=url,
134 description=description or "unknown",
138 description=description or "unknown",
135 description_sort=description.upper() or "unknown",
139 description_sort=description.upper() or "unknown",
136 lastchange=d,
140 lastchange=d,
137 lastchange_sort=d[1]-d[0],
141 lastchange_sort=d[1]-d[0],
138 sessionvars=sessionvars,
142 sessionvars=sessionvars,
139 archives=archivelist(u, "tip", url))
143 archives=archivelist(u, "tip", url))
140 if (not sortcolumn
144 if (not sortcolumn
141 or (sortcolumn, descending) == self.repos_sorted):
145 or (sortcolumn, descending) == self.repos_sorted):
142 # fast path for unsorted output
146 # fast path for unsorted output
143 row['parity'] = parity
147 row['parity'] = parity
144 parity = 1 - parity
148 parity = 1 - parity
145 yield row
149 yield row
146 else:
150 else:
147 rows.append((row["%s_sort" % sortcolumn], row))
151 rows.append((row["%s_sort" % sortcolumn], row))
148 if rows:
152 if rows:
149 rows.sort()
153 rows.sort()
150 if descending:
154 if descending:
151 rows.reverse()
155 rows.reverse()
152 for key, row in rows:
156 for key, row in rows:
153 row['parity'] = parity
157 row['parity'] = parity
154 parity = 1 - parity
158 parity = 1 - parity
155 yield row
159 yield row
156
160
157 virtual = req.env.get("PATH_INFO", "").strip('/')
161 virtual = req.env.get("PATH_INFO", "").strip('/')
158 if virtual.startswith('static/'):
162 if virtual.startswith('static/'):
159 static = os.path.join(templater.templatepath(), 'static')
163 static = os.path.join(templater.templatepath(), 'static')
160 fname = virtual[7:]
164 fname = virtual[7:]
161 req.write(staticfile(static, fname, req) or
165 req.write(staticfile(static, fname, req) or
162 tmpl('error', error='%r not found' % fname))
166 tmpl('error', error='%r not found' % fname))
163 elif virtual:
167 elif virtual:
164 while virtual:
168 while virtual:
165 real = dict(self.repos).get(virtual)
169 real = dict(self.repos).get(virtual)
166 if real:
170 if real:
167 break
171 break
168 up = virtual.rfind('/')
172 up = virtual.rfind('/')
169 if up < 0:
173 if up < 0:
170 break
174 break
171 virtual = virtual[:up]
175 virtual = virtual[:up]
172 if real:
176 if real:
173 req.env['REPO_NAME'] = virtual
177 req.env['REPO_NAME'] = virtual
174 try:
178 try:
175 hgweb(real).run_wsgi(req)
179 hgweb(real).run_wsgi(req)
176 except IOError, inst:
180 except IOError, inst:
177 req.write(tmpl("error", error=inst.strerror))
181 req.write(tmpl("error", error=inst.strerror))
178 except hg.RepoError, inst:
182 except hg.RepoError, inst:
179 req.write(tmpl("error", error=str(inst)))
183 req.write(tmpl("error", error=str(inst)))
180 else:
184 else:
181 req.write(tmpl("notfound", repo=virtual))
185 req.write(tmpl("notfound", repo=virtual))
182 else:
186 else:
183 if req.form.has_key('static'):
187 if req.form.has_key('static'):
184 static = os.path.join(templater.templatepath(), "static")
188 static = os.path.join(templater.templatepath(), "static")
185 fname = req.form['static'][0]
189 fname = req.form['static'][0]
186 req.write(staticfile(static, fname, req)
190 req.write(staticfile(static, fname, req)
187 or tmpl("error", error="%r not found" % fname))
191 or tmpl("error", error="%r not found" % fname))
188 else:
192 else:
189 sortable = ["name", "description", "contact", "lastchange"]
193 sortable = ["name", "description", "contact", "lastchange"]
190 sortcolumn, descending = self.repos_sorted
194 sortcolumn, descending = self.repos_sorted
191 if req.form.has_key('sort'):
195 if req.form.has_key('sort'):
192 sortcolumn = req.form['sort'][0]
196 sortcolumn = req.form['sort'][0]
193 descending = sortcolumn.startswith('-')
197 descending = sortcolumn.startswith('-')
194 if descending:
198 if descending:
195 sortcolumn = sortcolumn[1:]
199 sortcolumn = sortcolumn[1:]
196 if sortcolumn not in sortable:
200 if sortcolumn not in sortable:
197 sortcolumn = ""
201 sortcolumn = ""
198
202
199 sort = [("sort_%s" % column,
203 sort = [("sort_%s" % column,
200 "%s%s" % ((not descending and column == sortcolumn)
204 "%s%s" % ((not descending and column == sortcolumn)
201 and "-" or "", column))
205 and "-" or "", column))
202 for column in sortable]
206 for column in sortable]
203 req.write(tmpl("index", entries=entries,
207 req.write(tmpl("index", entries=entries,
204 sortcolumn=sortcolumn, descending=descending,
208 sortcolumn=sortcolumn, descending=descending,
205 motd=self.motd,
206 **dict(sort)))
209 **dict(sort)))
General Comments 0
You need to be logged in to leave comments. Login now