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