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