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