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