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