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