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