##// END OF EJS Templates
web: provide diffstat to the changeset page...
Steven Brown -
r14490:1d3e2349 default
parent child Browse files
Show More
@@ -1,831 +1,836 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os, mimetypes, re, cgi, copy
8 import os, mimetypes, re, cgi, copy
9 import webutil
9 import webutil
10 from mercurial import error, encoding, archival, templater, templatefilters
10 from mercurial import error, encoding, archival, templater, templatefilters
11 from mercurial.node import short, hex
11 from mercurial.node import short, hex
12 from mercurial.util import binary
12 from mercurial.util import binary
13 from common import paritygen, staticfile, get_contact, ErrorResponse
13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 from mercurial import graphmod
15 from mercurial import graphmod
16 from mercurial import help as helpmod
16 from mercurial import help as helpmod
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18
18
19 # __all__ is populated with the allowed commands. Be sure to add to it if
19 # __all__ is populated with the allowed commands. Be sure to add to it if
20 # you're adding a new command, or the new command won't work.
20 # you're adding a new command, or the new command won't work.
21
21
22 __all__ = [
22 __all__ = [
23 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
23 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
24 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
24 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
25 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
25 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
26 ]
26 ]
27
27
28 def log(web, req, tmpl):
28 def log(web, req, tmpl):
29 if 'file' in req.form and req.form['file'][0]:
29 if 'file' in req.form and req.form['file'][0]:
30 return filelog(web, req, tmpl)
30 return filelog(web, req, tmpl)
31 else:
31 else:
32 return changelog(web, req, tmpl)
32 return changelog(web, req, tmpl)
33
33
34 def rawfile(web, req, tmpl):
34 def rawfile(web, req, tmpl):
35 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
35 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
36 if not path:
36 if not path:
37 content = manifest(web, req, tmpl)
37 content = manifest(web, req, tmpl)
38 req.respond(HTTP_OK, web.ctype)
38 req.respond(HTTP_OK, web.ctype)
39 return content
39 return content
40
40
41 try:
41 try:
42 fctx = webutil.filectx(web.repo, req)
42 fctx = webutil.filectx(web.repo, req)
43 except error.LookupError, inst:
43 except error.LookupError, inst:
44 try:
44 try:
45 content = manifest(web, req, tmpl)
45 content = manifest(web, req, tmpl)
46 req.respond(HTTP_OK, web.ctype)
46 req.respond(HTTP_OK, web.ctype)
47 return content
47 return content
48 except ErrorResponse:
48 except ErrorResponse:
49 raise inst
49 raise inst
50
50
51 path = fctx.path()
51 path = fctx.path()
52 text = fctx.data()
52 text = fctx.data()
53 mt = mimetypes.guess_type(path)[0]
53 mt = mimetypes.guess_type(path)[0]
54 if mt is None:
54 if mt is None:
55 mt = binary(text) and 'application/octet-stream' or 'text/plain'
55 mt = binary(text) and 'application/octet-stream' or 'text/plain'
56 if mt.startswith('text/'):
56 if mt.startswith('text/'):
57 mt += '; charset="%s"' % encoding.encoding
57 mt += '; charset="%s"' % encoding.encoding
58
58
59 req.respond(HTTP_OK, mt, path, len(text))
59 req.respond(HTTP_OK, mt, path, len(text))
60 return [text]
60 return [text]
61
61
62 def _filerevision(web, tmpl, fctx):
62 def _filerevision(web, tmpl, fctx):
63 f = fctx.path()
63 f = fctx.path()
64 text = fctx.data()
64 text = fctx.data()
65 parity = paritygen(web.stripecount)
65 parity = paritygen(web.stripecount)
66
66
67 if binary(text):
67 if binary(text):
68 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
68 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
69 text = '(binary:%s)' % mt
69 text = '(binary:%s)' % mt
70
70
71 def lines():
71 def lines():
72 for lineno, t in enumerate(text.splitlines(True)):
72 for lineno, t in enumerate(text.splitlines(True)):
73 yield {"line": t,
73 yield {"line": t,
74 "lineid": "l%d" % (lineno + 1),
74 "lineid": "l%d" % (lineno + 1),
75 "linenumber": "% 6d" % (lineno + 1),
75 "linenumber": "% 6d" % (lineno + 1),
76 "parity": parity.next()}
76 "parity": parity.next()}
77
77
78 return tmpl("filerevision",
78 return tmpl("filerevision",
79 file=f,
79 file=f,
80 path=webutil.up(f),
80 path=webutil.up(f),
81 text=lines(),
81 text=lines(),
82 rev=fctx.rev(),
82 rev=fctx.rev(),
83 node=fctx.hex(),
83 node=fctx.hex(),
84 author=fctx.user(),
84 author=fctx.user(),
85 date=fctx.date(),
85 date=fctx.date(),
86 desc=fctx.description(),
86 desc=fctx.description(),
87 branch=webutil.nodebranchnodefault(fctx),
87 branch=webutil.nodebranchnodefault(fctx),
88 parent=webutil.parents(fctx),
88 parent=webutil.parents(fctx),
89 child=webutil.children(fctx),
89 child=webutil.children(fctx),
90 rename=webutil.renamelink(fctx),
90 rename=webutil.renamelink(fctx),
91 permissions=fctx.manifest().flags(f))
91 permissions=fctx.manifest().flags(f))
92
92
93 def file(web, req, tmpl):
93 def file(web, req, tmpl):
94 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
94 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
95 if not path:
95 if not path:
96 return manifest(web, req, tmpl)
96 return manifest(web, req, tmpl)
97 try:
97 try:
98 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
98 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
99 except error.LookupError, inst:
99 except error.LookupError, inst:
100 try:
100 try:
101 return manifest(web, req, tmpl)
101 return manifest(web, req, tmpl)
102 except ErrorResponse:
102 except ErrorResponse:
103 raise inst
103 raise inst
104
104
105 def _search(web, req, tmpl):
105 def _search(web, req, tmpl):
106
106
107 query = req.form['rev'][0]
107 query = req.form['rev'][0]
108 revcount = web.maxchanges
108 revcount = web.maxchanges
109 if 'revcount' in req.form:
109 if 'revcount' in req.form:
110 revcount = int(req.form.get('revcount', [revcount])[0])
110 revcount = int(req.form.get('revcount', [revcount])[0])
111 revcount = max(revcount, 1)
111 revcount = max(revcount, 1)
112 tmpl.defaults['sessionvars']['revcount'] = revcount
112 tmpl.defaults['sessionvars']['revcount'] = revcount
113
113
114 lessvars = copy.copy(tmpl.defaults['sessionvars'])
114 lessvars = copy.copy(tmpl.defaults['sessionvars'])
115 lessvars['revcount'] = max(revcount / 2, 1)
115 lessvars['revcount'] = max(revcount / 2, 1)
116 lessvars['rev'] = query
116 lessvars['rev'] = query
117 morevars = copy.copy(tmpl.defaults['sessionvars'])
117 morevars = copy.copy(tmpl.defaults['sessionvars'])
118 morevars['revcount'] = revcount * 2
118 morevars['revcount'] = revcount * 2
119 morevars['rev'] = query
119 morevars['rev'] = query
120
120
121 def changelist(**map):
121 def changelist(**map):
122 count = 0
122 count = 0
123 qw = query.lower().split()
123 qw = query.lower().split()
124
124
125 def revgen():
125 def revgen():
126 for i in xrange(len(web.repo) - 1, 0, -100):
126 for i in xrange(len(web.repo) - 1, 0, -100):
127 l = []
127 l = []
128 for j in xrange(max(0, i - 100), i + 1):
128 for j in xrange(max(0, i - 100), i + 1):
129 ctx = web.repo[j]
129 ctx = web.repo[j]
130 l.append(ctx)
130 l.append(ctx)
131 l.reverse()
131 l.reverse()
132 for e in l:
132 for e in l:
133 yield e
133 yield e
134
134
135 for ctx in revgen():
135 for ctx in revgen():
136 miss = 0
136 miss = 0
137 for q in qw:
137 for q in qw:
138 if not (q in ctx.user().lower() or
138 if not (q in ctx.user().lower() or
139 q in ctx.description().lower() or
139 q in ctx.description().lower() or
140 q in " ".join(ctx.files()).lower()):
140 q in " ".join(ctx.files()).lower()):
141 miss = 1
141 miss = 1
142 break
142 break
143 if miss:
143 if miss:
144 continue
144 continue
145
145
146 count += 1
146 count += 1
147 n = ctx.node()
147 n = ctx.node()
148 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
148 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
149 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
149 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
150
150
151 yield tmpl('searchentry',
151 yield tmpl('searchentry',
152 parity=parity.next(),
152 parity=parity.next(),
153 author=ctx.user(),
153 author=ctx.user(),
154 parent=webutil.parents(ctx),
154 parent=webutil.parents(ctx),
155 child=webutil.children(ctx),
155 child=webutil.children(ctx),
156 changelogtag=showtags,
156 changelogtag=showtags,
157 desc=ctx.description(),
157 desc=ctx.description(),
158 date=ctx.date(),
158 date=ctx.date(),
159 files=files,
159 files=files,
160 rev=ctx.rev(),
160 rev=ctx.rev(),
161 node=hex(n),
161 node=hex(n),
162 tags=webutil.nodetagsdict(web.repo, n),
162 tags=webutil.nodetagsdict(web.repo, n),
163 bookmarks=webutil.nodebookmarksdict(web.repo, n),
163 bookmarks=webutil.nodebookmarksdict(web.repo, n),
164 inbranch=webutil.nodeinbranch(web.repo, ctx),
164 inbranch=webutil.nodeinbranch(web.repo, ctx),
165 branches=webutil.nodebranchdict(web.repo, ctx))
165 branches=webutil.nodebranchdict(web.repo, ctx))
166
166
167 if count >= revcount:
167 if count >= revcount:
168 break
168 break
169
169
170 tip = web.repo['tip']
170 tip = web.repo['tip']
171 parity = paritygen(web.stripecount)
171 parity = paritygen(web.stripecount)
172
172
173 return tmpl('search', query=query, node=tip.hex(),
173 return tmpl('search', query=query, node=tip.hex(),
174 entries=changelist, archives=web.archivelist("tip"),
174 entries=changelist, archives=web.archivelist("tip"),
175 morevars=morevars, lessvars=lessvars)
175 morevars=morevars, lessvars=lessvars)
176
176
177 def changelog(web, req, tmpl, shortlog=False):
177 def changelog(web, req, tmpl, shortlog=False):
178
178
179 if 'node' in req.form:
179 if 'node' in req.form:
180 ctx = webutil.changectx(web.repo, req)
180 ctx = webutil.changectx(web.repo, req)
181 else:
181 else:
182 if 'rev' in req.form:
182 if 'rev' in req.form:
183 hi = req.form['rev'][0]
183 hi = req.form['rev'][0]
184 else:
184 else:
185 hi = len(web.repo) - 1
185 hi = len(web.repo) - 1
186 try:
186 try:
187 ctx = web.repo[hi]
187 ctx = web.repo[hi]
188 except error.RepoError:
188 except error.RepoError:
189 return _search(web, req, tmpl) # XXX redirect to 404 page?
189 return _search(web, req, tmpl) # XXX redirect to 404 page?
190
190
191 def changelist(limit=0, **map):
191 def changelist(limit=0, **map):
192 l = [] # build a list in forward order for efficiency
192 l = [] # build a list in forward order for efficiency
193 for i in xrange(start, end):
193 for i in xrange(start, end):
194 ctx = web.repo[i]
194 ctx = web.repo[i]
195 n = ctx.node()
195 n = ctx.node()
196 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
196 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
197 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
197 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
198
198
199 l.insert(0, {"parity": parity.next(),
199 l.insert(0, {"parity": parity.next(),
200 "author": ctx.user(),
200 "author": ctx.user(),
201 "parent": webutil.parents(ctx, i - 1),
201 "parent": webutil.parents(ctx, i - 1),
202 "child": webutil.children(ctx, i + 1),
202 "child": webutil.children(ctx, i + 1),
203 "changelogtag": showtags,
203 "changelogtag": showtags,
204 "desc": ctx.description(),
204 "desc": ctx.description(),
205 "date": ctx.date(),
205 "date": ctx.date(),
206 "files": files,
206 "files": files,
207 "rev": i,
207 "rev": i,
208 "node": hex(n),
208 "node": hex(n),
209 "tags": webutil.nodetagsdict(web.repo, n),
209 "tags": webutil.nodetagsdict(web.repo, n),
210 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
210 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
211 "inbranch": webutil.nodeinbranch(web.repo, ctx),
211 "inbranch": webutil.nodeinbranch(web.repo, ctx),
212 "branches": webutil.nodebranchdict(web.repo, ctx)
212 "branches": webutil.nodebranchdict(web.repo, ctx)
213 })
213 })
214
214
215 if limit > 0:
215 if limit > 0:
216 l = l[:limit]
216 l = l[:limit]
217
217
218 for e in l:
218 for e in l:
219 yield e
219 yield e
220
220
221 revcount = shortlog and web.maxshortchanges or web.maxchanges
221 revcount = shortlog and web.maxshortchanges or web.maxchanges
222 if 'revcount' in req.form:
222 if 'revcount' in req.form:
223 revcount = int(req.form.get('revcount', [revcount])[0])
223 revcount = int(req.form.get('revcount', [revcount])[0])
224 revcount = max(revcount, 1)
224 revcount = max(revcount, 1)
225 tmpl.defaults['sessionvars']['revcount'] = revcount
225 tmpl.defaults['sessionvars']['revcount'] = revcount
226
226
227 lessvars = copy.copy(tmpl.defaults['sessionvars'])
227 lessvars = copy.copy(tmpl.defaults['sessionvars'])
228 lessvars['revcount'] = max(revcount / 2, 1)
228 lessvars['revcount'] = max(revcount / 2, 1)
229 morevars = copy.copy(tmpl.defaults['sessionvars'])
229 morevars = copy.copy(tmpl.defaults['sessionvars'])
230 morevars['revcount'] = revcount * 2
230 morevars['revcount'] = revcount * 2
231
231
232 count = len(web.repo)
232 count = len(web.repo)
233 pos = ctx.rev()
233 pos = ctx.rev()
234 start = max(0, pos - revcount + 1)
234 start = max(0, pos - revcount + 1)
235 end = min(count, start + revcount)
235 end = min(count, start + revcount)
236 pos = end - 1
236 pos = end - 1
237 parity = paritygen(web.stripecount, offset=start - end)
237 parity = paritygen(web.stripecount, offset=start - end)
238
238
239 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
239 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
240
240
241 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
241 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
242 node=ctx.hex(), rev=pos, changesets=count,
242 node=ctx.hex(), rev=pos, changesets=count,
243 entries=lambda **x: changelist(limit=0,**x),
243 entries=lambda **x: changelist(limit=0,**x),
244 latestentry=lambda **x: changelist(limit=1,**x),
244 latestentry=lambda **x: changelist(limit=1,**x),
245 archives=web.archivelist("tip"), revcount=revcount,
245 archives=web.archivelist("tip"), revcount=revcount,
246 morevars=morevars, lessvars=lessvars)
246 morevars=morevars, lessvars=lessvars)
247
247
248 def shortlog(web, req, tmpl):
248 def shortlog(web, req, tmpl):
249 return changelog(web, req, tmpl, shortlog = True)
249 return changelog(web, req, tmpl, shortlog = True)
250
250
251 def changeset(web, req, tmpl):
251 def changeset(web, req, tmpl):
252 ctx = webutil.changectx(web.repo, req)
252 ctx = webutil.changectx(web.repo, req)
253 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
253 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
254 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
254 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
255 ctx.node())
255 ctx.node())
256 showbranch = webutil.nodebranchnodefault(ctx)
256 showbranch = webutil.nodebranchnodefault(ctx)
257
257
258 files = []
258 files = []
259 parity = paritygen(web.stripecount)
259 parity = paritygen(web.stripecount)
260 for f in ctx.files():
260 for f in ctx.files():
261 template = f in ctx and 'filenodelink' or 'filenolink'
261 template = f in ctx and 'filenodelink' or 'filenolink'
262 files.append(tmpl(template,
262 files.append(tmpl(template,
263 node=ctx.hex(), file=f,
263 node=ctx.hex(), file=f,
264 parity=parity.next()))
264 parity=parity.next()))
265
265
266 parity = paritygen(web.stripecount)
267 style = web.config('web', 'style', 'paper')
266 style = web.config('web', 'style', 'paper')
268 if 'style' in req.form:
267 if 'style' in req.form:
269 style = req.form['style'][0]
268 style = req.form['style'][0]
270
269
270 parity = paritygen(web.stripecount)
271 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
271 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
272
273 parity = paritygen(web.stripecount)
274 diffstat = lambda: webutil.diffstat(tmpl, ctx, parity)
275
272 return tmpl('changeset',
276 return tmpl('changeset',
273 diff=diffs,
277 diff=diffs,
274 rev=ctx.rev(),
278 rev=ctx.rev(),
275 node=ctx.hex(),
279 node=ctx.hex(),
276 parent=webutil.parents(ctx),
280 parent=webutil.parents(ctx),
277 child=webutil.children(ctx),
281 child=webutil.children(ctx),
278 changesettag=showtags,
282 changesettag=showtags,
279 changesetbookmark=showbookmarks,
283 changesetbookmark=showbookmarks,
280 changesetbranch=showbranch,
284 changesetbranch=showbranch,
281 author=ctx.user(),
285 author=ctx.user(),
282 desc=ctx.description(),
286 desc=ctx.description(),
283 date=ctx.date(),
287 date=ctx.date(),
284 files=files,
288 files=files,
289 diffstat=diffstat,
285 archives=web.archivelist(ctx.hex()),
290 archives=web.archivelist(ctx.hex()),
286 tags=webutil.nodetagsdict(web.repo, ctx.node()),
291 tags=webutil.nodetagsdict(web.repo, ctx.node()),
287 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
292 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
288 branch=webutil.nodebranchnodefault(ctx),
293 branch=webutil.nodebranchnodefault(ctx),
289 inbranch=webutil.nodeinbranch(web.repo, ctx),
294 inbranch=webutil.nodeinbranch(web.repo, ctx),
290 branches=webutil.nodebranchdict(web.repo, ctx))
295 branches=webutil.nodebranchdict(web.repo, ctx))
291
296
292 rev = changeset
297 rev = changeset
293
298
294 def manifest(web, req, tmpl):
299 def manifest(web, req, tmpl):
295 ctx = webutil.changectx(web.repo, req)
300 ctx = webutil.changectx(web.repo, req)
296 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
301 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
297 mf = ctx.manifest()
302 mf = ctx.manifest()
298 node = ctx.node()
303 node = ctx.node()
299
304
300 files = {}
305 files = {}
301 dirs = {}
306 dirs = {}
302 parity = paritygen(web.stripecount)
307 parity = paritygen(web.stripecount)
303
308
304 if path and path[-1] != "/":
309 if path and path[-1] != "/":
305 path += "/"
310 path += "/"
306 l = len(path)
311 l = len(path)
307 abspath = "/" + path
312 abspath = "/" + path
308
313
309 for f, n in mf.iteritems():
314 for f, n in mf.iteritems():
310 if f[:l] != path:
315 if f[:l] != path:
311 continue
316 continue
312 remain = f[l:]
317 remain = f[l:]
313 elements = remain.split('/')
318 elements = remain.split('/')
314 if len(elements) == 1:
319 if len(elements) == 1:
315 files[remain] = f
320 files[remain] = f
316 else:
321 else:
317 h = dirs # need to retain ref to dirs (root)
322 h = dirs # need to retain ref to dirs (root)
318 for elem in elements[0:-1]:
323 for elem in elements[0:-1]:
319 if elem not in h:
324 if elem not in h:
320 h[elem] = {}
325 h[elem] = {}
321 h = h[elem]
326 h = h[elem]
322 if len(h) > 1:
327 if len(h) > 1:
323 break
328 break
324 h[None] = None # denotes files present
329 h[None] = None # denotes files present
325
330
326 if mf and not files and not dirs:
331 if mf and not files and not dirs:
327 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
332 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
328
333
329 def filelist(**map):
334 def filelist(**map):
330 for f in sorted(files):
335 for f in sorted(files):
331 full = files[f]
336 full = files[f]
332
337
333 fctx = ctx.filectx(full)
338 fctx = ctx.filectx(full)
334 yield {"file": full,
339 yield {"file": full,
335 "parity": parity.next(),
340 "parity": parity.next(),
336 "basename": f,
341 "basename": f,
337 "date": fctx.date(),
342 "date": fctx.date(),
338 "size": fctx.size(),
343 "size": fctx.size(),
339 "permissions": mf.flags(full)}
344 "permissions": mf.flags(full)}
340
345
341 def dirlist(**map):
346 def dirlist(**map):
342 for d in sorted(dirs):
347 for d in sorted(dirs):
343
348
344 emptydirs = []
349 emptydirs = []
345 h = dirs[d]
350 h = dirs[d]
346 while isinstance(h, dict) and len(h) == 1:
351 while isinstance(h, dict) and len(h) == 1:
347 k, v = h.items()[0]
352 k, v = h.items()[0]
348 if v:
353 if v:
349 emptydirs.append(k)
354 emptydirs.append(k)
350 h = v
355 h = v
351
356
352 path = "%s%s" % (abspath, d)
357 path = "%s%s" % (abspath, d)
353 yield {"parity": parity.next(),
358 yield {"parity": parity.next(),
354 "path": path,
359 "path": path,
355 "emptydirs": "/".join(emptydirs),
360 "emptydirs": "/".join(emptydirs),
356 "basename": d}
361 "basename": d}
357
362
358 return tmpl("manifest",
363 return tmpl("manifest",
359 rev=ctx.rev(),
364 rev=ctx.rev(),
360 node=hex(node),
365 node=hex(node),
361 path=abspath,
366 path=abspath,
362 up=webutil.up(abspath),
367 up=webutil.up(abspath),
363 upparity=parity.next(),
368 upparity=parity.next(),
364 fentries=filelist,
369 fentries=filelist,
365 dentries=dirlist,
370 dentries=dirlist,
366 archives=web.archivelist(hex(node)),
371 archives=web.archivelist(hex(node)),
367 tags=webutil.nodetagsdict(web.repo, node),
372 tags=webutil.nodetagsdict(web.repo, node),
368 bookmarks=webutil.nodebookmarksdict(web.repo, node),
373 bookmarks=webutil.nodebookmarksdict(web.repo, node),
369 inbranch=webutil.nodeinbranch(web.repo, ctx),
374 inbranch=webutil.nodeinbranch(web.repo, ctx),
370 branches=webutil.nodebranchdict(web.repo, ctx))
375 branches=webutil.nodebranchdict(web.repo, ctx))
371
376
372 def tags(web, req, tmpl):
377 def tags(web, req, tmpl):
373 i = web.repo.tagslist()
378 i = web.repo.tagslist()
374 i.reverse()
379 i.reverse()
375 parity = paritygen(web.stripecount)
380 parity = paritygen(web.stripecount)
376
381
377 def entries(notip=False, limit=0, **map):
382 def entries(notip=False, limit=0, **map):
378 count = 0
383 count = 0
379 for k, n in i:
384 for k, n in i:
380 if notip and k == "tip":
385 if notip and k == "tip":
381 continue
386 continue
382 if limit > 0 and count >= limit:
387 if limit > 0 and count >= limit:
383 continue
388 continue
384 count = count + 1
389 count = count + 1
385 yield {"parity": parity.next(),
390 yield {"parity": parity.next(),
386 "tag": k,
391 "tag": k,
387 "date": web.repo[n].date(),
392 "date": web.repo[n].date(),
388 "node": hex(n)}
393 "node": hex(n)}
389
394
390 return tmpl("tags",
395 return tmpl("tags",
391 node=hex(web.repo.changelog.tip()),
396 node=hex(web.repo.changelog.tip()),
392 entries=lambda **x: entries(False, 0, **x),
397 entries=lambda **x: entries(False, 0, **x),
393 entriesnotip=lambda **x: entries(True, 0, **x),
398 entriesnotip=lambda **x: entries(True, 0, **x),
394 latestentry=lambda **x: entries(True, 1, **x))
399 latestentry=lambda **x: entries(True, 1, **x))
395
400
396 def bookmarks(web, req, tmpl):
401 def bookmarks(web, req, tmpl):
397 i = web.repo._bookmarks.items()
402 i = web.repo._bookmarks.items()
398 parity = paritygen(web.stripecount)
403 parity = paritygen(web.stripecount)
399
404
400 def entries(limit=0, **map):
405 def entries(limit=0, **map):
401 count = 0
406 count = 0
402 for k, n in sorted(i):
407 for k, n in sorted(i):
403 if limit > 0 and count >= limit:
408 if limit > 0 and count >= limit:
404 continue
409 continue
405 count = count + 1
410 count = count + 1
406 yield {"parity": parity.next(),
411 yield {"parity": parity.next(),
407 "bookmark": k,
412 "bookmark": k,
408 "date": web.repo[n].date(),
413 "date": web.repo[n].date(),
409 "node": hex(n)}
414 "node": hex(n)}
410
415
411 return tmpl("bookmarks",
416 return tmpl("bookmarks",
412 node=hex(web.repo.changelog.tip()),
417 node=hex(web.repo.changelog.tip()),
413 entries=lambda **x: entries(0, **x),
418 entries=lambda **x: entries(0, **x),
414 latestentry=lambda **x: entries(1, **x))
419 latestentry=lambda **x: entries(1, **x))
415
420
416 def branches(web, req, tmpl):
421 def branches(web, req, tmpl):
417 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
422 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
418 heads = web.repo.heads()
423 heads = web.repo.heads()
419 parity = paritygen(web.stripecount)
424 parity = paritygen(web.stripecount)
420 sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
425 sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
421
426
422 def entries(limit, **map):
427 def entries(limit, **map):
423 count = 0
428 count = 0
424 for ctx in sorted(tips, key=sortkey, reverse=True):
429 for ctx in sorted(tips, key=sortkey, reverse=True):
425 if limit > 0 and count >= limit:
430 if limit > 0 and count >= limit:
426 return
431 return
427 count += 1
432 count += 1
428 if ctx.node() not in heads:
433 if ctx.node() not in heads:
429 status = 'inactive'
434 status = 'inactive'
430 elif not web.repo.branchheads(ctx.branch()):
435 elif not web.repo.branchheads(ctx.branch()):
431 status = 'closed'
436 status = 'closed'
432 else:
437 else:
433 status = 'open'
438 status = 'open'
434 yield {'parity': parity.next(),
439 yield {'parity': parity.next(),
435 'branch': ctx.branch(),
440 'branch': ctx.branch(),
436 'status': status,
441 'status': status,
437 'node': ctx.hex(),
442 'node': ctx.hex(),
438 'date': ctx.date()}
443 'date': ctx.date()}
439
444
440 return tmpl('branches', node=hex(web.repo.changelog.tip()),
445 return tmpl('branches', node=hex(web.repo.changelog.tip()),
441 entries=lambda **x: entries(0, **x),
446 entries=lambda **x: entries(0, **x),
442 latestentry=lambda **x: entries(1, **x))
447 latestentry=lambda **x: entries(1, **x))
443
448
444 def summary(web, req, tmpl):
449 def summary(web, req, tmpl):
445 i = web.repo.tagslist()
450 i = web.repo.tagslist()
446 i.reverse()
451 i.reverse()
447
452
448 def tagentries(**map):
453 def tagentries(**map):
449 parity = paritygen(web.stripecount)
454 parity = paritygen(web.stripecount)
450 count = 0
455 count = 0
451 for k, n in i:
456 for k, n in i:
452 if k == "tip": # skip tip
457 if k == "tip": # skip tip
453 continue
458 continue
454
459
455 count += 1
460 count += 1
456 if count > 10: # limit to 10 tags
461 if count > 10: # limit to 10 tags
457 break
462 break
458
463
459 yield tmpl("tagentry",
464 yield tmpl("tagentry",
460 parity=parity.next(),
465 parity=parity.next(),
461 tag=k,
466 tag=k,
462 node=hex(n),
467 node=hex(n),
463 date=web.repo[n].date())
468 date=web.repo[n].date())
464
469
465 def bookmarks(**map):
470 def bookmarks(**map):
466 parity = paritygen(web.stripecount)
471 parity = paritygen(web.stripecount)
467 b = web.repo._bookmarks.items()
472 b = web.repo._bookmarks.items()
468 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
473 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
469 yield {'parity': parity.next(),
474 yield {'parity': parity.next(),
470 'bookmark': k,
475 'bookmark': k,
471 'date': web.repo[n].date(),
476 'date': web.repo[n].date(),
472 'node': hex(n)}
477 'node': hex(n)}
473
478
474 def branches(**map):
479 def branches(**map):
475 parity = paritygen(web.stripecount)
480 parity = paritygen(web.stripecount)
476
481
477 b = web.repo.branchtags()
482 b = web.repo.branchtags()
478 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
483 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
479 for r, n, t in sorted(l):
484 for r, n, t in sorted(l):
480 yield {'parity': parity.next(),
485 yield {'parity': parity.next(),
481 'branch': t,
486 'branch': t,
482 'node': hex(n),
487 'node': hex(n),
483 'date': web.repo[n].date()}
488 'date': web.repo[n].date()}
484
489
485 def changelist(**map):
490 def changelist(**map):
486 parity = paritygen(web.stripecount, offset=start - end)
491 parity = paritygen(web.stripecount, offset=start - end)
487 l = [] # build a list in forward order for efficiency
492 l = [] # build a list in forward order for efficiency
488 for i in xrange(start, end):
493 for i in xrange(start, end):
489 ctx = web.repo[i]
494 ctx = web.repo[i]
490 n = ctx.node()
495 n = ctx.node()
491 hn = hex(n)
496 hn = hex(n)
492
497
493 l.insert(0, tmpl(
498 l.insert(0, tmpl(
494 'shortlogentry',
499 'shortlogentry',
495 parity=parity.next(),
500 parity=parity.next(),
496 author=ctx.user(),
501 author=ctx.user(),
497 desc=ctx.description(),
502 desc=ctx.description(),
498 date=ctx.date(),
503 date=ctx.date(),
499 rev=i,
504 rev=i,
500 node=hn,
505 node=hn,
501 tags=webutil.nodetagsdict(web.repo, n),
506 tags=webutil.nodetagsdict(web.repo, n),
502 bookmarks=webutil.nodebookmarksdict(web.repo, n),
507 bookmarks=webutil.nodebookmarksdict(web.repo, n),
503 inbranch=webutil.nodeinbranch(web.repo, ctx),
508 inbranch=webutil.nodeinbranch(web.repo, ctx),
504 branches=webutil.nodebranchdict(web.repo, ctx)))
509 branches=webutil.nodebranchdict(web.repo, ctx)))
505
510
506 yield l
511 yield l
507
512
508 tip = web.repo['tip']
513 tip = web.repo['tip']
509 count = len(web.repo)
514 count = len(web.repo)
510 start = max(0, count - web.maxchanges)
515 start = max(0, count - web.maxchanges)
511 end = min(count, start + web.maxchanges)
516 end = min(count, start + web.maxchanges)
512
517
513 return tmpl("summary",
518 return tmpl("summary",
514 desc=web.config("web", "description", "unknown"),
519 desc=web.config("web", "description", "unknown"),
515 owner=get_contact(web.config) or "unknown",
520 owner=get_contact(web.config) or "unknown",
516 lastchange=tip.date(),
521 lastchange=tip.date(),
517 tags=tagentries,
522 tags=tagentries,
518 bookmarks=bookmarks,
523 bookmarks=bookmarks,
519 branches=branches,
524 branches=branches,
520 shortlog=changelist,
525 shortlog=changelist,
521 node=tip.hex(),
526 node=tip.hex(),
522 archives=web.archivelist("tip"))
527 archives=web.archivelist("tip"))
523
528
524 def filediff(web, req, tmpl):
529 def filediff(web, req, tmpl):
525 fctx, ctx = None, None
530 fctx, ctx = None, None
526 try:
531 try:
527 fctx = webutil.filectx(web.repo, req)
532 fctx = webutil.filectx(web.repo, req)
528 except LookupError:
533 except LookupError:
529 ctx = webutil.changectx(web.repo, req)
534 ctx = webutil.changectx(web.repo, req)
530 path = webutil.cleanpath(web.repo, req.form['file'][0])
535 path = webutil.cleanpath(web.repo, req.form['file'][0])
531 if path not in ctx.files():
536 if path not in ctx.files():
532 raise
537 raise
533
538
534 if fctx is not None:
539 if fctx is not None:
535 n = fctx.node()
540 n = fctx.node()
536 path = fctx.path()
541 path = fctx.path()
537 else:
542 else:
538 n = ctx.node()
543 n = ctx.node()
539 # path already defined in except clause
544 # path already defined in except clause
540
545
541 parity = paritygen(web.stripecount)
546 parity = paritygen(web.stripecount)
542 style = web.config('web', 'style', 'paper')
547 style = web.config('web', 'style', 'paper')
543 if 'style' in req.form:
548 if 'style' in req.form:
544 style = req.form['style'][0]
549 style = req.form['style'][0]
545
550
546 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
551 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
547 rename = fctx and webutil.renamelink(fctx) or []
552 rename = fctx and webutil.renamelink(fctx) or []
548 ctx = fctx and fctx or ctx
553 ctx = fctx and fctx or ctx
549 return tmpl("filediff",
554 return tmpl("filediff",
550 file=path,
555 file=path,
551 node=hex(n),
556 node=hex(n),
552 rev=ctx.rev(),
557 rev=ctx.rev(),
553 date=ctx.date(),
558 date=ctx.date(),
554 desc=ctx.description(),
559 desc=ctx.description(),
555 author=ctx.user(),
560 author=ctx.user(),
556 rename=rename,
561 rename=rename,
557 branch=webutil.nodebranchnodefault(ctx),
562 branch=webutil.nodebranchnodefault(ctx),
558 parent=webutil.parents(ctx),
563 parent=webutil.parents(ctx),
559 child=webutil.children(ctx),
564 child=webutil.children(ctx),
560 diff=diffs)
565 diff=diffs)
561
566
562 diff = filediff
567 diff = filediff
563
568
564 def annotate(web, req, tmpl):
569 def annotate(web, req, tmpl):
565 fctx = webutil.filectx(web.repo, req)
570 fctx = webutil.filectx(web.repo, req)
566 f = fctx.path()
571 f = fctx.path()
567 parity = paritygen(web.stripecount)
572 parity = paritygen(web.stripecount)
568
573
569 def annotate(**map):
574 def annotate(**map):
570 last = None
575 last = None
571 if binary(fctx.data()):
576 if binary(fctx.data()):
572 mt = (mimetypes.guess_type(fctx.path())[0]
577 mt = (mimetypes.guess_type(fctx.path())[0]
573 or 'application/octet-stream')
578 or 'application/octet-stream')
574 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
579 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
575 '(binary:%s)' % mt)])
580 '(binary:%s)' % mt)])
576 else:
581 else:
577 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
582 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
578 for lineno, ((f, targetline), l) in lines:
583 for lineno, ((f, targetline), l) in lines:
579 fnode = f.filenode()
584 fnode = f.filenode()
580
585
581 if last != fnode:
586 if last != fnode:
582 last = fnode
587 last = fnode
583
588
584 yield {"parity": parity.next(),
589 yield {"parity": parity.next(),
585 "node": f.hex(),
590 "node": f.hex(),
586 "rev": f.rev(),
591 "rev": f.rev(),
587 "author": f.user(),
592 "author": f.user(),
588 "desc": f.description(),
593 "desc": f.description(),
589 "file": f.path(),
594 "file": f.path(),
590 "targetline": targetline,
595 "targetline": targetline,
591 "line": l,
596 "line": l,
592 "lineid": "l%d" % (lineno + 1),
597 "lineid": "l%d" % (lineno + 1),
593 "linenumber": "% 6d" % (lineno + 1),
598 "linenumber": "% 6d" % (lineno + 1),
594 "revdate": f.date()}
599 "revdate": f.date()}
595
600
596 return tmpl("fileannotate",
601 return tmpl("fileannotate",
597 file=f,
602 file=f,
598 annotate=annotate,
603 annotate=annotate,
599 path=webutil.up(f),
604 path=webutil.up(f),
600 rev=fctx.rev(),
605 rev=fctx.rev(),
601 node=fctx.hex(),
606 node=fctx.hex(),
602 author=fctx.user(),
607 author=fctx.user(),
603 date=fctx.date(),
608 date=fctx.date(),
604 desc=fctx.description(),
609 desc=fctx.description(),
605 rename=webutil.renamelink(fctx),
610 rename=webutil.renamelink(fctx),
606 branch=webutil.nodebranchnodefault(fctx),
611 branch=webutil.nodebranchnodefault(fctx),
607 parent=webutil.parents(fctx),
612 parent=webutil.parents(fctx),
608 child=webutil.children(fctx),
613 child=webutil.children(fctx),
609 permissions=fctx.manifest().flags(f))
614 permissions=fctx.manifest().flags(f))
610
615
611 def filelog(web, req, tmpl):
616 def filelog(web, req, tmpl):
612
617
613 try:
618 try:
614 fctx = webutil.filectx(web.repo, req)
619 fctx = webutil.filectx(web.repo, req)
615 f = fctx.path()
620 f = fctx.path()
616 fl = fctx.filelog()
621 fl = fctx.filelog()
617 except error.LookupError:
622 except error.LookupError:
618 f = webutil.cleanpath(web.repo, req.form['file'][0])
623 f = webutil.cleanpath(web.repo, req.form['file'][0])
619 fl = web.repo.file(f)
624 fl = web.repo.file(f)
620 numrevs = len(fl)
625 numrevs = len(fl)
621 if not numrevs: # file doesn't exist at all
626 if not numrevs: # file doesn't exist at all
622 raise
627 raise
623 rev = webutil.changectx(web.repo, req).rev()
628 rev = webutil.changectx(web.repo, req).rev()
624 first = fl.linkrev(0)
629 first = fl.linkrev(0)
625 if rev < first: # current rev is from before file existed
630 if rev < first: # current rev is from before file existed
626 raise
631 raise
627 frev = numrevs - 1
632 frev = numrevs - 1
628 while fl.linkrev(frev) > rev:
633 while fl.linkrev(frev) > rev:
629 frev -= 1
634 frev -= 1
630 fctx = web.repo.filectx(f, fl.linkrev(frev))
635 fctx = web.repo.filectx(f, fl.linkrev(frev))
631
636
632 revcount = web.maxshortchanges
637 revcount = web.maxshortchanges
633 if 'revcount' in req.form:
638 if 'revcount' in req.form:
634 revcount = int(req.form.get('revcount', [revcount])[0])
639 revcount = int(req.form.get('revcount', [revcount])[0])
635 revcount = max(revcount, 1)
640 revcount = max(revcount, 1)
636 tmpl.defaults['sessionvars']['revcount'] = revcount
641 tmpl.defaults['sessionvars']['revcount'] = revcount
637
642
638 lessvars = copy.copy(tmpl.defaults['sessionvars'])
643 lessvars = copy.copy(tmpl.defaults['sessionvars'])
639 lessvars['revcount'] = max(revcount / 2, 1)
644 lessvars['revcount'] = max(revcount / 2, 1)
640 morevars = copy.copy(tmpl.defaults['sessionvars'])
645 morevars = copy.copy(tmpl.defaults['sessionvars'])
641 morevars['revcount'] = revcount * 2
646 morevars['revcount'] = revcount * 2
642
647
643 count = fctx.filerev() + 1
648 count = fctx.filerev() + 1
644 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
649 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
645 end = min(count, start + revcount) # last rev on this page
650 end = min(count, start + revcount) # last rev on this page
646 parity = paritygen(web.stripecount, offset=start - end)
651 parity = paritygen(web.stripecount, offset=start - end)
647
652
648 def entries(limit=0, **map):
653 def entries(limit=0, **map):
649 l = []
654 l = []
650
655
651 repo = web.repo
656 repo = web.repo
652 for i in xrange(start, end):
657 for i in xrange(start, end):
653 iterfctx = fctx.filectx(i)
658 iterfctx = fctx.filectx(i)
654
659
655 l.insert(0, {"parity": parity.next(),
660 l.insert(0, {"parity": parity.next(),
656 "filerev": i,
661 "filerev": i,
657 "file": f,
662 "file": f,
658 "node": iterfctx.hex(),
663 "node": iterfctx.hex(),
659 "author": iterfctx.user(),
664 "author": iterfctx.user(),
660 "date": iterfctx.date(),
665 "date": iterfctx.date(),
661 "rename": webutil.renamelink(iterfctx),
666 "rename": webutil.renamelink(iterfctx),
662 "parent": webutil.parents(iterfctx),
667 "parent": webutil.parents(iterfctx),
663 "child": webutil.children(iterfctx),
668 "child": webutil.children(iterfctx),
664 "desc": iterfctx.description(),
669 "desc": iterfctx.description(),
665 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
670 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
666 "bookmarks": webutil.nodebookmarksdict(
671 "bookmarks": webutil.nodebookmarksdict(
667 repo, iterfctx.node()),
672 repo, iterfctx.node()),
668 "branch": webutil.nodebranchnodefault(iterfctx),
673 "branch": webutil.nodebranchnodefault(iterfctx),
669 "inbranch": webutil.nodeinbranch(repo, iterfctx),
674 "inbranch": webutil.nodeinbranch(repo, iterfctx),
670 "branches": webutil.nodebranchdict(repo, iterfctx)})
675 "branches": webutil.nodebranchdict(repo, iterfctx)})
671
676
672 if limit > 0:
677 if limit > 0:
673 l = l[:limit]
678 l = l[:limit]
674
679
675 for e in l:
680 for e in l:
676 yield e
681 yield e
677
682
678 nodefunc = lambda x: fctx.filectx(fileid=x)
683 nodefunc = lambda x: fctx.filectx(fileid=x)
679 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
684 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
680 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
685 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
681 entries=lambda **x: entries(limit=0, **x),
686 entries=lambda **x: entries(limit=0, **x),
682 latestentry=lambda **x: entries(limit=1, **x),
687 latestentry=lambda **x: entries(limit=1, **x),
683 revcount=revcount, morevars=morevars, lessvars=lessvars)
688 revcount=revcount, morevars=morevars, lessvars=lessvars)
684
689
685 def archive(web, req, tmpl):
690 def archive(web, req, tmpl):
686 type_ = req.form.get('type', [None])[0]
691 type_ = req.form.get('type', [None])[0]
687 allowed = web.configlist("web", "allow_archive")
692 allowed = web.configlist("web", "allow_archive")
688 key = req.form['node'][0]
693 key = req.form['node'][0]
689
694
690 if type_ not in web.archives:
695 if type_ not in web.archives:
691 msg = 'Unsupported archive type: %s' % type_
696 msg = 'Unsupported archive type: %s' % type_
692 raise ErrorResponse(HTTP_NOT_FOUND, msg)
697 raise ErrorResponse(HTTP_NOT_FOUND, msg)
693
698
694 if not ((type_ in allowed or
699 if not ((type_ in allowed or
695 web.configbool("web", "allow" + type_, False))):
700 web.configbool("web", "allow" + type_, False))):
696 msg = 'Archive type not allowed: %s' % type_
701 msg = 'Archive type not allowed: %s' % type_
697 raise ErrorResponse(HTTP_FORBIDDEN, msg)
702 raise ErrorResponse(HTTP_FORBIDDEN, msg)
698
703
699 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
704 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
700 cnode = web.repo.lookup(key)
705 cnode = web.repo.lookup(key)
701 arch_version = key
706 arch_version = key
702 if cnode == key or key == 'tip':
707 if cnode == key or key == 'tip':
703 arch_version = short(cnode)
708 arch_version = short(cnode)
704 name = "%s-%s" % (reponame, arch_version)
709 name = "%s-%s" % (reponame, arch_version)
705 mimetype, artype, extension, encoding = web.archive_specs[type_]
710 mimetype, artype, extension, encoding = web.archive_specs[type_]
706 headers = [
711 headers = [
707 ('Content-Type', mimetype),
712 ('Content-Type', mimetype),
708 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
713 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
709 ]
714 ]
710 if encoding:
715 if encoding:
711 headers.append(('Content-Encoding', encoding))
716 headers.append(('Content-Encoding', encoding))
712 req.header(headers)
717 req.header(headers)
713 req.respond(HTTP_OK)
718 req.respond(HTTP_OK)
714 archival.archive(web.repo, req, cnode, artype, prefix=name)
719 archival.archive(web.repo, req, cnode, artype, prefix=name)
715 return []
720 return []
716
721
717
722
718 def static(web, req, tmpl):
723 def static(web, req, tmpl):
719 fname = req.form['file'][0]
724 fname = req.form['file'][0]
720 # a repo owner may set web.static in .hg/hgrc to get any file
725 # a repo owner may set web.static in .hg/hgrc to get any file
721 # readable by the user running the CGI script
726 # readable by the user running the CGI script
722 static = web.config("web", "static", None, untrusted=False)
727 static = web.config("web", "static", None, untrusted=False)
723 if not static:
728 if not static:
724 tp = web.templatepath or templater.templatepath()
729 tp = web.templatepath or templater.templatepath()
725 if isinstance(tp, str):
730 if isinstance(tp, str):
726 tp = [tp]
731 tp = [tp]
727 static = [os.path.join(p, 'static') for p in tp]
732 static = [os.path.join(p, 'static') for p in tp]
728 return [staticfile(static, fname, req)]
733 return [staticfile(static, fname, req)]
729
734
730 def graph(web, req, tmpl):
735 def graph(web, req, tmpl):
731
736
732 rev = webutil.changectx(web.repo, req).rev()
737 rev = webutil.changectx(web.repo, req).rev()
733 bg_height = 39
738 bg_height = 39
734 revcount = web.maxshortchanges
739 revcount = web.maxshortchanges
735 if 'revcount' in req.form:
740 if 'revcount' in req.form:
736 revcount = int(req.form.get('revcount', [revcount])[0])
741 revcount = int(req.form.get('revcount', [revcount])[0])
737 revcount = max(revcount, 1)
742 revcount = max(revcount, 1)
738 tmpl.defaults['sessionvars']['revcount'] = revcount
743 tmpl.defaults['sessionvars']['revcount'] = revcount
739
744
740 lessvars = copy.copy(tmpl.defaults['sessionvars'])
745 lessvars = copy.copy(tmpl.defaults['sessionvars'])
741 lessvars['revcount'] = max(revcount / 2, 1)
746 lessvars['revcount'] = max(revcount / 2, 1)
742 morevars = copy.copy(tmpl.defaults['sessionvars'])
747 morevars = copy.copy(tmpl.defaults['sessionvars'])
743 morevars['revcount'] = revcount * 2
748 morevars['revcount'] = revcount * 2
744
749
745 max_rev = len(web.repo) - 1
750 max_rev = len(web.repo) - 1
746 revcount = min(max_rev, revcount)
751 revcount = min(max_rev, revcount)
747 revnode = web.repo.changelog.node(rev)
752 revnode = web.repo.changelog.node(rev)
748 revnode_hex = hex(revnode)
753 revnode_hex = hex(revnode)
749 uprev = min(max_rev, rev + revcount)
754 uprev = min(max_rev, rev + revcount)
750 downrev = max(0, rev - revcount)
755 downrev = max(0, rev - revcount)
751 count = len(web.repo)
756 count = len(web.repo)
752 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
757 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
753 startrev = rev
758 startrev = rev
754 # if starting revision is less than 60 set it to uprev
759 # if starting revision is less than 60 set it to uprev
755 if rev < web.maxshortchanges:
760 if rev < web.maxshortchanges:
756 startrev = uprev
761 startrev = uprev
757
762
758 dag = graphmod.dagwalker(web.repo, range(startrev, downrev - 1, -1))
763 dag = graphmod.dagwalker(web.repo, range(startrev, downrev - 1, -1))
759 tree = list(graphmod.colored(dag))
764 tree = list(graphmod.colored(dag))
760 canvasheight = (len(tree) + 1) * bg_height - 27
765 canvasheight = (len(tree) + 1) * bg_height - 27
761 data = []
766 data = []
762 for (id, type, ctx, vtx, edges) in tree:
767 for (id, type, ctx, vtx, edges) in tree:
763 if type != graphmod.CHANGESET:
768 if type != graphmod.CHANGESET:
764 continue
769 continue
765 node = str(ctx)
770 node = str(ctx)
766 age = templatefilters.age(ctx.date())
771 age = templatefilters.age(ctx.date())
767 desc = templatefilters.firstline(ctx.description())
772 desc = templatefilters.firstline(ctx.description())
768 desc = cgi.escape(templatefilters.nonempty(desc))
773 desc = cgi.escape(templatefilters.nonempty(desc))
769 user = cgi.escape(templatefilters.person(ctx.user()))
774 user = cgi.escape(templatefilters.person(ctx.user()))
770 branch = ctx.branch()
775 branch = ctx.branch()
771 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
776 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
772 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags(),
777 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags(),
773 ctx.bookmarks()))
778 ctx.bookmarks()))
774
779
775 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
780 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
776 lessvars=lessvars, morevars=morevars, downrev=downrev,
781 lessvars=lessvars, morevars=morevars, downrev=downrev,
777 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
782 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
778 node=revnode_hex, changenav=changenav)
783 node=revnode_hex, changenav=changenav)
779
784
780 def _getdoc(e):
785 def _getdoc(e):
781 doc = e[0].__doc__
786 doc = e[0].__doc__
782 if doc:
787 if doc:
783 doc = doc.split('\n')[0]
788 doc = doc.split('\n')[0]
784 else:
789 else:
785 doc = _('(no help text available)')
790 doc = _('(no help text available)')
786 return doc
791 return doc
787
792
788 def help(web, req, tmpl):
793 def help(web, req, tmpl):
789 from mercurial import commands # avoid cycle
794 from mercurial import commands # avoid cycle
790
795
791 topicname = req.form.get('node', [None])[0]
796 topicname = req.form.get('node', [None])[0]
792 if not topicname:
797 if not topicname:
793 def topics(**map):
798 def topics(**map):
794 for entries, summary, _ in helpmod.helptable:
799 for entries, summary, _ in helpmod.helptable:
795 entries = sorted(entries, key=len)
800 entries = sorted(entries, key=len)
796 yield {'topic': entries[-1], 'summary': summary}
801 yield {'topic': entries[-1], 'summary': summary}
797
802
798 early, other = [], []
803 early, other = [], []
799 primary = lambda s: s.split('|')[0]
804 primary = lambda s: s.split('|')[0]
800 for c, e in commands.table.iteritems():
805 for c, e in commands.table.iteritems():
801 doc = _getdoc(e)
806 doc = _getdoc(e)
802 if 'DEPRECATED' in doc or c.startswith('debug'):
807 if 'DEPRECATED' in doc or c.startswith('debug'):
803 continue
808 continue
804 cmd = primary(c)
809 cmd = primary(c)
805 if cmd.startswith('^'):
810 if cmd.startswith('^'):
806 early.append((cmd[1:], doc))
811 early.append((cmd[1:], doc))
807 else:
812 else:
808 other.append((cmd, doc))
813 other.append((cmd, doc))
809
814
810 early.sort()
815 early.sort()
811 other.sort()
816 other.sort()
812
817
813 def earlycommands(**map):
818 def earlycommands(**map):
814 for c, doc in early:
819 for c, doc in early:
815 yield {'topic': c, 'summary': doc}
820 yield {'topic': c, 'summary': doc}
816
821
817 def othercommands(**map):
822 def othercommands(**map):
818 for c, doc in other:
823 for c, doc in other:
819 yield {'topic': c, 'summary': doc}
824 yield {'topic': c, 'summary': doc}
820
825
821 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
826 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
822 othercommands=othercommands, title='Index')
827 othercommands=othercommands, title='Index')
823
828
824 u = webutil.wsgiui()
829 u = webutil.wsgiui()
825 u.pushbuffer()
830 u.pushbuffer()
826 try:
831 try:
827 commands.help_(u, topicname)
832 commands.help_(u, topicname)
828 except error.UnknownCommand:
833 except error.UnknownCommand:
829 raise ErrorResponse(HTTP_NOT_FOUND)
834 raise ErrorResponse(HTTP_NOT_FOUND)
830 doc = u.popbuffer()
835 doc = u.popbuffer()
831 return tmpl('help', topic=topicname, doc=doc)
836 return tmpl('help', topic=topicname, doc=doc)
@@ -1,233 +1,253 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
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-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, copy
9 import os, copy
10 from mercurial import match, patch, scmutil, error, ui
10 from mercurial import match, patch, scmutil, error, ui, util
11 from mercurial.node import hex, nullid
11 from mercurial.node import hex, nullid
12
12
13 def up(p):
13 def up(p):
14 if p[0] != "/":
14 if p[0] != "/":
15 p = "/" + p
15 p = "/" + p
16 if p[-1] == "/":
16 if p[-1] == "/":
17 p = p[:-1]
17 p = p[:-1]
18 up = os.path.dirname(p)
18 up = os.path.dirname(p)
19 if up == "/":
19 if up == "/":
20 return "/"
20 return "/"
21 return up + "/"
21 return up + "/"
22
22
23 def revnavgen(pos, pagelen, limit, nodefunc):
23 def revnavgen(pos, pagelen, limit, nodefunc):
24 def seq(factor, limit=None):
24 def seq(factor, limit=None):
25 if limit:
25 if limit:
26 yield limit
26 yield limit
27 if limit >= 20 and limit <= 40:
27 if limit >= 20 and limit <= 40:
28 yield 50
28 yield 50
29 else:
29 else:
30 yield 1 * factor
30 yield 1 * factor
31 yield 3 * factor
31 yield 3 * factor
32 for f in seq(factor * 10):
32 for f in seq(factor * 10):
33 yield f
33 yield f
34
34
35 navbefore = []
35 navbefore = []
36 navafter = []
36 navafter = []
37
37
38 last = 0
38 last = 0
39 for f in seq(1, pagelen):
39 for f in seq(1, pagelen):
40 if f < pagelen or f <= last:
40 if f < pagelen or f <= last:
41 continue
41 continue
42 if f > limit:
42 if f > limit:
43 break
43 break
44 last = f
44 last = f
45 if pos + f < limit:
45 if pos + f < limit:
46 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
46 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
47 if pos - f >= 0:
47 if pos - f >= 0:
48 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
48 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
49
49
50 navafter.append(("tip", "tip"))
50 navafter.append(("tip", "tip"))
51 try:
51 try:
52 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
52 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
53 except error.RepoError:
53 except error.RepoError:
54 pass
54 pass
55
55
56 def gen(l):
56 def gen(l):
57 def f(**map):
57 def f(**map):
58 for label, node in l:
58 for label, node in l:
59 yield {"label": label, "node": node}
59 yield {"label": label, "node": node}
60 return f
60 return f
61
61
62 return (dict(before=gen(navbefore), after=gen(navafter)),)
62 return (dict(before=gen(navbefore), after=gen(navafter)),)
63
63
64 def _siblings(siblings=[], hiderev=None):
64 def _siblings(siblings=[], hiderev=None):
65 siblings = [s for s in siblings if s.node() != nullid]
65 siblings = [s for s in siblings if s.node() != nullid]
66 if len(siblings) == 1 and siblings[0].rev() == hiderev:
66 if len(siblings) == 1 and siblings[0].rev() == hiderev:
67 return
67 return
68 for s in siblings:
68 for s in siblings:
69 d = {'node': s.hex(), 'rev': s.rev()}
69 d = {'node': s.hex(), 'rev': s.rev()}
70 d['user'] = s.user()
70 d['user'] = s.user()
71 d['date'] = s.date()
71 d['date'] = s.date()
72 d['description'] = s.description()
72 d['description'] = s.description()
73 d['branch'] = s.branch()
73 d['branch'] = s.branch()
74 if hasattr(s, 'path'):
74 if hasattr(s, 'path'):
75 d['file'] = s.path()
75 d['file'] = s.path()
76 yield d
76 yield d
77
77
78 def parents(ctx, hide=None):
78 def parents(ctx, hide=None):
79 return _siblings(ctx.parents(), hide)
79 return _siblings(ctx.parents(), hide)
80
80
81 def children(ctx, hide=None):
81 def children(ctx, hide=None):
82 return _siblings(ctx.children(), hide)
82 return _siblings(ctx.children(), hide)
83
83
84 def renamelink(fctx):
84 def renamelink(fctx):
85 r = fctx.renamed()
85 r = fctx.renamed()
86 if r:
86 if r:
87 return [dict(file=r[0], node=hex(r[1]))]
87 return [dict(file=r[0], node=hex(r[1]))]
88 return []
88 return []
89
89
90 def nodetagsdict(repo, node):
90 def nodetagsdict(repo, node):
91 return [{"name": i} for i in repo.nodetags(node)]
91 return [{"name": i} for i in repo.nodetags(node)]
92
92
93 def nodebookmarksdict(repo, node):
93 def nodebookmarksdict(repo, node):
94 return [{"name": i} for i in repo.nodebookmarks(node)]
94 return [{"name": i} for i in repo.nodebookmarks(node)]
95
95
96 def nodebranchdict(repo, ctx):
96 def nodebranchdict(repo, ctx):
97 branches = []
97 branches = []
98 branch = ctx.branch()
98 branch = ctx.branch()
99 # If this is an empty repo, ctx.node() == nullid,
99 # If this is an empty repo, ctx.node() == nullid,
100 # ctx.branch() == 'default', but branchtags() is
100 # ctx.branch() == 'default', but branchtags() is
101 # an empty dict. Using dict.get avoids a traceback.
101 # an empty dict. Using dict.get avoids a traceback.
102 if repo.branchtags().get(branch) == ctx.node():
102 if repo.branchtags().get(branch) == ctx.node():
103 branches.append({"name": branch})
103 branches.append({"name": branch})
104 return branches
104 return branches
105
105
106 def nodeinbranch(repo, ctx):
106 def nodeinbranch(repo, ctx):
107 branches = []
107 branches = []
108 branch = ctx.branch()
108 branch = ctx.branch()
109 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
109 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
110 branches.append({"name": branch})
110 branches.append({"name": branch})
111 return branches
111 return branches
112
112
113 def nodebranchnodefault(ctx):
113 def nodebranchnodefault(ctx):
114 branches = []
114 branches = []
115 branch = ctx.branch()
115 branch = ctx.branch()
116 if branch != 'default':
116 if branch != 'default':
117 branches.append({"name": branch})
117 branches.append({"name": branch})
118 return branches
118 return branches
119
119
120 def showtag(repo, tmpl, t1, node=nullid, **args):
120 def showtag(repo, tmpl, t1, node=nullid, **args):
121 for t in repo.nodetags(node):
121 for t in repo.nodetags(node):
122 yield tmpl(t1, tag=t, **args)
122 yield tmpl(t1, tag=t, **args)
123
123
124 def showbookmark(repo, tmpl, t1, node=nullid, **args):
124 def showbookmark(repo, tmpl, t1, node=nullid, **args):
125 for t in repo.nodebookmarks(node):
125 for t in repo.nodebookmarks(node):
126 yield tmpl(t1, bookmark=t, **args)
126 yield tmpl(t1, bookmark=t, **args)
127
127
128 def cleanpath(repo, path):
128 def cleanpath(repo, path):
129 path = path.lstrip('/')
129 path = path.lstrip('/')
130 return scmutil.canonpath(repo.root, '', path)
130 return scmutil.canonpath(repo.root, '', path)
131
131
132 def changectx(repo, req):
132 def changectx(repo, req):
133 changeid = "tip"
133 changeid = "tip"
134 if 'node' in req.form:
134 if 'node' in req.form:
135 changeid = req.form['node'][0]
135 changeid = req.form['node'][0]
136 elif 'manifest' in req.form:
136 elif 'manifest' in req.form:
137 changeid = req.form['manifest'][0]
137 changeid = req.form['manifest'][0]
138
138
139 try:
139 try:
140 ctx = repo[changeid]
140 ctx = repo[changeid]
141 except error.RepoError:
141 except error.RepoError:
142 man = repo.manifest
142 man = repo.manifest
143 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
143 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
144
144
145 return ctx
145 return ctx
146
146
147 def filectx(repo, req):
147 def filectx(repo, req):
148 path = cleanpath(repo, req.form['file'][0])
148 path = cleanpath(repo, req.form['file'][0])
149 if 'node' in req.form:
149 if 'node' in req.form:
150 changeid = req.form['node'][0]
150 changeid = req.form['node'][0]
151 else:
151 else:
152 changeid = req.form['filenode'][0]
152 changeid = req.form['filenode'][0]
153 try:
153 try:
154 fctx = repo[changeid][path]
154 fctx = repo[changeid][path]
155 except error.RepoError:
155 except error.RepoError:
156 fctx = repo.filectx(path, fileid=changeid)
156 fctx = repo.filectx(path, fileid=changeid)
157
157
158 return fctx
158 return fctx
159
159
160 def listfilediffs(tmpl, files, node, max):
160 def listfilediffs(tmpl, files, node, max):
161 for f in files[:max]:
161 for f in files[:max]:
162 yield tmpl('filedifflink', node=hex(node), file=f)
162 yield tmpl('filedifflink', node=hex(node), file=f)
163 if len(files) > max:
163 if len(files) > max:
164 yield tmpl('fileellipses')
164 yield tmpl('fileellipses')
165
165
166 def diffs(repo, tmpl, ctx, files, parity, style):
166 def diffs(repo, tmpl, ctx, files, parity, style):
167
167
168 def countgen():
168 def countgen():
169 start = 1
169 start = 1
170 while True:
170 while True:
171 yield start
171 yield start
172 start += 1
172 start += 1
173
173
174 blockcount = countgen()
174 blockcount = countgen()
175 def prettyprintlines(diff):
175 def prettyprintlines(diff):
176 blockno = blockcount.next()
176 blockno = blockcount.next()
177 for lineno, l in enumerate(diff.splitlines(True)):
177 for lineno, l in enumerate(diff.splitlines(True)):
178 lineno = "%d.%d" % (blockno, lineno + 1)
178 lineno = "%d.%d" % (blockno, lineno + 1)
179 if l.startswith('+'):
179 if l.startswith('+'):
180 ltype = "difflineplus"
180 ltype = "difflineplus"
181 elif l.startswith('-'):
181 elif l.startswith('-'):
182 ltype = "difflineminus"
182 ltype = "difflineminus"
183 elif l.startswith('@'):
183 elif l.startswith('@'):
184 ltype = "difflineat"
184 ltype = "difflineat"
185 else:
185 else:
186 ltype = "diffline"
186 ltype = "diffline"
187 yield tmpl(ltype,
187 yield tmpl(ltype,
188 line=l,
188 line=l,
189 lineid="l%s" % lineno,
189 lineid="l%s" % lineno,
190 linenumber="% 8s" % lineno)
190 linenumber="% 8s" % lineno)
191
191
192 if files:
192 if files:
193 m = match.exact(repo.root, repo.getcwd(), files)
193 m = match.exact(repo.root, repo.getcwd(), files)
194 else:
194 else:
195 m = match.always(repo.root, repo.getcwd())
195 m = match.always(repo.root, repo.getcwd())
196
196
197 diffopts = patch.diffopts(repo.ui, untrusted=True)
197 diffopts = patch.diffopts(repo.ui, untrusted=True)
198 parents = ctx.parents()
198 parents = ctx.parents()
199 node1 = parents and parents[0].node() or nullid
199 node1 = parents and parents[0].node() or nullid
200 node2 = ctx.node()
200 node2 = ctx.node()
201
201
202 block = []
202 block = []
203 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
203 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
204 if chunk.startswith('diff') and block:
204 if chunk.startswith('diff') and block:
205 yield tmpl('diffblock', parity=parity.next(),
205 yield tmpl('diffblock', parity=parity.next(),
206 lines=prettyprintlines(''.join(block)))
206 lines=prettyprintlines(''.join(block)))
207 block = []
207 block = []
208 if chunk.startswith('diff') and style != 'raw':
208 if chunk.startswith('diff') and style != 'raw':
209 chunk = ''.join(chunk.splitlines(True)[1:])
209 chunk = ''.join(chunk.splitlines(True)[1:])
210 block.append(chunk)
210 block.append(chunk)
211 yield tmpl('diffblock', parity=parity.next(),
211 yield tmpl('diffblock', parity=parity.next(),
212 lines=prettyprintlines(''.join(block)))
212 lines=prettyprintlines(''.join(block)))
213
213
214 def diffstat(tmpl, ctx, parity):
215 '''Return a diffstat template for each file in the cset.'''
216
217 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
218 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
219
220 statsdict = {}
221 if maxtotal > 0:
222 for filename, adds, removes, isbinary in stats:
223 total = adds + removes
224 addpct = (float(adds) / maxtotal) * 100
225 removepct = (float(removes) / maxtotal) * 100
226 statsdict[filename] = (total, addpct, removepct)
227
228 for f in ctx.files():
229 template = f in ctx and 'diffstatlink' or 'diffstatnolink'
230 total, addpct, removepct = statsdict.get(f, ('', 0, 0))
231 yield tmpl(template, node=ctx.hex(), file=f, total=total,
232 addpct=addpct, removepct=removepct, parity=parity.next())
233
214 class sessionvars(object):
234 class sessionvars(object):
215 def __init__(self, vars, start='?'):
235 def __init__(self, vars, start='?'):
216 self.start = start
236 self.start = start
217 self.vars = vars
237 self.vars = vars
218 def __getitem__(self, key):
238 def __getitem__(self, key):
219 return self.vars[key]
239 return self.vars[key]
220 def __setitem__(self, key, value):
240 def __setitem__(self, key, value):
221 self.vars[key] = value
241 self.vars[key] = value
222 def __copy__(self):
242 def __copy__(self):
223 return sessionvars(copy.copy(self.vars), self.start)
243 return sessionvars(copy.copy(self.vars), self.start)
224 def __iter__(self):
244 def __iter__(self):
225 separator = self.start
245 separator = self.start
226 for key, value in self.vars.iteritems():
246 for key, value in self.vars.iteritems():
227 yield {'name': key, 'value': str(value), 'separator': separator}
247 yield {'name': key, 'value': str(value), 'separator': separator}
228 separator = '&'
248 separator = '&'
229
249
230 class wsgiui(ui.ui):
250 class wsgiui(ui.ui):
231 # default termwidth breaks under mod_wsgi
251 # default termwidth breaks under mod_wsgi
232 def termwidth(self):
252 def termwidth(self):
233 return 80
253 return 80
General Comments 0
You need to be logged in to leave comments. Login now