##// END OF EJS Templates
hgweb: simplify branches with repo.branchmap().iterbranches()
Brodie Rao -
r20194:9985e188 default
parent child Browse files
Show More
@@ -1,1109 +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 item: (not item[1], item[0].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 tag, hs, tip, closed in web.repo.branchmap().iterbranches():
546 tips.append(web.repo[n])
546 tips.append((web.repo[tip], closed))
547 for ctx in sorted(tips, key=sortkey, reverse=True):
547 for ctx, closed 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 closed:
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.branchmap()
599 b = web.repo.branchmap()
600 l = [(-web.repo.changelog.rev(tip), tip, tag)
600 l = [(-web.repo.changelog.rev(tip), tip, tag)
601 for tag, heads, tip, closed in b.iterbranches()]
601 for tag, heads, tip, closed in b.iterbranches()]
602 for r, n, t in sorted(l):
602 for r, n, t in sorted(l):
603 yield {'parity': parity.next(),
603 yield {'parity': parity.next(),
604 'branch': t,
604 'branch': t,
605 'node': hex(n),
605 'node': hex(n),
606 'date': web.repo[n].date()}
606 'date': web.repo[n].date()}
607
607
608 def changelist(**map):
608 def changelist(**map):
609 parity = paritygen(web.stripecount, offset=start - end)
609 parity = paritygen(web.stripecount, offset=start - end)
610 l = [] # build a list in forward order for efficiency
610 l = [] # build a list in forward order for efficiency
611 revs = []
611 revs = []
612 if start < end:
612 if start < end:
613 revs = web.repo.changelog.revs(start, end - 1)
613 revs = web.repo.changelog.revs(start, end - 1)
614 for i in revs:
614 for i in revs:
615 ctx = web.repo[i]
615 ctx = web.repo[i]
616 n = ctx.node()
616 n = ctx.node()
617 hn = hex(n)
617 hn = hex(n)
618
618
619 l.append(tmpl(
619 l.append(tmpl(
620 'shortlogentry',
620 'shortlogentry',
621 parity=parity.next(),
621 parity=parity.next(),
622 author=ctx.user(),
622 author=ctx.user(),
623 desc=ctx.description(),
623 desc=ctx.description(),
624 extra=ctx.extra(),
624 extra=ctx.extra(),
625 date=ctx.date(),
625 date=ctx.date(),
626 rev=i,
626 rev=i,
627 node=hn,
627 node=hn,
628 tags=webutil.nodetagsdict(web.repo, n),
628 tags=webutil.nodetagsdict(web.repo, n),
629 bookmarks=webutil.nodebookmarksdict(web.repo, n),
629 bookmarks=webutil.nodebookmarksdict(web.repo, n),
630 inbranch=webutil.nodeinbranch(web.repo, ctx),
630 inbranch=webutil.nodeinbranch(web.repo, ctx),
631 branches=webutil.nodebranchdict(web.repo, ctx)))
631 branches=webutil.nodebranchdict(web.repo, ctx)))
632
632
633 l.reverse()
633 l.reverse()
634 yield l
634 yield l
635
635
636 tip = web.repo['tip']
636 tip = web.repo['tip']
637 count = len(web.repo)
637 count = len(web.repo)
638 start = max(0, count - web.maxchanges)
638 start = max(0, count - web.maxchanges)
639 end = min(count, start + web.maxchanges)
639 end = min(count, start + web.maxchanges)
640
640
641 return tmpl("summary",
641 return tmpl("summary",
642 desc=web.config("web", "description", "unknown"),
642 desc=web.config("web", "description", "unknown"),
643 owner=get_contact(web.config) or "unknown",
643 owner=get_contact(web.config) or "unknown",
644 lastchange=tip.date(),
644 lastchange=tip.date(),
645 tags=tagentries,
645 tags=tagentries,
646 bookmarks=bookmarks,
646 bookmarks=bookmarks,
647 branches=branches,
647 branches=branches,
648 shortlog=changelist,
648 shortlog=changelist,
649 node=tip.hex(),
649 node=tip.hex(),
650 archives=web.archivelist("tip"))
650 archives=web.archivelist("tip"))
651
651
652 def filediff(web, req, tmpl):
652 def filediff(web, req, tmpl):
653 fctx, ctx = None, None
653 fctx, ctx = None, None
654 try:
654 try:
655 fctx = webutil.filectx(web.repo, req)
655 fctx = webutil.filectx(web.repo, req)
656 except LookupError:
656 except LookupError:
657 ctx = webutil.changectx(web.repo, req)
657 ctx = webutil.changectx(web.repo, req)
658 path = webutil.cleanpath(web.repo, req.form['file'][0])
658 path = webutil.cleanpath(web.repo, req.form['file'][0])
659 if path not in ctx.files():
659 if path not in ctx.files():
660 raise
660 raise
661
661
662 if fctx is not None:
662 if fctx is not None:
663 n = fctx.node()
663 n = fctx.node()
664 path = fctx.path()
664 path = fctx.path()
665 ctx = fctx.changectx()
665 ctx = fctx.changectx()
666 else:
666 else:
667 n = ctx.node()
667 n = ctx.node()
668 # path already defined in except clause
668 # path already defined in except clause
669
669
670 parity = paritygen(web.stripecount)
670 parity = paritygen(web.stripecount)
671 style = web.config('web', 'style', 'paper')
671 style = web.config('web', 'style', 'paper')
672 if 'style' in req.form:
672 if 'style' in req.form:
673 style = req.form['style'][0]
673 style = req.form['style'][0]
674
674
675 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
675 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
676 rename = fctx and webutil.renamelink(fctx) or []
676 rename = fctx and webutil.renamelink(fctx) or []
677 ctx = fctx and fctx or ctx
677 ctx = fctx and fctx or ctx
678 return tmpl("filediff",
678 return tmpl("filediff",
679 file=path,
679 file=path,
680 node=hex(n),
680 node=hex(n),
681 rev=ctx.rev(),
681 rev=ctx.rev(),
682 date=ctx.date(),
682 date=ctx.date(),
683 desc=ctx.description(),
683 desc=ctx.description(),
684 extra=ctx.extra(),
684 extra=ctx.extra(),
685 author=ctx.user(),
685 author=ctx.user(),
686 rename=rename,
686 rename=rename,
687 branch=webutil.nodebranchnodefault(ctx),
687 branch=webutil.nodebranchnodefault(ctx),
688 parent=webutil.parents(ctx),
688 parent=webutil.parents(ctx),
689 child=webutil.children(ctx),
689 child=webutil.children(ctx),
690 diff=diffs)
690 diff=diffs)
691
691
692 diff = filediff
692 diff = filediff
693
693
694 def comparison(web, req, tmpl):
694 def comparison(web, req, tmpl):
695 ctx = webutil.changectx(web.repo, req)
695 ctx = webutil.changectx(web.repo, req)
696 if 'file' not in req.form:
696 if 'file' not in req.form:
697 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
697 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
698 path = webutil.cleanpath(web.repo, req.form['file'][0])
698 path = webutil.cleanpath(web.repo, req.form['file'][0])
699 rename = path in ctx and webutil.renamelink(ctx[path]) or []
699 rename = path in ctx and webutil.renamelink(ctx[path]) or []
700
700
701 parsecontext = lambda v: v == 'full' and -1 or int(v)
701 parsecontext = lambda v: v == 'full' and -1 or int(v)
702 if 'context' in req.form:
702 if 'context' in req.form:
703 context = parsecontext(req.form['context'][0])
703 context = parsecontext(req.form['context'][0])
704 else:
704 else:
705 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
705 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
706
706
707 def filelines(f):
707 def filelines(f):
708 if util.binary(f.data()):
708 if util.binary(f.data()):
709 mt = mimetypes.guess_type(f.path())[0]
709 mt = mimetypes.guess_type(f.path())[0]
710 if not mt:
710 if not mt:
711 mt = 'application/octet-stream'
711 mt = 'application/octet-stream'
712 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
712 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
713 return f.data().splitlines()
713 return f.data().splitlines()
714
714
715 if path in ctx:
715 if path in ctx:
716 fctx = ctx[path]
716 fctx = ctx[path]
717 rightrev = fctx.filerev()
717 rightrev = fctx.filerev()
718 rightnode = fctx.filenode()
718 rightnode = fctx.filenode()
719 rightlines = filelines(fctx)
719 rightlines = filelines(fctx)
720 parents = fctx.parents()
720 parents = fctx.parents()
721 if not parents:
721 if not parents:
722 leftrev = -1
722 leftrev = -1
723 leftnode = nullid
723 leftnode = nullid
724 leftlines = ()
724 leftlines = ()
725 else:
725 else:
726 pfctx = parents[0]
726 pfctx = parents[0]
727 leftrev = pfctx.filerev()
727 leftrev = pfctx.filerev()
728 leftnode = pfctx.filenode()
728 leftnode = pfctx.filenode()
729 leftlines = filelines(pfctx)
729 leftlines = filelines(pfctx)
730 else:
730 else:
731 rightrev = -1
731 rightrev = -1
732 rightnode = nullid
732 rightnode = nullid
733 rightlines = ()
733 rightlines = ()
734 fctx = ctx.parents()[0][path]
734 fctx = ctx.parents()[0][path]
735 leftrev = fctx.filerev()
735 leftrev = fctx.filerev()
736 leftnode = fctx.filenode()
736 leftnode = fctx.filenode()
737 leftlines = filelines(fctx)
737 leftlines = filelines(fctx)
738
738
739 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
739 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
740 return tmpl('filecomparison',
740 return tmpl('filecomparison',
741 file=path,
741 file=path,
742 node=hex(ctx.node()),
742 node=hex(ctx.node()),
743 rev=ctx.rev(),
743 rev=ctx.rev(),
744 date=ctx.date(),
744 date=ctx.date(),
745 desc=ctx.description(),
745 desc=ctx.description(),
746 extra=ctx.extra(),
746 extra=ctx.extra(),
747 author=ctx.user(),
747 author=ctx.user(),
748 rename=rename,
748 rename=rename,
749 branch=webutil.nodebranchnodefault(ctx),
749 branch=webutil.nodebranchnodefault(ctx),
750 parent=webutil.parents(fctx),
750 parent=webutil.parents(fctx),
751 child=webutil.children(fctx),
751 child=webutil.children(fctx),
752 leftrev=leftrev,
752 leftrev=leftrev,
753 leftnode=hex(leftnode),
753 leftnode=hex(leftnode),
754 rightrev=rightrev,
754 rightrev=rightrev,
755 rightnode=hex(rightnode),
755 rightnode=hex(rightnode),
756 comparison=comparison)
756 comparison=comparison)
757
757
758 def annotate(web, req, tmpl):
758 def annotate(web, req, tmpl):
759 fctx = webutil.filectx(web.repo, req)
759 fctx = webutil.filectx(web.repo, req)
760 f = fctx.path()
760 f = fctx.path()
761 parity = paritygen(web.stripecount)
761 parity = paritygen(web.stripecount)
762 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
762 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
763
763
764 def annotate(**map):
764 def annotate(**map):
765 last = None
765 last = None
766 if util.binary(fctx.data()):
766 if util.binary(fctx.data()):
767 mt = (mimetypes.guess_type(fctx.path())[0]
767 mt = (mimetypes.guess_type(fctx.path())[0]
768 or 'application/octet-stream')
768 or 'application/octet-stream')
769 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
769 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
770 '(binary:%s)' % mt)])
770 '(binary:%s)' % mt)])
771 else:
771 else:
772 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
772 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
773 diffopts=diffopts))
773 diffopts=diffopts))
774 for lineno, ((f, targetline), l) in lines:
774 for lineno, ((f, targetline), l) in lines:
775 fnode = f.filenode()
775 fnode = f.filenode()
776
776
777 if last != fnode:
777 if last != fnode:
778 last = fnode
778 last = fnode
779
779
780 yield {"parity": parity.next(),
780 yield {"parity": parity.next(),
781 "node": f.hex(),
781 "node": f.hex(),
782 "rev": f.rev(),
782 "rev": f.rev(),
783 "author": f.user(),
783 "author": f.user(),
784 "desc": f.description(),
784 "desc": f.description(),
785 "extra": f.extra(),
785 "extra": f.extra(),
786 "file": f.path(),
786 "file": f.path(),
787 "targetline": targetline,
787 "targetline": targetline,
788 "line": l,
788 "line": l,
789 "lineid": "l%d" % (lineno + 1),
789 "lineid": "l%d" % (lineno + 1),
790 "linenumber": "% 6d" % (lineno + 1),
790 "linenumber": "% 6d" % (lineno + 1),
791 "revdate": f.date()}
791 "revdate": f.date()}
792
792
793 return tmpl("fileannotate",
793 return tmpl("fileannotate",
794 file=f,
794 file=f,
795 annotate=annotate,
795 annotate=annotate,
796 path=webutil.up(f),
796 path=webutil.up(f),
797 rev=fctx.rev(),
797 rev=fctx.rev(),
798 node=fctx.hex(),
798 node=fctx.hex(),
799 author=fctx.user(),
799 author=fctx.user(),
800 date=fctx.date(),
800 date=fctx.date(),
801 desc=fctx.description(),
801 desc=fctx.description(),
802 extra=fctx.extra(),
802 extra=fctx.extra(),
803 rename=webutil.renamelink(fctx),
803 rename=webutil.renamelink(fctx),
804 branch=webutil.nodebranchnodefault(fctx),
804 branch=webutil.nodebranchnodefault(fctx),
805 parent=webutil.parents(fctx),
805 parent=webutil.parents(fctx),
806 child=webutil.children(fctx),
806 child=webutil.children(fctx),
807 permissions=fctx.manifest().flags(f))
807 permissions=fctx.manifest().flags(f))
808
808
809 def filelog(web, req, tmpl):
809 def filelog(web, req, tmpl):
810
810
811 try:
811 try:
812 fctx = webutil.filectx(web.repo, req)
812 fctx = webutil.filectx(web.repo, req)
813 f = fctx.path()
813 f = fctx.path()
814 fl = fctx.filelog()
814 fl = fctx.filelog()
815 except error.LookupError:
815 except error.LookupError:
816 f = webutil.cleanpath(web.repo, req.form['file'][0])
816 f = webutil.cleanpath(web.repo, req.form['file'][0])
817 fl = web.repo.file(f)
817 fl = web.repo.file(f)
818 numrevs = len(fl)
818 numrevs = len(fl)
819 if not numrevs: # file doesn't exist at all
819 if not numrevs: # file doesn't exist at all
820 raise
820 raise
821 rev = webutil.changectx(web.repo, req).rev()
821 rev = webutil.changectx(web.repo, req).rev()
822 first = fl.linkrev(0)
822 first = fl.linkrev(0)
823 if rev < first: # current rev is from before file existed
823 if rev < first: # current rev is from before file existed
824 raise
824 raise
825 frev = numrevs - 1
825 frev = numrevs - 1
826 while fl.linkrev(frev) > rev:
826 while fl.linkrev(frev) > rev:
827 frev -= 1
827 frev -= 1
828 fctx = web.repo.filectx(f, fl.linkrev(frev))
828 fctx = web.repo.filectx(f, fl.linkrev(frev))
829
829
830 revcount = web.maxshortchanges
830 revcount = web.maxshortchanges
831 if 'revcount' in req.form:
831 if 'revcount' in req.form:
832 try:
832 try:
833 revcount = int(req.form.get('revcount', [revcount])[0])
833 revcount = int(req.form.get('revcount', [revcount])[0])
834 revcount = max(revcount, 1)
834 revcount = max(revcount, 1)
835 tmpl.defaults['sessionvars']['revcount'] = revcount
835 tmpl.defaults['sessionvars']['revcount'] = revcount
836 except ValueError:
836 except ValueError:
837 pass
837 pass
838
838
839 lessvars = copy.copy(tmpl.defaults['sessionvars'])
839 lessvars = copy.copy(tmpl.defaults['sessionvars'])
840 lessvars['revcount'] = max(revcount / 2, 1)
840 lessvars['revcount'] = max(revcount / 2, 1)
841 morevars = copy.copy(tmpl.defaults['sessionvars'])
841 morevars = copy.copy(tmpl.defaults['sessionvars'])
842 morevars['revcount'] = revcount * 2
842 morevars['revcount'] = revcount * 2
843
843
844 count = fctx.filerev() + 1
844 count = fctx.filerev() + 1
845 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
846 end = min(count, start + revcount) # last rev on this page
846 end = min(count, start + revcount) # last rev on this page
847 parity = paritygen(web.stripecount, offset=start - end)
847 parity = paritygen(web.stripecount, offset=start - end)
848
848
849 def entries():
849 def entries():
850 l = []
850 l = []
851
851
852 repo = web.repo
852 repo = web.repo
853 revs = fctx.filelog().revs(start, end - 1)
853 revs = fctx.filelog().revs(start, end - 1)
854 for i in revs:
854 for i in revs:
855 iterfctx = fctx.filectx(i)
855 iterfctx = fctx.filectx(i)
856
856
857 l.append({"parity": parity.next(),
857 l.append({"parity": parity.next(),
858 "filerev": i,
858 "filerev": i,
859 "file": f,
859 "file": f,
860 "node": iterfctx.hex(),
860 "node": iterfctx.hex(),
861 "author": iterfctx.user(),
861 "author": iterfctx.user(),
862 "date": iterfctx.date(),
862 "date": iterfctx.date(),
863 "rename": webutil.renamelink(iterfctx),
863 "rename": webutil.renamelink(iterfctx),
864 "parent": webutil.parents(iterfctx),
864 "parent": webutil.parents(iterfctx),
865 "child": webutil.children(iterfctx),
865 "child": webutil.children(iterfctx),
866 "desc": iterfctx.description(),
866 "desc": iterfctx.description(),
867 "extra": iterfctx.extra(),
867 "extra": iterfctx.extra(),
868 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
868 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
869 "bookmarks": webutil.nodebookmarksdict(
869 "bookmarks": webutil.nodebookmarksdict(
870 repo, iterfctx.node()),
870 repo, iterfctx.node()),
871 "branch": webutil.nodebranchnodefault(iterfctx),
871 "branch": webutil.nodebranchnodefault(iterfctx),
872 "inbranch": webutil.nodeinbranch(repo, iterfctx),
872 "inbranch": webutil.nodeinbranch(repo, iterfctx),
873 "branches": webutil.nodebranchdict(repo, iterfctx)})
873 "branches": webutil.nodebranchdict(repo, iterfctx)})
874 for e in reversed(l):
874 for e in reversed(l):
875 yield e
875 yield e
876
876
877 entries = list(entries())
877 entries = list(entries())
878 latestentry = entries[:1]
878 latestentry = entries[:1]
879
879
880 revnav = webutil.filerevnav(web.repo, fctx.path())
880 revnav = webutil.filerevnav(web.repo, fctx.path())
881 nav = revnav.gen(end - 1, revcount, count)
881 nav = revnav.gen(end - 1, revcount, count)
882 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
882 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
883 entries=entries,
883 entries=entries,
884 latestentry=latestentry,
884 latestentry=latestentry,
885 revcount=revcount, morevars=morevars, lessvars=lessvars)
885 revcount=revcount, morevars=morevars, lessvars=lessvars)
886
886
887 def archive(web, req, tmpl):
887 def archive(web, req, tmpl):
888 type_ = req.form.get('type', [None])[0]
888 type_ = req.form.get('type', [None])[0]
889 allowed = web.configlist("web", "allow_archive")
889 allowed = web.configlist("web", "allow_archive")
890 key = req.form['node'][0]
890 key = req.form['node'][0]
891
891
892 if type_ not in web.archives:
892 if type_ not in web.archives:
893 msg = 'Unsupported archive type: %s' % type_
893 msg = 'Unsupported archive type: %s' % type_
894 raise ErrorResponse(HTTP_NOT_FOUND, msg)
894 raise ErrorResponse(HTTP_NOT_FOUND, msg)
895
895
896 if not ((type_ in allowed or
896 if not ((type_ in allowed or
897 web.configbool("web", "allow" + type_, False))):
897 web.configbool("web", "allow" + type_, False))):
898 msg = 'Archive type not allowed: %s' % type_
898 msg = 'Archive type not allowed: %s' % type_
899 raise ErrorResponse(HTTP_FORBIDDEN, msg)
899 raise ErrorResponse(HTTP_FORBIDDEN, msg)
900
900
901 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
901 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
902 cnode = web.repo.lookup(key)
902 cnode = web.repo.lookup(key)
903 arch_version = key
903 arch_version = key
904 if cnode == key or key == 'tip':
904 if cnode == key or key == 'tip':
905 arch_version = short(cnode)
905 arch_version = short(cnode)
906 name = "%s-%s" % (reponame, arch_version)
906 name = "%s-%s" % (reponame, arch_version)
907
907
908 ctx = webutil.changectx(web.repo, req)
908 ctx = webutil.changectx(web.repo, req)
909 pats = []
909 pats = []
910 matchfn = None
910 matchfn = None
911 file = req.form.get('file', None)
911 file = req.form.get('file', None)
912 if file:
912 if file:
913 pats = ['path:' + file[0]]
913 pats = ['path:' + file[0]]
914 matchfn = scmutil.match(ctx, pats, default='path')
914 matchfn = scmutil.match(ctx, pats, default='path')
915 if pats:
915 if pats:
916 files = [f for f in ctx.manifest().keys() if matchfn(f)]
916 files = [f for f in ctx.manifest().keys() if matchfn(f)]
917 if not files:
917 if not files:
918 raise ErrorResponse(HTTP_NOT_FOUND,
918 raise ErrorResponse(HTTP_NOT_FOUND,
919 'file(s) not found: %s' % file[0])
919 'file(s) not found: %s' % file[0])
920
920
921 mimetype, artype, extension, encoding = web.archive_specs[type_]
921 mimetype, artype, extension, encoding = web.archive_specs[type_]
922 headers = [
922 headers = [
923 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
923 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
924 ]
924 ]
925 if encoding:
925 if encoding:
926 headers.append(('Content-Encoding', encoding))
926 headers.append(('Content-Encoding', encoding))
927 req.headers.extend(headers)
927 req.headers.extend(headers)
928 req.respond(HTTP_OK, mimetype)
928 req.respond(HTTP_OK, mimetype)
929
929
930 archival.archive(web.repo, req, cnode, artype, prefix=name,
930 archival.archive(web.repo, req, cnode, artype, prefix=name,
931 matchfn=matchfn,
931 matchfn=matchfn,
932 subrepos=web.configbool("web", "archivesubrepos"))
932 subrepos=web.configbool("web", "archivesubrepos"))
933 return []
933 return []
934
934
935
935
936 def static(web, req, tmpl):
936 def static(web, req, tmpl):
937 fname = req.form['file'][0]
937 fname = req.form['file'][0]
938 # 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
939 # readable by the user running the CGI script
939 # readable by the user running the CGI script
940 static = web.config("web", "static", None, untrusted=False)
940 static = web.config("web", "static", None, untrusted=False)
941 if not static:
941 if not static:
942 tp = web.templatepath or templater.templatepath()
942 tp = web.templatepath or templater.templatepath()
943 if isinstance(tp, str):
943 if isinstance(tp, str):
944 tp = [tp]
944 tp = [tp]
945 static = [os.path.join(p, 'static') for p in tp]
945 static = [os.path.join(p, 'static') for p in tp]
946 staticfile(static, fname, req)
946 staticfile(static, fname, req)
947 return []
947 return []
948
948
949 def graph(web, req, tmpl):
949 def graph(web, req, tmpl):
950
950
951 ctx = webutil.changectx(web.repo, req)
951 ctx = webutil.changectx(web.repo, req)
952 rev = ctx.rev()
952 rev = ctx.rev()
953
953
954 bg_height = 39
954 bg_height = 39
955 revcount = web.maxshortchanges
955 revcount = web.maxshortchanges
956 if 'revcount' in req.form:
956 if 'revcount' in req.form:
957 try:
957 try:
958 revcount = int(req.form.get('revcount', [revcount])[0])
958 revcount = int(req.form.get('revcount', [revcount])[0])
959 revcount = max(revcount, 1)
959 revcount = max(revcount, 1)
960 tmpl.defaults['sessionvars']['revcount'] = revcount
960 tmpl.defaults['sessionvars']['revcount'] = revcount
961 except ValueError:
961 except ValueError:
962 pass
962 pass
963
963
964 lessvars = copy.copy(tmpl.defaults['sessionvars'])
964 lessvars = copy.copy(tmpl.defaults['sessionvars'])
965 lessvars['revcount'] = max(revcount / 2, 1)
965 lessvars['revcount'] = max(revcount / 2, 1)
966 morevars = copy.copy(tmpl.defaults['sessionvars'])
966 morevars = copy.copy(tmpl.defaults['sessionvars'])
967 morevars['revcount'] = revcount * 2
967 morevars['revcount'] = revcount * 2
968
968
969 count = len(web.repo)
969 count = len(web.repo)
970 pos = rev
970 pos = rev
971
971
972 uprev = min(max(0, count - 1), rev + revcount)
972 uprev = min(max(0, count - 1), rev + revcount)
973 downrev = max(0, rev - revcount)
973 downrev = max(0, rev - revcount)
974 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
974 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
975
975
976 tree = []
976 tree = []
977 if pos != -1:
977 if pos != -1:
978 allrevs = web.repo.changelog.revs(pos, 0)
978 allrevs = web.repo.changelog.revs(pos, 0)
979 revs = []
979 revs = []
980 for i in allrevs:
980 for i in allrevs:
981 revs.append(i)
981 revs.append(i)
982 if len(revs) >= revcount:
982 if len(revs) >= revcount:
983 break
983 break
984
984
985 dag = graphmod.dagwalker(web.repo, revs)
985 dag = graphmod.dagwalker(web.repo, revs)
986 tree = list(graphmod.colored(dag, web.repo))
986 tree = list(graphmod.colored(dag, web.repo))
987
987
988 def getcolumns(tree):
988 def getcolumns(tree):
989 cols = 0
989 cols = 0
990 for (id, type, ctx, vtx, edges) in tree:
990 for (id, type, ctx, vtx, edges) in tree:
991 if type != graphmod.CHANGESET:
991 if type != graphmod.CHANGESET:
992 continue
992 continue
993 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]),
994 max([edge[1] for edge in edges] or [0]))
994 max([edge[1] for edge in edges] or [0]))
995 return cols
995 return cols
996
996
997 def graphdata(usetuples, **map):
997 def graphdata(usetuples, **map):
998 data = []
998 data = []
999
999
1000 row = 0
1000 row = 0
1001 for (id, type, ctx, vtx, edges) in tree:
1001 for (id, type, ctx, vtx, edges) in tree:
1002 if type != graphmod.CHANGESET:
1002 if type != graphmod.CHANGESET:
1003 continue
1003 continue
1004 node = str(ctx)
1004 node = str(ctx)
1005 age = templatefilters.age(ctx.date())
1005 age = templatefilters.age(ctx.date())
1006 desc = templatefilters.firstline(ctx.description())
1006 desc = templatefilters.firstline(ctx.description())
1007 desc = cgi.escape(templatefilters.nonempty(desc))
1007 desc = cgi.escape(templatefilters.nonempty(desc))
1008 user = cgi.escape(templatefilters.person(ctx.user()))
1008 user = cgi.escape(templatefilters.person(ctx.user()))
1009 branch = cgi.escape(ctx.branch())
1009 branch = cgi.escape(ctx.branch())
1010 try:
1010 try:
1011 branchnode = web.repo.branchtip(branch)
1011 branchnode = web.repo.branchtip(branch)
1012 except error.RepoLookupError:
1012 except error.RepoLookupError:
1013 branchnode = None
1013 branchnode = None
1014 branch = branch, branchnode == ctx.node()
1014 branch = branch, branchnode == ctx.node()
1015
1015
1016 if usetuples:
1016 if usetuples:
1017 data.append((node, vtx, edges, desc, user, age, branch,
1017 data.append((node, vtx, edges, desc, user, age, branch,
1018 [cgi.escape(x) for x in ctx.tags()],
1018 [cgi.escape(x) for x in ctx.tags()],
1019 [cgi.escape(x) for x in ctx.bookmarks()]))
1019 [cgi.escape(x) for x in ctx.bookmarks()]))
1020 else:
1020 else:
1021 edgedata = [dict(col=edge[0], nextcol=edge[1],
1021 edgedata = [dict(col=edge[0], nextcol=edge[1],
1022 color=(edge[2] - 1) % 6 + 1,
1022 color=(edge[2] - 1) % 6 + 1,
1023 width=edge[3], bcolor=edge[4])
1023 width=edge[3], bcolor=edge[4])
1024 for edge in edges]
1024 for edge in edges]
1025
1025
1026 data.append(
1026 data.append(
1027 dict(node=node,
1027 dict(node=node,
1028 col=vtx[0],
1028 col=vtx[0],
1029 color=(vtx[1] - 1) % 6 + 1,
1029 color=(vtx[1] - 1) % 6 + 1,
1030 edges=edgedata,
1030 edges=edgedata,
1031 row=row,
1031 row=row,
1032 nextrow=row + 1,
1032 nextrow=row + 1,
1033 desc=desc,
1033 desc=desc,
1034 user=user,
1034 user=user,
1035 age=age,
1035 age=age,
1036 bookmarks=webutil.nodebookmarksdict(
1036 bookmarks=webutil.nodebookmarksdict(
1037 web.repo, ctx.node()),
1037 web.repo, ctx.node()),
1038 branches=webutil.nodebranchdict(web.repo, ctx),
1038 branches=webutil.nodebranchdict(web.repo, ctx),
1039 inbranch=webutil.nodeinbranch(web.repo, ctx),
1039 inbranch=webutil.nodeinbranch(web.repo, ctx),
1040 tags=webutil.nodetagsdict(web.repo, ctx.node())))
1040 tags=webutil.nodetagsdict(web.repo, ctx.node())))
1041
1041
1042 row += 1
1042 row += 1
1043
1043
1044 return data
1044 return data
1045
1045
1046 cols = getcolumns(tree)
1046 cols = getcolumns(tree)
1047 rows = len(tree)
1047 rows = len(tree)
1048 canvasheight = (rows + 1) * bg_height - 27
1048 canvasheight = (rows + 1) * bg_height - 27
1049
1049
1050 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1050 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1051 lessvars=lessvars, morevars=morevars, downrev=downrev,
1051 lessvars=lessvars, morevars=morevars, downrev=downrev,
1052 cols=cols, rows=rows,
1052 cols=cols, rows=rows,
1053 canvaswidth=(cols + 1) * bg_height,
1053 canvaswidth=(cols + 1) * bg_height,
1054 truecanvasheight=rows * bg_height,
1054 truecanvasheight=rows * bg_height,
1055 canvasheight=canvasheight, bg_height=bg_height,
1055 canvasheight=canvasheight, bg_height=bg_height,
1056 jsdata=lambda **x: graphdata(True, **x),
1056 jsdata=lambda **x: graphdata(True, **x),
1057 nodes=lambda **x: graphdata(False, **x),
1057 nodes=lambda **x: graphdata(False, **x),
1058 node=ctx.hex(), changenav=changenav)
1058 node=ctx.hex(), changenav=changenav)
1059
1059
1060 def _getdoc(e):
1060 def _getdoc(e):
1061 doc = e[0].__doc__
1061 doc = e[0].__doc__
1062 if doc:
1062 if doc:
1063 doc = _(doc).split('\n')[0]
1063 doc = _(doc).split('\n')[0]
1064 else:
1064 else:
1065 doc = _('(no help text available)')
1065 doc = _('(no help text available)')
1066 return doc
1066 return doc
1067
1067
1068 def help(web, req, tmpl):
1068 def help(web, req, tmpl):
1069 from mercurial import commands # avoid cycle
1069 from mercurial import commands # avoid cycle
1070
1070
1071 topicname = req.form.get('node', [None])[0]
1071 topicname = req.form.get('node', [None])[0]
1072 if not topicname:
1072 if not topicname:
1073 def topics(**map):
1073 def topics(**map):
1074 for entries, summary, _ in helpmod.helptable:
1074 for entries, summary, _ in helpmod.helptable:
1075 yield {'topic': entries[0], 'summary': summary}
1075 yield {'topic': entries[0], 'summary': summary}
1076
1076
1077 early, other = [], []
1077 early, other = [], []
1078 primary = lambda s: s.split('|')[0]
1078 primary = lambda s: s.split('|')[0]
1079 for c, e in commands.table.iteritems():
1079 for c, e in commands.table.iteritems():
1080 doc = _getdoc(e)
1080 doc = _getdoc(e)
1081 if 'DEPRECATED' in doc or c.startswith('debug'):
1081 if 'DEPRECATED' in doc or c.startswith('debug'):
1082 continue
1082 continue
1083 cmd = primary(c)
1083 cmd = primary(c)
1084 if cmd.startswith('^'):
1084 if cmd.startswith('^'):
1085 early.append((cmd[1:], doc))
1085 early.append((cmd[1:], doc))
1086 else:
1086 else:
1087 other.append((cmd, doc))
1087 other.append((cmd, doc))
1088
1088
1089 early.sort()
1089 early.sort()
1090 other.sort()
1090 other.sort()
1091
1091
1092 def earlycommands(**map):
1092 def earlycommands(**map):
1093 for c, doc in early:
1093 for c, doc in early:
1094 yield {'topic': c, 'summary': doc}
1094 yield {'topic': c, 'summary': doc}
1095
1095
1096 def othercommands(**map):
1096 def othercommands(**map):
1097 for c, doc in other:
1097 for c, doc in other:
1098 yield {'topic': c, 'summary': doc}
1098 yield {'topic': c, 'summary': doc}
1099
1099
1100 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1100 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1101 othercommands=othercommands, title='Index')
1101 othercommands=othercommands, title='Index')
1102
1102
1103 u = webutil.wsgiui()
1103 u = webutil.wsgiui()
1104 u.verbose = True
1104 u.verbose = True
1105 try:
1105 try:
1106 doc = helpmod.help_(u, topicname)
1106 doc = helpmod.help_(u, topicname)
1107 except error.UnknownCommand:
1107 except error.UnknownCommand:
1108 raise ErrorResponse(HTTP_NOT_FOUND)
1108 raise ErrorResponse(HTTP_NOT_FOUND)
1109 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