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