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