##// END OF EJS Templates
webcommands: document "branches" web command
Gregory Szorc -
r24082:32dabf81 default
parent child Browse files
Show More
@@ -1,1138 +1,1150 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 """
545 /branches
546 ---------
547
548 Show information about branches.
549
550 All known branches are contained in the output, even closed branches.
551
552 No arguments are accepted.
553
554 The ``branches`` template is rendered.
555 """
544 tips = []
556 tips = []
545 heads = web.repo.heads()
557 heads = web.repo.heads()
546 parity = paritygen(web.stripecount)
558 parity = paritygen(web.stripecount)
547 sortkey = lambda item: (not item[1], item[0].rev())
559 sortkey = lambda item: (not item[1], item[0].rev())
548
560
549 def entries(limit, **map):
561 def entries(limit, **map):
550 count = 0
562 count = 0
551 if not tips:
563 if not tips:
552 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
564 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
553 tips.append((web.repo[tip], closed))
565 tips.append((web.repo[tip], closed))
554 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
566 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
555 if limit > 0 and count >= limit:
567 if limit > 0 and count >= limit:
556 return
568 return
557 count += 1
569 count += 1
558 if closed:
570 if closed:
559 status = 'closed'
571 status = 'closed'
560 elif ctx.node() not in heads:
572 elif ctx.node() not in heads:
561 status = 'inactive'
573 status = 'inactive'
562 else:
574 else:
563 status = 'open'
575 status = 'open'
564 yield {'parity': parity.next(),
576 yield {'parity': parity.next(),
565 'branch': ctx.branch(),
577 'branch': ctx.branch(),
566 'status': status,
578 'status': status,
567 'node': ctx.hex(),
579 'node': ctx.hex(),
568 'date': ctx.date()}
580 'date': ctx.date()}
569
581
570 return tmpl('branches', node=hex(web.repo.changelog.tip()),
582 return tmpl('branches', node=hex(web.repo.changelog.tip()),
571 entries=lambda **x: entries(0, **x),
583 entries=lambda **x: entries(0, **x),
572 latestentry=lambda **x: entries(1, **x))
584 latestentry=lambda **x: entries(1, **x))
573
585
574 @webcommand('summary')
586 @webcommand('summary')
575 def summary(web, req, tmpl):
587 def summary(web, req, tmpl):
576 i = reversed(web.repo.tagslist())
588 i = reversed(web.repo.tagslist())
577
589
578 def tagentries(**map):
590 def tagentries(**map):
579 parity = paritygen(web.stripecount)
591 parity = paritygen(web.stripecount)
580 count = 0
592 count = 0
581 for k, n in i:
593 for k, n in i:
582 if k == "tip": # skip tip
594 if k == "tip": # skip tip
583 continue
595 continue
584
596
585 count += 1
597 count += 1
586 if count > 10: # limit to 10 tags
598 if count > 10: # limit to 10 tags
587 break
599 break
588
600
589 yield tmpl("tagentry",
601 yield tmpl("tagentry",
590 parity=parity.next(),
602 parity=parity.next(),
591 tag=k,
603 tag=k,
592 node=hex(n),
604 node=hex(n),
593 date=web.repo[n].date())
605 date=web.repo[n].date())
594
606
595 def bookmarks(**map):
607 def bookmarks(**map):
596 parity = paritygen(web.stripecount)
608 parity = paritygen(web.stripecount)
597 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
609 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
610 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
599 yield {'parity': parity.next(),
611 yield {'parity': parity.next(),
600 'bookmark': k,
612 'bookmark': k,
601 'date': web.repo[n].date(),
613 'date': web.repo[n].date(),
602 'node': hex(n)}
614 'node': hex(n)}
603
615
604 def branches(**map):
616 def branches(**map):
605 parity = paritygen(web.stripecount)
617 parity = paritygen(web.stripecount)
606
618
607 b = web.repo.branchmap()
619 b = web.repo.branchmap()
608 l = [(-web.repo.changelog.rev(tip), tip, tag)
620 l = [(-web.repo.changelog.rev(tip), tip, tag)
609 for tag, heads, tip, closed in b.iterbranches()]
621 for tag, heads, tip, closed in b.iterbranches()]
610 for r, n, t in sorted(l):
622 for r, n, t in sorted(l):
611 yield {'parity': parity.next(),
623 yield {'parity': parity.next(),
612 'branch': t,
624 'branch': t,
613 'node': hex(n),
625 'node': hex(n),
614 'date': web.repo[n].date()}
626 'date': web.repo[n].date()}
615
627
616 def changelist(**map):
628 def changelist(**map):
617 parity = paritygen(web.stripecount, offset=start - end)
629 parity = paritygen(web.stripecount, offset=start - end)
618 l = [] # build a list in forward order for efficiency
630 l = [] # build a list in forward order for efficiency
619 revs = []
631 revs = []
620 if start < end:
632 if start < end:
621 revs = web.repo.changelog.revs(start, end - 1)
633 revs = web.repo.changelog.revs(start, end - 1)
622 for i in revs:
634 for i in revs:
623 ctx = web.repo[i]
635 ctx = web.repo[i]
624 n = ctx.node()
636 n = ctx.node()
625 hn = hex(n)
637 hn = hex(n)
626
638
627 l.append(tmpl(
639 l.append(tmpl(
628 'shortlogentry',
640 'shortlogentry',
629 parity=parity.next(),
641 parity=parity.next(),
630 author=ctx.user(),
642 author=ctx.user(),
631 desc=ctx.description(),
643 desc=ctx.description(),
632 extra=ctx.extra(),
644 extra=ctx.extra(),
633 date=ctx.date(),
645 date=ctx.date(),
634 rev=i,
646 rev=i,
635 node=hn,
647 node=hn,
636 tags=webutil.nodetagsdict(web.repo, n),
648 tags=webutil.nodetagsdict(web.repo, n),
637 bookmarks=webutil.nodebookmarksdict(web.repo, n),
649 bookmarks=webutil.nodebookmarksdict(web.repo, n),
638 inbranch=webutil.nodeinbranch(web.repo, ctx),
650 inbranch=webutil.nodeinbranch(web.repo, ctx),
639 branches=webutil.nodebranchdict(web.repo, ctx)))
651 branches=webutil.nodebranchdict(web.repo, ctx)))
640
652
641 l.reverse()
653 l.reverse()
642 yield l
654 yield l
643
655
644 tip = web.repo['tip']
656 tip = web.repo['tip']
645 count = len(web.repo)
657 count = len(web.repo)
646 start = max(0, count - web.maxchanges)
658 start = max(0, count - web.maxchanges)
647 end = min(count, start + web.maxchanges)
659 end = min(count, start + web.maxchanges)
648
660
649 return tmpl("summary",
661 return tmpl("summary",
650 desc=web.config("web", "description", "unknown"),
662 desc=web.config("web", "description", "unknown"),
651 owner=get_contact(web.config) or "unknown",
663 owner=get_contact(web.config) or "unknown",
652 lastchange=tip.date(),
664 lastchange=tip.date(),
653 tags=tagentries,
665 tags=tagentries,
654 bookmarks=bookmarks,
666 bookmarks=bookmarks,
655 branches=branches,
667 branches=branches,
656 shortlog=changelist,
668 shortlog=changelist,
657 node=tip.hex(),
669 node=tip.hex(),
658 archives=web.archivelist("tip"))
670 archives=web.archivelist("tip"))
659
671
660 @webcommand('filediff')
672 @webcommand('filediff')
661 def filediff(web, req, tmpl):
673 def filediff(web, req, tmpl):
662 fctx, ctx = None, None
674 fctx, ctx = None, None
663 try:
675 try:
664 fctx = webutil.filectx(web.repo, req)
676 fctx = webutil.filectx(web.repo, req)
665 except LookupError:
677 except LookupError:
666 ctx = webutil.changectx(web.repo, req)
678 ctx = webutil.changectx(web.repo, req)
667 path = webutil.cleanpath(web.repo, req.form['file'][0])
679 path = webutil.cleanpath(web.repo, req.form['file'][0])
668 if path not in ctx.files():
680 if path not in ctx.files():
669 raise
681 raise
670
682
671 if fctx is not None:
683 if fctx is not None:
672 n = fctx.node()
684 n = fctx.node()
673 path = fctx.path()
685 path = fctx.path()
674 ctx = fctx.changectx()
686 ctx = fctx.changectx()
675 else:
687 else:
676 n = ctx.node()
688 n = ctx.node()
677 # path already defined in except clause
689 # path already defined in except clause
678
690
679 parity = paritygen(web.stripecount)
691 parity = paritygen(web.stripecount)
680 style = web.config('web', 'style', 'paper')
692 style = web.config('web', 'style', 'paper')
681 if 'style' in req.form:
693 if 'style' in req.form:
682 style = req.form['style'][0]
694 style = req.form['style'][0]
683
695
684 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
696 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
685 rename = fctx and webutil.renamelink(fctx) or []
697 rename = fctx and webutil.renamelink(fctx) or []
686 ctx = fctx and fctx or ctx
698 ctx = fctx and fctx or ctx
687 return tmpl("filediff",
699 return tmpl("filediff",
688 file=path,
700 file=path,
689 node=hex(n),
701 node=hex(n),
690 rev=ctx.rev(),
702 rev=ctx.rev(),
691 date=ctx.date(),
703 date=ctx.date(),
692 desc=ctx.description(),
704 desc=ctx.description(),
693 extra=ctx.extra(),
705 extra=ctx.extra(),
694 author=ctx.user(),
706 author=ctx.user(),
695 rename=rename,
707 rename=rename,
696 branch=webutil.nodebranchnodefault(ctx),
708 branch=webutil.nodebranchnodefault(ctx),
697 parent=webutil.parents(ctx),
709 parent=webutil.parents(ctx),
698 child=webutil.children(ctx),
710 child=webutil.children(ctx),
699 diff=diffs)
711 diff=diffs)
700
712
701 diff = webcommand('diff')(filediff)
713 diff = webcommand('diff')(filediff)
702
714
703 @webcommand('comparison')
715 @webcommand('comparison')
704 def comparison(web, req, tmpl):
716 def comparison(web, req, tmpl):
705 ctx = webutil.changectx(web.repo, req)
717 ctx = webutil.changectx(web.repo, req)
706 if 'file' not in req.form:
718 if 'file' not in req.form:
707 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
719 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
708 path = webutil.cleanpath(web.repo, req.form['file'][0])
720 path = webutil.cleanpath(web.repo, req.form['file'][0])
709 rename = path in ctx and webutil.renamelink(ctx[path]) or []
721 rename = path in ctx and webutil.renamelink(ctx[path]) or []
710
722
711 parsecontext = lambda v: v == 'full' and -1 or int(v)
723 parsecontext = lambda v: v == 'full' and -1 or int(v)
712 if 'context' in req.form:
724 if 'context' in req.form:
713 context = parsecontext(req.form['context'][0])
725 context = parsecontext(req.form['context'][0])
714 else:
726 else:
715 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
727 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
716
728
717 def filelines(f):
729 def filelines(f):
718 if util.binary(f.data()):
730 if util.binary(f.data()):
719 mt = mimetypes.guess_type(f.path())[0]
731 mt = mimetypes.guess_type(f.path())[0]
720 if not mt:
732 if not mt:
721 mt = 'application/octet-stream'
733 mt = 'application/octet-stream'
722 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
734 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
723 return f.data().splitlines()
735 return f.data().splitlines()
724
736
725 parent = ctx.p1()
737 parent = ctx.p1()
726 leftrev = parent.rev()
738 leftrev = parent.rev()
727 leftnode = parent.node()
739 leftnode = parent.node()
728 rightrev = ctx.rev()
740 rightrev = ctx.rev()
729 rightnode = ctx.node()
741 rightnode = ctx.node()
730 if path in ctx:
742 if path in ctx:
731 fctx = ctx[path]
743 fctx = ctx[path]
732 rightlines = filelines(fctx)
744 rightlines = filelines(fctx)
733 if path not in parent:
745 if path not in parent:
734 leftlines = ()
746 leftlines = ()
735 else:
747 else:
736 pfctx = parent[path]
748 pfctx = parent[path]
737 leftlines = filelines(pfctx)
749 leftlines = filelines(pfctx)
738 else:
750 else:
739 rightlines = ()
751 rightlines = ()
740 fctx = ctx.parents()[0][path]
752 fctx = ctx.parents()[0][path]
741 leftlines = filelines(fctx)
753 leftlines = filelines(fctx)
742
754
743 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
755 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
744 return tmpl('filecomparison',
756 return tmpl('filecomparison',
745 file=path,
757 file=path,
746 node=hex(ctx.node()),
758 node=hex(ctx.node()),
747 rev=ctx.rev(),
759 rev=ctx.rev(),
748 date=ctx.date(),
760 date=ctx.date(),
749 desc=ctx.description(),
761 desc=ctx.description(),
750 extra=ctx.extra(),
762 extra=ctx.extra(),
751 author=ctx.user(),
763 author=ctx.user(),
752 rename=rename,
764 rename=rename,
753 branch=webutil.nodebranchnodefault(ctx),
765 branch=webutil.nodebranchnodefault(ctx),
754 parent=webutil.parents(fctx),
766 parent=webutil.parents(fctx),
755 child=webutil.children(fctx),
767 child=webutil.children(fctx),
756 leftrev=leftrev,
768 leftrev=leftrev,
757 leftnode=hex(leftnode),
769 leftnode=hex(leftnode),
758 rightrev=rightrev,
770 rightrev=rightrev,
759 rightnode=hex(rightnode),
771 rightnode=hex(rightnode),
760 comparison=comparison)
772 comparison=comparison)
761
773
762 @webcommand('annotate')
774 @webcommand('annotate')
763 def annotate(web, req, tmpl):
775 def annotate(web, req, tmpl):
764 fctx = webutil.filectx(web.repo, req)
776 fctx = webutil.filectx(web.repo, req)
765 f = fctx.path()
777 f = fctx.path()
766 parity = paritygen(web.stripecount)
778 parity = paritygen(web.stripecount)
767 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
779 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
768 section='annotate', whitespace=True)
780 section='annotate', whitespace=True)
769
781
770 def annotate(**map):
782 def annotate(**map):
771 last = None
783 last = None
772 if util.binary(fctx.data()):
784 if util.binary(fctx.data()):
773 mt = (mimetypes.guess_type(fctx.path())[0]
785 mt = (mimetypes.guess_type(fctx.path())[0]
774 or 'application/octet-stream')
786 or 'application/octet-stream')
775 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
787 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
776 '(binary:%s)' % mt)])
788 '(binary:%s)' % mt)])
777 else:
789 else:
778 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
790 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
779 diffopts=diffopts))
791 diffopts=diffopts))
780 for lineno, ((f, targetline), l) in lines:
792 for lineno, ((f, targetline), l) in lines:
781 fnode = f.filenode()
793 fnode = f.filenode()
782
794
783 if last != fnode:
795 if last != fnode:
784 last = fnode
796 last = fnode
785
797
786 yield {"parity": parity.next(),
798 yield {"parity": parity.next(),
787 "node": f.hex(),
799 "node": f.hex(),
788 "rev": f.rev(),
800 "rev": f.rev(),
789 "author": f.user(),
801 "author": f.user(),
790 "desc": f.description(),
802 "desc": f.description(),
791 "extra": f.extra(),
803 "extra": f.extra(),
792 "file": f.path(),
804 "file": f.path(),
793 "targetline": targetline,
805 "targetline": targetline,
794 "line": l,
806 "line": l,
795 "lineid": "l%d" % (lineno + 1),
807 "lineid": "l%d" % (lineno + 1),
796 "linenumber": "% 6d" % (lineno + 1),
808 "linenumber": "% 6d" % (lineno + 1),
797 "revdate": f.date()}
809 "revdate": f.date()}
798
810
799 return tmpl("fileannotate",
811 return tmpl("fileannotate",
800 file=f,
812 file=f,
801 annotate=annotate,
813 annotate=annotate,
802 path=webutil.up(f),
814 path=webutil.up(f),
803 rev=fctx.rev(),
815 rev=fctx.rev(),
804 node=fctx.hex(),
816 node=fctx.hex(),
805 author=fctx.user(),
817 author=fctx.user(),
806 date=fctx.date(),
818 date=fctx.date(),
807 desc=fctx.description(),
819 desc=fctx.description(),
808 extra=fctx.extra(),
820 extra=fctx.extra(),
809 rename=webutil.renamelink(fctx),
821 rename=webutil.renamelink(fctx),
810 branch=webutil.nodebranchnodefault(fctx),
822 branch=webutil.nodebranchnodefault(fctx),
811 parent=webutil.parents(fctx),
823 parent=webutil.parents(fctx),
812 child=webutil.children(fctx),
824 child=webutil.children(fctx),
813 permissions=fctx.manifest().flags(f))
825 permissions=fctx.manifest().flags(f))
814
826
815 @webcommand('filelog')
827 @webcommand('filelog')
816 def filelog(web, req, tmpl):
828 def filelog(web, req, tmpl):
817
829
818 try:
830 try:
819 fctx = webutil.filectx(web.repo, req)
831 fctx = webutil.filectx(web.repo, req)
820 f = fctx.path()
832 f = fctx.path()
821 fl = fctx.filelog()
833 fl = fctx.filelog()
822 except error.LookupError:
834 except error.LookupError:
823 f = webutil.cleanpath(web.repo, req.form['file'][0])
835 f = webutil.cleanpath(web.repo, req.form['file'][0])
824 fl = web.repo.file(f)
836 fl = web.repo.file(f)
825 numrevs = len(fl)
837 numrevs = len(fl)
826 if not numrevs: # file doesn't exist at all
838 if not numrevs: # file doesn't exist at all
827 raise
839 raise
828 rev = webutil.changectx(web.repo, req).rev()
840 rev = webutil.changectx(web.repo, req).rev()
829 first = fl.linkrev(0)
841 first = fl.linkrev(0)
830 if rev < first: # current rev is from before file existed
842 if rev < first: # current rev is from before file existed
831 raise
843 raise
832 frev = numrevs - 1
844 frev = numrevs - 1
833 while fl.linkrev(frev) > rev:
845 while fl.linkrev(frev) > rev:
834 frev -= 1
846 frev -= 1
835 fctx = web.repo.filectx(f, fl.linkrev(frev))
847 fctx = web.repo.filectx(f, fl.linkrev(frev))
836
848
837 revcount = web.maxshortchanges
849 revcount = web.maxshortchanges
838 if 'revcount' in req.form:
850 if 'revcount' in req.form:
839 try:
851 try:
840 revcount = int(req.form.get('revcount', [revcount])[0])
852 revcount = int(req.form.get('revcount', [revcount])[0])
841 revcount = max(revcount, 1)
853 revcount = max(revcount, 1)
842 tmpl.defaults['sessionvars']['revcount'] = revcount
854 tmpl.defaults['sessionvars']['revcount'] = revcount
843 except ValueError:
855 except ValueError:
844 pass
856 pass
845
857
846 lessvars = copy.copy(tmpl.defaults['sessionvars'])
858 lessvars = copy.copy(tmpl.defaults['sessionvars'])
847 lessvars['revcount'] = max(revcount / 2, 1)
859 lessvars['revcount'] = max(revcount / 2, 1)
848 morevars = copy.copy(tmpl.defaults['sessionvars'])
860 morevars = copy.copy(tmpl.defaults['sessionvars'])
849 morevars['revcount'] = revcount * 2
861 morevars['revcount'] = revcount * 2
850
862
851 count = fctx.filerev() + 1
863 count = fctx.filerev() + 1
852 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
864 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
853 end = min(count, start + revcount) # last rev on this page
865 end = min(count, start + revcount) # last rev on this page
854 parity = paritygen(web.stripecount, offset=start - end)
866 parity = paritygen(web.stripecount, offset=start - end)
855
867
856 def entries():
868 def entries():
857 l = []
869 l = []
858
870
859 repo = web.repo
871 repo = web.repo
860 revs = fctx.filelog().revs(start, end - 1)
872 revs = fctx.filelog().revs(start, end - 1)
861 for i in revs:
873 for i in revs:
862 iterfctx = fctx.filectx(i)
874 iterfctx = fctx.filectx(i)
863
875
864 l.append({"parity": parity.next(),
876 l.append({"parity": parity.next(),
865 "filerev": i,
877 "filerev": i,
866 "file": f,
878 "file": f,
867 "node": iterfctx.hex(),
879 "node": iterfctx.hex(),
868 "author": iterfctx.user(),
880 "author": iterfctx.user(),
869 "date": iterfctx.date(),
881 "date": iterfctx.date(),
870 "rename": webutil.renamelink(iterfctx),
882 "rename": webutil.renamelink(iterfctx),
871 "parent": webutil.parents(iterfctx),
883 "parent": webutil.parents(iterfctx),
872 "child": webutil.children(iterfctx),
884 "child": webutil.children(iterfctx),
873 "desc": iterfctx.description(),
885 "desc": iterfctx.description(),
874 "extra": iterfctx.extra(),
886 "extra": iterfctx.extra(),
875 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
887 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
876 "bookmarks": webutil.nodebookmarksdict(
888 "bookmarks": webutil.nodebookmarksdict(
877 repo, iterfctx.node()),
889 repo, iterfctx.node()),
878 "branch": webutil.nodebranchnodefault(iterfctx),
890 "branch": webutil.nodebranchnodefault(iterfctx),
879 "inbranch": webutil.nodeinbranch(repo, iterfctx),
891 "inbranch": webutil.nodeinbranch(repo, iterfctx),
880 "branches": webutil.nodebranchdict(repo, iterfctx)})
892 "branches": webutil.nodebranchdict(repo, iterfctx)})
881 for e in reversed(l):
893 for e in reversed(l):
882 yield e
894 yield e
883
895
884 entries = list(entries())
896 entries = list(entries())
885 latestentry = entries[:1]
897 latestentry = entries[:1]
886
898
887 revnav = webutil.filerevnav(web.repo, fctx.path())
899 revnav = webutil.filerevnav(web.repo, fctx.path())
888 nav = revnav.gen(end - 1, revcount, count)
900 nav = revnav.gen(end - 1, revcount, count)
889 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
901 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
890 entries=entries,
902 entries=entries,
891 latestentry=latestentry,
903 latestentry=latestentry,
892 revcount=revcount, morevars=morevars, lessvars=lessvars)
904 revcount=revcount, morevars=morevars, lessvars=lessvars)
893
905
894 @webcommand('archive')
906 @webcommand('archive')
895 def archive(web, req, tmpl):
907 def archive(web, req, tmpl):
896 type_ = req.form.get('type', [None])[0]
908 type_ = req.form.get('type', [None])[0]
897 allowed = web.configlist("web", "allow_archive")
909 allowed = web.configlist("web", "allow_archive")
898 key = req.form['node'][0]
910 key = req.form['node'][0]
899
911
900 if type_ not in web.archives:
912 if type_ not in web.archives:
901 msg = 'Unsupported archive type: %s' % type_
913 msg = 'Unsupported archive type: %s' % type_
902 raise ErrorResponse(HTTP_NOT_FOUND, msg)
914 raise ErrorResponse(HTTP_NOT_FOUND, msg)
903
915
904 if not ((type_ in allowed or
916 if not ((type_ in allowed or
905 web.configbool("web", "allow" + type_, False))):
917 web.configbool("web", "allow" + type_, False))):
906 msg = 'Archive type not allowed: %s' % type_
918 msg = 'Archive type not allowed: %s' % type_
907 raise ErrorResponse(HTTP_FORBIDDEN, msg)
919 raise ErrorResponse(HTTP_FORBIDDEN, msg)
908
920
909 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
921 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
910 cnode = web.repo.lookup(key)
922 cnode = web.repo.lookup(key)
911 arch_version = key
923 arch_version = key
912 if cnode == key or key == 'tip':
924 if cnode == key or key == 'tip':
913 arch_version = short(cnode)
925 arch_version = short(cnode)
914 name = "%s-%s" % (reponame, arch_version)
926 name = "%s-%s" % (reponame, arch_version)
915
927
916 ctx = webutil.changectx(web.repo, req)
928 ctx = webutil.changectx(web.repo, req)
917 pats = []
929 pats = []
918 matchfn = scmutil.match(ctx, [])
930 matchfn = scmutil.match(ctx, [])
919 file = req.form.get('file', None)
931 file = req.form.get('file', None)
920 if file:
932 if file:
921 pats = ['path:' + file[0]]
933 pats = ['path:' + file[0]]
922 matchfn = scmutil.match(ctx, pats, default='path')
934 matchfn = scmutil.match(ctx, pats, default='path')
923 if pats:
935 if pats:
924 files = [f for f in ctx.manifest().keys() if matchfn(f)]
936 files = [f for f in ctx.manifest().keys() if matchfn(f)]
925 if not files:
937 if not files:
926 raise ErrorResponse(HTTP_NOT_FOUND,
938 raise ErrorResponse(HTTP_NOT_FOUND,
927 'file(s) not found: %s' % file[0])
939 'file(s) not found: %s' % file[0])
928
940
929 mimetype, artype, extension, encoding = web.archive_specs[type_]
941 mimetype, artype, extension, encoding = web.archive_specs[type_]
930 headers = [
942 headers = [
931 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
943 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
932 ]
944 ]
933 if encoding:
945 if encoding:
934 headers.append(('Content-Encoding', encoding))
946 headers.append(('Content-Encoding', encoding))
935 req.headers.extend(headers)
947 req.headers.extend(headers)
936 req.respond(HTTP_OK, mimetype)
948 req.respond(HTTP_OK, mimetype)
937
949
938 archival.archive(web.repo, req, cnode, artype, prefix=name,
950 archival.archive(web.repo, req, cnode, artype, prefix=name,
939 matchfn=matchfn,
951 matchfn=matchfn,
940 subrepos=web.configbool("web", "archivesubrepos"))
952 subrepos=web.configbool("web", "archivesubrepos"))
941 return []
953 return []
942
954
943
955
944 @webcommand('static')
956 @webcommand('static')
945 def static(web, req, tmpl):
957 def static(web, req, tmpl):
946 fname = req.form['file'][0]
958 fname = req.form['file'][0]
947 # a repo owner may set web.static in .hg/hgrc to get any file
959 # a repo owner may set web.static in .hg/hgrc to get any file
948 # readable by the user running the CGI script
960 # readable by the user running the CGI script
949 static = web.config("web", "static", None, untrusted=False)
961 static = web.config("web", "static", None, untrusted=False)
950 if not static:
962 if not static:
951 tp = web.templatepath or templater.templatepaths()
963 tp = web.templatepath or templater.templatepaths()
952 if isinstance(tp, str):
964 if isinstance(tp, str):
953 tp = [tp]
965 tp = [tp]
954 static = [os.path.join(p, 'static') for p in tp]
966 static = [os.path.join(p, 'static') for p in tp]
955 staticfile(static, fname, req)
967 staticfile(static, fname, req)
956 return []
968 return []
957
969
958 @webcommand('graph')
970 @webcommand('graph')
959 def graph(web, req, tmpl):
971 def graph(web, req, tmpl):
960
972
961 ctx = webutil.changectx(web.repo, req)
973 ctx = webutil.changectx(web.repo, req)
962 rev = ctx.rev()
974 rev = ctx.rev()
963
975
964 bg_height = 39
976 bg_height = 39
965 revcount = web.maxshortchanges
977 revcount = web.maxshortchanges
966 if 'revcount' in req.form:
978 if 'revcount' in req.form:
967 try:
979 try:
968 revcount = int(req.form.get('revcount', [revcount])[0])
980 revcount = int(req.form.get('revcount', [revcount])[0])
969 revcount = max(revcount, 1)
981 revcount = max(revcount, 1)
970 tmpl.defaults['sessionvars']['revcount'] = revcount
982 tmpl.defaults['sessionvars']['revcount'] = revcount
971 except ValueError:
983 except ValueError:
972 pass
984 pass
973
985
974 lessvars = copy.copy(tmpl.defaults['sessionvars'])
986 lessvars = copy.copy(tmpl.defaults['sessionvars'])
975 lessvars['revcount'] = max(revcount / 2, 1)
987 lessvars['revcount'] = max(revcount / 2, 1)
976 morevars = copy.copy(tmpl.defaults['sessionvars'])
988 morevars = copy.copy(tmpl.defaults['sessionvars'])
977 morevars['revcount'] = revcount * 2
989 morevars['revcount'] = revcount * 2
978
990
979 count = len(web.repo)
991 count = len(web.repo)
980 pos = rev
992 pos = rev
981
993
982 uprev = min(max(0, count - 1), rev + revcount)
994 uprev = min(max(0, count - 1), rev + revcount)
983 downrev = max(0, rev - revcount)
995 downrev = max(0, rev - revcount)
984 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
996 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
985
997
986 tree = []
998 tree = []
987 if pos != -1:
999 if pos != -1:
988 allrevs = web.repo.changelog.revs(pos, 0)
1000 allrevs = web.repo.changelog.revs(pos, 0)
989 revs = []
1001 revs = []
990 for i in allrevs:
1002 for i in allrevs:
991 revs.append(i)
1003 revs.append(i)
992 if len(revs) >= revcount:
1004 if len(revs) >= revcount:
993 break
1005 break
994
1006
995 # We have to feed a baseset to dagwalker as it is expecting smartset
1007 # 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
1008 # object. This does not have a big impact on hgweb performance itself
997 # since hgweb graphing code is not itself lazy yet.
1009 # since hgweb graphing code is not itself lazy yet.
998 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
1010 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
999 # As we said one line above... not lazy.
1011 # As we said one line above... not lazy.
1000 tree = list(graphmod.colored(dag, web.repo))
1012 tree = list(graphmod.colored(dag, web.repo))
1001
1013
1002 def getcolumns(tree):
1014 def getcolumns(tree):
1003 cols = 0
1015 cols = 0
1004 for (id, type, ctx, vtx, edges) in tree:
1016 for (id, type, ctx, vtx, edges) in tree:
1005 if type != graphmod.CHANGESET:
1017 if type != graphmod.CHANGESET:
1006 continue
1018 continue
1007 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1019 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1008 max([edge[1] for edge in edges] or [0]))
1020 max([edge[1] for edge in edges] or [0]))
1009 return cols
1021 return cols
1010
1022
1011 def graphdata(usetuples, **map):
1023 def graphdata(usetuples, **map):
1012 data = []
1024 data = []
1013
1025
1014 row = 0
1026 row = 0
1015 for (id, type, ctx, vtx, edges) in tree:
1027 for (id, type, ctx, vtx, edges) in tree:
1016 if type != graphmod.CHANGESET:
1028 if type != graphmod.CHANGESET:
1017 continue
1029 continue
1018 node = str(ctx)
1030 node = str(ctx)
1019 age = templatefilters.age(ctx.date())
1031 age = templatefilters.age(ctx.date())
1020 desc = templatefilters.firstline(ctx.description())
1032 desc = templatefilters.firstline(ctx.description())
1021 desc = cgi.escape(templatefilters.nonempty(desc))
1033 desc = cgi.escape(templatefilters.nonempty(desc))
1022 user = cgi.escape(templatefilters.person(ctx.user()))
1034 user = cgi.escape(templatefilters.person(ctx.user()))
1023 branch = cgi.escape(ctx.branch())
1035 branch = cgi.escape(ctx.branch())
1024 try:
1036 try:
1025 branchnode = web.repo.branchtip(branch)
1037 branchnode = web.repo.branchtip(branch)
1026 except error.RepoLookupError:
1038 except error.RepoLookupError:
1027 branchnode = None
1039 branchnode = None
1028 branch = branch, branchnode == ctx.node()
1040 branch = branch, branchnode == ctx.node()
1029
1041
1030 if usetuples:
1042 if usetuples:
1031 data.append((node, vtx, edges, desc, user, age, branch,
1043 data.append((node, vtx, edges, desc, user, age, branch,
1032 [cgi.escape(x) for x in ctx.tags()],
1044 [cgi.escape(x) for x in ctx.tags()],
1033 [cgi.escape(x) for x in ctx.bookmarks()]))
1045 [cgi.escape(x) for x in ctx.bookmarks()]))
1034 else:
1046 else:
1035 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1047 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1036 'color': (edge[2] - 1) % 6 + 1,
1048 'color': (edge[2] - 1) % 6 + 1,
1037 'width': edge[3], 'bcolor': edge[4]}
1049 'width': edge[3], 'bcolor': edge[4]}
1038 for edge in edges]
1050 for edge in edges]
1039
1051
1040 data.append(
1052 data.append(
1041 {'node': node,
1053 {'node': node,
1042 'col': vtx[0],
1054 'col': vtx[0],
1043 'color': (vtx[1] - 1) % 6 + 1,
1055 'color': (vtx[1] - 1) % 6 + 1,
1044 'edges': edgedata,
1056 'edges': edgedata,
1045 'row': row,
1057 'row': row,
1046 'nextrow': row + 1,
1058 'nextrow': row + 1,
1047 'desc': desc,
1059 'desc': desc,
1048 'user': user,
1060 'user': user,
1049 'age': age,
1061 'age': age,
1050 'bookmarks': webutil.nodebookmarksdict(
1062 'bookmarks': webutil.nodebookmarksdict(
1051 web.repo, ctx.node()),
1063 web.repo, ctx.node()),
1052 'branches': webutil.nodebranchdict(web.repo, ctx),
1064 'branches': webutil.nodebranchdict(web.repo, ctx),
1053 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1065 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1054 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1066 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1055
1067
1056 row += 1
1068 row += 1
1057
1069
1058 return data
1070 return data
1059
1071
1060 cols = getcolumns(tree)
1072 cols = getcolumns(tree)
1061 rows = len(tree)
1073 rows = len(tree)
1062 canvasheight = (rows + 1) * bg_height - 27
1074 canvasheight = (rows + 1) * bg_height - 27
1063
1075
1064 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1076 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1065 lessvars=lessvars, morevars=morevars, downrev=downrev,
1077 lessvars=lessvars, morevars=morevars, downrev=downrev,
1066 cols=cols, rows=rows,
1078 cols=cols, rows=rows,
1067 canvaswidth=(cols + 1) * bg_height,
1079 canvaswidth=(cols + 1) * bg_height,
1068 truecanvasheight=rows * bg_height,
1080 truecanvasheight=rows * bg_height,
1069 canvasheight=canvasheight, bg_height=bg_height,
1081 canvasheight=canvasheight, bg_height=bg_height,
1070 jsdata=lambda **x: graphdata(True, **x),
1082 jsdata=lambda **x: graphdata(True, **x),
1071 nodes=lambda **x: graphdata(False, **x),
1083 nodes=lambda **x: graphdata(False, **x),
1072 node=ctx.hex(), changenav=changenav)
1084 node=ctx.hex(), changenav=changenav)
1073
1085
1074 def _getdoc(e):
1086 def _getdoc(e):
1075 doc = e[0].__doc__
1087 doc = e[0].__doc__
1076 if doc:
1088 if doc:
1077 doc = _(doc).split('\n')[0]
1089 doc = _(doc).split('\n')[0]
1078 else:
1090 else:
1079 doc = _('(no help text available)')
1091 doc = _('(no help text available)')
1080 return doc
1092 return doc
1081
1093
1082 @webcommand('help')
1094 @webcommand('help')
1083 def help(web, req, tmpl):
1095 def help(web, req, tmpl):
1084 """
1096 """
1085 /help[/{topic}]
1097 /help[/{topic}]
1086 ---------------
1098 ---------------
1087
1099
1088 Render help documentation.
1100 Render help documentation.
1089
1101
1090 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1102 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
1103 is defined, that help topic will be rendered. If not, an index of
1092 available help topics will be rendered.
1104 available help topics will be rendered.
1093
1105
1094 The ``help`` template will be rendered when requesting help for a topic.
1106 The ``help`` template will be rendered when requesting help for a topic.
1095 ``helptopics`` will be rendered for the index of help topics.
1107 ``helptopics`` will be rendered for the index of help topics.
1096 """
1108 """
1097 from mercurial import commands # avoid cycle
1109 from mercurial import commands # avoid cycle
1098 from mercurial import help as helpmod # avoid cycle
1110 from mercurial import help as helpmod # avoid cycle
1099
1111
1100 topicname = req.form.get('node', [None])[0]
1112 topicname = req.form.get('node', [None])[0]
1101 if not topicname:
1113 if not topicname:
1102 def topics(**map):
1114 def topics(**map):
1103 for entries, summary, _doc in helpmod.helptable:
1115 for entries, summary, _doc in helpmod.helptable:
1104 yield {'topic': entries[0], 'summary': summary}
1116 yield {'topic': entries[0], 'summary': summary}
1105
1117
1106 early, other = [], []
1118 early, other = [], []
1107 primary = lambda s: s.split('|')[0]
1119 primary = lambda s: s.split('|')[0]
1108 for c, e in commands.table.iteritems():
1120 for c, e in commands.table.iteritems():
1109 doc = _getdoc(e)
1121 doc = _getdoc(e)
1110 if 'DEPRECATED' in doc or c.startswith('debug'):
1122 if 'DEPRECATED' in doc or c.startswith('debug'):
1111 continue
1123 continue
1112 cmd = primary(c)
1124 cmd = primary(c)
1113 if cmd.startswith('^'):
1125 if cmd.startswith('^'):
1114 early.append((cmd[1:], doc))
1126 early.append((cmd[1:], doc))
1115 else:
1127 else:
1116 other.append((cmd, doc))
1128 other.append((cmd, doc))
1117
1129
1118 early.sort()
1130 early.sort()
1119 other.sort()
1131 other.sort()
1120
1132
1121 def earlycommands(**map):
1133 def earlycommands(**map):
1122 for c, doc in early:
1134 for c, doc in early:
1123 yield {'topic': c, 'summary': doc}
1135 yield {'topic': c, 'summary': doc}
1124
1136
1125 def othercommands(**map):
1137 def othercommands(**map):
1126 for c, doc in other:
1138 for c, doc in other:
1127 yield {'topic': c, 'summary': doc}
1139 yield {'topic': c, 'summary': doc}
1128
1140
1129 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1141 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1130 othercommands=othercommands, title='Index')
1142 othercommands=othercommands, title='Index')
1131
1143
1132 u = webutil.wsgiui()
1144 u = webutil.wsgiui()
1133 u.verbose = True
1145 u.verbose = True
1134 try:
1146 try:
1135 doc = helpmod.help_(u, topicname)
1147 doc = helpmod.help_(u, topicname)
1136 except error.UnknownCommand:
1148 except error.UnknownCommand:
1137 raise ErrorResponse(HTTP_NOT_FOUND)
1149 raise ErrorResponse(HTTP_NOT_FOUND)
1138 return tmpl('help', topic=topicname, doc=doc)
1150 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now