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