##// END OF EJS Templates
hgweb: introduction a filerevnav subclass...
Pierre-Yves David -
r18408:f332a64f default
parent child Browse files
Show More
@@ -1,981 +1,981
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(web.repo.changectx).gen(pos, revcount, count)
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(nodefunc).gen(end - 1, revcount, count)
775 nav = webutil.filerevnav(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(web.repo.changectx).gen(pos, revcount, count)
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,378 +1,381
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 __init__(self, nodefunc):
44 def __init__(self, nodefunc):
45 """Navigation generation object
45 """Navigation generation object
46
46
47 :nodefun: factory for a changectx from a revision
47 :nodefun: factory for a changectx from a revision
48 """
48 """
49 self.nodefunc = nodefunc
49 self.nodefunc = nodefunc
50
50
51 def __nonzero__(self):
51 def __nonzero__(self):
52 """return True if any revision to navigate over"""
52 """return True if any revision to navigate over"""
53 try:
53 try:
54 self.nodefunc(0)
54 self.nodefunc(0)
55 return True
55 return True
56 except error.RepoError:
56 except error.RepoError:
57 return False
57 return False
58
58
59 def hex(self, rev):
59 def hex(self, rev):
60 return self.nodefunc(rev).hex()
60 return self.nodefunc(rev).hex()
61
61
62 def gen(self, pos, pagelen, limit):
62 def gen(self, pos, pagelen, limit):
63 """computes label and revision id for navigation link
63 """computes label and revision id for navigation link
64
64
65 :pos: is the revision relative to which we generate navigation.
65 :pos: is the revision relative to which we generate navigation.
66 :pagelen: the size of each navigation page
66 :pagelen: the size of each navigation page
67 :limit: how far shall we link
67 :limit: how far shall we link
68
68
69 The return is:
69 The return is:
70 - a single element tuple
70 - a single element tuple
71 - containing a dictionary with a `before` and `after` key
71 - containing a dictionary with a `before` and `after` key
72 - values are generator functions taking arbitrary number of kwargs
72 - values are generator functions taking arbitrary number of kwargs
73 - yield items are dictionaries with `label` and `node` keys
73 - yield items are dictionaries with `label` and `node` keys
74 """
74 """
75 if not self:
75 if not self:
76 # empty repo
76 # empty repo
77 return ({'before': (), 'after': ()},)
77 return ({'before': (), 'after': ()},)
78
78
79 navbefore = [("(0)", self.hex(0))]
79 navbefore = [("(0)", self.hex(0))]
80 navafter = []
80 navafter = []
81
81
82 for f in _navseq(1, pagelen):
82 for f in _navseq(1, pagelen):
83 if f > limit:
83 if f > limit:
84 break
84 break
85 if pos + f < limit:
85 if pos + f < limit:
86 navafter.append(("+%d" % f, self.hex(pos + f)))
86 navafter.append(("+%d" % f, self.hex(pos + f)))
87 if pos - f >= 0:
87 if pos - f >= 0:
88 navbefore.insert(0, ("-%d" % f, self.hex(pos - f)))
88 navbefore.insert(0, ("-%d" % f, self.hex(pos - f)))
89
89
90 navafter.append(("tip", "tip"))
90 navafter.append(("tip", "tip"))
91
91
92 data = lambda i: {"label": i[0], "node": i[1]}
92 data = lambda i: {"label": i[0], "node": i[1]}
93 return ({'before': lambda **map: (data(i) for i in navbefore),
93 return ({'before': lambda **map: (data(i) for i in navbefore),
94 'after': lambda **map: (data(i) for i in navafter)},)
94 'after': lambda **map: (data(i) for i in navafter)},)
95
95
96 class filerevnav(revnav):
97 pass
98
96 def _siblings(siblings=[], hiderev=None):
99 def _siblings(siblings=[], hiderev=None):
97 siblings = [s for s in siblings if s.node() != nullid]
100 siblings = [s for s in siblings if s.node() != nullid]
98 if len(siblings) == 1 and siblings[0].rev() == hiderev:
101 if len(siblings) == 1 and siblings[0].rev() == hiderev:
99 return
102 return
100 for s in siblings:
103 for s in siblings:
101 d = {'node': s.hex(), 'rev': s.rev()}
104 d = {'node': s.hex(), 'rev': s.rev()}
102 d['user'] = s.user()
105 d['user'] = s.user()
103 d['date'] = s.date()
106 d['date'] = s.date()
104 d['description'] = s.description()
107 d['description'] = s.description()
105 d['branch'] = s.branch()
108 d['branch'] = s.branch()
106 if util.safehasattr(s, 'path'):
109 if util.safehasattr(s, 'path'):
107 d['file'] = s.path()
110 d['file'] = s.path()
108 yield d
111 yield d
109
112
110 def parents(ctx, hide=None):
113 def parents(ctx, hide=None):
111 return _siblings(ctx.parents(), hide)
114 return _siblings(ctx.parents(), hide)
112
115
113 def children(ctx, hide=None):
116 def children(ctx, hide=None):
114 return _siblings(ctx.children(), hide)
117 return _siblings(ctx.children(), hide)
115
118
116 def renamelink(fctx):
119 def renamelink(fctx):
117 r = fctx.renamed()
120 r = fctx.renamed()
118 if r:
121 if r:
119 return [dict(file=r[0], node=hex(r[1]))]
122 return [dict(file=r[0], node=hex(r[1]))]
120 return []
123 return []
121
124
122 def nodetagsdict(repo, node):
125 def nodetagsdict(repo, node):
123 return [{"name": i} for i in repo.nodetags(node)]
126 return [{"name": i} for i in repo.nodetags(node)]
124
127
125 def nodebookmarksdict(repo, node):
128 def nodebookmarksdict(repo, node):
126 return [{"name": i} for i in repo.nodebookmarks(node)]
129 return [{"name": i} for i in repo.nodebookmarks(node)]
127
130
128 def nodebranchdict(repo, ctx):
131 def nodebranchdict(repo, ctx):
129 branches = []
132 branches = []
130 branch = ctx.branch()
133 branch = ctx.branch()
131 # If this is an empty repo, ctx.node() == nullid,
134 # If this is an empty repo, ctx.node() == nullid,
132 # ctx.branch() == 'default'.
135 # ctx.branch() == 'default'.
133 try:
136 try:
134 branchnode = repo.branchtip(branch)
137 branchnode = repo.branchtip(branch)
135 except error.RepoLookupError:
138 except error.RepoLookupError:
136 branchnode = None
139 branchnode = None
137 if branchnode == ctx.node():
140 if branchnode == ctx.node():
138 branches.append({"name": branch})
141 branches.append({"name": branch})
139 return branches
142 return branches
140
143
141 def nodeinbranch(repo, ctx):
144 def nodeinbranch(repo, ctx):
142 branches = []
145 branches = []
143 branch = ctx.branch()
146 branch = ctx.branch()
144 try:
147 try:
145 branchnode = repo.branchtip(branch)
148 branchnode = repo.branchtip(branch)
146 except error.RepoLookupError:
149 except error.RepoLookupError:
147 branchnode = None
150 branchnode = None
148 if branch != 'default' and branchnode != ctx.node():
151 if branch != 'default' and branchnode != ctx.node():
149 branches.append({"name": branch})
152 branches.append({"name": branch})
150 return branches
153 return branches
151
154
152 def nodebranchnodefault(ctx):
155 def nodebranchnodefault(ctx):
153 branches = []
156 branches = []
154 branch = ctx.branch()
157 branch = ctx.branch()
155 if branch != 'default':
158 if branch != 'default':
156 branches.append({"name": branch})
159 branches.append({"name": branch})
157 return branches
160 return branches
158
161
159 def showtag(repo, tmpl, t1, node=nullid, **args):
162 def showtag(repo, tmpl, t1, node=nullid, **args):
160 for t in repo.nodetags(node):
163 for t in repo.nodetags(node):
161 yield tmpl(t1, tag=t, **args)
164 yield tmpl(t1, tag=t, **args)
162
165
163 def showbookmark(repo, tmpl, t1, node=nullid, **args):
166 def showbookmark(repo, tmpl, t1, node=nullid, **args):
164 for t in repo.nodebookmarks(node):
167 for t in repo.nodebookmarks(node):
165 yield tmpl(t1, bookmark=t, **args)
168 yield tmpl(t1, bookmark=t, **args)
166
169
167 def cleanpath(repo, path):
170 def cleanpath(repo, path):
168 path = path.lstrip('/')
171 path = path.lstrip('/')
169 return scmutil.canonpath(repo.root, '', path)
172 return scmutil.canonpath(repo.root, '', path)
170
173
171 def changeidctx (repo, changeid):
174 def changeidctx (repo, changeid):
172 try:
175 try:
173 ctx = repo[changeid]
176 ctx = repo[changeid]
174 except error.RepoError:
177 except error.RepoError:
175 man = repo.manifest
178 man = repo.manifest
176 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
179 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
177
180
178 return ctx
181 return ctx
179
182
180 def changectx (repo, req):
183 def changectx (repo, req):
181 changeid = "tip"
184 changeid = "tip"
182 if 'node' in req.form:
185 if 'node' in req.form:
183 changeid = req.form['node'][0]
186 changeid = req.form['node'][0]
184 ipos=changeid.find(':')
187 ipos=changeid.find(':')
185 if ipos != -1:
188 if ipos != -1:
186 changeid = changeid[(ipos + 1):]
189 changeid = changeid[(ipos + 1):]
187 elif 'manifest' in req.form:
190 elif 'manifest' in req.form:
188 changeid = req.form['manifest'][0]
191 changeid = req.form['manifest'][0]
189
192
190 return changeidctx(repo, changeid)
193 return changeidctx(repo, changeid)
191
194
192 def basechangectx(repo, req):
195 def basechangectx(repo, req):
193 if 'node' in req.form:
196 if 'node' in req.form:
194 changeid = req.form['node'][0]
197 changeid = req.form['node'][0]
195 ipos=changeid.find(':')
198 ipos=changeid.find(':')
196 if ipos != -1:
199 if ipos != -1:
197 changeid = changeid[:ipos]
200 changeid = changeid[:ipos]
198 return changeidctx(repo, changeid)
201 return changeidctx(repo, changeid)
199
202
200 return None
203 return None
201
204
202 def filectx(repo, req):
205 def filectx(repo, req):
203 if 'file' not in req.form:
206 if 'file' not in req.form:
204 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
207 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
205 path = cleanpath(repo, req.form['file'][0])
208 path = cleanpath(repo, req.form['file'][0])
206 if 'node' in req.form:
209 if 'node' in req.form:
207 changeid = req.form['node'][0]
210 changeid = req.form['node'][0]
208 elif 'filenode' in req.form:
211 elif 'filenode' in req.form:
209 changeid = req.form['filenode'][0]
212 changeid = req.form['filenode'][0]
210 else:
213 else:
211 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
214 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
212 try:
215 try:
213 fctx = repo[changeid][path]
216 fctx = repo[changeid][path]
214 except error.RepoError:
217 except error.RepoError:
215 fctx = repo.filectx(path, fileid=changeid)
218 fctx = repo.filectx(path, fileid=changeid)
216
219
217 return fctx
220 return fctx
218
221
219 def listfilediffs(tmpl, files, node, max):
222 def listfilediffs(tmpl, files, node, max):
220 for f in files[:max]:
223 for f in files[:max]:
221 yield tmpl('filedifflink', node=hex(node), file=f)
224 yield tmpl('filedifflink', node=hex(node), file=f)
222 if len(files) > max:
225 if len(files) > max:
223 yield tmpl('fileellipses')
226 yield tmpl('fileellipses')
224
227
225 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
228 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
226
229
227 def countgen():
230 def countgen():
228 start = 1
231 start = 1
229 while True:
232 while True:
230 yield start
233 yield start
231 start += 1
234 start += 1
232
235
233 blockcount = countgen()
236 blockcount = countgen()
234 def prettyprintlines(diff, blockno):
237 def prettyprintlines(diff, blockno):
235 for lineno, l in enumerate(diff.splitlines(True)):
238 for lineno, l in enumerate(diff.splitlines(True)):
236 lineno = "%d.%d" % (blockno, lineno + 1)
239 lineno = "%d.%d" % (blockno, lineno + 1)
237 if l.startswith('+'):
240 if l.startswith('+'):
238 ltype = "difflineplus"
241 ltype = "difflineplus"
239 elif l.startswith('-'):
242 elif l.startswith('-'):
240 ltype = "difflineminus"
243 ltype = "difflineminus"
241 elif l.startswith('@'):
244 elif l.startswith('@'):
242 ltype = "difflineat"
245 ltype = "difflineat"
243 else:
246 else:
244 ltype = "diffline"
247 ltype = "diffline"
245 yield tmpl(ltype,
248 yield tmpl(ltype,
246 line=l,
249 line=l,
247 lineid="l%s" % lineno,
250 lineid="l%s" % lineno,
248 linenumber="% 8s" % lineno)
251 linenumber="% 8s" % lineno)
249
252
250 if files:
253 if files:
251 m = match.exact(repo.root, repo.getcwd(), files)
254 m = match.exact(repo.root, repo.getcwd(), files)
252 else:
255 else:
253 m = match.always(repo.root, repo.getcwd())
256 m = match.always(repo.root, repo.getcwd())
254
257
255 diffopts = patch.diffopts(repo.ui, untrusted=True)
258 diffopts = patch.diffopts(repo.ui, untrusted=True)
256 if basectx is None:
259 if basectx is None:
257 parents = ctx.parents()
260 parents = ctx.parents()
258 node1 = parents and parents[0].node() or nullid
261 node1 = parents and parents[0].node() or nullid
259 else:
262 else:
260 node1 = basectx.node()
263 node1 = basectx.node()
261 node2 = ctx.node()
264 node2 = ctx.node()
262
265
263 block = []
266 block = []
264 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
267 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
265 if chunk.startswith('diff') and block:
268 if chunk.startswith('diff') and block:
266 blockno = blockcount.next()
269 blockno = blockcount.next()
267 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
270 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
268 lines=prettyprintlines(''.join(block), blockno))
271 lines=prettyprintlines(''.join(block), blockno))
269 block = []
272 block = []
270 if chunk.startswith('diff') and style != 'raw':
273 if chunk.startswith('diff') and style != 'raw':
271 chunk = ''.join(chunk.splitlines(True)[1:])
274 chunk = ''.join(chunk.splitlines(True)[1:])
272 block.append(chunk)
275 block.append(chunk)
273 blockno = blockcount.next()
276 blockno = blockcount.next()
274 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
277 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
275 lines=prettyprintlines(''.join(block), blockno))
278 lines=prettyprintlines(''.join(block), blockno))
276
279
277 def compare(tmpl, context, leftlines, rightlines):
280 def compare(tmpl, context, leftlines, rightlines):
278 '''Generator function that provides side-by-side comparison data.'''
281 '''Generator function that provides side-by-side comparison data.'''
279
282
280 def compline(type, leftlineno, leftline, rightlineno, rightline):
283 def compline(type, leftlineno, leftline, rightlineno, rightline):
281 lineid = leftlineno and ("l%s" % leftlineno) or ''
284 lineid = leftlineno and ("l%s" % leftlineno) or ''
282 lineid += rightlineno and ("r%s" % rightlineno) or ''
285 lineid += rightlineno and ("r%s" % rightlineno) or ''
283 return tmpl('comparisonline',
286 return tmpl('comparisonline',
284 type=type,
287 type=type,
285 lineid=lineid,
288 lineid=lineid,
286 leftlinenumber="% 6s" % (leftlineno or ''),
289 leftlinenumber="% 6s" % (leftlineno or ''),
287 leftline=leftline or '',
290 leftline=leftline or '',
288 rightlinenumber="% 6s" % (rightlineno or ''),
291 rightlinenumber="% 6s" % (rightlineno or ''),
289 rightline=rightline or '')
292 rightline=rightline or '')
290
293
291 def getblock(opcodes):
294 def getblock(opcodes):
292 for type, llo, lhi, rlo, rhi in opcodes:
295 for type, llo, lhi, rlo, rhi in opcodes:
293 len1 = lhi - llo
296 len1 = lhi - llo
294 len2 = rhi - rlo
297 len2 = rhi - rlo
295 count = min(len1, len2)
298 count = min(len1, len2)
296 for i in xrange(count):
299 for i in xrange(count):
297 yield compline(type=type,
300 yield compline(type=type,
298 leftlineno=llo + i + 1,
301 leftlineno=llo + i + 1,
299 leftline=leftlines[llo + i],
302 leftline=leftlines[llo + i],
300 rightlineno=rlo + i + 1,
303 rightlineno=rlo + i + 1,
301 rightline=rightlines[rlo + i])
304 rightline=rightlines[rlo + i])
302 if len1 > len2:
305 if len1 > len2:
303 for i in xrange(llo + count, lhi):
306 for i in xrange(llo + count, lhi):
304 yield compline(type=type,
307 yield compline(type=type,
305 leftlineno=i + 1,
308 leftlineno=i + 1,
306 leftline=leftlines[i],
309 leftline=leftlines[i],
307 rightlineno=None,
310 rightlineno=None,
308 rightline=None)
311 rightline=None)
309 elif len2 > len1:
312 elif len2 > len1:
310 for i in xrange(rlo + count, rhi):
313 for i in xrange(rlo + count, rhi):
311 yield compline(type=type,
314 yield compline(type=type,
312 leftlineno=None,
315 leftlineno=None,
313 leftline=None,
316 leftline=None,
314 rightlineno=i + 1,
317 rightlineno=i + 1,
315 rightline=rightlines[i])
318 rightline=rightlines[i])
316
319
317 s = difflib.SequenceMatcher(None, leftlines, rightlines)
320 s = difflib.SequenceMatcher(None, leftlines, rightlines)
318 if context < 0:
321 if context < 0:
319 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
322 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
320 else:
323 else:
321 for oc in s.get_grouped_opcodes(n=context):
324 for oc in s.get_grouped_opcodes(n=context):
322 yield tmpl('comparisonblock', lines=getblock(oc))
325 yield tmpl('comparisonblock', lines=getblock(oc))
323
326
324 def diffstatgen(ctx, basectx):
327 def diffstatgen(ctx, basectx):
325 '''Generator function that provides the diffstat data.'''
328 '''Generator function that provides the diffstat data.'''
326
329
327 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
330 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
328 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
331 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
329 while True:
332 while True:
330 yield stats, maxname, maxtotal, addtotal, removetotal, binary
333 yield stats, maxname, maxtotal, addtotal, removetotal, binary
331
334
332 def diffsummary(statgen):
335 def diffsummary(statgen):
333 '''Return a short summary of the diff.'''
336 '''Return a short summary of the diff.'''
334
337
335 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
338 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
336 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
339 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
337 len(stats), addtotal, removetotal)
340 len(stats), addtotal, removetotal)
338
341
339 def diffstat(tmpl, ctx, statgen, parity):
342 def diffstat(tmpl, ctx, statgen, parity):
340 '''Return a diffstat template for each file in the diff.'''
343 '''Return a diffstat template for each file in the diff.'''
341
344
342 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
345 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
343 files = ctx.files()
346 files = ctx.files()
344
347
345 def pct(i):
348 def pct(i):
346 if maxtotal == 0:
349 if maxtotal == 0:
347 return 0
350 return 0
348 return (float(i) / maxtotal) * 100
351 return (float(i) / maxtotal) * 100
349
352
350 fileno = 0
353 fileno = 0
351 for filename, adds, removes, isbinary in stats:
354 for filename, adds, removes, isbinary in stats:
352 template = filename in files and 'diffstatlink' or 'diffstatnolink'
355 template = filename in files and 'diffstatlink' or 'diffstatnolink'
353 total = adds + removes
356 total = adds + removes
354 fileno += 1
357 fileno += 1
355 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
358 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
356 total=total, addpct=pct(adds), removepct=pct(removes),
359 total=total, addpct=pct(adds), removepct=pct(removes),
357 parity=parity.next())
360 parity=parity.next())
358
361
359 class sessionvars(object):
362 class sessionvars(object):
360 def __init__(self, vars, start='?'):
363 def __init__(self, vars, start='?'):
361 self.start = start
364 self.start = start
362 self.vars = vars
365 self.vars = vars
363 def __getitem__(self, key):
366 def __getitem__(self, key):
364 return self.vars[key]
367 return self.vars[key]
365 def __setitem__(self, key, value):
368 def __setitem__(self, key, value):
366 self.vars[key] = value
369 self.vars[key] = value
367 def __copy__(self):
370 def __copy__(self):
368 return sessionvars(copy.copy(self.vars), self.start)
371 return sessionvars(copy.copy(self.vars), self.start)
369 def __iter__(self):
372 def __iter__(self):
370 separator = self.start
373 separator = self.start
371 for key, value in sorted(self.vars.iteritems()):
374 for key, value in sorted(self.vars.iteritems()):
372 yield {'name': key, 'value': str(value), 'separator': separator}
375 yield {'name': key, 'value': str(value), 'separator': separator}
373 separator = '&'
376 separator = '&'
374
377
375 class wsgiui(ui.ui):
378 class wsgiui(ui.ui):
376 # default termwidth breaks under mod_wsgi
379 # default termwidth breaks under mod_wsgi
377 def termwidth(self):
380 def termwidth(self):
378 return 80
381 return 80
General Comments 0
You need to be logged in to leave comments. Login now