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