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