##// END OF EJS Templates
hgweb: remove unused argument of changelist function in changelog...
Alexander Plavin -
r19735:69072511 default
parent child Browse files
Show More
@@ -1,1075 +1,1075 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, nullid
11 from mercurial.node import short, hex, nullid
12 from mercurial import util
12 from mercurial import util
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, patch
15 from mercurial import graphmod, patch
16 from mercurial import help as helpmod
16 from mercurial import help as helpmod
17 from mercurial import scmutil
17 from mercurial import scmutil
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19 from mercurial.error import ParseError, RepoLookupError, Abort
19 from mercurial.error import ParseError, RepoLookupError, Abort
20 from mercurial import revset
20 from mercurial import revset
21
21
22 # __all__ is populated with the allowed commands. Be sure to add to it if
22 # __all__ is populated with the allowed commands. Be sure to add to it if
23 # you're adding a new command, or the new command won't work.
23 # you're adding a new command, or the new command won't work.
24
24
25 __all__ = [
25 __all__ = [
26 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
26 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
27 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
27 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
28 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
28 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
29 ]
29 ]
30
30
31 def log(web, req, tmpl):
31 def log(web, req, tmpl):
32 if 'file' in req.form and req.form['file'][0]:
32 if 'file' in req.form and req.form['file'][0]:
33 return filelog(web, req, tmpl)
33 return filelog(web, req, tmpl)
34 else:
34 else:
35 return changelog(web, req, tmpl)
35 return changelog(web, req, tmpl)
36
36
37 def rawfile(web, req, tmpl):
37 def rawfile(web, req, tmpl):
38 guessmime = web.configbool('web', 'guessmime', False)
38 guessmime = web.configbool('web', 'guessmime', False)
39
39
40 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
40 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
41 if not path:
41 if not path:
42 content = manifest(web, req, tmpl)
42 content = manifest(web, req, tmpl)
43 req.respond(HTTP_OK, web.ctype)
43 req.respond(HTTP_OK, web.ctype)
44 return content
44 return content
45
45
46 try:
46 try:
47 fctx = webutil.filectx(web.repo, req)
47 fctx = webutil.filectx(web.repo, req)
48 except error.LookupError, inst:
48 except error.LookupError, inst:
49 try:
49 try:
50 content = manifest(web, req, tmpl)
50 content = manifest(web, req, tmpl)
51 req.respond(HTTP_OK, web.ctype)
51 req.respond(HTTP_OK, web.ctype)
52 return content
52 return content
53 except ErrorResponse:
53 except ErrorResponse:
54 raise inst
54 raise inst
55
55
56 path = fctx.path()
56 path = fctx.path()
57 text = fctx.data()
57 text = fctx.data()
58 mt = 'application/binary'
58 mt = 'application/binary'
59 if guessmime:
59 if guessmime:
60 mt = mimetypes.guess_type(path)[0]
60 mt = mimetypes.guess_type(path)[0]
61 if mt is None:
61 if mt is None:
62 mt = util.binary(text) and 'application/binary' or 'text/plain'
62 mt = util.binary(text) and 'application/binary' or 'text/plain'
63 if mt.startswith('text/'):
63 if mt.startswith('text/'):
64 mt += '; charset="%s"' % encoding.encoding
64 mt += '; charset="%s"' % encoding.encoding
65
65
66 req.respond(HTTP_OK, mt, path, body=text)
66 req.respond(HTTP_OK, mt, path, body=text)
67 return []
67 return []
68
68
69 def _filerevision(web, tmpl, fctx):
69 def _filerevision(web, tmpl, fctx):
70 f = fctx.path()
70 f = fctx.path()
71 text = fctx.data()
71 text = fctx.data()
72 parity = paritygen(web.stripecount)
72 parity = paritygen(web.stripecount)
73
73
74 if util.binary(text):
74 if util.binary(text):
75 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
75 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
76 text = '(binary:%s)' % mt
76 text = '(binary:%s)' % mt
77
77
78 def lines():
78 def lines():
79 for lineno, t in enumerate(text.splitlines(True)):
79 for lineno, t in enumerate(text.splitlines(True)):
80 yield {"line": t,
80 yield {"line": t,
81 "lineid": "l%d" % (lineno + 1),
81 "lineid": "l%d" % (lineno + 1),
82 "linenumber": "% 6d" % (lineno + 1),
82 "linenumber": "% 6d" % (lineno + 1),
83 "parity": parity.next()}
83 "parity": parity.next()}
84
84
85 return tmpl("filerevision",
85 return tmpl("filerevision",
86 file=f,
86 file=f,
87 path=webutil.up(f),
87 path=webutil.up(f),
88 text=lines(),
88 text=lines(),
89 rev=fctx.rev(),
89 rev=fctx.rev(),
90 node=fctx.hex(),
90 node=fctx.hex(),
91 author=fctx.user(),
91 author=fctx.user(),
92 date=fctx.date(),
92 date=fctx.date(),
93 desc=fctx.description(),
93 desc=fctx.description(),
94 extra=fctx.extra(),
94 extra=fctx.extra(),
95 branch=webutil.nodebranchnodefault(fctx),
95 branch=webutil.nodebranchnodefault(fctx),
96 parent=webutil.parents(fctx),
96 parent=webutil.parents(fctx),
97 child=webutil.children(fctx),
97 child=webutil.children(fctx),
98 rename=webutil.renamelink(fctx),
98 rename=webutil.renamelink(fctx),
99 permissions=fctx.manifest().flags(f))
99 permissions=fctx.manifest().flags(f))
100
100
101 def file(web, req, tmpl):
101 def file(web, req, tmpl):
102 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
102 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
103 if not path:
103 if not path:
104 return manifest(web, req, tmpl)
104 return manifest(web, req, tmpl)
105 try:
105 try:
106 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
106 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
107 except error.LookupError, inst:
107 except error.LookupError, inst:
108 try:
108 try:
109 return manifest(web, req, tmpl)
109 return manifest(web, req, tmpl)
110 except ErrorResponse:
110 except ErrorResponse:
111 raise inst
111 raise inst
112
112
113 def _search(web, req, tmpl):
113 def _search(web, req, tmpl):
114 MODE_REVISION = 'rev'
114 MODE_REVISION = 'rev'
115 MODE_KEYWORD = 'keyword'
115 MODE_KEYWORD = 'keyword'
116 MODE_REVSET = 'revset'
116 MODE_REVSET = 'revset'
117
117
118 def revsearch(ctx):
118 def revsearch(ctx):
119 yield ctx
119 yield ctx
120
120
121 def keywordsearch(query):
121 def keywordsearch(query):
122 lower = encoding.lower
122 lower = encoding.lower
123 qw = lower(query).split()
123 qw = lower(query).split()
124
124
125 def revgen():
125 def revgen():
126 cl = web.repo.changelog
126 cl = web.repo.changelog
127 for i in xrange(len(web.repo) - 1, 0, -100):
127 for i in xrange(len(web.repo) - 1, 0, -100):
128 l = []
128 l = []
129 for j in cl.revs(max(0, i - 99), i):
129 for j in cl.revs(max(0, i - 99), i):
130 ctx = web.repo[j]
130 ctx = web.repo[j]
131 l.append(ctx)
131 l.append(ctx)
132 l.reverse()
132 l.reverse()
133 for e in l:
133 for e in l:
134 yield e
134 yield e
135
135
136 for ctx in revgen():
136 for ctx in revgen():
137 miss = 0
137 miss = 0
138 for q in qw:
138 for q in qw:
139 if not (q in lower(ctx.user()) or
139 if not (q in lower(ctx.user()) or
140 q in lower(ctx.description()) or
140 q in lower(ctx.description()) or
141 q in lower(" ".join(ctx.files()))):
141 q in lower(" ".join(ctx.files()))):
142 miss = 1
142 miss = 1
143 break
143 break
144 if miss:
144 if miss:
145 continue
145 continue
146
146
147 yield ctx
147 yield ctx
148
148
149 def revsetsearch(revs):
149 def revsetsearch(revs):
150 for r in revs:
150 for r in revs:
151 yield web.repo[r]
151 yield web.repo[r]
152
152
153 searchfuncs = {
153 searchfuncs = {
154 MODE_REVISION: revsearch,
154 MODE_REVISION: revsearch,
155 MODE_KEYWORD: keywordsearch,
155 MODE_KEYWORD: keywordsearch,
156 MODE_REVSET: revsetsearch,
156 MODE_REVSET: revsetsearch,
157 }
157 }
158
158
159 def getsearchmode(query):
159 def getsearchmode(query):
160 try:
160 try:
161 ctx = web.repo[query]
161 ctx = web.repo[query]
162 except (error.RepoError, error.LookupError):
162 except (error.RepoError, error.LookupError):
163 # query is not an exact revision pointer, need to
163 # query is not an exact revision pointer, need to
164 # decide if it's a revset expession or keywords
164 # decide if it's a revset expession or keywords
165 pass
165 pass
166 else:
166 else:
167 return MODE_REVISION, ctx
167 return MODE_REVISION, ctx
168
168
169 revdef = 'reverse(%s)' % query
169 revdef = 'reverse(%s)' % query
170 try:
170 try:
171 tree, pos = revset.parse(revdef)
171 tree, pos = revset.parse(revdef)
172 except ParseError:
172 except ParseError:
173 # can't parse to a revset tree
173 # can't parse to a revset tree
174 return MODE_KEYWORD, query
174 return MODE_KEYWORD, query
175
175
176 if revset.depth(tree) <= 2:
176 if revset.depth(tree) <= 2:
177 # no revset syntax used
177 # no revset syntax used
178 return MODE_KEYWORD, query
178 return MODE_KEYWORD, query
179
179
180 if util.any((token, (value or '')[:3]) == ('string', 're:')
180 if util.any((token, (value or '')[:3]) == ('string', 're:')
181 for token, value, pos in revset.tokenize(revdef)):
181 for token, value, pos in revset.tokenize(revdef)):
182 return MODE_KEYWORD, query
182 return MODE_KEYWORD, query
183
183
184 funcsused = revset.funcsused(tree)
184 funcsused = revset.funcsused(tree)
185 if not funcsused.issubset(revset.safesymbols):
185 if not funcsused.issubset(revset.safesymbols):
186 return MODE_KEYWORD, query
186 return MODE_KEYWORD, query
187
187
188 mfunc = revset.match(web.repo.ui, revdef)
188 mfunc = revset.match(web.repo.ui, revdef)
189 try:
189 try:
190 revs = mfunc(web.repo, list(web.repo))
190 revs = mfunc(web.repo, list(web.repo))
191 return MODE_REVSET, revs
191 return MODE_REVSET, revs
192 # ParseError: wrongly placed tokens, wrongs arguments, etc
192 # ParseError: wrongly placed tokens, wrongs arguments, etc
193 # RepoLookupError: no such revision, e.g. in 'revision:'
193 # RepoLookupError: no such revision, e.g. in 'revision:'
194 # Abort: bookmark/tag not exists
194 # Abort: bookmark/tag not exists
195 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
195 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
196 except (ParseError, RepoLookupError, Abort, LookupError):
196 except (ParseError, RepoLookupError, Abort, LookupError):
197 return MODE_KEYWORD, query
197 return MODE_KEYWORD, query
198
198
199 def changelist(**map):
199 def changelist(**map):
200 count = 0
200 count = 0
201
201
202 for ctx in searchfunc(funcarg):
202 for ctx in searchfunc(funcarg):
203 count += 1
203 count += 1
204 n = ctx.node()
204 n = ctx.node()
205 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
205 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
206 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
206 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
207
207
208 yield tmpl('searchentry',
208 yield tmpl('searchentry',
209 parity=parity.next(),
209 parity=parity.next(),
210 author=ctx.user(),
210 author=ctx.user(),
211 parent=webutil.parents(ctx),
211 parent=webutil.parents(ctx),
212 child=webutil.children(ctx),
212 child=webutil.children(ctx),
213 changelogtag=showtags,
213 changelogtag=showtags,
214 desc=ctx.description(),
214 desc=ctx.description(),
215 extra=ctx.extra(),
215 extra=ctx.extra(),
216 date=ctx.date(),
216 date=ctx.date(),
217 files=files,
217 files=files,
218 rev=ctx.rev(),
218 rev=ctx.rev(),
219 node=hex(n),
219 node=hex(n),
220 tags=webutil.nodetagsdict(web.repo, n),
220 tags=webutil.nodetagsdict(web.repo, n),
221 bookmarks=webutil.nodebookmarksdict(web.repo, n),
221 bookmarks=webutil.nodebookmarksdict(web.repo, n),
222 inbranch=webutil.nodeinbranch(web.repo, ctx),
222 inbranch=webutil.nodeinbranch(web.repo, ctx),
223 branches=webutil.nodebranchdict(web.repo, ctx))
223 branches=webutil.nodebranchdict(web.repo, ctx))
224
224
225 if count >= revcount:
225 if count >= revcount:
226 break
226 break
227
227
228 query = req.form['rev'][0]
228 query = req.form['rev'][0]
229 revcount = web.maxchanges
229 revcount = web.maxchanges
230 if 'revcount' in req.form:
230 if 'revcount' in req.form:
231 revcount = int(req.form.get('revcount', [revcount])[0])
231 revcount = int(req.form.get('revcount', [revcount])[0])
232 revcount = max(revcount, 1)
232 revcount = max(revcount, 1)
233 tmpl.defaults['sessionvars']['revcount'] = revcount
233 tmpl.defaults['sessionvars']['revcount'] = revcount
234
234
235 lessvars = copy.copy(tmpl.defaults['sessionvars'])
235 lessvars = copy.copy(tmpl.defaults['sessionvars'])
236 lessvars['revcount'] = max(revcount / 2, 1)
236 lessvars['revcount'] = max(revcount / 2, 1)
237 lessvars['rev'] = query
237 lessvars['rev'] = query
238 morevars = copy.copy(tmpl.defaults['sessionvars'])
238 morevars = copy.copy(tmpl.defaults['sessionvars'])
239 morevars['revcount'] = revcount * 2
239 morevars['revcount'] = revcount * 2
240 morevars['rev'] = query
240 morevars['rev'] = query
241
241
242 mode, funcarg = getsearchmode(query)
242 mode, funcarg = getsearchmode(query)
243 searchfunc = searchfuncs[mode]
243 searchfunc = searchfuncs[mode]
244
244
245 tip = web.repo['tip']
245 tip = web.repo['tip']
246 parity = paritygen(web.stripecount)
246 parity = paritygen(web.stripecount)
247
247
248 return tmpl('search', query=query, node=tip.hex(),
248 return tmpl('search', query=query, node=tip.hex(),
249 entries=changelist, archives=web.archivelist("tip"),
249 entries=changelist, archives=web.archivelist("tip"),
250 morevars=morevars, lessvars=lessvars)
250 morevars=morevars, lessvars=lessvars)
251
251
252 def changelog(web, req, tmpl, shortlog=False):
252 def changelog(web, req, tmpl, shortlog=False):
253
253
254 query = ''
254 query = ''
255 if 'node' in req.form:
255 if 'node' in req.form:
256 ctx = webutil.changectx(web.repo, req)
256 ctx = webutil.changectx(web.repo, req)
257 elif 'rev' in req.form:
257 elif 'rev' in req.form:
258 return _search(web, req, tmpl)
258 return _search(web, req, tmpl)
259 else:
259 else:
260 ctx = web.repo['tip']
260 ctx = web.repo['tip']
261
261
262 def changelist(latestonly, **map):
262 def changelist(latestonly):
263 revs = []
263 revs = []
264 if pos != -1:
264 if pos != -1:
265 revs = web.repo.changelog.revs(pos, 0)
265 revs = web.repo.changelog.revs(pos, 0)
266 if latestonly:
266 if latestonly:
267 revs = (revs.next(),)
267 revs = (revs.next(),)
268 curcount = 0
268 curcount = 0
269 for i in revs:
269 for i in revs:
270 ctx = web.repo[i]
270 ctx = web.repo[i]
271 n = ctx.node()
271 n = ctx.node()
272 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
272 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
273 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
273 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
274
274
275 curcount += 1
275 curcount += 1
276 if curcount > revcount:
276 if curcount > revcount:
277 break
277 break
278 yield {"parity": parity.next(),
278 yield {"parity": parity.next(),
279 "author": ctx.user(),
279 "author": ctx.user(),
280 "parent": webutil.parents(ctx, i - 1),
280 "parent": webutil.parents(ctx, i - 1),
281 "child": webutil.children(ctx, i + 1),
281 "child": webutil.children(ctx, i + 1),
282 "changelogtag": showtags,
282 "changelogtag": showtags,
283 "desc": ctx.description(),
283 "desc": ctx.description(),
284 "extra": ctx.extra(),
284 "extra": ctx.extra(),
285 "date": ctx.date(),
285 "date": ctx.date(),
286 "files": files,
286 "files": files,
287 "rev": i,
287 "rev": i,
288 "node": hex(n),
288 "node": hex(n),
289 "tags": webutil.nodetagsdict(web.repo, n),
289 "tags": webutil.nodetagsdict(web.repo, n),
290 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
290 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
291 "inbranch": webutil.nodeinbranch(web.repo, ctx),
291 "inbranch": webutil.nodeinbranch(web.repo, ctx),
292 "branches": webutil.nodebranchdict(web.repo, ctx)
292 "branches": webutil.nodebranchdict(web.repo, ctx)
293 }
293 }
294
294
295 revcount = shortlog and web.maxshortchanges or web.maxchanges
295 revcount = shortlog and web.maxshortchanges or web.maxchanges
296 if 'revcount' in req.form:
296 if 'revcount' in req.form:
297 revcount = int(req.form.get('revcount', [revcount])[0])
297 revcount = int(req.form.get('revcount', [revcount])[0])
298 revcount = max(revcount, 1)
298 revcount = max(revcount, 1)
299 tmpl.defaults['sessionvars']['revcount'] = revcount
299 tmpl.defaults['sessionvars']['revcount'] = revcount
300
300
301 lessvars = copy.copy(tmpl.defaults['sessionvars'])
301 lessvars = copy.copy(tmpl.defaults['sessionvars'])
302 lessvars['revcount'] = max(revcount / 2, 1)
302 lessvars['revcount'] = max(revcount / 2, 1)
303 morevars = copy.copy(tmpl.defaults['sessionvars'])
303 morevars = copy.copy(tmpl.defaults['sessionvars'])
304 morevars['revcount'] = revcount * 2
304 morevars['revcount'] = revcount * 2
305
305
306 count = len(web.repo)
306 count = len(web.repo)
307 pos = ctx.rev()
307 pos = ctx.rev()
308 parity = paritygen(web.stripecount)
308 parity = paritygen(web.stripecount)
309
309
310 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
310 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
311
311
312 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
312 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
313 node=ctx.hex(), rev=pos, changesets=count,
313 node=ctx.hex(), rev=pos, changesets=count,
314 entries=lambda **x: changelist(latestonly=False, **x),
314 entries=lambda **x: changelist(latestonly=False),
315 latestentry=lambda **x: changelist(latestonly=True, **x),
315 latestentry=lambda **x: changelist(latestonly=True),
316 archives=web.archivelist("tip"), revcount=revcount,
316 archives=web.archivelist("tip"), revcount=revcount,
317 morevars=morevars, lessvars=lessvars, query=query)
317 morevars=morevars, lessvars=lessvars, query=query)
318
318
319 def shortlog(web, req, tmpl):
319 def shortlog(web, req, tmpl):
320 return changelog(web, req, tmpl, shortlog = True)
320 return changelog(web, req, tmpl, shortlog = True)
321
321
322 def changeset(web, req, tmpl):
322 def changeset(web, req, tmpl):
323 ctx = webutil.changectx(web.repo, req)
323 ctx = webutil.changectx(web.repo, req)
324 basectx = webutil.basechangectx(web.repo, req)
324 basectx = webutil.basechangectx(web.repo, req)
325 if basectx is None:
325 if basectx is None:
326 basectx = ctx.p1()
326 basectx = ctx.p1()
327 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
327 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
328 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
328 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
329 ctx.node())
329 ctx.node())
330 showbranch = webutil.nodebranchnodefault(ctx)
330 showbranch = webutil.nodebranchnodefault(ctx)
331
331
332 files = []
332 files = []
333 parity = paritygen(web.stripecount)
333 parity = paritygen(web.stripecount)
334 for blockno, f in enumerate(ctx.files()):
334 for blockno, f in enumerate(ctx.files()):
335 template = f in ctx and 'filenodelink' or 'filenolink'
335 template = f in ctx and 'filenodelink' or 'filenolink'
336 files.append(tmpl(template,
336 files.append(tmpl(template,
337 node=ctx.hex(), file=f, blockno=blockno + 1,
337 node=ctx.hex(), file=f, blockno=blockno + 1,
338 parity=parity.next()))
338 parity=parity.next()))
339
339
340 style = web.config('web', 'style', 'paper')
340 style = web.config('web', 'style', 'paper')
341 if 'style' in req.form:
341 if 'style' in req.form:
342 style = req.form['style'][0]
342 style = req.form['style'][0]
343
343
344 parity = paritygen(web.stripecount)
344 parity = paritygen(web.stripecount)
345 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
345 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
346
346
347 parity = paritygen(web.stripecount)
347 parity = paritygen(web.stripecount)
348 diffstatgen = webutil.diffstatgen(ctx, basectx)
348 diffstatgen = webutil.diffstatgen(ctx, basectx)
349 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
349 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
350
350
351 return tmpl('changeset',
351 return tmpl('changeset',
352 diff=diffs,
352 diff=diffs,
353 rev=ctx.rev(),
353 rev=ctx.rev(),
354 node=ctx.hex(),
354 node=ctx.hex(),
355 parent=webutil.parents(ctx),
355 parent=webutil.parents(ctx),
356 child=webutil.children(ctx),
356 child=webutil.children(ctx),
357 basenode=basectx.hex(),
357 basenode=basectx.hex(),
358 changesettag=showtags,
358 changesettag=showtags,
359 changesetbookmark=showbookmarks,
359 changesetbookmark=showbookmarks,
360 changesetbranch=showbranch,
360 changesetbranch=showbranch,
361 author=ctx.user(),
361 author=ctx.user(),
362 desc=ctx.description(),
362 desc=ctx.description(),
363 extra=ctx.extra(),
363 extra=ctx.extra(),
364 date=ctx.date(),
364 date=ctx.date(),
365 files=files,
365 files=files,
366 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
366 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
367 diffstat=diffstat,
367 diffstat=diffstat,
368 archives=web.archivelist(ctx.hex()),
368 archives=web.archivelist(ctx.hex()),
369 tags=webutil.nodetagsdict(web.repo, ctx.node()),
369 tags=webutil.nodetagsdict(web.repo, ctx.node()),
370 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
370 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
371 branch=webutil.nodebranchnodefault(ctx),
371 branch=webutil.nodebranchnodefault(ctx),
372 inbranch=webutil.nodeinbranch(web.repo, ctx),
372 inbranch=webutil.nodeinbranch(web.repo, ctx),
373 branches=webutil.nodebranchdict(web.repo, ctx))
373 branches=webutil.nodebranchdict(web.repo, ctx))
374
374
375 rev = changeset
375 rev = changeset
376
376
377 def decodepath(path):
377 def decodepath(path):
378 """Hook for mapping a path in the repository to a path in the
378 """Hook for mapping a path in the repository to a path in the
379 working copy.
379 working copy.
380
380
381 Extensions (e.g., largefiles) can override this to remap files in
381 Extensions (e.g., largefiles) can override this to remap files in
382 the virtual file system presented by the manifest command below."""
382 the virtual file system presented by the manifest command below."""
383 return path
383 return path
384
384
385 def manifest(web, req, tmpl):
385 def manifest(web, req, tmpl):
386 ctx = webutil.changectx(web.repo, req)
386 ctx = webutil.changectx(web.repo, req)
387 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
387 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
388 mf = ctx.manifest()
388 mf = ctx.manifest()
389 node = ctx.node()
389 node = ctx.node()
390
390
391 files = {}
391 files = {}
392 dirs = {}
392 dirs = {}
393 parity = paritygen(web.stripecount)
393 parity = paritygen(web.stripecount)
394
394
395 if path and path[-1] != "/":
395 if path and path[-1] != "/":
396 path += "/"
396 path += "/"
397 l = len(path)
397 l = len(path)
398 abspath = "/" + path
398 abspath = "/" + path
399
399
400 for full, n in mf.iteritems():
400 for full, n in mf.iteritems():
401 # the virtual path (working copy path) used for the full
401 # the virtual path (working copy path) used for the full
402 # (repository) path
402 # (repository) path
403 f = decodepath(full)
403 f = decodepath(full)
404
404
405 if f[:l] != path:
405 if f[:l] != path:
406 continue
406 continue
407 remain = f[l:]
407 remain = f[l:]
408 elements = remain.split('/')
408 elements = remain.split('/')
409 if len(elements) == 1:
409 if len(elements) == 1:
410 files[remain] = full
410 files[remain] = full
411 else:
411 else:
412 h = dirs # need to retain ref to dirs (root)
412 h = dirs # need to retain ref to dirs (root)
413 for elem in elements[0:-1]:
413 for elem in elements[0:-1]:
414 if elem not in h:
414 if elem not in h:
415 h[elem] = {}
415 h[elem] = {}
416 h = h[elem]
416 h = h[elem]
417 if len(h) > 1:
417 if len(h) > 1:
418 break
418 break
419 h[None] = None # denotes files present
419 h[None] = None # denotes files present
420
420
421 if mf and not files and not dirs:
421 if mf and not files and not dirs:
422 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
422 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
423
423
424 def filelist(**map):
424 def filelist(**map):
425 for f in sorted(files):
425 for f in sorted(files):
426 full = files[f]
426 full = files[f]
427
427
428 fctx = ctx.filectx(full)
428 fctx = ctx.filectx(full)
429 yield {"file": full,
429 yield {"file": full,
430 "parity": parity.next(),
430 "parity": parity.next(),
431 "basename": f,
431 "basename": f,
432 "date": fctx.date(),
432 "date": fctx.date(),
433 "size": fctx.size(),
433 "size": fctx.size(),
434 "permissions": mf.flags(full)}
434 "permissions": mf.flags(full)}
435
435
436 def dirlist(**map):
436 def dirlist(**map):
437 for d in sorted(dirs):
437 for d in sorted(dirs):
438
438
439 emptydirs = []
439 emptydirs = []
440 h = dirs[d]
440 h = dirs[d]
441 while isinstance(h, dict) and len(h) == 1:
441 while isinstance(h, dict) and len(h) == 1:
442 k, v = h.items()[0]
442 k, v = h.items()[0]
443 if v:
443 if v:
444 emptydirs.append(k)
444 emptydirs.append(k)
445 h = v
445 h = v
446
446
447 path = "%s%s" % (abspath, d)
447 path = "%s%s" % (abspath, d)
448 yield {"parity": parity.next(),
448 yield {"parity": parity.next(),
449 "path": path,
449 "path": path,
450 "emptydirs": "/".join(emptydirs),
450 "emptydirs": "/".join(emptydirs),
451 "basename": d}
451 "basename": d}
452
452
453 return tmpl("manifest",
453 return tmpl("manifest",
454 rev=ctx.rev(),
454 rev=ctx.rev(),
455 node=hex(node),
455 node=hex(node),
456 path=abspath,
456 path=abspath,
457 up=webutil.up(abspath),
457 up=webutil.up(abspath),
458 upparity=parity.next(),
458 upparity=parity.next(),
459 fentries=filelist,
459 fentries=filelist,
460 dentries=dirlist,
460 dentries=dirlist,
461 archives=web.archivelist(hex(node)),
461 archives=web.archivelist(hex(node)),
462 tags=webutil.nodetagsdict(web.repo, node),
462 tags=webutil.nodetagsdict(web.repo, node),
463 bookmarks=webutil.nodebookmarksdict(web.repo, node),
463 bookmarks=webutil.nodebookmarksdict(web.repo, node),
464 inbranch=webutil.nodeinbranch(web.repo, ctx),
464 inbranch=webutil.nodeinbranch(web.repo, ctx),
465 branches=webutil.nodebranchdict(web.repo, ctx))
465 branches=webutil.nodebranchdict(web.repo, ctx))
466
466
467 def tags(web, req, tmpl):
467 def tags(web, req, tmpl):
468 i = list(reversed(web.repo.tagslist()))
468 i = list(reversed(web.repo.tagslist()))
469 parity = paritygen(web.stripecount)
469 parity = paritygen(web.stripecount)
470
470
471 def entries(notip, latestonly, **map):
471 def entries(notip, latestonly, **map):
472 t = i
472 t = i
473 if notip:
473 if notip:
474 t = [(k, n) for k, n in i if k != "tip"]
474 t = [(k, n) for k, n in i if k != "tip"]
475 if latestonly:
475 if latestonly:
476 t = t[:1]
476 t = t[:1]
477 for k, n in t:
477 for k, n in t:
478 yield {"parity": parity.next(),
478 yield {"parity": parity.next(),
479 "tag": k,
479 "tag": k,
480 "date": web.repo[n].date(),
480 "date": web.repo[n].date(),
481 "node": hex(n)}
481 "node": hex(n)}
482
482
483 return tmpl("tags",
483 return tmpl("tags",
484 node=hex(web.repo.changelog.tip()),
484 node=hex(web.repo.changelog.tip()),
485 entries=lambda **x: entries(False, False, **x),
485 entries=lambda **x: entries(False, False, **x),
486 entriesnotip=lambda **x: entries(True, False, **x),
486 entriesnotip=lambda **x: entries(True, False, **x),
487 latestentry=lambda **x: entries(True, True, **x))
487 latestentry=lambda **x: entries(True, True, **x))
488
488
489 def bookmarks(web, req, tmpl):
489 def bookmarks(web, req, tmpl):
490 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
490 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
491 parity = paritygen(web.stripecount)
491 parity = paritygen(web.stripecount)
492
492
493 def entries(latestonly, **map):
493 def entries(latestonly, **map):
494 if latestonly:
494 if latestonly:
495 t = [min(i)]
495 t = [min(i)]
496 else:
496 else:
497 t = sorted(i)
497 t = sorted(i)
498 for k, n in t:
498 for k, n in t:
499 yield {"parity": parity.next(),
499 yield {"parity": parity.next(),
500 "bookmark": k,
500 "bookmark": k,
501 "date": web.repo[n].date(),
501 "date": web.repo[n].date(),
502 "node": hex(n)}
502 "node": hex(n)}
503
503
504 return tmpl("bookmarks",
504 return tmpl("bookmarks",
505 node=hex(web.repo.changelog.tip()),
505 node=hex(web.repo.changelog.tip()),
506 entries=lambda **x: entries(latestonly=False, **x),
506 entries=lambda **x: entries(latestonly=False, **x),
507 latestentry=lambda **x: entries(latestonly=True, **x))
507 latestentry=lambda **x: entries(latestonly=True, **x))
508
508
509 def branches(web, req, tmpl):
509 def branches(web, req, tmpl):
510 tips = []
510 tips = []
511 heads = web.repo.heads()
511 heads = web.repo.heads()
512 parity = paritygen(web.stripecount)
512 parity = paritygen(web.stripecount)
513 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
513 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
514
514
515 def entries(limit, **map):
515 def entries(limit, **map):
516 count = 0
516 count = 0
517 if not tips:
517 if not tips:
518 for t, n in web.repo.branchtags().iteritems():
518 for t, n in web.repo.branchtags().iteritems():
519 tips.append(web.repo[n])
519 tips.append(web.repo[n])
520 for ctx in sorted(tips, key=sortkey, reverse=True):
520 for ctx in sorted(tips, key=sortkey, reverse=True):
521 if limit > 0 and count >= limit:
521 if limit > 0 and count >= limit:
522 return
522 return
523 count += 1
523 count += 1
524 if not web.repo.branchheads(ctx.branch()):
524 if not web.repo.branchheads(ctx.branch()):
525 status = 'closed'
525 status = 'closed'
526 elif ctx.node() not in heads:
526 elif ctx.node() not in heads:
527 status = 'inactive'
527 status = 'inactive'
528 else:
528 else:
529 status = 'open'
529 status = 'open'
530 yield {'parity': parity.next(),
530 yield {'parity': parity.next(),
531 'branch': ctx.branch(),
531 'branch': ctx.branch(),
532 'status': status,
532 'status': status,
533 'node': ctx.hex(),
533 'node': ctx.hex(),
534 'date': ctx.date()}
534 'date': ctx.date()}
535
535
536 return tmpl('branches', node=hex(web.repo.changelog.tip()),
536 return tmpl('branches', node=hex(web.repo.changelog.tip()),
537 entries=lambda **x: entries(0, **x),
537 entries=lambda **x: entries(0, **x),
538 latestentry=lambda **x: entries(1, **x))
538 latestentry=lambda **x: entries(1, **x))
539
539
540 def summary(web, req, tmpl):
540 def summary(web, req, tmpl):
541 i = reversed(web.repo.tagslist())
541 i = reversed(web.repo.tagslist())
542
542
543 def tagentries(**map):
543 def tagentries(**map):
544 parity = paritygen(web.stripecount)
544 parity = paritygen(web.stripecount)
545 count = 0
545 count = 0
546 for k, n in i:
546 for k, n in i:
547 if k == "tip": # skip tip
547 if k == "tip": # skip tip
548 continue
548 continue
549
549
550 count += 1
550 count += 1
551 if count > 10: # limit to 10 tags
551 if count > 10: # limit to 10 tags
552 break
552 break
553
553
554 yield tmpl("tagentry",
554 yield tmpl("tagentry",
555 parity=parity.next(),
555 parity=parity.next(),
556 tag=k,
556 tag=k,
557 node=hex(n),
557 node=hex(n),
558 date=web.repo[n].date())
558 date=web.repo[n].date())
559
559
560 def bookmarks(**map):
560 def bookmarks(**map):
561 parity = paritygen(web.stripecount)
561 parity = paritygen(web.stripecount)
562 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
562 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
563 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
563 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
564 yield {'parity': parity.next(),
564 yield {'parity': parity.next(),
565 'bookmark': k,
565 'bookmark': k,
566 'date': web.repo[n].date(),
566 'date': web.repo[n].date(),
567 'node': hex(n)}
567 'node': hex(n)}
568
568
569 def branches(**map):
569 def branches(**map):
570 parity = paritygen(web.stripecount)
570 parity = paritygen(web.stripecount)
571
571
572 b = web.repo.branchtags()
572 b = web.repo.branchtags()
573 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
573 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
574 for r, n, t in sorted(l):
574 for r, n, t in sorted(l):
575 yield {'parity': parity.next(),
575 yield {'parity': parity.next(),
576 'branch': t,
576 'branch': t,
577 'node': hex(n),
577 'node': hex(n),
578 'date': web.repo[n].date()}
578 'date': web.repo[n].date()}
579
579
580 def changelist(**map):
580 def changelist(**map):
581 parity = paritygen(web.stripecount, offset=start - end)
581 parity = paritygen(web.stripecount, offset=start - end)
582 l = [] # build a list in forward order for efficiency
582 l = [] # build a list in forward order for efficiency
583 revs = []
583 revs = []
584 if start < end:
584 if start < end:
585 revs = web.repo.changelog.revs(start, end - 1)
585 revs = web.repo.changelog.revs(start, end - 1)
586 for i in revs:
586 for i in revs:
587 ctx = web.repo[i]
587 ctx = web.repo[i]
588 n = ctx.node()
588 n = ctx.node()
589 hn = hex(n)
589 hn = hex(n)
590
590
591 l.append(tmpl(
591 l.append(tmpl(
592 'shortlogentry',
592 'shortlogentry',
593 parity=parity.next(),
593 parity=parity.next(),
594 author=ctx.user(),
594 author=ctx.user(),
595 desc=ctx.description(),
595 desc=ctx.description(),
596 extra=ctx.extra(),
596 extra=ctx.extra(),
597 date=ctx.date(),
597 date=ctx.date(),
598 rev=i,
598 rev=i,
599 node=hn,
599 node=hn,
600 tags=webutil.nodetagsdict(web.repo, n),
600 tags=webutil.nodetagsdict(web.repo, n),
601 bookmarks=webutil.nodebookmarksdict(web.repo, n),
601 bookmarks=webutil.nodebookmarksdict(web.repo, n),
602 inbranch=webutil.nodeinbranch(web.repo, ctx),
602 inbranch=webutil.nodeinbranch(web.repo, ctx),
603 branches=webutil.nodebranchdict(web.repo, ctx)))
603 branches=webutil.nodebranchdict(web.repo, ctx)))
604
604
605 l.reverse()
605 l.reverse()
606 yield l
606 yield l
607
607
608 tip = web.repo['tip']
608 tip = web.repo['tip']
609 count = len(web.repo)
609 count = len(web.repo)
610 start = max(0, count - web.maxchanges)
610 start = max(0, count - web.maxchanges)
611 end = min(count, start + web.maxchanges)
611 end = min(count, start + web.maxchanges)
612
612
613 return tmpl("summary",
613 return tmpl("summary",
614 desc=web.config("web", "description", "unknown"),
614 desc=web.config("web", "description", "unknown"),
615 owner=get_contact(web.config) or "unknown",
615 owner=get_contact(web.config) or "unknown",
616 lastchange=tip.date(),
616 lastchange=tip.date(),
617 tags=tagentries,
617 tags=tagentries,
618 bookmarks=bookmarks,
618 bookmarks=bookmarks,
619 branches=branches,
619 branches=branches,
620 shortlog=changelist,
620 shortlog=changelist,
621 node=tip.hex(),
621 node=tip.hex(),
622 archives=web.archivelist("tip"))
622 archives=web.archivelist("tip"))
623
623
624 def filediff(web, req, tmpl):
624 def filediff(web, req, tmpl):
625 fctx, ctx = None, None
625 fctx, ctx = None, None
626 try:
626 try:
627 fctx = webutil.filectx(web.repo, req)
627 fctx = webutil.filectx(web.repo, req)
628 except LookupError:
628 except LookupError:
629 ctx = webutil.changectx(web.repo, req)
629 ctx = webutil.changectx(web.repo, req)
630 path = webutil.cleanpath(web.repo, req.form['file'][0])
630 path = webutil.cleanpath(web.repo, req.form['file'][0])
631 if path not in ctx.files():
631 if path not in ctx.files():
632 raise
632 raise
633
633
634 if fctx is not None:
634 if fctx is not None:
635 n = fctx.node()
635 n = fctx.node()
636 path = fctx.path()
636 path = fctx.path()
637 ctx = fctx.changectx()
637 ctx = fctx.changectx()
638 else:
638 else:
639 n = ctx.node()
639 n = ctx.node()
640 # path already defined in except clause
640 # path already defined in except clause
641
641
642 parity = paritygen(web.stripecount)
642 parity = paritygen(web.stripecount)
643 style = web.config('web', 'style', 'paper')
643 style = web.config('web', 'style', 'paper')
644 if 'style' in req.form:
644 if 'style' in req.form:
645 style = req.form['style'][0]
645 style = req.form['style'][0]
646
646
647 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
647 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
648 rename = fctx and webutil.renamelink(fctx) or []
648 rename = fctx and webutil.renamelink(fctx) or []
649 ctx = fctx and fctx or ctx
649 ctx = fctx and fctx or ctx
650 return tmpl("filediff",
650 return tmpl("filediff",
651 file=path,
651 file=path,
652 node=hex(n),
652 node=hex(n),
653 rev=ctx.rev(),
653 rev=ctx.rev(),
654 date=ctx.date(),
654 date=ctx.date(),
655 desc=ctx.description(),
655 desc=ctx.description(),
656 extra=ctx.extra(),
656 extra=ctx.extra(),
657 author=ctx.user(),
657 author=ctx.user(),
658 rename=rename,
658 rename=rename,
659 branch=webutil.nodebranchnodefault(ctx),
659 branch=webutil.nodebranchnodefault(ctx),
660 parent=webutil.parents(ctx),
660 parent=webutil.parents(ctx),
661 child=webutil.children(ctx),
661 child=webutil.children(ctx),
662 diff=diffs)
662 diff=diffs)
663
663
664 diff = filediff
664 diff = filediff
665
665
666 def comparison(web, req, tmpl):
666 def comparison(web, req, tmpl):
667 ctx = webutil.changectx(web.repo, req)
667 ctx = webutil.changectx(web.repo, req)
668 if 'file' not in req.form:
668 if 'file' not in req.form:
669 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
669 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
670 path = webutil.cleanpath(web.repo, req.form['file'][0])
670 path = webutil.cleanpath(web.repo, req.form['file'][0])
671 rename = path in ctx and webutil.renamelink(ctx[path]) or []
671 rename = path in ctx and webutil.renamelink(ctx[path]) or []
672
672
673 parsecontext = lambda v: v == 'full' and -1 or int(v)
673 parsecontext = lambda v: v == 'full' and -1 or int(v)
674 if 'context' in req.form:
674 if 'context' in req.form:
675 context = parsecontext(req.form['context'][0])
675 context = parsecontext(req.form['context'][0])
676 else:
676 else:
677 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
677 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
678
678
679 def filelines(f):
679 def filelines(f):
680 if util.binary(f.data()):
680 if util.binary(f.data()):
681 mt = mimetypes.guess_type(f.path())[0]
681 mt = mimetypes.guess_type(f.path())[0]
682 if not mt:
682 if not mt:
683 mt = 'application/octet-stream'
683 mt = 'application/octet-stream'
684 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
684 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
685 return f.data().splitlines()
685 return f.data().splitlines()
686
686
687 if path in ctx:
687 if path in ctx:
688 fctx = ctx[path]
688 fctx = ctx[path]
689 rightrev = fctx.filerev()
689 rightrev = fctx.filerev()
690 rightnode = fctx.filenode()
690 rightnode = fctx.filenode()
691 rightlines = filelines(fctx)
691 rightlines = filelines(fctx)
692 parents = fctx.parents()
692 parents = fctx.parents()
693 if not parents:
693 if not parents:
694 leftrev = -1
694 leftrev = -1
695 leftnode = nullid
695 leftnode = nullid
696 leftlines = ()
696 leftlines = ()
697 else:
697 else:
698 pfctx = parents[0]
698 pfctx = parents[0]
699 leftrev = pfctx.filerev()
699 leftrev = pfctx.filerev()
700 leftnode = pfctx.filenode()
700 leftnode = pfctx.filenode()
701 leftlines = filelines(pfctx)
701 leftlines = filelines(pfctx)
702 else:
702 else:
703 rightrev = -1
703 rightrev = -1
704 rightnode = nullid
704 rightnode = nullid
705 rightlines = ()
705 rightlines = ()
706 fctx = ctx.parents()[0][path]
706 fctx = ctx.parents()[0][path]
707 leftrev = fctx.filerev()
707 leftrev = fctx.filerev()
708 leftnode = fctx.filenode()
708 leftnode = fctx.filenode()
709 leftlines = filelines(fctx)
709 leftlines = filelines(fctx)
710
710
711 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
711 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
712 return tmpl('filecomparison',
712 return tmpl('filecomparison',
713 file=path,
713 file=path,
714 node=hex(ctx.node()),
714 node=hex(ctx.node()),
715 rev=ctx.rev(),
715 rev=ctx.rev(),
716 date=ctx.date(),
716 date=ctx.date(),
717 desc=ctx.description(),
717 desc=ctx.description(),
718 extra=ctx.extra(),
718 extra=ctx.extra(),
719 author=ctx.user(),
719 author=ctx.user(),
720 rename=rename,
720 rename=rename,
721 branch=webutil.nodebranchnodefault(ctx),
721 branch=webutil.nodebranchnodefault(ctx),
722 parent=webutil.parents(fctx),
722 parent=webutil.parents(fctx),
723 child=webutil.children(fctx),
723 child=webutil.children(fctx),
724 leftrev=leftrev,
724 leftrev=leftrev,
725 leftnode=hex(leftnode),
725 leftnode=hex(leftnode),
726 rightrev=rightrev,
726 rightrev=rightrev,
727 rightnode=hex(rightnode),
727 rightnode=hex(rightnode),
728 comparison=comparison)
728 comparison=comparison)
729
729
730 def annotate(web, req, tmpl):
730 def annotate(web, req, tmpl):
731 fctx = webutil.filectx(web.repo, req)
731 fctx = webutil.filectx(web.repo, req)
732 f = fctx.path()
732 f = fctx.path()
733 parity = paritygen(web.stripecount)
733 parity = paritygen(web.stripecount)
734 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
734 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
735
735
736 def annotate(**map):
736 def annotate(**map):
737 last = None
737 last = None
738 if util.binary(fctx.data()):
738 if util.binary(fctx.data()):
739 mt = (mimetypes.guess_type(fctx.path())[0]
739 mt = (mimetypes.guess_type(fctx.path())[0]
740 or 'application/octet-stream')
740 or 'application/octet-stream')
741 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
741 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
742 '(binary:%s)' % mt)])
742 '(binary:%s)' % mt)])
743 else:
743 else:
744 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
744 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
745 diffopts=diffopts))
745 diffopts=diffopts))
746 for lineno, ((f, targetline), l) in lines:
746 for lineno, ((f, targetline), l) in lines:
747 fnode = f.filenode()
747 fnode = f.filenode()
748
748
749 if last != fnode:
749 if last != fnode:
750 last = fnode
750 last = fnode
751
751
752 yield {"parity": parity.next(),
752 yield {"parity": parity.next(),
753 "node": f.hex(),
753 "node": f.hex(),
754 "rev": f.rev(),
754 "rev": f.rev(),
755 "author": f.user(),
755 "author": f.user(),
756 "desc": f.description(),
756 "desc": f.description(),
757 "extra": f.extra(),
757 "extra": f.extra(),
758 "file": f.path(),
758 "file": f.path(),
759 "targetline": targetline,
759 "targetline": targetline,
760 "line": l,
760 "line": l,
761 "lineid": "l%d" % (lineno + 1),
761 "lineid": "l%d" % (lineno + 1),
762 "linenumber": "% 6d" % (lineno + 1),
762 "linenumber": "% 6d" % (lineno + 1),
763 "revdate": f.date()}
763 "revdate": f.date()}
764
764
765 return tmpl("fileannotate",
765 return tmpl("fileannotate",
766 file=f,
766 file=f,
767 annotate=annotate,
767 annotate=annotate,
768 path=webutil.up(f),
768 path=webutil.up(f),
769 rev=fctx.rev(),
769 rev=fctx.rev(),
770 node=fctx.hex(),
770 node=fctx.hex(),
771 author=fctx.user(),
771 author=fctx.user(),
772 date=fctx.date(),
772 date=fctx.date(),
773 desc=fctx.description(),
773 desc=fctx.description(),
774 extra=fctx.extra(),
774 extra=fctx.extra(),
775 rename=webutil.renamelink(fctx),
775 rename=webutil.renamelink(fctx),
776 branch=webutil.nodebranchnodefault(fctx),
776 branch=webutil.nodebranchnodefault(fctx),
777 parent=webutil.parents(fctx),
777 parent=webutil.parents(fctx),
778 child=webutil.children(fctx),
778 child=webutil.children(fctx),
779 permissions=fctx.manifest().flags(f))
779 permissions=fctx.manifest().flags(f))
780
780
781 def filelog(web, req, tmpl):
781 def filelog(web, req, tmpl):
782
782
783 try:
783 try:
784 fctx = webutil.filectx(web.repo, req)
784 fctx = webutil.filectx(web.repo, req)
785 f = fctx.path()
785 f = fctx.path()
786 fl = fctx.filelog()
786 fl = fctx.filelog()
787 except error.LookupError:
787 except error.LookupError:
788 f = webutil.cleanpath(web.repo, req.form['file'][0])
788 f = webutil.cleanpath(web.repo, req.form['file'][0])
789 fl = web.repo.file(f)
789 fl = web.repo.file(f)
790 numrevs = len(fl)
790 numrevs = len(fl)
791 if not numrevs: # file doesn't exist at all
791 if not numrevs: # file doesn't exist at all
792 raise
792 raise
793 rev = webutil.changectx(web.repo, req).rev()
793 rev = webutil.changectx(web.repo, req).rev()
794 first = fl.linkrev(0)
794 first = fl.linkrev(0)
795 if rev < first: # current rev is from before file existed
795 if rev < first: # current rev is from before file existed
796 raise
796 raise
797 frev = numrevs - 1
797 frev = numrevs - 1
798 while fl.linkrev(frev) > rev:
798 while fl.linkrev(frev) > rev:
799 frev -= 1
799 frev -= 1
800 fctx = web.repo.filectx(f, fl.linkrev(frev))
800 fctx = web.repo.filectx(f, fl.linkrev(frev))
801
801
802 revcount = web.maxshortchanges
802 revcount = web.maxshortchanges
803 if 'revcount' in req.form:
803 if 'revcount' in req.form:
804 revcount = int(req.form.get('revcount', [revcount])[0])
804 revcount = int(req.form.get('revcount', [revcount])[0])
805 revcount = max(revcount, 1)
805 revcount = max(revcount, 1)
806 tmpl.defaults['sessionvars']['revcount'] = revcount
806 tmpl.defaults['sessionvars']['revcount'] = revcount
807
807
808 lessvars = copy.copy(tmpl.defaults['sessionvars'])
808 lessvars = copy.copy(tmpl.defaults['sessionvars'])
809 lessvars['revcount'] = max(revcount / 2, 1)
809 lessvars['revcount'] = max(revcount / 2, 1)
810 morevars = copy.copy(tmpl.defaults['sessionvars'])
810 morevars = copy.copy(tmpl.defaults['sessionvars'])
811 morevars['revcount'] = revcount * 2
811 morevars['revcount'] = revcount * 2
812
812
813 count = fctx.filerev() + 1
813 count = fctx.filerev() + 1
814 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
814 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
815 end = min(count, start + revcount) # last rev on this page
815 end = min(count, start + revcount) # last rev on this page
816 parity = paritygen(web.stripecount, offset=start - end)
816 parity = paritygen(web.stripecount, offset=start - end)
817
817
818 def entries(latestonly, **map):
818 def entries(latestonly, **map):
819 l = []
819 l = []
820
820
821 repo = web.repo
821 repo = web.repo
822 revs = repo.changelog.revs(start, end - 1)
822 revs = repo.changelog.revs(start, end - 1)
823 if latestonly:
823 if latestonly:
824 for r in revs:
824 for r in revs:
825 pass
825 pass
826 revs = (r,)
826 revs = (r,)
827 for i in revs:
827 for i in revs:
828 iterfctx = fctx.filectx(i)
828 iterfctx = fctx.filectx(i)
829
829
830 l.append({"parity": parity.next(),
830 l.append({"parity": parity.next(),
831 "filerev": i,
831 "filerev": i,
832 "file": f,
832 "file": f,
833 "node": iterfctx.hex(),
833 "node": iterfctx.hex(),
834 "author": iterfctx.user(),
834 "author": iterfctx.user(),
835 "date": iterfctx.date(),
835 "date": iterfctx.date(),
836 "rename": webutil.renamelink(iterfctx),
836 "rename": webutil.renamelink(iterfctx),
837 "parent": webutil.parents(iterfctx),
837 "parent": webutil.parents(iterfctx),
838 "child": webutil.children(iterfctx),
838 "child": webutil.children(iterfctx),
839 "desc": iterfctx.description(),
839 "desc": iterfctx.description(),
840 "extra": iterfctx.extra(),
840 "extra": iterfctx.extra(),
841 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
841 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
842 "bookmarks": webutil.nodebookmarksdict(
842 "bookmarks": webutil.nodebookmarksdict(
843 repo, iterfctx.node()),
843 repo, iterfctx.node()),
844 "branch": webutil.nodebranchnodefault(iterfctx),
844 "branch": webutil.nodebranchnodefault(iterfctx),
845 "inbranch": webutil.nodeinbranch(repo, iterfctx),
845 "inbranch": webutil.nodeinbranch(repo, iterfctx),
846 "branches": webutil.nodebranchdict(repo, iterfctx)})
846 "branches": webutil.nodebranchdict(repo, iterfctx)})
847 for e in reversed(l):
847 for e in reversed(l):
848 yield e
848 yield e
849
849
850 revnav = webutil.filerevnav(web.repo, fctx.path())
850 revnav = webutil.filerevnav(web.repo, fctx.path())
851 nav = revnav.gen(end - 1, revcount, count)
851 nav = revnav.gen(end - 1, revcount, count)
852 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
852 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
853 entries=lambda **x: entries(latestonly=False, **x),
853 entries=lambda **x: entries(latestonly=False, **x),
854 latestentry=lambda **x: entries(latestonly=True, **x),
854 latestentry=lambda **x: entries(latestonly=True, **x),
855 revcount=revcount, morevars=morevars, lessvars=lessvars)
855 revcount=revcount, morevars=morevars, lessvars=lessvars)
856
856
857 def archive(web, req, tmpl):
857 def archive(web, req, tmpl):
858 type_ = req.form.get('type', [None])[0]
858 type_ = req.form.get('type', [None])[0]
859 allowed = web.configlist("web", "allow_archive")
859 allowed = web.configlist("web", "allow_archive")
860 key = req.form['node'][0]
860 key = req.form['node'][0]
861
861
862 if type_ not in web.archives:
862 if type_ not in web.archives:
863 msg = 'Unsupported archive type: %s' % type_
863 msg = 'Unsupported archive type: %s' % type_
864 raise ErrorResponse(HTTP_NOT_FOUND, msg)
864 raise ErrorResponse(HTTP_NOT_FOUND, msg)
865
865
866 if not ((type_ in allowed or
866 if not ((type_ in allowed or
867 web.configbool("web", "allow" + type_, False))):
867 web.configbool("web", "allow" + type_, False))):
868 msg = 'Archive type not allowed: %s' % type_
868 msg = 'Archive type not allowed: %s' % type_
869 raise ErrorResponse(HTTP_FORBIDDEN, msg)
869 raise ErrorResponse(HTTP_FORBIDDEN, msg)
870
870
871 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
871 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
872 cnode = web.repo.lookup(key)
872 cnode = web.repo.lookup(key)
873 arch_version = key
873 arch_version = key
874 if cnode == key or key == 'tip':
874 if cnode == key or key == 'tip':
875 arch_version = short(cnode)
875 arch_version = short(cnode)
876 name = "%s-%s" % (reponame, arch_version)
876 name = "%s-%s" % (reponame, arch_version)
877
877
878 ctx = webutil.changectx(web.repo, req)
878 ctx = webutil.changectx(web.repo, req)
879 pats = []
879 pats = []
880 matchfn = None
880 matchfn = None
881 file = req.form.get('file', None)
881 file = req.form.get('file', None)
882 if file:
882 if file:
883 pats = ['path:' + file[0]]
883 pats = ['path:' + file[0]]
884 matchfn = scmutil.match(ctx, pats, default='path')
884 matchfn = scmutil.match(ctx, pats, default='path')
885 if pats:
885 if pats:
886 files = [f for f in ctx.manifest().keys() if matchfn(f)]
886 files = [f for f in ctx.manifest().keys() if matchfn(f)]
887 if not files:
887 if not files:
888 raise ErrorResponse(HTTP_NOT_FOUND,
888 raise ErrorResponse(HTTP_NOT_FOUND,
889 'file(s) not found: %s' % file[0])
889 'file(s) not found: %s' % file[0])
890
890
891 mimetype, artype, extension, encoding = web.archive_specs[type_]
891 mimetype, artype, extension, encoding = web.archive_specs[type_]
892 headers = [
892 headers = [
893 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
893 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
894 ]
894 ]
895 if encoding:
895 if encoding:
896 headers.append(('Content-Encoding', encoding))
896 headers.append(('Content-Encoding', encoding))
897 req.headers.extend(headers)
897 req.headers.extend(headers)
898 req.respond(HTTP_OK, mimetype)
898 req.respond(HTTP_OK, mimetype)
899
899
900 archival.archive(web.repo, req, cnode, artype, prefix=name,
900 archival.archive(web.repo, req, cnode, artype, prefix=name,
901 matchfn=matchfn,
901 matchfn=matchfn,
902 subrepos=web.configbool("web", "archivesubrepos"))
902 subrepos=web.configbool("web", "archivesubrepos"))
903 return []
903 return []
904
904
905
905
906 def static(web, req, tmpl):
906 def static(web, req, tmpl):
907 fname = req.form['file'][0]
907 fname = req.form['file'][0]
908 # a repo owner may set web.static in .hg/hgrc to get any file
908 # a repo owner may set web.static in .hg/hgrc to get any file
909 # readable by the user running the CGI script
909 # readable by the user running the CGI script
910 static = web.config("web", "static", None, untrusted=False)
910 static = web.config("web", "static", None, untrusted=False)
911 if not static:
911 if not static:
912 tp = web.templatepath or templater.templatepath()
912 tp = web.templatepath or templater.templatepath()
913 if isinstance(tp, str):
913 if isinstance(tp, str):
914 tp = [tp]
914 tp = [tp]
915 static = [os.path.join(p, 'static') for p in tp]
915 static = [os.path.join(p, 'static') for p in tp]
916 staticfile(static, fname, req)
916 staticfile(static, fname, req)
917 return []
917 return []
918
918
919 def graph(web, req, tmpl):
919 def graph(web, req, tmpl):
920
920
921 ctx = webutil.changectx(web.repo, req)
921 ctx = webutil.changectx(web.repo, req)
922 rev = ctx.rev()
922 rev = ctx.rev()
923
923
924 bg_height = 39
924 bg_height = 39
925 revcount = web.maxshortchanges
925 revcount = web.maxshortchanges
926 if 'revcount' in req.form:
926 if 'revcount' in req.form:
927 revcount = int(req.form.get('revcount', [revcount])[0])
927 revcount = int(req.form.get('revcount', [revcount])[0])
928 revcount = max(revcount, 1)
928 revcount = max(revcount, 1)
929 tmpl.defaults['sessionvars']['revcount'] = revcount
929 tmpl.defaults['sessionvars']['revcount'] = revcount
930
930
931 lessvars = copy.copy(tmpl.defaults['sessionvars'])
931 lessvars = copy.copy(tmpl.defaults['sessionvars'])
932 lessvars['revcount'] = max(revcount / 2, 1)
932 lessvars['revcount'] = max(revcount / 2, 1)
933 morevars = copy.copy(tmpl.defaults['sessionvars'])
933 morevars = copy.copy(tmpl.defaults['sessionvars'])
934 morevars['revcount'] = revcount * 2
934 morevars['revcount'] = revcount * 2
935
935
936 count = len(web.repo)
936 count = len(web.repo)
937 pos = rev
937 pos = rev
938
938
939 uprev = min(max(0, count - 1), rev + revcount)
939 uprev = min(max(0, count - 1), rev + revcount)
940 downrev = max(0, rev - revcount)
940 downrev = max(0, rev - revcount)
941 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
941 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
942
942
943 tree = []
943 tree = []
944 if pos != -1:
944 if pos != -1:
945 allrevs = web.repo.changelog.revs(pos, 0)
945 allrevs = web.repo.changelog.revs(pos, 0)
946 revs = []
946 revs = []
947 for i in allrevs:
947 for i in allrevs:
948 revs.append(i)
948 revs.append(i)
949 if len(revs) >= revcount:
949 if len(revs) >= revcount:
950 break
950 break
951
951
952 dag = graphmod.dagwalker(web.repo, revs)
952 dag = graphmod.dagwalker(web.repo, revs)
953 tree = list(graphmod.colored(dag, web.repo))
953 tree = list(graphmod.colored(dag, web.repo))
954
954
955 def getcolumns(tree):
955 def getcolumns(tree):
956 cols = 0
956 cols = 0
957 for (id, type, ctx, vtx, edges) in tree:
957 for (id, type, ctx, vtx, edges) in tree:
958 if type != graphmod.CHANGESET:
958 if type != graphmod.CHANGESET:
959 continue
959 continue
960 cols = max(cols, max([edge[0] for edge in edges] or [0]),
960 cols = max(cols, max([edge[0] for edge in edges] or [0]),
961 max([edge[1] for edge in edges] or [0]))
961 max([edge[1] for edge in edges] or [0]))
962 return cols
962 return cols
963
963
964 def graphdata(usetuples, **map):
964 def graphdata(usetuples, **map):
965 data = []
965 data = []
966
966
967 row = 0
967 row = 0
968 for (id, type, ctx, vtx, edges) in tree:
968 for (id, type, ctx, vtx, edges) in tree:
969 if type != graphmod.CHANGESET:
969 if type != graphmod.CHANGESET:
970 continue
970 continue
971 node = str(ctx)
971 node = str(ctx)
972 age = templatefilters.age(ctx.date())
972 age = templatefilters.age(ctx.date())
973 desc = templatefilters.firstline(ctx.description())
973 desc = templatefilters.firstline(ctx.description())
974 desc = cgi.escape(templatefilters.nonempty(desc))
974 desc = cgi.escape(templatefilters.nonempty(desc))
975 user = cgi.escape(templatefilters.person(ctx.user()))
975 user = cgi.escape(templatefilters.person(ctx.user()))
976 branch = ctx.branch()
976 branch = ctx.branch()
977 try:
977 try:
978 branchnode = web.repo.branchtip(branch)
978 branchnode = web.repo.branchtip(branch)
979 except error.RepoLookupError:
979 except error.RepoLookupError:
980 branchnode = None
980 branchnode = None
981 branch = branch, branchnode == ctx.node()
981 branch = branch, branchnode == ctx.node()
982
982
983 if usetuples:
983 if usetuples:
984 data.append((node, vtx, edges, desc, user, age, branch,
984 data.append((node, vtx, edges, desc, user, age, branch,
985 ctx.tags(), ctx.bookmarks()))
985 ctx.tags(), ctx.bookmarks()))
986 else:
986 else:
987 edgedata = [dict(col=edge[0], nextcol=edge[1],
987 edgedata = [dict(col=edge[0], nextcol=edge[1],
988 color=(edge[2] - 1) % 6 + 1,
988 color=(edge[2] - 1) % 6 + 1,
989 width=edge[3], bcolor=edge[4])
989 width=edge[3], bcolor=edge[4])
990 for edge in edges]
990 for edge in edges]
991
991
992 data.append(
992 data.append(
993 dict(node=node,
993 dict(node=node,
994 col=vtx[0],
994 col=vtx[0],
995 color=(vtx[1] - 1) % 6 + 1,
995 color=(vtx[1] - 1) % 6 + 1,
996 edges=edgedata,
996 edges=edgedata,
997 row=row,
997 row=row,
998 nextrow=row + 1,
998 nextrow=row + 1,
999 desc=desc,
999 desc=desc,
1000 user=user,
1000 user=user,
1001 age=age,
1001 age=age,
1002 bookmarks=webutil.nodebookmarksdict(
1002 bookmarks=webutil.nodebookmarksdict(
1003 web.repo, ctx.node()),
1003 web.repo, ctx.node()),
1004 branches=webutil.nodebranchdict(web.repo, ctx),
1004 branches=webutil.nodebranchdict(web.repo, ctx),
1005 inbranch=webutil.nodeinbranch(web.repo, ctx),
1005 inbranch=webutil.nodeinbranch(web.repo, ctx),
1006 tags=webutil.nodetagsdict(web.repo, ctx.node())))
1006 tags=webutil.nodetagsdict(web.repo, ctx.node())))
1007
1007
1008 row += 1
1008 row += 1
1009
1009
1010 return data
1010 return data
1011
1011
1012 cols = getcolumns(tree)
1012 cols = getcolumns(tree)
1013 rows = len(tree)
1013 rows = len(tree)
1014 canvasheight = (rows + 1) * bg_height - 27
1014 canvasheight = (rows + 1) * bg_height - 27
1015
1015
1016 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1016 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1017 lessvars=lessvars, morevars=morevars, downrev=downrev,
1017 lessvars=lessvars, morevars=morevars, downrev=downrev,
1018 cols=cols, rows=rows,
1018 cols=cols, rows=rows,
1019 canvaswidth=(cols + 1) * bg_height,
1019 canvaswidth=(cols + 1) * bg_height,
1020 truecanvasheight=rows * bg_height,
1020 truecanvasheight=rows * bg_height,
1021 canvasheight=canvasheight, bg_height=bg_height,
1021 canvasheight=canvasheight, bg_height=bg_height,
1022 jsdata=lambda **x: graphdata(True, **x),
1022 jsdata=lambda **x: graphdata(True, **x),
1023 nodes=lambda **x: graphdata(False, **x),
1023 nodes=lambda **x: graphdata(False, **x),
1024 node=ctx.hex(), changenav=changenav)
1024 node=ctx.hex(), changenav=changenav)
1025
1025
1026 def _getdoc(e):
1026 def _getdoc(e):
1027 doc = e[0].__doc__
1027 doc = e[0].__doc__
1028 if doc:
1028 if doc:
1029 doc = _(doc).split('\n')[0]
1029 doc = _(doc).split('\n')[0]
1030 else:
1030 else:
1031 doc = _('(no help text available)')
1031 doc = _('(no help text available)')
1032 return doc
1032 return doc
1033
1033
1034 def help(web, req, tmpl):
1034 def help(web, req, tmpl):
1035 from mercurial import commands # avoid cycle
1035 from mercurial import commands # avoid cycle
1036
1036
1037 topicname = req.form.get('node', [None])[0]
1037 topicname = req.form.get('node', [None])[0]
1038 if not topicname:
1038 if not topicname:
1039 def topics(**map):
1039 def topics(**map):
1040 for entries, summary, _ in helpmod.helptable:
1040 for entries, summary, _ in helpmod.helptable:
1041 yield {'topic': entries[0], 'summary': summary}
1041 yield {'topic': entries[0], 'summary': summary}
1042
1042
1043 early, other = [], []
1043 early, other = [], []
1044 primary = lambda s: s.split('|')[0]
1044 primary = lambda s: s.split('|')[0]
1045 for c, e in commands.table.iteritems():
1045 for c, e in commands.table.iteritems():
1046 doc = _getdoc(e)
1046 doc = _getdoc(e)
1047 if 'DEPRECATED' in doc or c.startswith('debug'):
1047 if 'DEPRECATED' in doc or c.startswith('debug'):
1048 continue
1048 continue
1049 cmd = primary(c)
1049 cmd = primary(c)
1050 if cmd.startswith('^'):
1050 if cmd.startswith('^'):
1051 early.append((cmd[1:], doc))
1051 early.append((cmd[1:], doc))
1052 else:
1052 else:
1053 other.append((cmd, doc))
1053 other.append((cmd, doc))
1054
1054
1055 early.sort()
1055 early.sort()
1056 other.sort()
1056 other.sort()
1057
1057
1058 def earlycommands(**map):
1058 def earlycommands(**map):
1059 for c, doc in early:
1059 for c, doc in early:
1060 yield {'topic': c, 'summary': doc}
1060 yield {'topic': c, 'summary': doc}
1061
1061
1062 def othercommands(**map):
1062 def othercommands(**map):
1063 for c, doc in other:
1063 for c, doc in other:
1064 yield {'topic': c, 'summary': doc}
1064 yield {'topic': c, 'summary': doc}
1065
1065
1066 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1066 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1067 othercommands=othercommands, title='Index')
1067 othercommands=othercommands, title='Index')
1068
1068
1069 u = webutil.wsgiui()
1069 u = webutil.wsgiui()
1070 u.verbose = True
1070 u.verbose = True
1071 try:
1071 try:
1072 doc = helpmod.help_(u, topicname)
1072 doc = helpmod.help_(u, topicname)
1073 except error.UnknownCommand:
1073 except error.UnknownCommand:
1074 raise ErrorResponse(HTTP_NOT_FOUND)
1074 raise ErrorResponse(HTTP_NOT_FOUND)
1075 return tmpl('help', topic=topicname, doc=doc)
1075 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now