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