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