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