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