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