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