##// END OF EJS Templates
hgweb: add block numbers to diff regions and related links...
Paul Boddie -
r16308:2695aaf4 default
parent child Browse files
Show More
@@ -1,845 +1,845
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os, mimetypes, re, cgi, copy
8 import os, mimetypes, re, cgi, copy
9 import webutil
9 import webutil
10 from mercurial import error, encoding, archival, templater, templatefilters
10 from mercurial import error, encoding, archival, templater, templatefilters
11 from mercurial.node import short, hex
11 from mercurial.node import short, hex
12 from mercurial.util import binary
12 from mercurial.util import binary
13 from common import paritygen, staticfile, get_contact, ErrorResponse
13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 from mercurial import graphmod, patch
15 from mercurial import graphmod, patch
16 from mercurial import help as helpmod
16 from mercurial import help as helpmod
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18
18
19 # __all__ is populated with the allowed commands. Be sure to add to it if
19 # __all__ is populated with the allowed commands. Be sure to add to it if
20 # you're adding a new command, or the new command won't work.
20 # you're adding a new command, or the new command won't work.
21
21
22 __all__ = [
22 __all__ = [
23 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
23 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
24 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
24 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
25 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
25 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
26 ]
26 ]
27
27
28 def log(web, req, tmpl):
28 def log(web, req, tmpl):
29 if 'file' in req.form and req.form['file'][0]:
29 if 'file' in req.form and req.form['file'][0]:
30 return filelog(web, req, tmpl)
30 return filelog(web, req, tmpl)
31 else:
31 else:
32 return changelog(web, req, tmpl)
32 return changelog(web, req, tmpl)
33
33
34 def rawfile(web, req, tmpl):
34 def rawfile(web, req, tmpl):
35 guessmime = web.configbool('web', 'guessmime', False)
35 guessmime = web.configbool('web', 'guessmime', False)
36
36
37 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
37 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
38 if not path:
38 if not path:
39 content = manifest(web, req, tmpl)
39 content = manifest(web, req, tmpl)
40 req.respond(HTTP_OK, web.ctype)
40 req.respond(HTTP_OK, web.ctype)
41 return content
41 return content
42
42
43 try:
43 try:
44 fctx = webutil.filectx(web.repo, req)
44 fctx = webutil.filectx(web.repo, req)
45 except error.LookupError, inst:
45 except error.LookupError, inst:
46 try:
46 try:
47 content = manifest(web, req, tmpl)
47 content = manifest(web, req, tmpl)
48 req.respond(HTTP_OK, web.ctype)
48 req.respond(HTTP_OK, web.ctype)
49 return content
49 return content
50 except ErrorResponse:
50 except ErrorResponse:
51 raise inst
51 raise inst
52
52
53 path = fctx.path()
53 path = fctx.path()
54 text = fctx.data()
54 text = fctx.data()
55 mt = 'application/binary'
55 mt = 'application/binary'
56 if guessmime:
56 if guessmime:
57 mt = mimetypes.guess_type(path)[0]
57 mt = mimetypes.guess_type(path)[0]
58 if mt is None:
58 if mt is None:
59 mt = binary(text) and 'application/binary' or 'text/plain'
59 mt = binary(text) and 'application/binary' or 'text/plain'
60 if mt.startswith('text/'):
60 if mt.startswith('text/'):
61 mt += '; charset="%s"' % encoding.encoding
61 mt += '; charset="%s"' % encoding.encoding
62
62
63 req.respond(HTTP_OK, mt, path, len(text))
63 req.respond(HTTP_OK, mt, path, len(text))
64 return [text]
64 return [text]
65
65
66 def _filerevision(web, tmpl, fctx):
66 def _filerevision(web, tmpl, fctx):
67 f = fctx.path()
67 f = fctx.path()
68 text = fctx.data()
68 text = fctx.data()
69 parity = paritygen(web.stripecount)
69 parity = paritygen(web.stripecount)
70
70
71 if binary(text):
71 if binary(text):
72 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
72 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
73 text = '(binary:%s)' % mt
73 text = '(binary:%s)' % mt
74
74
75 def lines():
75 def lines():
76 for lineno, t in enumerate(text.splitlines(True)):
76 for lineno, t in enumerate(text.splitlines(True)):
77 yield {"line": t,
77 yield {"line": t,
78 "lineid": "l%d" % (lineno + 1),
78 "lineid": "l%d" % (lineno + 1),
79 "linenumber": "% 6d" % (lineno + 1),
79 "linenumber": "% 6d" % (lineno + 1),
80 "parity": parity.next()}
80 "parity": parity.next()}
81
81
82 return tmpl("filerevision",
82 return tmpl("filerevision",
83 file=f,
83 file=f,
84 path=webutil.up(f),
84 path=webutil.up(f),
85 text=lines(),
85 text=lines(),
86 rev=fctx.rev(),
86 rev=fctx.rev(),
87 node=fctx.hex(),
87 node=fctx.hex(),
88 author=fctx.user(),
88 author=fctx.user(),
89 date=fctx.date(),
89 date=fctx.date(),
90 desc=fctx.description(),
90 desc=fctx.description(),
91 branch=webutil.nodebranchnodefault(fctx),
91 branch=webutil.nodebranchnodefault(fctx),
92 parent=webutil.parents(fctx),
92 parent=webutil.parents(fctx),
93 child=webutil.children(fctx),
93 child=webutil.children(fctx),
94 rename=webutil.renamelink(fctx),
94 rename=webutil.renamelink(fctx),
95 permissions=fctx.manifest().flags(f))
95 permissions=fctx.manifest().flags(f))
96
96
97 def file(web, req, tmpl):
97 def file(web, req, tmpl):
98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
99 if not path:
99 if not path:
100 return manifest(web, req, tmpl)
100 return manifest(web, req, tmpl)
101 try:
101 try:
102 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
102 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
103 except error.LookupError, inst:
103 except error.LookupError, inst:
104 try:
104 try:
105 return manifest(web, req, tmpl)
105 return manifest(web, req, tmpl)
106 except ErrorResponse:
106 except ErrorResponse:
107 raise inst
107 raise inst
108
108
109 def _search(web, req, tmpl):
109 def _search(web, req, tmpl):
110
110
111 query = req.form['rev'][0]
111 query = req.form['rev'][0]
112 revcount = web.maxchanges
112 revcount = web.maxchanges
113 if 'revcount' in req.form:
113 if 'revcount' in req.form:
114 revcount = int(req.form.get('revcount', [revcount])[0])
114 revcount = int(req.form.get('revcount', [revcount])[0])
115 revcount = max(revcount, 1)
115 revcount = max(revcount, 1)
116 tmpl.defaults['sessionvars']['revcount'] = revcount
116 tmpl.defaults['sessionvars']['revcount'] = revcount
117
117
118 lessvars = copy.copy(tmpl.defaults['sessionvars'])
118 lessvars = copy.copy(tmpl.defaults['sessionvars'])
119 lessvars['revcount'] = max(revcount / 2, 1)
119 lessvars['revcount'] = max(revcount / 2, 1)
120 lessvars['rev'] = query
120 lessvars['rev'] = query
121 morevars = copy.copy(tmpl.defaults['sessionvars'])
121 morevars = copy.copy(tmpl.defaults['sessionvars'])
122 morevars['revcount'] = revcount * 2
122 morevars['revcount'] = revcount * 2
123 morevars['rev'] = query
123 morevars['rev'] = query
124
124
125 def changelist(**map):
125 def changelist(**map):
126 count = 0
126 count = 0
127 lower = encoding.lower
127 lower = encoding.lower
128 qw = lower(query).split()
128 qw = lower(query).split()
129
129
130 def revgen():
130 def revgen():
131 for i in xrange(len(web.repo) - 1, 0, -100):
131 for i in xrange(len(web.repo) - 1, 0, -100):
132 l = []
132 l = []
133 for j in xrange(max(0, i - 100), i + 1):
133 for j in xrange(max(0, i - 100), i + 1):
134 ctx = web.repo[j]
134 ctx = web.repo[j]
135 l.append(ctx)
135 l.append(ctx)
136 l.reverse()
136 l.reverse()
137 for e in l:
137 for e in l:
138 yield e
138 yield e
139
139
140 for ctx in revgen():
140 for ctx in revgen():
141 miss = 0
141 miss = 0
142 for q in qw:
142 for q in qw:
143 if not (q in lower(ctx.user()) or
143 if not (q in lower(ctx.user()) or
144 q in lower(ctx.description()) or
144 q in lower(ctx.description()) or
145 q in lower(" ".join(ctx.files()))):
145 q in lower(" ".join(ctx.files()))):
146 miss = 1
146 miss = 1
147 break
147 break
148 if miss:
148 if miss:
149 continue
149 continue
150
150
151 count += 1
151 count += 1
152 n = ctx.node()
152 n = ctx.node()
153 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
153 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
154 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
154 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
155
155
156 yield tmpl('searchentry',
156 yield tmpl('searchentry',
157 parity=parity.next(),
157 parity=parity.next(),
158 author=ctx.user(),
158 author=ctx.user(),
159 parent=webutil.parents(ctx),
159 parent=webutil.parents(ctx),
160 child=webutil.children(ctx),
160 child=webutil.children(ctx),
161 changelogtag=showtags,
161 changelogtag=showtags,
162 desc=ctx.description(),
162 desc=ctx.description(),
163 date=ctx.date(),
163 date=ctx.date(),
164 files=files,
164 files=files,
165 rev=ctx.rev(),
165 rev=ctx.rev(),
166 node=hex(n),
166 node=hex(n),
167 tags=webutil.nodetagsdict(web.repo, n),
167 tags=webutil.nodetagsdict(web.repo, n),
168 bookmarks=webutil.nodebookmarksdict(web.repo, n),
168 bookmarks=webutil.nodebookmarksdict(web.repo, n),
169 inbranch=webutil.nodeinbranch(web.repo, ctx),
169 inbranch=webutil.nodeinbranch(web.repo, ctx),
170 branches=webutil.nodebranchdict(web.repo, ctx))
170 branches=webutil.nodebranchdict(web.repo, ctx))
171
171
172 if count >= revcount:
172 if count >= revcount:
173 break
173 break
174
174
175 tip = web.repo['tip']
175 tip = web.repo['tip']
176 parity = paritygen(web.stripecount)
176 parity = paritygen(web.stripecount)
177
177
178 return tmpl('search', query=query, node=tip.hex(),
178 return tmpl('search', query=query, node=tip.hex(),
179 entries=changelist, archives=web.archivelist("tip"),
179 entries=changelist, archives=web.archivelist("tip"),
180 morevars=morevars, lessvars=lessvars)
180 morevars=morevars, lessvars=lessvars)
181
181
182 def changelog(web, req, tmpl, shortlog=False):
182 def changelog(web, req, tmpl, shortlog=False):
183
183
184 if 'node' in req.form:
184 if 'node' in req.form:
185 ctx = webutil.changectx(web.repo, req)
185 ctx = webutil.changectx(web.repo, req)
186 else:
186 else:
187 if 'rev' in req.form:
187 if 'rev' in req.form:
188 hi = req.form['rev'][0]
188 hi = req.form['rev'][0]
189 else:
189 else:
190 hi = len(web.repo) - 1
190 hi = len(web.repo) - 1
191 try:
191 try:
192 ctx = web.repo[hi]
192 ctx = web.repo[hi]
193 except error.RepoError:
193 except error.RepoError:
194 return _search(web, req, tmpl) # XXX redirect to 404 page?
194 return _search(web, req, tmpl) # XXX redirect to 404 page?
195
195
196 def changelist(limit=0, **map):
196 def changelist(limit=0, **map):
197 l = [] # build a list in forward order for efficiency
197 l = [] # build a list in forward order for efficiency
198 for i in xrange(start, end):
198 for i in xrange(start, end):
199 ctx = web.repo[i]
199 ctx = web.repo[i]
200 n = ctx.node()
200 n = ctx.node()
201 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
201 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
202 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
202 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
203
203
204 l.insert(0, {"parity": parity.next(),
204 l.insert(0, {"parity": parity.next(),
205 "author": ctx.user(),
205 "author": ctx.user(),
206 "parent": webutil.parents(ctx, i - 1),
206 "parent": webutil.parents(ctx, i - 1),
207 "child": webutil.children(ctx, i + 1),
207 "child": webutil.children(ctx, i + 1),
208 "changelogtag": showtags,
208 "changelogtag": showtags,
209 "desc": ctx.description(),
209 "desc": ctx.description(),
210 "date": ctx.date(),
210 "date": ctx.date(),
211 "files": files,
211 "files": files,
212 "rev": i,
212 "rev": i,
213 "node": hex(n),
213 "node": hex(n),
214 "tags": webutil.nodetagsdict(web.repo, n),
214 "tags": webutil.nodetagsdict(web.repo, n),
215 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
215 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
216 "inbranch": webutil.nodeinbranch(web.repo, ctx),
216 "inbranch": webutil.nodeinbranch(web.repo, ctx),
217 "branches": webutil.nodebranchdict(web.repo, ctx)
217 "branches": webutil.nodebranchdict(web.repo, ctx)
218 })
218 })
219
219
220 if limit > 0:
220 if limit > 0:
221 l = l[:limit]
221 l = l[:limit]
222
222
223 for e in l:
223 for e in l:
224 yield e
224 yield e
225
225
226 revcount = shortlog and web.maxshortchanges or web.maxchanges
226 revcount = shortlog and web.maxshortchanges or web.maxchanges
227 if 'revcount' in req.form:
227 if 'revcount' in req.form:
228 revcount = int(req.form.get('revcount', [revcount])[0])
228 revcount = int(req.form.get('revcount', [revcount])[0])
229 revcount = max(revcount, 1)
229 revcount = max(revcount, 1)
230 tmpl.defaults['sessionvars']['revcount'] = revcount
230 tmpl.defaults['sessionvars']['revcount'] = revcount
231
231
232 lessvars = copy.copy(tmpl.defaults['sessionvars'])
232 lessvars = copy.copy(tmpl.defaults['sessionvars'])
233 lessvars['revcount'] = max(revcount / 2, 1)
233 lessvars['revcount'] = max(revcount / 2, 1)
234 morevars = copy.copy(tmpl.defaults['sessionvars'])
234 morevars = copy.copy(tmpl.defaults['sessionvars'])
235 morevars['revcount'] = revcount * 2
235 morevars['revcount'] = revcount * 2
236
236
237 count = len(web.repo)
237 count = len(web.repo)
238 pos = ctx.rev()
238 pos = ctx.rev()
239 start = max(0, pos - revcount + 1)
239 start = max(0, pos - revcount + 1)
240 end = min(count, start + revcount)
240 end = min(count, start + revcount)
241 pos = end - 1
241 pos = end - 1
242 parity = paritygen(web.stripecount, offset=start - end)
242 parity = paritygen(web.stripecount, offset=start - end)
243
243
244 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
244 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
245
245
246 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
246 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
247 node=ctx.hex(), rev=pos, changesets=count,
247 node=ctx.hex(), rev=pos, changesets=count,
248 entries=lambda **x: changelist(limit=0,**x),
248 entries=lambda **x: changelist(limit=0,**x),
249 latestentry=lambda **x: changelist(limit=1,**x),
249 latestentry=lambda **x: changelist(limit=1,**x),
250 archives=web.archivelist("tip"), revcount=revcount,
250 archives=web.archivelist("tip"), revcount=revcount,
251 morevars=morevars, lessvars=lessvars)
251 morevars=morevars, lessvars=lessvars)
252
252
253 def shortlog(web, req, tmpl):
253 def shortlog(web, req, tmpl):
254 return changelog(web, req, tmpl, shortlog = True)
254 return changelog(web, req, tmpl, shortlog = True)
255
255
256 def changeset(web, req, tmpl):
256 def changeset(web, req, tmpl):
257 ctx = webutil.changectx(web.repo, req)
257 ctx = webutil.changectx(web.repo, req)
258 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
258 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
259 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
259 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
260 ctx.node())
260 ctx.node())
261 showbranch = webutil.nodebranchnodefault(ctx)
261 showbranch = webutil.nodebranchnodefault(ctx)
262
262
263 files = []
263 files = []
264 parity = paritygen(web.stripecount)
264 parity = paritygen(web.stripecount)
265 for f in ctx.files():
265 for blockno, f in enumerate(ctx.files()):
266 template = f in ctx and 'filenodelink' or 'filenolink'
266 template = f in ctx and 'filenodelink' or 'filenolink'
267 files.append(tmpl(template,
267 files.append(tmpl(template,
268 node=ctx.hex(), file=f,
268 node=ctx.hex(), file=f, blockno=blockno + 1,
269 parity=parity.next()))
269 parity=parity.next()))
270
270
271 style = web.config('web', 'style', 'paper')
271 style = web.config('web', 'style', 'paper')
272 if 'style' in req.form:
272 if 'style' in req.form:
273 style = req.form['style'][0]
273 style = req.form['style'][0]
274
274
275 parity = paritygen(web.stripecount)
275 parity = paritygen(web.stripecount)
276 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
276 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
277
277
278 parity = paritygen(web.stripecount)
278 parity = paritygen(web.stripecount)
279 diffstatgen = webutil.diffstatgen(ctx)
279 diffstatgen = webutil.diffstatgen(ctx)
280 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
280 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
281
281
282 return tmpl('changeset',
282 return tmpl('changeset',
283 diff=diffs,
283 diff=diffs,
284 rev=ctx.rev(),
284 rev=ctx.rev(),
285 node=ctx.hex(),
285 node=ctx.hex(),
286 parent=webutil.parents(ctx),
286 parent=webutil.parents(ctx),
287 child=webutil.children(ctx),
287 child=webutil.children(ctx),
288 changesettag=showtags,
288 changesettag=showtags,
289 changesetbookmark=showbookmarks,
289 changesetbookmark=showbookmarks,
290 changesetbranch=showbranch,
290 changesetbranch=showbranch,
291 author=ctx.user(),
291 author=ctx.user(),
292 desc=ctx.description(),
292 desc=ctx.description(),
293 date=ctx.date(),
293 date=ctx.date(),
294 files=files,
294 files=files,
295 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
295 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
296 diffstat=diffstat,
296 diffstat=diffstat,
297 archives=web.archivelist(ctx.hex()),
297 archives=web.archivelist(ctx.hex()),
298 tags=webutil.nodetagsdict(web.repo, ctx.node()),
298 tags=webutil.nodetagsdict(web.repo, ctx.node()),
299 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
299 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
300 branch=webutil.nodebranchnodefault(ctx),
300 branch=webutil.nodebranchnodefault(ctx),
301 inbranch=webutil.nodeinbranch(web.repo, ctx),
301 inbranch=webutil.nodeinbranch(web.repo, ctx),
302 branches=webutil.nodebranchdict(web.repo, ctx))
302 branches=webutil.nodebranchdict(web.repo, ctx))
303
303
304 rev = changeset
304 rev = changeset
305
305
306 def manifest(web, req, tmpl):
306 def manifest(web, req, tmpl):
307 ctx = webutil.changectx(web.repo, req)
307 ctx = webutil.changectx(web.repo, req)
308 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
308 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
309 mf = ctx.manifest()
309 mf = ctx.manifest()
310 node = ctx.node()
310 node = ctx.node()
311
311
312 files = {}
312 files = {}
313 dirs = {}
313 dirs = {}
314 parity = paritygen(web.stripecount)
314 parity = paritygen(web.stripecount)
315
315
316 if path and path[-1] != "/":
316 if path and path[-1] != "/":
317 path += "/"
317 path += "/"
318 l = len(path)
318 l = len(path)
319 abspath = "/" + path
319 abspath = "/" + path
320
320
321 for f, n in mf.iteritems():
321 for f, n in mf.iteritems():
322 if f[:l] != path:
322 if f[:l] != path:
323 continue
323 continue
324 remain = f[l:]
324 remain = f[l:]
325 elements = remain.split('/')
325 elements = remain.split('/')
326 if len(elements) == 1:
326 if len(elements) == 1:
327 files[remain] = f
327 files[remain] = f
328 else:
328 else:
329 h = dirs # need to retain ref to dirs (root)
329 h = dirs # need to retain ref to dirs (root)
330 for elem in elements[0:-1]:
330 for elem in elements[0:-1]:
331 if elem not in h:
331 if elem not in h:
332 h[elem] = {}
332 h[elem] = {}
333 h = h[elem]
333 h = h[elem]
334 if len(h) > 1:
334 if len(h) > 1:
335 break
335 break
336 h[None] = None # denotes files present
336 h[None] = None # denotes files present
337
337
338 if mf and not files and not dirs:
338 if mf and not files and not dirs:
339 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
339 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
340
340
341 def filelist(**map):
341 def filelist(**map):
342 for f in sorted(files):
342 for f in sorted(files):
343 full = files[f]
343 full = files[f]
344
344
345 fctx = ctx.filectx(full)
345 fctx = ctx.filectx(full)
346 yield {"file": full,
346 yield {"file": full,
347 "parity": parity.next(),
347 "parity": parity.next(),
348 "basename": f,
348 "basename": f,
349 "date": fctx.date(),
349 "date": fctx.date(),
350 "size": fctx.size(),
350 "size": fctx.size(),
351 "permissions": mf.flags(full)}
351 "permissions": mf.flags(full)}
352
352
353 def dirlist(**map):
353 def dirlist(**map):
354 for d in sorted(dirs):
354 for d in sorted(dirs):
355
355
356 emptydirs = []
356 emptydirs = []
357 h = dirs[d]
357 h = dirs[d]
358 while isinstance(h, dict) and len(h) == 1:
358 while isinstance(h, dict) and len(h) == 1:
359 k, v = h.items()[0]
359 k, v = h.items()[0]
360 if v:
360 if v:
361 emptydirs.append(k)
361 emptydirs.append(k)
362 h = v
362 h = v
363
363
364 path = "%s%s" % (abspath, d)
364 path = "%s%s" % (abspath, d)
365 yield {"parity": parity.next(),
365 yield {"parity": parity.next(),
366 "path": path,
366 "path": path,
367 "emptydirs": "/".join(emptydirs),
367 "emptydirs": "/".join(emptydirs),
368 "basename": d}
368 "basename": d}
369
369
370 return tmpl("manifest",
370 return tmpl("manifest",
371 rev=ctx.rev(),
371 rev=ctx.rev(),
372 node=hex(node),
372 node=hex(node),
373 path=abspath,
373 path=abspath,
374 up=webutil.up(abspath),
374 up=webutil.up(abspath),
375 upparity=parity.next(),
375 upparity=parity.next(),
376 fentries=filelist,
376 fentries=filelist,
377 dentries=dirlist,
377 dentries=dirlist,
378 archives=web.archivelist(hex(node)),
378 archives=web.archivelist(hex(node)),
379 tags=webutil.nodetagsdict(web.repo, node),
379 tags=webutil.nodetagsdict(web.repo, node),
380 bookmarks=webutil.nodebookmarksdict(web.repo, node),
380 bookmarks=webutil.nodebookmarksdict(web.repo, node),
381 inbranch=webutil.nodeinbranch(web.repo, ctx),
381 inbranch=webutil.nodeinbranch(web.repo, ctx),
382 branches=webutil.nodebranchdict(web.repo, ctx))
382 branches=webutil.nodebranchdict(web.repo, ctx))
383
383
384 def tags(web, req, tmpl):
384 def tags(web, req, tmpl):
385 i = web.repo.tagslist()
385 i = web.repo.tagslist()
386 i.reverse()
386 i.reverse()
387 parity = paritygen(web.stripecount)
387 parity = paritygen(web.stripecount)
388
388
389 def entries(notip=False, limit=0, **map):
389 def entries(notip=False, limit=0, **map):
390 count = 0
390 count = 0
391 for k, n in i:
391 for k, n in i:
392 if notip and k == "tip":
392 if notip and k == "tip":
393 continue
393 continue
394 if limit > 0 and count >= limit:
394 if limit > 0 and count >= limit:
395 continue
395 continue
396 count = count + 1
396 count = count + 1
397 yield {"parity": parity.next(),
397 yield {"parity": parity.next(),
398 "tag": k,
398 "tag": k,
399 "date": web.repo[n].date(),
399 "date": web.repo[n].date(),
400 "node": hex(n)}
400 "node": hex(n)}
401
401
402 return tmpl("tags",
402 return tmpl("tags",
403 node=hex(web.repo.changelog.tip()),
403 node=hex(web.repo.changelog.tip()),
404 entries=lambda **x: entries(False, 0, **x),
404 entries=lambda **x: entries(False, 0, **x),
405 entriesnotip=lambda **x: entries(True, 0, **x),
405 entriesnotip=lambda **x: entries(True, 0, **x),
406 latestentry=lambda **x: entries(True, 1, **x))
406 latestentry=lambda **x: entries(True, 1, **x))
407
407
408 def bookmarks(web, req, tmpl):
408 def bookmarks(web, req, tmpl):
409 i = web.repo._bookmarks.items()
409 i = web.repo._bookmarks.items()
410 parity = paritygen(web.stripecount)
410 parity = paritygen(web.stripecount)
411
411
412 def entries(limit=0, **map):
412 def entries(limit=0, **map):
413 count = 0
413 count = 0
414 for k, n in sorted(i):
414 for k, n in sorted(i):
415 if limit > 0 and count >= limit:
415 if limit > 0 and count >= limit:
416 continue
416 continue
417 count = count + 1
417 count = count + 1
418 yield {"parity": parity.next(),
418 yield {"parity": parity.next(),
419 "bookmark": k,
419 "bookmark": k,
420 "date": web.repo[n].date(),
420 "date": web.repo[n].date(),
421 "node": hex(n)}
421 "node": hex(n)}
422
422
423 return tmpl("bookmarks",
423 return tmpl("bookmarks",
424 node=hex(web.repo.changelog.tip()),
424 node=hex(web.repo.changelog.tip()),
425 entries=lambda **x: entries(0, **x),
425 entries=lambda **x: entries(0, **x),
426 latestentry=lambda **x: entries(1, **x))
426 latestentry=lambda **x: entries(1, **x))
427
427
428 def branches(web, req, tmpl):
428 def branches(web, req, tmpl):
429 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
429 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
430 heads = web.repo.heads()
430 heads = web.repo.heads()
431 parity = paritygen(web.stripecount)
431 parity = paritygen(web.stripecount)
432 sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
432 sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
433
433
434 def entries(limit, **map):
434 def entries(limit, **map):
435 count = 0
435 count = 0
436 for ctx in sorted(tips, key=sortkey, reverse=True):
436 for ctx in sorted(tips, key=sortkey, reverse=True):
437 if limit > 0 and count >= limit:
437 if limit > 0 and count >= limit:
438 return
438 return
439 count += 1
439 count += 1
440 if not web.repo.branchheads(ctx.branch()):
440 if not web.repo.branchheads(ctx.branch()):
441 status = 'closed'
441 status = 'closed'
442 elif ctx.node() not in heads:
442 elif ctx.node() not in heads:
443 status = 'inactive'
443 status = 'inactive'
444 else:
444 else:
445 status = 'open'
445 status = 'open'
446 yield {'parity': parity.next(),
446 yield {'parity': parity.next(),
447 'branch': ctx.branch(),
447 'branch': ctx.branch(),
448 'status': status,
448 'status': status,
449 'node': ctx.hex(),
449 'node': ctx.hex(),
450 'date': ctx.date()}
450 'date': ctx.date()}
451
451
452 return tmpl('branches', node=hex(web.repo.changelog.tip()),
452 return tmpl('branches', node=hex(web.repo.changelog.tip()),
453 entries=lambda **x: entries(0, **x),
453 entries=lambda **x: entries(0, **x),
454 latestentry=lambda **x: entries(1, **x))
454 latestentry=lambda **x: entries(1, **x))
455
455
456 def summary(web, req, tmpl):
456 def summary(web, req, tmpl):
457 i = web.repo.tagslist()
457 i = web.repo.tagslist()
458 i.reverse()
458 i.reverse()
459
459
460 def tagentries(**map):
460 def tagentries(**map):
461 parity = paritygen(web.stripecount)
461 parity = paritygen(web.stripecount)
462 count = 0
462 count = 0
463 for k, n in i:
463 for k, n in i:
464 if k == "tip": # skip tip
464 if k == "tip": # skip tip
465 continue
465 continue
466
466
467 count += 1
467 count += 1
468 if count > 10: # limit to 10 tags
468 if count > 10: # limit to 10 tags
469 break
469 break
470
470
471 yield tmpl("tagentry",
471 yield tmpl("tagentry",
472 parity=parity.next(),
472 parity=parity.next(),
473 tag=k,
473 tag=k,
474 node=hex(n),
474 node=hex(n),
475 date=web.repo[n].date())
475 date=web.repo[n].date())
476
476
477 def bookmarks(**map):
477 def bookmarks(**map):
478 parity = paritygen(web.stripecount)
478 parity = paritygen(web.stripecount)
479 b = web.repo._bookmarks.items()
479 b = web.repo._bookmarks.items()
480 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
480 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
481 yield {'parity': parity.next(),
481 yield {'parity': parity.next(),
482 'bookmark': k,
482 'bookmark': k,
483 'date': web.repo[n].date(),
483 'date': web.repo[n].date(),
484 'node': hex(n)}
484 'node': hex(n)}
485
485
486 def branches(**map):
486 def branches(**map):
487 parity = paritygen(web.stripecount)
487 parity = paritygen(web.stripecount)
488
488
489 b = web.repo.branchtags()
489 b = web.repo.branchtags()
490 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
490 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
491 for r, n, t in sorted(l):
491 for r, n, t in sorted(l):
492 yield {'parity': parity.next(),
492 yield {'parity': parity.next(),
493 'branch': t,
493 'branch': t,
494 'node': hex(n),
494 'node': hex(n),
495 'date': web.repo[n].date()}
495 'date': web.repo[n].date()}
496
496
497 def changelist(**map):
497 def changelist(**map):
498 parity = paritygen(web.stripecount, offset=start - end)
498 parity = paritygen(web.stripecount, offset=start - end)
499 l = [] # build a list in forward order for efficiency
499 l = [] # build a list in forward order for efficiency
500 for i in xrange(start, end):
500 for i in xrange(start, end):
501 ctx = web.repo[i]
501 ctx = web.repo[i]
502 n = ctx.node()
502 n = ctx.node()
503 hn = hex(n)
503 hn = hex(n)
504
504
505 l.insert(0, tmpl(
505 l.insert(0, tmpl(
506 'shortlogentry',
506 'shortlogentry',
507 parity=parity.next(),
507 parity=parity.next(),
508 author=ctx.user(),
508 author=ctx.user(),
509 desc=ctx.description(),
509 desc=ctx.description(),
510 date=ctx.date(),
510 date=ctx.date(),
511 rev=i,
511 rev=i,
512 node=hn,
512 node=hn,
513 tags=webutil.nodetagsdict(web.repo, n),
513 tags=webutil.nodetagsdict(web.repo, n),
514 bookmarks=webutil.nodebookmarksdict(web.repo, n),
514 bookmarks=webutil.nodebookmarksdict(web.repo, n),
515 inbranch=webutil.nodeinbranch(web.repo, ctx),
515 inbranch=webutil.nodeinbranch(web.repo, ctx),
516 branches=webutil.nodebranchdict(web.repo, ctx)))
516 branches=webutil.nodebranchdict(web.repo, ctx)))
517
517
518 yield l
518 yield l
519
519
520 tip = web.repo['tip']
520 tip = web.repo['tip']
521 count = len(web.repo)
521 count = len(web.repo)
522 start = max(0, count - web.maxchanges)
522 start = max(0, count - web.maxchanges)
523 end = min(count, start + web.maxchanges)
523 end = min(count, start + web.maxchanges)
524
524
525 return tmpl("summary",
525 return tmpl("summary",
526 desc=web.config("web", "description", "unknown"),
526 desc=web.config("web", "description", "unknown"),
527 owner=get_contact(web.config) or "unknown",
527 owner=get_contact(web.config) or "unknown",
528 lastchange=tip.date(),
528 lastchange=tip.date(),
529 tags=tagentries,
529 tags=tagentries,
530 bookmarks=bookmarks,
530 bookmarks=bookmarks,
531 branches=branches,
531 branches=branches,
532 shortlog=changelist,
532 shortlog=changelist,
533 node=tip.hex(),
533 node=tip.hex(),
534 archives=web.archivelist("tip"))
534 archives=web.archivelist("tip"))
535
535
536 def filediff(web, req, tmpl):
536 def filediff(web, req, tmpl):
537 fctx, ctx = None, None
537 fctx, ctx = None, None
538 try:
538 try:
539 fctx = webutil.filectx(web.repo, req)
539 fctx = webutil.filectx(web.repo, req)
540 except LookupError:
540 except LookupError:
541 ctx = webutil.changectx(web.repo, req)
541 ctx = webutil.changectx(web.repo, req)
542 path = webutil.cleanpath(web.repo, req.form['file'][0])
542 path = webutil.cleanpath(web.repo, req.form['file'][0])
543 if path not in ctx.files():
543 if path not in ctx.files():
544 raise
544 raise
545
545
546 if fctx is not None:
546 if fctx is not None:
547 n = fctx.node()
547 n = fctx.node()
548 path = fctx.path()
548 path = fctx.path()
549 else:
549 else:
550 n = ctx.node()
550 n = ctx.node()
551 # path already defined in except clause
551 # path already defined in except clause
552
552
553 parity = paritygen(web.stripecount)
553 parity = paritygen(web.stripecount)
554 style = web.config('web', 'style', 'paper')
554 style = web.config('web', 'style', 'paper')
555 if 'style' in req.form:
555 if 'style' in req.form:
556 style = req.form['style'][0]
556 style = req.form['style'][0]
557
557
558 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
558 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
559 rename = fctx and webutil.renamelink(fctx) or []
559 rename = fctx and webutil.renamelink(fctx) or []
560 ctx = fctx and fctx or ctx
560 ctx = fctx and fctx or ctx
561 return tmpl("filediff",
561 return tmpl("filediff",
562 file=path,
562 file=path,
563 node=hex(n),
563 node=hex(n),
564 rev=ctx.rev(),
564 rev=ctx.rev(),
565 date=ctx.date(),
565 date=ctx.date(),
566 desc=ctx.description(),
566 desc=ctx.description(),
567 author=ctx.user(),
567 author=ctx.user(),
568 rename=rename,
568 rename=rename,
569 branch=webutil.nodebranchnodefault(ctx),
569 branch=webutil.nodebranchnodefault(ctx),
570 parent=webutil.parents(ctx),
570 parent=webutil.parents(ctx),
571 child=webutil.children(ctx),
571 child=webutil.children(ctx),
572 diff=diffs)
572 diff=diffs)
573
573
574 diff = filediff
574 diff = filediff
575
575
576 def annotate(web, req, tmpl):
576 def annotate(web, req, tmpl):
577 fctx = webutil.filectx(web.repo, req)
577 fctx = webutil.filectx(web.repo, req)
578 f = fctx.path()
578 f = fctx.path()
579 parity = paritygen(web.stripecount)
579 parity = paritygen(web.stripecount)
580 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
580 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
581
581
582 def annotate(**map):
582 def annotate(**map):
583 last = None
583 last = None
584 if binary(fctx.data()):
584 if binary(fctx.data()):
585 mt = (mimetypes.guess_type(fctx.path())[0]
585 mt = (mimetypes.guess_type(fctx.path())[0]
586 or 'application/octet-stream')
586 or 'application/octet-stream')
587 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
587 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
588 '(binary:%s)' % mt)])
588 '(binary:%s)' % mt)])
589 else:
589 else:
590 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
590 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
591 diffopts=diffopts))
591 diffopts=diffopts))
592 for lineno, ((f, targetline), l) in lines:
592 for lineno, ((f, targetline), l) in lines:
593 fnode = f.filenode()
593 fnode = f.filenode()
594
594
595 if last != fnode:
595 if last != fnode:
596 last = fnode
596 last = fnode
597
597
598 yield {"parity": parity.next(),
598 yield {"parity": parity.next(),
599 "node": f.hex(),
599 "node": f.hex(),
600 "rev": f.rev(),
600 "rev": f.rev(),
601 "author": f.user(),
601 "author": f.user(),
602 "desc": f.description(),
602 "desc": f.description(),
603 "file": f.path(),
603 "file": f.path(),
604 "targetline": targetline,
604 "targetline": targetline,
605 "line": l,
605 "line": l,
606 "lineid": "l%d" % (lineno + 1),
606 "lineid": "l%d" % (lineno + 1),
607 "linenumber": "% 6d" % (lineno + 1),
607 "linenumber": "% 6d" % (lineno + 1),
608 "revdate": f.date()}
608 "revdate": f.date()}
609
609
610 return tmpl("fileannotate",
610 return tmpl("fileannotate",
611 file=f,
611 file=f,
612 annotate=annotate,
612 annotate=annotate,
613 path=webutil.up(f),
613 path=webutil.up(f),
614 rev=fctx.rev(),
614 rev=fctx.rev(),
615 node=fctx.hex(),
615 node=fctx.hex(),
616 author=fctx.user(),
616 author=fctx.user(),
617 date=fctx.date(),
617 date=fctx.date(),
618 desc=fctx.description(),
618 desc=fctx.description(),
619 rename=webutil.renamelink(fctx),
619 rename=webutil.renamelink(fctx),
620 branch=webutil.nodebranchnodefault(fctx),
620 branch=webutil.nodebranchnodefault(fctx),
621 parent=webutil.parents(fctx),
621 parent=webutil.parents(fctx),
622 child=webutil.children(fctx),
622 child=webutil.children(fctx),
623 permissions=fctx.manifest().flags(f))
623 permissions=fctx.manifest().flags(f))
624
624
625 def filelog(web, req, tmpl):
625 def filelog(web, req, tmpl):
626
626
627 try:
627 try:
628 fctx = webutil.filectx(web.repo, req)
628 fctx = webutil.filectx(web.repo, req)
629 f = fctx.path()
629 f = fctx.path()
630 fl = fctx.filelog()
630 fl = fctx.filelog()
631 except error.LookupError:
631 except error.LookupError:
632 f = webutil.cleanpath(web.repo, req.form['file'][0])
632 f = webutil.cleanpath(web.repo, req.form['file'][0])
633 fl = web.repo.file(f)
633 fl = web.repo.file(f)
634 numrevs = len(fl)
634 numrevs = len(fl)
635 if not numrevs: # file doesn't exist at all
635 if not numrevs: # file doesn't exist at all
636 raise
636 raise
637 rev = webutil.changectx(web.repo, req).rev()
637 rev = webutil.changectx(web.repo, req).rev()
638 first = fl.linkrev(0)
638 first = fl.linkrev(0)
639 if rev < first: # current rev is from before file existed
639 if rev < first: # current rev is from before file existed
640 raise
640 raise
641 frev = numrevs - 1
641 frev = numrevs - 1
642 while fl.linkrev(frev) > rev:
642 while fl.linkrev(frev) > rev:
643 frev -= 1
643 frev -= 1
644 fctx = web.repo.filectx(f, fl.linkrev(frev))
644 fctx = web.repo.filectx(f, fl.linkrev(frev))
645
645
646 revcount = web.maxshortchanges
646 revcount = web.maxshortchanges
647 if 'revcount' in req.form:
647 if 'revcount' in req.form:
648 revcount = int(req.form.get('revcount', [revcount])[0])
648 revcount = int(req.form.get('revcount', [revcount])[0])
649 revcount = max(revcount, 1)
649 revcount = max(revcount, 1)
650 tmpl.defaults['sessionvars']['revcount'] = revcount
650 tmpl.defaults['sessionvars']['revcount'] = revcount
651
651
652 lessvars = copy.copy(tmpl.defaults['sessionvars'])
652 lessvars = copy.copy(tmpl.defaults['sessionvars'])
653 lessvars['revcount'] = max(revcount / 2, 1)
653 lessvars['revcount'] = max(revcount / 2, 1)
654 morevars = copy.copy(tmpl.defaults['sessionvars'])
654 morevars = copy.copy(tmpl.defaults['sessionvars'])
655 morevars['revcount'] = revcount * 2
655 morevars['revcount'] = revcount * 2
656
656
657 count = fctx.filerev() + 1
657 count = fctx.filerev() + 1
658 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
658 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
659 end = min(count, start + revcount) # last rev on this page
659 end = min(count, start + revcount) # last rev on this page
660 parity = paritygen(web.stripecount, offset=start - end)
660 parity = paritygen(web.stripecount, offset=start - end)
661
661
662 def entries(limit=0, **map):
662 def entries(limit=0, **map):
663 l = []
663 l = []
664
664
665 repo = web.repo
665 repo = web.repo
666 for i in xrange(start, end):
666 for i in xrange(start, end):
667 iterfctx = fctx.filectx(i)
667 iterfctx = fctx.filectx(i)
668
668
669 l.insert(0, {"parity": parity.next(),
669 l.insert(0, {"parity": parity.next(),
670 "filerev": i,
670 "filerev": i,
671 "file": f,
671 "file": f,
672 "node": iterfctx.hex(),
672 "node": iterfctx.hex(),
673 "author": iterfctx.user(),
673 "author": iterfctx.user(),
674 "date": iterfctx.date(),
674 "date": iterfctx.date(),
675 "rename": webutil.renamelink(iterfctx),
675 "rename": webutil.renamelink(iterfctx),
676 "parent": webutil.parents(iterfctx),
676 "parent": webutil.parents(iterfctx),
677 "child": webutil.children(iterfctx),
677 "child": webutil.children(iterfctx),
678 "desc": iterfctx.description(),
678 "desc": iterfctx.description(),
679 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
679 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
680 "bookmarks": webutil.nodebookmarksdict(
680 "bookmarks": webutil.nodebookmarksdict(
681 repo, iterfctx.node()),
681 repo, iterfctx.node()),
682 "branch": webutil.nodebranchnodefault(iterfctx),
682 "branch": webutil.nodebranchnodefault(iterfctx),
683 "inbranch": webutil.nodeinbranch(repo, iterfctx),
683 "inbranch": webutil.nodeinbranch(repo, iterfctx),
684 "branches": webutil.nodebranchdict(repo, iterfctx)})
684 "branches": webutil.nodebranchdict(repo, iterfctx)})
685
685
686 if limit > 0:
686 if limit > 0:
687 l = l[:limit]
687 l = l[:limit]
688
688
689 for e in l:
689 for e in l:
690 yield e
690 yield e
691
691
692 nodefunc = lambda x: fctx.filectx(fileid=x)
692 nodefunc = lambda x: fctx.filectx(fileid=x)
693 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
693 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
694 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
694 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
695 entries=lambda **x: entries(limit=0, **x),
695 entries=lambda **x: entries(limit=0, **x),
696 latestentry=lambda **x: entries(limit=1, **x),
696 latestentry=lambda **x: entries(limit=1, **x),
697 revcount=revcount, morevars=morevars, lessvars=lessvars)
697 revcount=revcount, morevars=morevars, lessvars=lessvars)
698
698
699 def archive(web, req, tmpl):
699 def archive(web, req, tmpl):
700 type_ = req.form.get('type', [None])[0]
700 type_ = req.form.get('type', [None])[0]
701 allowed = web.configlist("web", "allow_archive")
701 allowed = web.configlist("web", "allow_archive")
702 key = req.form['node'][0]
702 key = req.form['node'][0]
703
703
704 if type_ not in web.archives:
704 if type_ not in web.archives:
705 msg = 'Unsupported archive type: %s' % type_
705 msg = 'Unsupported archive type: %s' % type_
706 raise ErrorResponse(HTTP_NOT_FOUND, msg)
706 raise ErrorResponse(HTTP_NOT_FOUND, msg)
707
707
708 if not ((type_ in allowed or
708 if not ((type_ in allowed or
709 web.configbool("web", "allow" + type_, False))):
709 web.configbool("web", "allow" + type_, False))):
710 msg = 'Archive type not allowed: %s' % type_
710 msg = 'Archive type not allowed: %s' % type_
711 raise ErrorResponse(HTTP_FORBIDDEN, msg)
711 raise ErrorResponse(HTTP_FORBIDDEN, msg)
712
712
713 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
713 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
714 cnode = web.repo.lookup(key)
714 cnode = web.repo.lookup(key)
715 arch_version = key
715 arch_version = key
716 if cnode == key or key == 'tip':
716 if cnode == key or key == 'tip':
717 arch_version = short(cnode)
717 arch_version = short(cnode)
718 name = "%s-%s" % (reponame, arch_version)
718 name = "%s-%s" % (reponame, arch_version)
719 mimetype, artype, extension, encoding = web.archive_specs[type_]
719 mimetype, artype, extension, encoding = web.archive_specs[type_]
720 headers = [
720 headers = [
721 ('Content-Type', mimetype),
721 ('Content-Type', mimetype),
722 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
722 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
723 ]
723 ]
724 if encoding:
724 if encoding:
725 headers.append(('Content-Encoding', encoding))
725 headers.append(('Content-Encoding', encoding))
726 req.header(headers)
726 req.header(headers)
727 req.respond(HTTP_OK)
727 req.respond(HTTP_OK)
728 archival.archive(web.repo, req, cnode, artype, prefix=name)
728 archival.archive(web.repo, req, cnode, artype, prefix=name)
729 return []
729 return []
730
730
731
731
732 def static(web, req, tmpl):
732 def static(web, req, tmpl):
733 fname = req.form['file'][0]
733 fname = req.form['file'][0]
734 # a repo owner may set web.static in .hg/hgrc to get any file
734 # a repo owner may set web.static in .hg/hgrc to get any file
735 # readable by the user running the CGI script
735 # readable by the user running the CGI script
736 static = web.config("web", "static", None, untrusted=False)
736 static = web.config("web", "static", None, untrusted=False)
737 if not static:
737 if not static:
738 tp = web.templatepath or templater.templatepath()
738 tp = web.templatepath or templater.templatepath()
739 if isinstance(tp, str):
739 if isinstance(tp, str):
740 tp = [tp]
740 tp = [tp]
741 static = [os.path.join(p, 'static') for p in tp]
741 static = [os.path.join(p, 'static') for p in tp]
742 return [staticfile(static, fname, req)]
742 return [staticfile(static, fname, req)]
743
743
744 def graph(web, req, tmpl):
744 def graph(web, req, tmpl):
745
745
746 rev = webutil.changectx(web.repo, req).rev()
746 rev = webutil.changectx(web.repo, req).rev()
747 bg_height = 39
747 bg_height = 39
748 revcount = web.maxshortchanges
748 revcount = web.maxshortchanges
749 if 'revcount' in req.form:
749 if 'revcount' in req.form:
750 revcount = int(req.form.get('revcount', [revcount])[0])
750 revcount = int(req.form.get('revcount', [revcount])[0])
751 revcount = max(revcount, 1)
751 revcount = max(revcount, 1)
752 tmpl.defaults['sessionvars']['revcount'] = revcount
752 tmpl.defaults['sessionvars']['revcount'] = revcount
753
753
754 lessvars = copy.copy(tmpl.defaults['sessionvars'])
754 lessvars = copy.copy(tmpl.defaults['sessionvars'])
755 lessvars['revcount'] = max(revcount / 2, 1)
755 lessvars['revcount'] = max(revcount / 2, 1)
756 morevars = copy.copy(tmpl.defaults['sessionvars'])
756 morevars = copy.copy(tmpl.defaults['sessionvars'])
757 morevars['revcount'] = revcount * 2
757 morevars['revcount'] = revcount * 2
758
758
759 max_rev = len(web.repo) - 1
759 max_rev = len(web.repo) - 1
760 revcount = min(max_rev, revcount)
760 revcount = min(max_rev, revcount)
761 revnode = web.repo.changelog.node(rev)
761 revnode = web.repo.changelog.node(rev)
762 revnode_hex = hex(revnode)
762 revnode_hex = hex(revnode)
763 uprev = min(max_rev, rev + revcount)
763 uprev = min(max_rev, rev + revcount)
764 downrev = max(0, rev - revcount)
764 downrev = max(0, rev - revcount)
765 count = len(web.repo)
765 count = len(web.repo)
766 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
766 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
767 startrev = rev
767 startrev = rev
768 # if starting revision is less than 60 set it to uprev
768 # if starting revision is less than 60 set it to uprev
769 if rev < web.maxshortchanges:
769 if rev < web.maxshortchanges:
770 startrev = uprev
770 startrev = uprev
771
771
772 dag = graphmod.dagwalker(web.repo, range(startrev, downrev - 1, -1))
772 dag = graphmod.dagwalker(web.repo, range(startrev, downrev - 1, -1))
773 tree = list(graphmod.colored(dag, web.repo))
773 tree = list(graphmod.colored(dag, web.repo))
774 canvasheight = (len(tree) + 1) * bg_height - 27
774 canvasheight = (len(tree) + 1) * bg_height - 27
775 data = []
775 data = []
776 for (id, type, ctx, vtx, edges) in tree:
776 for (id, type, ctx, vtx, edges) in tree:
777 if type != graphmod.CHANGESET:
777 if type != graphmod.CHANGESET:
778 continue
778 continue
779 node = str(ctx)
779 node = str(ctx)
780 age = templatefilters.age(ctx.date())
780 age = templatefilters.age(ctx.date())
781 desc = templatefilters.firstline(ctx.description())
781 desc = templatefilters.firstline(ctx.description())
782 desc = cgi.escape(templatefilters.nonempty(desc))
782 desc = cgi.escape(templatefilters.nonempty(desc))
783 user = cgi.escape(templatefilters.person(ctx.user()))
783 user = cgi.escape(templatefilters.person(ctx.user()))
784 branch = ctx.branch()
784 branch = ctx.branch()
785 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
785 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
786 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags(),
786 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags(),
787 ctx.bookmarks()))
787 ctx.bookmarks()))
788
788
789 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
789 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
790 lessvars=lessvars, morevars=morevars, downrev=downrev,
790 lessvars=lessvars, morevars=morevars, downrev=downrev,
791 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
791 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
792 node=revnode_hex, changenav=changenav)
792 node=revnode_hex, changenav=changenav)
793
793
794 def _getdoc(e):
794 def _getdoc(e):
795 doc = e[0].__doc__
795 doc = e[0].__doc__
796 if doc:
796 if doc:
797 doc = doc.split('\n')[0]
797 doc = doc.split('\n')[0]
798 else:
798 else:
799 doc = _('(no help text available)')
799 doc = _('(no help text available)')
800 return doc
800 return doc
801
801
802 def help(web, req, tmpl):
802 def help(web, req, tmpl):
803 from mercurial import commands # avoid cycle
803 from mercurial import commands # avoid cycle
804
804
805 topicname = req.form.get('node', [None])[0]
805 topicname = req.form.get('node', [None])[0]
806 if not topicname:
806 if not topicname:
807 def topics(**map):
807 def topics(**map):
808 for entries, summary, _ in helpmod.helptable:
808 for entries, summary, _ in helpmod.helptable:
809 entries = sorted(entries, key=len)
809 entries = sorted(entries, key=len)
810 yield {'topic': entries[-1], 'summary': summary}
810 yield {'topic': entries[-1], 'summary': summary}
811
811
812 early, other = [], []
812 early, other = [], []
813 primary = lambda s: s.split('|')[0]
813 primary = lambda s: s.split('|')[0]
814 for c, e in commands.table.iteritems():
814 for c, e in commands.table.iteritems():
815 doc = _getdoc(e)
815 doc = _getdoc(e)
816 if 'DEPRECATED' in doc or c.startswith('debug'):
816 if 'DEPRECATED' in doc or c.startswith('debug'):
817 continue
817 continue
818 cmd = primary(c)
818 cmd = primary(c)
819 if cmd.startswith('^'):
819 if cmd.startswith('^'):
820 early.append((cmd[1:], doc))
820 early.append((cmd[1:], doc))
821 else:
821 else:
822 other.append((cmd, doc))
822 other.append((cmd, doc))
823
823
824 early.sort()
824 early.sort()
825 other.sort()
825 other.sort()
826
826
827 def earlycommands(**map):
827 def earlycommands(**map):
828 for c, doc in early:
828 for c, doc in early:
829 yield {'topic': c, 'summary': doc}
829 yield {'topic': c, 'summary': doc}
830
830
831 def othercommands(**map):
831 def othercommands(**map):
832 for c, doc in other:
832 for c, doc in other:
833 yield {'topic': c, 'summary': doc}
833 yield {'topic': c, 'summary': doc}
834
834
835 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
835 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
836 othercommands=othercommands, title='Index')
836 othercommands=othercommands, title='Index')
837
837
838 u = webutil.wsgiui()
838 u = webutil.wsgiui()
839 u.pushbuffer()
839 u.pushbuffer()
840 try:
840 try:
841 commands.help_(u, topicname)
841 commands.help_(u, topicname)
842 except error.UnknownCommand:
842 except error.UnknownCommand:
843 raise ErrorResponse(HTTP_NOT_FOUND)
843 raise ErrorResponse(HTTP_NOT_FOUND)
844 doc = u.popbuffer()
844 doc = u.popbuffer()
845 return tmpl('help', topic=topicname, doc=doc)
845 return tmpl('help', topic=topicname, doc=doc)
@@ -1,269 +1,270
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
13
14 def up(p):
14 def up(p):
15 if p[0] != "/":
15 if p[0] != "/":
16 p = "/" + p
16 p = "/" + p
17 if p[-1] == "/":
17 if p[-1] == "/":
18 p = p[:-1]
18 p = p[:-1]
19 up = os.path.dirname(p)
19 up = os.path.dirname(p)
20 if up == "/":
20 if up == "/":
21 return "/"
21 return "/"
22 return up + "/"
22 return up + "/"
23
23
24 def revnavgen(pos, pagelen, limit, nodefunc):
24 def revnavgen(pos, pagelen, limit, nodefunc):
25 def seq(factor, limit=None):
25 def seq(factor, limit=None):
26 if limit:
26 if limit:
27 yield limit
27 yield limit
28 if limit >= 20 and limit <= 40:
28 if limit >= 20 and limit <= 40:
29 yield 50
29 yield 50
30 else:
30 else:
31 yield 1 * factor
31 yield 1 * factor
32 yield 3 * factor
32 yield 3 * factor
33 for f in seq(factor * 10):
33 for f in seq(factor * 10):
34 yield f
34 yield f
35
35
36 navbefore = []
36 navbefore = []
37 navafter = []
37 navafter = []
38
38
39 last = 0
39 last = 0
40 for f in seq(1, pagelen):
40 for f in seq(1, pagelen):
41 if f < pagelen or f <= last:
41 if f < pagelen or f <= last:
42 continue
42 continue
43 if f > limit:
43 if f > limit:
44 break
44 break
45 last = f
45 last = f
46 if pos + f < limit:
46 if pos + f < limit:
47 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
47 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
48 if pos - f >= 0:
48 if pos - f >= 0:
49 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
49 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
50
50
51 navafter.append(("tip", "tip"))
51 navafter.append(("tip", "tip"))
52 try:
52 try:
53 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
53 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
54 except error.RepoError:
54 except error.RepoError:
55 pass
55 pass
56
56
57 def gen(l):
57 def gen(l):
58 def f(**map):
58 def f(**map):
59 for label, node in l:
59 for label, node in l:
60 yield {"label": label, "node": node}
60 yield {"label": label, "node": node}
61 return f
61 return f
62
62
63 return (dict(before=gen(navbefore), after=gen(navafter)),)
63 return (dict(before=gen(navbefore), after=gen(navafter)),)
64
64
65 def _siblings(siblings=[], hiderev=None):
65 def _siblings(siblings=[], hiderev=None):
66 siblings = [s for s in siblings if s.node() != nullid]
66 siblings = [s for s in siblings if s.node() != nullid]
67 if len(siblings) == 1 and siblings[0].rev() == hiderev:
67 if len(siblings) == 1 and siblings[0].rev() == hiderev:
68 return
68 return
69 for s in siblings:
69 for s in siblings:
70 d = {'node': s.hex(), 'rev': s.rev()}
70 d = {'node': s.hex(), 'rev': s.rev()}
71 d['user'] = s.user()
71 d['user'] = s.user()
72 d['date'] = s.date()
72 d['date'] = s.date()
73 d['description'] = s.description()
73 d['description'] = s.description()
74 d['branch'] = s.branch()
74 d['branch'] = s.branch()
75 if util.safehasattr(s, 'path'):
75 if util.safehasattr(s, 'path'):
76 d['file'] = s.path()
76 d['file'] = s.path()
77 yield d
77 yield d
78
78
79 def parents(ctx, hide=None):
79 def parents(ctx, hide=None):
80 return _siblings(ctx.parents(), hide)
80 return _siblings(ctx.parents(), hide)
81
81
82 def children(ctx, hide=None):
82 def children(ctx, hide=None):
83 return _siblings(ctx.children(), hide)
83 return _siblings(ctx.children(), hide)
84
84
85 def renamelink(fctx):
85 def renamelink(fctx):
86 r = fctx.renamed()
86 r = fctx.renamed()
87 if r:
87 if r:
88 return [dict(file=r[0], node=hex(r[1]))]
88 return [dict(file=r[0], node=hex(r[1]))]
89 return []
89 return []
90
90
91 def nodetagsdict(repo, node):
91 def nodetagsdict(repo, node):
92 return [{"name": i} for i in repo.nodetags(node)]
92 return [{"name": i} for i in repo.nodetags(node)]
93
93
94 def nodebookmarksdict(repo, node):
94 def nodebookmarksdict(repo, node):
95 return [{"name": i} for i in repo.nodebookmarks(node)]
95 return [{"name": i} for i in repo.nodebookmarks(node)]
96
96
97 def nodebranchdict(repo, ctx):
97 def nodebranchdict(repo, ctx):
98 branches = []
98 branches = []
99 branch = ctx.branch()
99 branch = ctx.branch()
100 # If this is an empty repo, ctx.node() == nullid,
100 # If this is an empty repo, ctx.node() == nullid,
101 # ctx.branch() == 'default', but branchtags() is
101 # ctx.branch() == 'default', but branchtags() is
102 # an empty dict. Using dict.get avoids a traceback.
102 # an empty dict. Using dict.get avoids a traceback.
103 if repo.branchtags().get(branch) == ctx.node():
103 if repo.branchtags().get(branch) == ctx.node():
104 branches.append({"name": branch})
104 branches.append({"name": branch})
105 return branches
105 return branches
106
106
107 def nodeinbranch(repo, ctx):
107 def nodeinbranch(repo, ctx):
108 branches = []
108 branches = []
109 branch = ctx.branch()
109 branch = ctx.branch()
110 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
110 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
111 branches.append({"name": branch})
111 branches.append({"name": branch})
112 return branches
112 return branches
113
113
114 def nodebranchnodefault(ctx):
114 def nodebranchnodefault(ctx):
115 branches = []
115 branches = []
116 branch = ctx.branch()
116 branch = ctx.branch()
117 if branch != 'default':
117 if branch != 'default':
118 branches.append({"name": branch})
118 branches.append({"name": branch})
119 return branches
119 return branches
120
120
121 def showtag(repo, tmpl, t1, node=nullid, **args):
121 def showtag(repo, tmpl, t1, node=nullid, **args):
122 for t in repo.nodetags(node):
122 for t in repo.nodetags(node):
123 yield tmpl(t1, tag=t, **args)
123 yield tmpl(t1, tag=t, **args)
124
124
125 def showbookmark(repo, tmpl, t1, node=nullid, **args):
125 def showbookmark(repo, tmpl, t1, node=nullid, **args):
126 for t in repo.nodebookmarks(node):
126 for t in repo.nodebookmarks(node):
127 yield tmpl(t1, bookmark=t, **args)
127 yield tmpl(t1, bookmark=t, **args)
128
128
129 def cleanpath(repo, path):
129 def cleanpath(repo, path):
130 path = path.lstrip('/')
130 path = path.lstrip('/')
131 return scmutil.canonpath(repo.root, '', path)
131 return scmutil.canonpath(repo.root, '', path)
132
132
133 def changectx(repo, req):
133 def changectx(repo, req):
134 changeid = "tip"
134 changeid = "tip"
135 if 'node' in req.form:
135 if 'node' in req.form:
136 changeid = req.form['node'][0]
136 changeid = req.form['node'][0]
137 elif 'manifest' in req.form:
137 elif 'manifest' in req.form:
138 changeid = req.form['manifest'][0]
138 changeid = req.form['manifest'][0]
139
139
140 try:
140 try:
141 ctx = repo[changeid]
141 ctx = repo[changeid]
142 except error.RepoError:
142 except error.RepoError:
143 man = repo.manifest
143 man = repo.manifest
144 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
144 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
145
145
146 return ctx
146 return ctx
147
147
148 def filectx(repo, req):
148 def filectx(repo, req):
149 path = cleanpath(repo, req.form['file'][0])
149 path = cleanpath(repo, req.form['file'][0])
150 if 'node' in req.form:
150 if 'node' in req.form:
151 changeid = req.form['node'][0]
151 changeid = req.form['node'][0]
152 else:
152 else:
153 changeid = req.form['filenode'][0]
153 changeid = req.form['filenode'][0]
154 try:
154 try:
155 fctx = repo[changeid][path]
155 fctx = repo[changeid][path]
156 except error.RepoError:
156 except error.RepoError:
157 fctx = repo.filectx(path, fileid=changeid)
157 fctx = repo.filectx(path, fileid=changeid)
158
158
159 return fctx
159 return fctx
160
160
161 def listfilediffs(tmpl, files, node, max):
161 def listfilediffs(tmpl, files, node, max):
162 for f in files[:max]:
162 for f in files[:max]:
163 yield tmpl('filedifflink', node=hex(node), file=f)
163 yield tmpl('filedifflink', node=hex(node), file=f)
164 if len(files) > max:
164 if len(files) > max:
165 yield tmpl('fileellipses')
165 yield tmpl('fileellipses')
166
166
167 def diffs(repo, tmpl, ctx, files, parity, style):
167 def diffs(repo, tmpl, ctx, files, parity, style):
168
168
169 def countgen():
169 def countgen():
170 start = 1
170 start = 1
171 while True:
171 while True:
172 yield start
172 yield start
173 start += 1
173 start += 1
174
174
175 blockcount = countgen()
175 blockcount = countgen()
176 def prettyprintlines(diff):
176 def prettyprintlines(diff, blockno):
177 blockno = blockcount.next()
178 for lineno, l in enumerate(diff.splitlines(True)):
177 for lineno, l in enumerate(diff.splitlines(True)):
179 lineno = "%d.%d" % (blockno, lineno + 1)
178 lineno = "%d.%d" % (blockno, lineno + 1)
180 if l.startswith('+'):
179 if l.startswith('+'):
181 ltype = "difflineplus"
180 ltype = "difflineplus"
182 elif l.startswith('-'):
181 elif l.startswith('-'):
183 ltype = "difflineminus"
182 ltype = "difflineminus"
184 elif l.startswith('@'):
183 elif l.startswith('@'):
185 ltype = "difflineat"
184 ltype = "difflineat"
186 else:
185 else:
187 ltype = "diffline"
186 ltype = "diffline"
188 yield tmpl(ltype,
187 yield tmpl(ltype,
189 line=l,
188 line=l,
190 lineid="l%s" % lineno,
189 lineid="l%s" % lineno,
191 linenumber="% 8s" % lineno)
190 linenumber="% 8s" % lineno)
192
191
193 if files:
192 if files:
194 m = match.exact(repo.root, repo.getcwd(), files)
193 m = match.exact(repo.root, repo.getcwd(), files)
195 else:
194 else:
196 m = match.always(repo.root, repo.getcwd())
195 m = match.always(repo.root, repo.getcwd())
197
196
198 diffopts = patch.diffopts(repo.ui, untrusted=True)
197 diffopts = patch.diffopts(repo.ui, untrusted=True)
199 parents = ctx.parents()
198 parents = ctx.parents()
200 node1 = parents and parents[0].node() or nullid
199 node1 = parents and parents[0].node() or nullid
201 node2 = ctx.node()
200 node2 = ctx.node()
202
201
203 block = []
202 block = []
204 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
203 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
205 if chunk.startswith('diff') and block:
204 if chunk.startswith('diff') and block:
206 yield tmpl('diffblock', parity=parity.next(),
205 blockno = blockcount.next()
207 lines=prettyprintlines(''.join(block)))
206 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
207 lines=prettyprintlines(''.join(block), blockno))
208 block = []
208 block = []
209 if chunk.startswith('diff') and style != 'raw':
209 if chunk.startswith('diff') and style != 'raw':
210 chunk = ''.join(chunk.splitlines(True)[1:])
210 chunk = ''.join(chunk.splitlines(True)[1:])
211 block.append(chunk)
211 block.append(chunk)
212 yield tmpl('diffblock', parity=parity.next(),
212 blockno = blockcount.next()
213 lines=prettyprintlines(''.join(block)))
213 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
214 lines=prettyprintlines(''.join(block), blockno))
214
215
215 def diffstatgen(ctx):
216 def diffstatgen(ctx):
216 '''Generator function that provides the diffstat data.'''
217 '''Generator function that provides the diffstat data.'''
217
218
218 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
219 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
219 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
220 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
220 while True:
221 while True:
221 yield stats, maxname, maxtotal, addtotal, removetotal, binary
222 yield stats, maxname, maxtotal, addtotal, removetotal, binary
222
223
223 def diffsummary(statgen):
224 def diffsummary(statgen):
224 '''Return a short summary of the diff.'''
225 '''Return a short summary of the diff.'''
225
226
226 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
227 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
227 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
228 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
228 len(stats), addtotal, removetotal)
229 len(stats), addtotal, removetotal)
229
230
230 def diffstat(tmpl, ctx, statgen, parity):
231 def diffstat(tmpl, ctx, statgen, parity):
231 '''Return a diffstat template for each file in the diff.'''
232 '''Return a diffstat template for each file in the diff.'''
232
233
233 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
234 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
234 files = ctx.files()
235 files = ctx.files()
235
236
236 def pct(i):
237 def pct(i):
237 if maxtotal == 0:
238 if maxtotal == 0:
238 return 0
239 return 0
239 return (float(i) / maxtotal) * 100
240 return (float(i) / maxtotal) * 100
240
241
241 fileno = 0
242 fileno = 0
242 for filename, adds, removes, isbinary in stats:
243 for filename, adds, removes, isbinary in stats:
243 template = filename in files and 'diffstatlink' or 'diffstatnolink'
244 template = filename in files and 'diffstatlink' or 'diffstatnolink'
244 total = adds + removes
245 total = adds + removes
245 fileno += 1
246 fileno += 1
246 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
247 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
247 total=total, addpct=pct(adds), removepct=pct(removes),
248 total=total, addpct=pct(adds), removepct=pct(removes),
248 parity=parity.next())
249 parity=parity.next())
249
250
250 class sessionvars(object):
251 class sessionvars(object):
251 def __init__(self, vars, start='?'):
252 def __init__(self, vars, start='?'):
252 self.start = start
253 self.start = start
253 self.vars = vars
254 self.vars = vars
254 def __getitem__(self, key):
255 def __getitem__(self, key):
255 return self.vars[key]
256 return self.vars[key]
256 def __setitem__(self, key, value):
257 def __setitem__(self, key, value):
257 self.vars[key] = value
258 self.vars[key] = value
258 def __copy__(self):
259 def __copy__(self):
259 return sessionvars(copy.copy(self.vars), self.start)
260 return sessionvars(copy.copy(self.vars), self.start)
260 def __iter__(self):
261 def __iter__(self):
261 separator = self.start
262 separator = self.start
262 for key, value in self.vars.iteritems():
263 for key, value in self.vars.iteritems():
263 yield {'name': key, 'value': str(value), 'separator': separator}
264 yield {'name': key, 'value': str(value), 'separator': separator}
264 separator = '&'
265 separator = '&'
265
266
266 class wsgiui(ui.ui):
267 class wsgiui(ui.ui):
267 # default termwidth breaks under mod_wsgi
268 # default termwidth breaks under mod_wsgi
268 def termwidth(self):
269 def termwidth(self):
269 return 80
270 return 80
@@ -1,557 +1,602
1 $ "$TESTDIR/hghave" serve execbit || exit 80
1 $ "$TESTDIR/hghave" serve execbit || exit 80
2
2
3 setting up repo
3 setting up repo
4
4
5 $ hg init test
5 $ hg init test
6 $ cd test
6 $ cd test
7 $ echo a > a
7 $ echo a > a
8 $ echo b > b
8 $ echo b > b
9 $ hg ci -Ama
9 $ hg ci -Ama
10 adding a
10 adding a
11 adding b
11 adding b
12
12
13 change permissions for git diffs
13 change permissions for git diffs
14
14
15 $ chmod +x a
15 $ chmod +x a
16 $ hg ci -Amb
16 $ hg ci -Amb
17
17
18 set up hgweb
18 set up hgweb
19
19
20 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
20 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
21 $ cat hg.pid >> $DAEMON_PIDS
21 $ cat hg.pid >> $DAEMON_PIDS
22
22
23 revision
23 revision
24
24
25 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/0'
25 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/0'
26 200 Script output follows
26 200 Script output follows
27
27
28 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
28 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
29 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
29 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
30 <head>
30 <head>
31 <link rel="icon" href="/static/hgicon.png" type="image/png" />
31 <link rel="icon" href="/static/hgicon.png" type="image/png" />
32 <meta name="robots" content="index, nofollow" />
32 <meta name="robots" content="index, nofollow" />
33 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
33 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
34 <script type="text/javascript" src="/static/mercurial.js"></script>
34 <script type="text/javascript" src="/static/mercurial.js"></script>
35
35
36 <title>test: 0cd96de13884</title>
36 <title>test: 0cd96de13884</title>
37 </head>
37 </head>
38 <body>
38 <body>
39 <div class="container">
39 <div class="container">
40 <div class="menu">
40 <div class="menu">
41 <div class="logo">
41 <div class="logo">
42 <a href="http://mercurial.selenic.com/">
42 <a href="http://mercurial.selenic.com/">
43 <img src="/static/hglogo.png" alt="mercurial" /></a>
43 <img src="/static/hglogo.png" alt="mercurial" /></a>
44 </div>
44 </div>
45 <ul>
45 <ul>
46 <li><a href="/shortlog/0cd96de13884">log</a></li>
46 <li><a href="/shortlog/0cd96de13884">log</a></li>
47 <li><a href="/graph/0cd96de13884">graph</a></li>
47 <li><a href="/graph/0cd96de13884">graph</a></li>
48 <li><a href="/tags">tags</a></li>
48 <li><a href="/tags">tags</a></li>
49 <li><a href="/bookmarks">bookmarks</a></li>
49 <li><a href="/bookmarks">bookmarks</a></li>
50 <li><a href="/branches">branches</a></li>
50 <li><a href="/branches">branches</a></li>
51 </ul>
51 </ul>
52 <ul>
52 <ul>
53 <li class="active">changeset</li>
53 <li class="active">changeset</li>
54 <li><a href="/raw-rev/0cd96de13884">raw</a></li>
54 <li><a href="/raw-rev/0cd96de13884">raw</a></li>
55 <li><a href="/file/0cd96de13884">browse</a></li>
55 <li><a href="/file/0cd96de13884">browse</a></li>
56 </ul>
56 </ul>
57 <ul>
57 <ul>
58
58
59 </ul>
59 </ul>
60 <ul>
60 <ul>
61 <li><a href="/help">help</a></li>
61 <li><a href="/help">help</a></li>
62 </ul>
62 </ul>
63 </div>
63 </div>
64
64
65 <div class="main">
65 <div class="main">
66
66
67 <h2><a href="/">test</a></h2>
67 <h2><a href="/">test</a></h2>
68 <h3>changeset 0:0cd96de13884 </h3>
68 <h3>changeset 0:0cd96de13884 </h3>
69
69
70 <form class="search" action="/log">
70 <form class="search" action="/log">
71
71
72 <p><input name="rev" id="search1" type="text" size="30" /></p>
72 <p><input name="rev" id="search1" type="text" size="30" /></p>
73 <div id="hint">find changesets by author, revision,
73 <div id="hint">find changesets by author, revision,
74 files, or words in the commit message</div>
74 files, or words in the commit message</div>
75 </form>
75 </form>
76
76
77 <div class="description">a</div>
77 <div class="description">a</div>
78
78
79 <table id="changesetEntry">
79 <table id="changesetEntry">
80 <tr>
80 <tr>
81 <th class="author">author</th>
81 <th class="author">author</th>
82 <td class="author">&#116;&#101;&#115;&#116;</td>
82 <td class="author">&#116;&#101;&#115;&#116;</td>
83 </tr>
83 </tr>
84 <tr>
84 <tr>
85 <th class="date">date</th>
85 <th class="date">date</th>
86 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
86 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
87 <tr>
87 <tr>
88 <th class="author">parents</th>
88 <th class="author">parents</th>
89 <td class="author"></td>
89 <td class="author"></td>
90 </tr>
90 </tr>
91 <tr>
91 <tr>
92 <th class="author">children</th>
92 <th class="author">children</th>
93 <td class="author"> <a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td>
93 <td class="author"> <a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td>
94 </tr>
94 </tr>
95 <tr>
95 <tr>
96 <th class="files">files</th>
96 <th class="files">files</th>
97 <td class="files"><a href="/file/0cd96de13884/a">a</a> <a href="/file/0cd96de13884/b">b</a> </td>
97 <td class="files"><a href="/file/0cd96de13884/a">a</a> <a href="/file/0cd96de13884/b">b</a> </td>
98 </tr>
98 </tr>
99 <tr>
99 <tr>
100 <th class="diffstat">diffstat</th>
100 <th class="diffstat">diffstat</th>
101 <td class="diffstat">
101 <td class="diffstat">
102 2 files changed, 2 insertions(+), 0 deletions(-)
102 2 files changed, 2 insertions(+), 0 deletions(-)
103
103
104 <a id="diffstatexpand" href="javascript:showDiffstat()"/>[<tt>+</tt>]</a>
104 <a id="diffstatexpand" href="javascript:showDiffstat()"/>[<tt>+</tt>]</a>
105 <div id="diffstatdetails" style="display:none;">
105 <div id="diffstatdetails" style="display:none;">
106 <a href="javascript:hideDiffstat()"/>[<tt>-</tt>]</a>
106 <a href="javascript:hideDiffstat()"/>[<tt>-</tt>]</a>
107 <p>
107 <p>
108 <table> <tr class="parity0">
108 <table> <tr class="parity0">
109 <td class="diffstat-file"><a href="#l1.1">a</a></td>
109 <td class="diffstat-file"><a href="#l1.1">a</a></td>
110 <td class="diffstat-total" align="right">1</td>
110 <td class="diffstat-total" align="right">1</td>
111 <td class="diffstat-graph">
111 <td class="diffstat-graph">
112 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
112 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
113 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
113 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
114 </td>
114 </td>
115 </tr>
115 </tr>
116 <tr class="parity1">
116 <tr class="parity1">
117 <td class="diffstat-file"><a href="#l2.1">b</a></td>
117 <td class="diffstat-file"><a href="#l2.1">b</a></td>
118 <td class="diffstat-total" align="right">1</td>
118 <td class="diffstat-total" align="right">1</td>
119 <td class="diffstat-graph">
119 <td class="diffstat-graph">
120 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
120 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
121 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
121 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
122 </td>
122 </td>
123 </tr>
123 </tr>
124 </table>
124 </table>
125 </div>
125 </div>
126 </td>
126 </td>
127 </tr>
127 </tr>
128 </table>
128 </table>
129
129
130 <div class="overflow">
130 <div class="overflow">
131 <div class="sourcefirst"> line diff</div>
131 <div class="sourcefirst"> line diff</div>
132
132
133 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
133 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
134 </span><a href="#l1.2" id="l1.2"> 1.2</a> <span class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000
134 </span><a href="#l1.2" id="l1.2"> 1.2</a> <span class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000
135 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="atline">@@ -0,0 +1,1 @@
135 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="atline">@@ -0,0 +1,1 @@
136 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="plusline">+a
136 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="plusline">+a
137 </span></pre></div><div class="source bottomline parity1"><pre><a href="#l2.1" id="l2.1"> 2.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
137 </span></pre></div><div class="source bottomline parity1"><pre><a href="#l2.1" id="l2.1"> 2.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
138 </span><a href="#l2.2" id="l2.2"> 2.2</a> <span class="plusline">+++ b/b Thu Jan 01 00:00:00 1970 +0000
138 </span><a href="#l2.2" id="l2.2"> 2.2</a> <span class="plusline">+++ b/b Thu Jan 01 00:00:00 1970 +0000
139 </span><a href="#l2.3" id="l2.3"> 2.3</a> <span class="atline">@@ -0,0 +1,1 @@
139 </span><a href="#l2.3" id="l2.3"> 2.3</a> <span class="atline">@@ -0,0 +1,1 @@
140 </span><a href="#l2.4" id="l2.4"> 2.4</a> <span class="plusline">+b
140 </span><a href="#l2.4" id="l2.4"> 2.4</a> <span class="plusline">+b
141 </span></pre></div>
141 </span></pre></div>
142 </div>
142 </div>
143
143
144 </div>
144 </div>
145 </div>
145 </div>
146 <script type="text/javascript">process_dates()</script>
146 <script type="text/javascript">process_dates()</script>
147
147
148
148
149 </body>
149 </body>
150 </html>
150 </html>
151
151
152
152
153 raw revision
153 raw revision
154
154
155 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/raw-rev/0'
155 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/raw-rev/0'
156 200 Script output follows
156 200 Script output follows
157
157
158
158
159 # HG changeset patch
159 # HG changeset patch
160 # User test
160 # User test
161 # Date 0 0
161 # Date 0 0
162 # Node ID 0cd96de13884b090099512d4794ae87ad067ea8e
162 # Node ID 0cd96de13884b090099512d4794ae87ad067ea8e
163
163
164 a
164 a
165
165
166 diff -r 000000000000 -r 0cd96de13884 a
166 diff -r 000000000000 -r 0cd96de13884 a
167 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
167 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
168 +++ b/a Thu Jan 01 00:00:00 1970 +0000
168 +++ b/a Thu Jan 01 00:00:00 1970 +0000
169 @@ -0,0 +1,1 @@
169 @@ -0,0 +1,1 @@
170 +a
170 +a
171 diff -r 000000000000 -r 0cd96de13884 b
171 diff -r 000000000000 -r 0cd96de13884 b
172 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
172 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
173 +++ b/b Thu Jan 01 00:00:00 1970 +0000
173 +++ b/b Thu Jan 01 00:00:00 1970 +0000
174 @@ -0,0 +1,1 @@
174 @@ -0,0 +1,1 @@
175 +b
175 +b
176
176
177
177
178 diff removed file
178 diff removed file
179
179
180 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/tip/a'
180 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/tip/a'
181 200 Script output follows
181 200 Script output follows
182
182
183 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
183 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
184 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
184 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
185 <head>
185 <head>
186 <link rel="icon" href="/static/hgicon.png" type="image/png" />
186 <link rel="icon" href="/static/hgicon.png" type="image/png" />
187 <meta name="robots" content="index, nofollow" />
187 <meta name="robots" content="index, nofollow" />
188 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
188 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
189 <script type="text/javascript" src="/static/mercurial.js"></script>
189 <script type="text/javascript" src="/static/mercurial.js"></script>
190
190
191 <title>test: a diff</title>
191 <title>test: a diff</title>
192 </head>
192 </head>
193 <body>
193 <body>
194
194
195 <div class="container">
195 <div class="container">
196 <div class="menu">
196 <div class="menu">
197 <div class="logo">
197 <div class="logo">
198 <a href="http://mercurial.selenic.com/">
198 <a href="http://mercurial.selenic.com/">
199 <img src="/static/hglogo.png" alt="mercurial" /></a>
199 <img src="/static/hglogo.png" alt="mercurial" /></a>
200 </div>
200 </div>
201 <ul>
201 <ul>
202 <li><a href="/shortlog/78e4ebad7cdf">log</a></li>
202 <li><a href="/shortlog/78e4ebad7cdf">log</a></li>
203 <li><a href="/graph/78e4ebad7cdf">graph</a></li>
203 <li><a href="/graph/78e4ebad7cdf">graph</a></li>
204 <li><a href="/tags">tags</a></li>
204 <li><a href="/tags">tags</a></li>
205 <li><a href="/bookmarks">bookmarks</a></li>
205 <li><a href="/bookmarks">bookmarks</a></li>
206 <li><a href="/branches">branches</a></li>
206 <li><a href="/branches">branches</a></li>
207 </ul>
207 </ul>
208 <ul>
208 <ul>
209 <li><a href="/rev/78e4ebad7cdf">changeset</a></li>
209 <li><a href="/rev/78e4ebad7cdf">changeset</a></li>
210 <li><a href="/file/78e4ebad7cdf">browse</a></li>
210 <li><a href="/file/78e4ebad7cdf">browse</a></li>
211 </ul>
211 </ul>
212 <ul>
212 <ul>
213 <li><a href="/file/78e4ebad7cdf/a">file</a></li>
213 <li><a href="/file/78e4ebad7cdf/a">file</a></li>
214 <li><a href="/file/tip/a">latest</a></li>
214 <li><a href="/file/tip/a">latest</a></li>
215 <li class="active">diff</li>
215 <li class="active">diff</li>
216 <li><a href="/annotate/78e4ebad7cdf/a">annotate</a></li>
216 <li><a href="/annotate/78e4ebad7cdf/a">annotate</a></li>
217 <li><a href="/log/78e4ebad7cdf/a">file log</a></li>
217 <li><a href="/log/78e4ebad7cdf/a">file log</a></li>
218 <li><a href="/raw-file/78e4ebad7cdf/a">raw</a></li>
218 <li><a href="/raw-file/78e4ebad7cdf/a">raw</a></li>
219 </ul>
219 </ul>
220 <ul>
220 <ul>
221 <li><a href="/help">help</a></li>
221 <li><a href="/help">help</a></li>
222 </ul>
222 </ul>
223 </div>
223 </div>
224
224
225 <div class="main">
225 <div class="main">
226 <h2><a href="/">test</a></h2>
226 <h2><a href="/">test</a></h2>
227 <h3>diff a @ 1:78e4ebad7cdf</h3>
227 <h3>diff a @ 1:78e4ebad7cdf</h3>
228
228
229 <form class="search" action="/log">
229 <form class="search" action="/log">
230 <p></p>
230 <p></p>
231 <p><input name="rev" id="search1" type="text" size="30" /></p>
231 <p><input name="rev" id="search1" type="text" size="30" /></p>
232 <div id="hint">find changesets by author, revision,
232 <div id="hint">find changesets by author, revision,
233 files, or words in the commit message</div>
233 files, or words in the commit message</div>
234 </form>
234 </form>
235
235
236 <div class="description">b</div>
236 <div class="description">b</div>
237
237
238 <table id="changesetEntry">
238 <table id="changesetEntry">
239 <tr>
239 <tr>
240 <th>author</th>
240 <th>author</th>
241 <td>&#116;&#101;&#115;&#116;</td>
241 <td>&#116;&#101;&#115;&#116;</td>
242 </tr>
242 </tr>
243 <tr>
243 <tr>
244 <th>date</th>
244 <th>date</th>
245 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
245 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
246 </tr>
246 </tr>
247 <tr>
247 <tr>
248 <th>parents</th>
248 <th>parents</th>
249 <td></td>
249 <td></td>
250 </tr>
250 </tr>
251 <tr>
251 <tr>
252 <th>children</th>
252 <th>children</th>
253 <td></td>
253 <td></td>
254 </tr>
254 </tr>
255
255
256 </table>
256 </table>
257
257
258 <div class="overflow">
258 <div class="overflow">
259 <div class="sourcefirst"> line diff</div>
259 <div class="sourcefirst"> line diff</div>
260
260
261 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
261 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> <span class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000
262 </span><a href="#l1.2" id="l1.2"> 1.2</a> <span class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000
262 </span><a href="#l1.2" id="l1.2"> 1.2</a> <span class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000
263 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="atline">@@ -0,0 +1,1 @@
263 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="atline">@@ -0,0 +1,1 @@
264 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="plusline">+a
264 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="plusline">+a
265 </span></pre></div>
265 </span></pre></div>
266 </div>
266 </div>
267 </div>
267 </div>
268 </div>
268 </div>
269
269
270 <script type="text/javascript">process_dates()</script>
270 <script type="text/javascript">process_dates()</script>
271
271
272
272
273 </body>
273 </body>
274 </html>
274 </html>
275
275
276
276
277 set up hgweb with git diffs
277 set up hgweb with git diffs
278
278
279 $ "$TESTDIR/killdaemons.py"
279 $ "$TESTDIR/killdaemons.py"
280 $ hg serve --config 'diff.git=1' -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
280 $ hg serve --config 'diff.git=1' -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
281 $ cat hg.pid >> $DAEMON_PIDS
281 $ cat hg.pid >> $DAEMON_PIDS
282
282
283 revision
283 revision
284
284
285 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/0'
285 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/0'
286 200 Script output follows
286 200 Script output follows
287
287
288 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
288 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
289 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
289 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
290 <head>
290 <head>
291 <link rel="icon" href="/static/hgicon.png" type="image/png" />
291 <link rel="icon" href="/static/hgicon.png" type="image/png" />
292 <meta name="robots" content="index, nofollow" />
292 <meta name="robots" content="index, nofollow" />
293 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
293 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
294 <script type="text/javascript" src="/static/mercurial.js"></script>
294 <script type="text/javascript" src="/static/mercurial.js"></script>
295
295
296 <title>test: 0cd96de13884</title>
296 <title>test: 0cd96de13884</title>
297 </head>
297 </head>
298 <body>
298 <body>
299 <div class="container">
299 <div class="container">
300 <div class="menu">
300 <div class="menu">
301 <div class="logo">
301 <div class="logo">
302 <a href="http://mercurial.selenic.com/">
302 <a href="http://mercurial.selenic.com/">
303 <img src="/static/hglogo.png" alt="mercurial" /></a>
303 <img src="/static/hglogo.png" alt="mercurial" /></a>
304 </div>
304 </div>
305 <ul>
305 <ul>
306 <li><a href="/shortlog/0cd96de13884">log</a></li>
306 <li><a href="/shortlog/0cd96de13884">log</a></li>
307 <li><a href="/graph/0cd96de13884">graph</a></li>
307 <li><a href="/graph/0cd96de13884">graph</a></li>
308 <li><a href="/tags">tags</a></li>
308 <li><a href="/tags">tags</a></li>
309 <li><a href="/bookmarks">bookmarks</a></li>
309 <li><a href="/bookmarks">bookmarks</a></li>
310 <li><a href="/branches">branches</a></li>
310 <li><a href="/branches">branches</a></li>
311 </ul>
311 </ul>
312 <ul>
312 <ul>
313 <li class="active">changeset</li>
313 <li class="active">changeset</li>
314 <li><a href="/raw-rev/0cd96de13884">raw</a></li>
314 <li><a href="/raw-rev/0cd96de13884">raw</a></li>
315 <li><a href="/file/0cd96de13884">browse</a></li>
315 <li><a href="/file/0cd96de13884">browse</a></li>
316 </ul>
316 </ul>
317 <ul>
317 <ul>
318
318
319 </ul>
319 </ul>
320 <ul>
320 <ul>
321 <li><a href="/help">help</a></li>
321 <li><a href="/help">help</a></li>
322 </ul>
322 </ul>
323 </div>
323 </div>
324
324
325 <div class="main">
325 <div class="main">
326
326
327 <h2><a href="/">test</a></h2>
327 <h2><a href="/">test</a></h2>
328 <h3>changeset 0:0cd96de13884 </h3>
328 <h3>changeset 0:0cd96de13884 </h3>
329
329
330 <form class="search" action="/log">
330 <form class="search" action="/log">
331
331
332 <p><input name="rev" id="search1" type="text" size="30" /></p>
332 <p><input name="rev" id="search1" type="text" size="30" /></p>
333 <div id="hint">find changesets by author, revision,
333 <div id="hint">find changesets by author, revision,
334 files, or words in the commit message</div>
334 files, or words in the commit message</div>
335 </form>
335 </form>
336
336
337 <div class="description">a</div>
337 <div class="description">a</div>
338
338
339 <table id="changesetEntry">
339 <table id="changesetEntry">
340 <tr>
340 <tr>
341 <th class="author">author</th>
341 <th class="author">author</th>
342 <td class="author">&#116;&#101;&#115;&#116;</td>
342 <td class="author">&#116;&#101;&#115;&#116;</td>
343 </tr>
343 </tr>
344 <tr>
344 <tr>
345 <th class="date">date</th>
345 <th class="date">date</th>
346 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
346 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
347 <tr>
347 <tr>
348 <th class="author">parents</th>
348 <th class="author">parents</th>
349 <td class="author"></td>
349 <td class="author"></td>
350 </tr>
350 </tr>
351 <tr>
351 <tr>
352 <th class="author">children</th>
352 <th class="author">children</th>
353 <td class="author"> <a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td>
353 <td class="author"> <a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td>
354 </tr>
354 </tr>
355 <tr>
355 <tr>
356 <th class="files">files</th>
356 <th class="files">files</th>
357 <td class="files"><a href="/file/0cd96de13884/a">a</a> <a href="/file/0cd96de13884/b">b</a> </td>
357 <td class="files"><a href="/file/0cd96de13884/a">a</a> <a href="/file/0cd96de13884/b">b</a> </td>
358 </tr>
358 </tr>
359 <tr>
359 <tr>
360 <th class="diffstat">diffstat</th>
360 <th class="diffstat">diffstat</th>
361 <td class="diffstat">
361 <td class="diffstat">
362 2 files changed, 2 insertions(+), 0 deletions(-)
362 2 files changed, 2 insertions(+), 0 deletions(-)
363
363
364 <a id="diffstatexpand" href="javascript:showDiffstat()"/>[<tt>+</tt>]</a>
364 <a id="diffstatexpand" href="javascript:showDiffstat()"/>[<tt>+</tt>]</a>
365 <div id="diffstatdetails" style="display:none;">
365 <div id="diffstatdetails" style="display:none;">
366 <a href="javascript:hideDiffstat()"/>[<tt>-</tt>]</a>
366 <a href="javascript:hideDiffstat()"/>[<tt>-</tt>]</a>
367 <p>
367 <p>
368 <table> <tr class="parity0">
368 <table> <tr class="parity0">
369 <td class="diffstat-file"><a href="#l1.1">a</a></td>
369 <td class="diffstat-file"><a href="#l1.1">a</a></td>
370 <td class="diffstat-total" align="right">1</td>
370 <td class="diffstat-total" align="right">1</td>
371 <td class="diffstat-graph">
371 <td class="diffstat-graph">
372 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
372 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
373 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
373 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
374 </td>
374 </td>
375 </tr>
375 </tr>
376 <tr class="parity1">
376 <tr class="parity1">
377 <td class="diffstat-file"><a href="#l2.1">b</a></td>
377 <td class="diffstat-file"><a href="#l2.1">b</a></td>
378 <td class="diffstat-total" align="right">1</td>
378 <td class="diffstat-total" align="right">1</td>
379 <td class="diffstat-graph">
379 <td class="diffstat-graph">
380 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
380 <span class="diffstat-add" style="width:100.0%;">&nbsp;</span>
381 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
381 <span class="diffstat-remove" style="width:0.0%;">&nbsp;</span>
382 </td>
382 </td>
383 </tr>
383 </tr>
384 </table>
384 </table>
385 </div>
385 </div>
386 </td>
386 </td>
387 </tr>
387 </tr>
388 </table>
388 </table>
389
389
390 <div class="overflow">
390 <div class="overflow">
391 <div class="sourcefirst"> line diff</div>
391 <div class="sourcefirst"> line diff</div>
392
392
393 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> new file mode 100644
393 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> new file mode 100644
394 <a href="#l1.2" id="l1.2"> 1.2</a> <span class="minusline">--- /dev/null
394 <a href="#l1.2" id="l1.2"> 1.2</a> <span class="minusline">--- /dev/null
395 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="plusline">+++ b/a
395 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="plusline">+++ b/a
396 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="atline">@@ -0,0 +1,1 @@
396 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="atline">@@ -0,0 +1,1 @@
397 </span><a href="#l1.5" id="l1.5"> 1.5</a> <span class="plusline">+a
397 </span><a href="#l1.5" id="l1.5"> 1.5</a> <span class="plusline">+a
398 </span></pre></div><div class="source bottomline parity1"><pre><a href="#l2.1" id="l2.1"> 2.1</a> new file mode 100644
398 </span></pre></div><div class="source bottomline parity1"><pre><a href="#l2.1" id="l2.1"> 2.1</a> new file mode 100644
399 <a href="#l2.2" id="l2.2"> 2.2</a> <span class="minusline">--- /dev/null
399 <a href="#l2.2" id="l2.2"> 2.2</a> <span class="minusline">--- /dev/null
400 </span><a href="#l2.3" id="l2.3"> 2.3</a> <span class="plusline">+++ b/b
400 </span><a href="#l2.3" id="l2.3"> 2.3</a> <span class="plusline">+++ b/b
401 </span><a href="#l2.4" id="l2.4"> 2.4</a> <span class="atline">@@ -0,0 +1,1 @@
401 </span><a href="#l2.4" id="l2.4"> 2.4</a> <span class="atline">@@ -0,0 +1,1 @@
402 </span><a href="#l2.5" id="l2.5"> 2.5</a> <span class="plusline">+b
402 </span><a href="#l2.5" id="l2.5"> 2.5</a> <span class="plusline">+b
403 </span></pre></div>
403 </span></pre></div>
404 </div>
404 </div>
405
405
406 </div>
406 </div>
407 </div>
407 </div>
408 <script type="text/javascript">process_dates()</script>
408 <script type="text/javascript">process_dates()</script>
409
409
410
410
411 </body>
411 </body>
412 </html>
412 </html>
413
413
414
414
415 revision
415 revision
416
416
417 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/raw-rev/0'
417 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/raw-rev/0'
418 200 Script output follows
418 200 Script output follows
419
419
420
420
421 # HG changeset patch
421 # HG changeset patch
422 # User test
422 # User test
423 # Date 0 0
423 # Date 0 0
424 # Node ID 0cd96de13884b090099512d4794ae87ad067ea8e
424 # Node ID 0cd96de13884b090099512d4794ae87ad067ea8e
425
425
426 a
426 a
427
427
428 diff --git a/a b/a
428 diff --git a/a b/a
429 new file mode 100644
429 new file mode 100644
430 --- /dev/null
430 --- /dev/null
431 +++ b/a
431 +++ b/a
432 @@ -0,0 +1,1 @@
432 @@ -0,0 +1,1 @@
433 +a
433 +a
434 diff --git a/b b/b
434 diff --git a/b b/b
435 new file mode 100644
435 new file mode 100644
436 --- /dev/null
436 --- /dev/null
437 +++ b/b
437 +++ b/b
438 @@ -0,0 +1,1 @@
438 @@ -0,0 +1,1 @@
439 +b
439 +b
440
440
441
441
442 diff removed file
442 diff removed file
443
443
444 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/tip/a'
444 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/tip/a'
445 200 Script output follows
445 200 Script output follows
446
446
447 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
447 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
448 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
448 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
449 <head>
449 <head>
450 <link rel="icon" href="/static/hgicon.png" type="image/png" />
450 <link rel="icon" href="/static/hgicon.png" type="image/png" />
451 <meta name="robots" content="index, nofollow" />
451 <meta name="robots" content="index, nofollow" />
452 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
452 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
453 <script type="text/javascript" src="/static/mercurial.js"></script>
453 <script type="text/javascript" src="/static/mercurial.js"></script>
454
454
455 <title>test: a diff</title>
455 <title>test: a diff</title>
456 </head>
456 </head>
457 <body>
457 <body>
458
458
459 <div class="container">
459 <div class="container">
460 <div class="menu">
460 <div class="menu">
461 <div class="logo">
461 <div class="logo">
462 <a href="http://mercurial.selenic.com/">
462 <a href="http://mercurial.selenic.com/">
463 <img src="/static/hglogo.png" alt="mercurial" /></a>
463 <img src="/static/hglogo.png" alt="mercurial" /></a>
464 </div>
464 </div>
465 <ul>
465 <ul>
466 <li><a href="/shortlog/78e4ebad7cdf">log</a></li>
466 <li><a href="/shortlog/78e4ebad7cdf">log</a></li>
467 <li><a href="/graph/78e4ebad7cdf">graph</a></li>
467 <li><a href="/graph/78e4ebad7cdf">graph</a></li>
468 <li><a href="/tags">tags</a></li>
468 <li><a href="/tags">tags</a></li>
469 <li><a href="/bookmarks">bookmarks</a></li>
469 <li><a href="/bookmarks">bookmarks</a></li>
470 <li><a href="/branches">branches</a></li>
470 <li><a href="/branches">branches</a></li>
471 </ul>
471 </ul>
472 <ul>
472 <ul>
473 <li><a href="/rev/78e4ebad7cdf">changeset</a></li>
473 <li><a href="/rev/78e4ebad7cdf">changeset</a></li>
474 <li><a href="/file/78e4ebad7cdf">browse</a></li>
474 <li><a href="/file/78e4ebad7cdf">browse</a></li>
475 </ul>
475 </ul>
476 <ul>
476 <ul>
477 <li><a href="/file/78e4ebad7cdf/a">file</a></li>
477 <li><a href="/file/78e4ebad7cdf/a">file</a></li>
478 <li><a href="/file/tip/a">latest</a></li>
478 <li><a href="/file/tip/a">latest</a></li>
479 <li class="active">diff</li>
479 <li class="active">diff</li>
480 <li><a href="/annotate/78e4ebad7cdf/a">annotate</a></li>
480 <li><a href="/annotate/78e4ebad7cdf/a">annotate</a></li>
481 <li><a href="/log/78e4ebad7cdf/a">file log</a></li>
481 <li><a href="/log/78e4ebad7cdf/a">file log</a></li>
482 <li><a href="/raw-file/78e4ebad7cdf/a">raw</a></li>
482 <li><a href="/raw-file/78e4ebad7cdf/a">raw</a></li>
483 </ul>
483 </ul>
484 <ul>
484 <ul>
485 <li><a href="/help">help</a></li>
485 <li><a href="/help">help</a></li>
486 </ul>
486 </ul>
487 </div>
487 </div>
488
488
489 <div class="main">
489 <div class="main">
490 <h2><a href="/">test</a></h2>
490 <h2><a href="/">test</a></h2>
491 <h3>diff a @ 1:78e4ebad7cdf</h3>
491 <h3>diff a @ 1:78e4ebad7cdf</h3>
492
492
493 <form class="search" action="/log">
493 <form class="search" action="/log">
494 <p></p>
494 <p></p>
495 <p><input name="rev" id="search1" type="text" size="30" /></p>
495 <p><input name="rev" id="search1" type="text" size="30" /></p>
496 <div id="hint">find changesets by author, revision,
496 <div id="hint">find changesets by author, revision,
497 files, or words in the commit message</div>
497 files, or words in the commit message</div>
498 </form>
498 </form>
499
499
500 <div class="description">b</div>
500 <div class="description">b</div>
501
501
502 <table id="changesetEntry">
502 <table id="changesetEntry">
503 <tr>
503 <tr>
504 <th>author</th>
504 <th>author</th>
505 <td>&#116;&#101;&#115;&#116;</td>
505 <td>&#116;&#101;&#115;&#116;</td>
506 </tr>
506 </tr>
507 <tr>
507 <tr>
508 <th>date</th>
508 <th>date</th>
509 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
509 <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
510 </tr>
510 </tr>
511 <tr>
511 <tr>
512 <th>parents</th>
512 <th>parents</th>
513 <td></td>
513 <td></td>
514 </tr>
514 </tr>
515 <tr>
515 <tr>
516 <th>children</th>
516 <th>children</th>
517 <td></td>
517 <td></td>
518 </tr>
518 </tr>
519
519
520 </table>
520 </table>
521
521
522 <div class="overflow">
522 <div class="overflow">
523 <div class="sourcefirst"> line diff</div>
523 <div class="sourcefirst"> line diff</div>
524
524
525 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> new file mode 100755
525 <div class="source bottomline parity0"><pre><a href="#l1.1" id="l1.1"> 1.1</a> new file mode 100755
526 <a href="#l1.2" id="l1.2"> 1.2</a> <span class="minusline">--- /dev/null
526 <a href="#l1.2" id="l1.2"> 1.2</a> <span class="minusline">--- /dev/null
527 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="plusline">+++ b/a
527 </span><a href="#l1.3" id="l1.3"> 1.3</a> <span class="plusline">+++ b/a
528 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="atline">@@ -0,0 +1,1 @@
528 </span><a href="#l1.4" id="l1.4"> 1.4</a> <span class="atline">@@ -0,0 +1,1 @@
529 </span><a href="#l1.5" id="l1.5"> 1.5</a> <span class="plusline">+a
529 </span><a href="#l1.5" id="l1.5"> 1.5</a> <span class="plusline">+a
530 </span></pre></div>
530 </span></pre></div>
531 </div>
531 </div>
532 </div>
532 </div>
533 </div>
533 </div>
534
534
535 <script type="text/javascript">process_dates()</script>
535 <script type="text/javascript">process_dates()</script>
536
536
537
537
538 </body>
538 </body>
539 </html>
539 </html>
540
540
541 $ cd ..
541 $ cd ..
542
542
543 test import rev as raw-rev
543 test import rev as raw-rev
544
544
545 $ hg clone -r0 test test1
545 $ hg clone -r0 test test1
546 adding changesets
546 adding changesets
547 adding manifests
547 adding manifests
548 adding file changes
548 adding file changes
549 added 1 changesets with 2 changes to 2 files
549 added 1 changesets with 2 changes to 2 files
550 updating to branch default
550 updating to branch default
551 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
551 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
552 $ cd test1
552 $ cd test1
553 $ hg import -q --exact http://localhost:$HGPORT/rev/1
553 $ hg import -q --exact http://localhost:$HGPORT/rev/1
554
554
555 raw revision with diff block numbers
556
557 $ "$TESTDIR/killdaemons.py"
558 $ cat <<EOF > .hg/hgrc
559 > [web]
560 > templates = rawdiff
561 > EOF
562 $ mkdir rawdiff
563 $ cat <<EOF > rawdiff/map
564 > mimetype = 'text/plain; charset={encoding}'
565 > changeset = '{diff}'
566 > difflineplus = '{line}'
567 > difflineminus = '{line}'
568 > difflineat = '{line}'
569 > diffline = '{line}'
570 > filenodelink = ''
571 > filenolink = ''
572 > fileline = '{line}'
573 > diffblock = 'Block: {blockno}\n{lines}\n'
574 > EOF
575 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
576 $ cat hg.pid >> $DAEMON_PIDS
577 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/raw-rev/0'
578 200 Script output follows
579
580 Block: 1
581 diff -r 000000000000 -r 0cd96de13884 a
582 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
583 +++ b/a Thu Jan 01 00:00:00 1970 +0000
584 @@ -0,0 +1,1 @@
585 +a
586
587 Block: 2
588 diff -r 000000000000 -r 0cd96de13884 b
589 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
590 +++ b/b Thu Jan 01 00:00:00 1970 +0000
591 @@ -0,0 +1,1 @@
592 +b
593
594 $ "$TESTDIR/killdaemons.py"
595 $ rm .hg/hgrc rawdiff/map
596 $ rmdir rawdiff
597 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
598 $ cat hg.pid >> $DAEMON_PIDS
599
555 errors
600 errors
556
601
557 $ cat ../test/errors.log
602 $ cat ../test/errors.log
General Comments 0
You need to be logged in to leave comments. Login now