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