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