##// END OF EJS Templates
hgweb/annotate: handle binary files like hgweb/file
Thomas Arendsen Hein -
r6203:3a75fcc9 default
parent child Browse files
Show More
@@ -1,945 +1,951 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
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
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes, re
9 import os, mimetypes, re
10 from mercurial.node import *
10 from mercurial.node import *
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import revlog, templater, templatefilters, changegroup
12 from mercurial import revlog, templater, templatefilters, changegroup
13 from common import get_mtime, style_map, paritygen, countgen, get_contact
13 from common import get_mtime, style_map, paritygen, countgen, get_contact
14 from common import ErrorResponse
14 from common import ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol
17 import webcommands, protocol
18
18
19 shortcuts = {
19 shortcuts = {
20 'cl': [('cmd', ['changelog']), ('rev', None)],
20 'cl': [('cmd', ['changelog']), ('rev', None)],
21 'sl': [('cmd', ['shortlog']), ('rev', None)],
21 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 'cs': [('cmd', ['changeset']), ('node', None)],
22 'cs': [('cmd', ['changeset']), ('node', None)],
23 'f': [('cmd', ['file']), ('filenode', None)],
23 'f': [('cmd', ['file']), ('filenode', None)],
24 'fl': [('cmd', ['filelog']), ('filenode', None)],
24 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 'fd': [('cmd', ['filediff']), ('node', None)],
25 'fd': [('cmd', ['filediff']), ('node', None)],
26 'fa': [('cmd', ['annotate']), ('filenode', None)],
26 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 'mf': [('cmd', ['manifest']), ('manifest', None)],
27 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 'ca': [('cmd', ['archive']), ('node', None)],
28 'ca': [('cmd', ['archive']), ('node', None)],
29 'tags': [('cmd', ['tags'])],
29 'tags': [('cmd', ['tags'])],
30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 'static': [('cmd', ['static']), ('file', None)]
31 'static': [('cmd', ['static']), ('file', None)]
32 }
32 }
33
33
34 def _up(p):
34 def _up(p):
35 if p[0] != "/":
35 if p[0] != "/":
36 p = "/" + p
36 p = "/" + p
37 if p[-1] == "/":
37 if p[-1] == "/":
38 p = p[:-1]
38 p = p[:-1]
39 up = os.path.dirname(p)
39 up = os.path.dirname(p)
40 if up == "/":
40 if up == "/":
41 return "/"
41 return "/"
42 return up + "/"
42 return up + "/"
43
43
44 def revnavgen(pos, pagelen, limit, nodefunc):
44 def revnavgen(pos, pagelen, limit, nodefunc):
45 def seq(factor, limit=None):
45 def seq(factor, limit=None):
46 if limit:
46 if limit:
47 yield limit
47 yield limit
48 if limit >= 20 and limit <= 40:
48 if limit >= 20 and limit <= 40:
49 yield 50
49 yield 50
50 else:
50 else:
51 yield 1 * factor
51 yield 1 * factor
52 yield 3 * factor
52 yield 3 * factor
53 for f in seq(factor * 10):
53 for f in seq(factor * 10):
54 yield f
54 yield f
55
55
56 def nav(**map):
56 def nav(**map):
57 l = []
57 l = []
58 last = 0
58 last = 0
59 for f in seq(1, pagelen):
59 for f in seq(1, pagelen):
60 if f < pagelen or f <= last:
60 if f < pagelen or f <= last:
61 continue
61 continue
62 if f > limit:
62 if f > limit:
63 break
63 break
64 last = f
64 last = f
65 if pos + f < limit:
65 if pos + f < limit:
66 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
66 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
67 if pos - f >= 0:
67 if pos - f >= 0:
68 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
68 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
69
69
70 try:
70 try:
71 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
71 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
72
72
73 for label, node in l:
73 for label, node in l:
74 yield {"label": label, "node": node}
74 yield {"label": label, "node": node}
75
75
76 yield {"label": "tip", "node": "tip"}
76 yield {"label": "tip", "node": "tip"}
77 except hg.RepoError:
77 except hg.RepoError:
78 pass
78 pass
79
79
80 return nav
80 return nav
81
81
82 class hgweb(object):
82 class hgweb(object):
83 def __init__(self, repo, name=None):
83 def __init__(self, repo, name=None):
84 if isinstance(repo, str):
84 if isinstance(repo, str):
85 parentui = ui.ui(report_untrusted=False, interactive=False)
85 parentui = ui.ui(report_untrusted=False, interactive=False)
86 self.repo = hg.repository(parentui, repo)
86 self.repo = hg.repository(parentui, repo)
87 else:
87 else:
88 self.repo = repo
88 self.repo = repo
89
89
90 hook.redirect(True)
90 hook.redirect(True)
91 self.mtime = -1
91 self.mtime = -1
92 self.reponame = name
92 self.reponame = name
93 self.archives = 'zip', 'gz', 'bz2'
93 self.archives = 'zip', 'gz', 'bz2'
94 self.stripecount = 1
94 self.stripecount = 1
95 self._capabilities = None
95 self._capabilities = None
96 # a repo owner may set web.templates in .hg/hgrc to get any file
96 # a repo owner may set web.templates in .hg/hgrc to get any file
97 # readable by the user running the CGI script
97 # readable by the user running the CGI script
98 self.templatepath = self.config("web", "templates",
98 self.templatepath = self.config("web", "templates",
99 templater.templatepath(),
99 templater.templatepath(),
100 untrusted=False)
100 untrusted=False)
101
101
102 # The CGI scripts are often run by a user different from the repo owner.
102 # The CGI scripts are often run by a user different from the repo owner.
103 # Trust the settings from the .hg/hgrc files by default.
103 # Trust the settings from the .hg/hgrc files by default.
104 def config(self, section, name, default=None, untrusted=True):
104 def config(self, section, name, default=None, untrusted=True):
105 return self.repo.ui.config(section, name, default,
105 return self.repo.ui.config(section, name, default,
106 untrusted=untrusted)
106 untrusted=untrusted)
107
107
108 def configbool(self, section, name, default=False, untrusted=True):
108 def configbool(self, section, name, default=False, untrusted=True):
109 return self.repo.ui.configbool(section, name, default,
109 return self.repo.ui.configbool(section, name, default,
110 untrusted=untrusted)
110 untrusted=untrusted)
111
111
112 def configlist(self, section, name, default=None, untrusted=True):
112 def configlist(self, section, name, default=None, untrusted=True):
113 return self.repo.ui.configlist(section, name, default,
113 return self.repo.ui.configlist(section, name, default,
114 untrusted=untrusted)
114 untrusted=untrusted)
115
115
116 def refresh(self):
116 def refresh(self):
117 mtime = get_mtime(self.repo.root)
117 mtime = get_mtime(self.repo.root)
118 if mtime != self.mtime:
118 if mtime != self.mtime:
119 self.mtime = mtime
119 self.mtime = mtime
120 self.repo = hg.repository(self.repo.ui, self.repo.root)
120 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 self.maxchanges = int(self.config("web", "maxchanges", 10))
121 self.maxchanges = int(self.config("web", "maxchanges", 10))
122 self.stripecount = int(self.config("web", "stripes", 1))
122 self.stripecount = int(self.config("web", "stripes", 1))
123 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
123 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
124 self.maxfiles = int(self.config("web", "maxfiles", 10))
124 self.maxfiles = int(self.config("web", "maxfiles", 10))
125 self.allowpull = self.configbool("web", "allowpull", True)
125 self.allowpull = self.configbool("web", "allowpull", True)
126 self.encoding = self.config("web", "encoding", util._encoding)
126 self.encoding = self.config("web", "encoding", util._encoding)
127 self._capabilities = None
127 self._capabilities = None
128
128
129 def capabilities(self):
129 def capabilities(self):
130 if self._capabilities is not None:
130 if self._capabilities is not None:
131 return self._capabilities
131 return self._capabilities
132 caps = ['lookup', 'changegroupsubset']
132 caps = ['lookup', 'changegroupsubset']
133 if self.configbool('server', 'uncompressed'):
133 if self.configbool('server', 'uncompressed'):
134 caps.append('stream=%d' % self.repo.changelog.version)
134 caps.append('stream=%d' % self.repo.changelog.version)
135 if changegroup.bundlepriority:
135 if changegroup.bundlepriority:
136 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
136 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
137 self._capabilities = caps
137 self._capabilities = caps
138 return caps
138 return caps
139
139
140 def run(self):
140 def run(self):
141 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
141 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
142 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
142 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
143 import mercurial.hgweb.wsgicgi as wsgicgi
143 import mercurial.hgweb.wsgicgi as wsgicgi
144 wsgicgi.launch(self)
144 wsgicgi.launch(self)
145
145
146 def __call__(self, env, respond):
146 def __call__(self, env, respond):
147 req = wsgirequest(env, respond)
147 req = wsgirequest(env, respond)
148 self.run_wsgi(req)
148 self.run_wsgi(req)
149 return req
149 return req
150
150
151 def run_wsgi(self, req):
151 def run_wsgi(self, req):
152
152
153 self.refresh()
153 self.refresh()
154
154
155 # expand form shortcuts
155 # expand form shortcuts
156
156
157 for k in shortcuts.iterkeys():
157 for k in shortcuts.iterkeys():
158 if k in req.form:
158 if k in req.form:
159 for name, value in shortcuts[k]:
159 for name, value in shortcuts[k]:
160 if value is None:
160 if value is None:
161 value = req.form[k]
161 value = req.form[k]
162 req.form[name] = value
162 req.form[name] = value
163 del req.form[k]
163 del req.form[k]
164
164
165 # work with CGI variables to create coherent structure
165 # work with CGI variables to create coherent structure
166 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
166 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
167
167
168 req.url = req.env['SCRIPT_NAME']
168 req.url = req.env['SCRIPT_NAME']
169 if not req.url.endswith('/'):
169 if not req.url.endswith('/'):
170 req.url += '/'
170 req.url += '/'
171 if 'REPO_NAME' in req.env:
171 if 'REPO_NAME' in req.env:
172 req.url += req.env['REPO_NAME'] + '/'
172 req.url += req.env['REPO_NAME'] + '/'
173
173
174 if req.env.get('PATH_INFO'):
174 if req.env.get('PATH_INFO'):
175 parts = req.env.get('PATH_INFO').strip('/').split('/')
175 parts = req.env.get('PATH_INFO').strip('/').split('/')
176 repo_parts = req.env.get('REPO_NAME', '').split('/')
176 repo_parts = req.env.get('REPO_NAME', '').split('/')
177 if parts[:len(repo_parts)] == repo_parts:
177 if parts[:len(repo_parts)] == repo_parts:
178 parts = parts[len(repo_parts):]
178 parts = parts[len(repo_parts):]
179 query = '/'.join(parts)
179 query = '/'.join(parts)
180 else:
180 else:
181 query = req.env['QUERY_STRING'].split('&', 1)[0]
181 query = req.env['QUERY_STRING'].split('&', 1)[0]
182 query = query.split(';', 1)[0]
182 query = query.split(';', 1)[0]
183
183
184 # translate user-visible url structure to internal structure
184 # translate user-visible url structure to internal structure
185
185
186 args = query.split('/', 2)
186 args = query.split('/', 2)
187 if 'cmd' not in req.form and args and args[0]:
187 if 'cmd' not in req.form and args and args[0]:
188
188
189 cmd = args.pop(0)
189 cmd = args.pop(0)
190 style = cmd.rfind('-')
190 style = cmd.rfind('-')
191 if style != -1:
191 if style != -1:
192 req.form['style'] = [cmd[:style]]
192 req.form['style'] = [cmd[:style]]
193 cmd = cmd[style+1:]
193 cmd = cmd[style+1:]
194
194
195 # avoid accepting e.g. style parameter as command
195 # avoid accepting e.g. style parameter as command
196 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
196 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
197 req.form['cmd'] = [cmd]
197 req.form['cmd'] = [cmd]
198
198
199 if args and args[0]:
199 if args and args[0]:
200 node = args.pop(0)
200 node = args.pop(0)
201 req.form['node'] = [node]
201 req.form['node'] = [node]
202 if args:
202 if args:
203 req.form['file'] = args
203 req.form['file'] = args
204
204
205 if cmd == 'static':
205 if cmd == 'static':
206 req.form['file'] = req.form['node']
206 req.form['file'] = req.form['node']
207 elif cmd == 'archive':
207 elif cmd == 'archive':
208 fn = req.form['node'][0]
208 fn = req.form['node'][0]
209 for type_, spec in self.archive_specs.iteritems():
209 for type_, spec in self.archive_specs.iteritems():
210 ext = spec[2]
210 ext = spec[2]
211 if fn.endswith(ext):
211 if fn.endswith(ext):
212 req.form['node'] = [fn[:-len(ext)]]
212 req.form['node'] = [fn[:-len(ext)]]
213 req.form['type'] = [type_]
213 req.form['type'] = [type_]
214
214
215 # process this if it's a protocol request
215 # process this if it's a protocol request
216
216
217 cmd = req.form.get('cmd', [''])[0]
217 cmd = req.form.get('cmd', [''])[0]
218 if cmd in protocol.__all__:
218 if cmd in protocol.__all__:
219 method = getattr(protocol, cmd)
219 method = getattr(protocol, cmd)
220 method(self, req)
220 method(self, req)
221 return
221 return
222
222
223 # process the web interface request
223 # process the web interface request
224
224
225 try:
225 try:
226
226
227 tmpl = self.templater(req)
227 tmpl = self.templater(req)
228 ctype = tmpl('mimetype', encoding=self.encoding)
228 ctype = tmpl('mimetype', encoding=self.encoding)
229 ctype = templater.stringify(ctype)
229 ctype = templater.stringify(ctype)
230
230
231 if cmd == '':
231 if cmd == '':
232 req.form['cmd'] = [tmpl.cache['default']]
232 req.form['cmd'] = [tmpl.cache['default']]
233 cmd = req.form['cmd'][0]
233 cmd = req.form['cmd'][0]
234
234
235 if cmd not in webcommands.__all__:
235 if cmd not in webcommands.__all__:
236 msg = 'No such method: %s' % cmd
236 msg = 'No such method: %s' % cmd
237 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
237 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
238 elif cmd == 'file' and 'raw' in req.form.get('style', []):
238 elif cmd == 'file' and 'raw' in req.form.get('style', []):
239 self.ctype = ctype
239 self.ctype = ctype
240 content = webcommands.rawfile(self, req, tmpl)
240 content = webcommands.rawfile(self, req, tmpl)
241 else:
241 else:
242 content = getattr(webcommands, cmd)(self, req, tmpl)
242 content = getattr(webcommands, cmd)(self, req, tmpl)
243 req.respond(HTTP_OK, ctype)
243 req.respond(HTTP_OK, ctype)
244
244
245 req.write(content)
245 req.write(content)
246 del tmpl
246 del tmpl
247
247
248 except revlog.LookupError, err:
248 except revlog.LookupError, err:
249 req.respond(HTTP_NOT_FOUND, ctype)
249 req.respond(HTTP_NOT_FOUND, ctype)
250 req.write(tmpl('error', error='revision not found: %s' % err.name))
250 req.write(tmpl('error', error='revision not found: %s' % err.name))
251 except (hg.RepoError, revlog.RevlogError), inst:
251 except (hg.RepoError, revlog.RevlogError), inst:
252 req.respond(HTTP_SERVER_ERROR, ctype)
252 req.respond(HTTP_SERVER_ERROR, ctype)
253 req.write(tmpl('error', error=str(inst)))
253 req.write(tmpl('error', error=str(inst)))
254 except ErrorResponse, inst:
254 except ErrorResponse, inst:
255 req.respond(inst.code, ctype)
255 req.respond(inst.code, ctype)
256 req.write(tmpl('error', error=inst.message))
256 req.write(tmpl('error', error=inst.message))
257
257
258 def templater(self, req):
258 def templater(self, req):
259
259
260 # determine scheme, port and server name
260 # determine scheme, port and server name
261 # this is needed to create absolute urls
261 # this is needed to create absolute urls
262
262
263 proto = req.env.get('wsgi.url_scheme')
263 proto = req.env.get('wsgi.url_scheme')
264 if proto == 'https':
264 if proto == 'https':
265 proto = 'https'
265 proto = 'https'
266 default_port = "443"
266 default_port = "443"
267 else:
267 else:
268 proto = 'http'
268 proto = 'http'
269 default_port = "80"
269 default_port = "80"
270
270
271 port = req.env["SERVER_PORT"]
271 port = req.env["SERVER_PORT"]
272 port = port != default_port and (":" + port) or ""
272 port = port != default_port and (":" + port) or ""
273 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
273 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
274 staticurl = self.config("web", "staticurl") or req.url + 'static/'
274 staticurl = self.config("web", "staticurl") or req.url + 'static/'
275 if not staticurl.endswith('/'):
275 if not staticurl.endswith('/'):
276 staticurl += '/'
276 staticurl += '/'
277
277
278 # some functions for the templater
278 # some functions for the templater
279
279
280 def header(**map):
280 def header(**map):
281 yield tmpl('header', encoding=self.encoding, **map)
281 yield tmpl('header', encoding=self.encoding, **map)
282
282
283 def footer(**map):
283 def footer(**map):
284 yield tmpl("footer", **map)
284 yield tmpl("footer", **map)
285
285
286 def motd(**map):
286 def motd(**map):
287 yield self.config("web", "motd", "")
287 yield self.config("web", "motd", "")
288
288
289 def sessionvars(**map):
289 def sessionvars(**map):
290 fields = []
290 fields = []
291 if 'style' in req.form:
291 if 'style' in req.form:
292 style = req.form['style'][0]
292 style = req.form['style'][0]
293 if style != self.config('web', 'style', ''):
293 if style != self.config('web', 'style', ''):
294 fields.append(('style', style))
294 fields.append(('style', style))
295
295
296 separator = req.url[-1] == '?' and ';' or '?'
296 separator = req.url[-1] == '?' and ';' or '?'
297 for name, value in fields:
297 for name, value in fields:
298 yield dict(name=name, value=value, separator=separator)
298 yield dict(name=name, value=value, separator=separator)
299 separator = ';'
299 separator = ';'
300
300
301 # figure out which style to use
301 # figure out which style to use
302
302
303 style = self.config("web", "style", "")
303 style = self.config("web", "style", "")
304 if 'style' in req.form:
304 if 'style' in req.form:
305 style = req.form['style'][0]
305 style = req.form['style'][0]
306 mapfile = style_map(self.templatepath, style)
306 mapfile = style_map(self.templatepath, style)
307
307
308 if not self.reponame:
308 if not self.reponame:
309 self.reponame = (self.config("web", "name")
309 self.reponame = (self.config("web", "name")
310 or req.env.get('REPO_NAME')
310 or req.env.get('REPO_NAME')
311 or req.url.strip('/') or self.repo.root)
311 or req.url.strip('/') or self.repo.root)
312
312
313 # create the templater
313 # create the templater
314
314
315 tmpl = templater.templater(mapfile, templatefilters.filters,
315 tmpl = templater.templater(mapfile, templatefilters.filters,
316 defaults={"url": req.url,
316 defaults={"url": req.url,
317 "staticurl": staticurl,
317 "staticurl": staticurl,
318 "urlbase": urlbase,
318 "urlbase": urlbase,
319 "repo": self.reponame,
319 "repo": self.reponame,
320 "header": header,
320 "header": header,
321 "footer": footer,
321 "footer": footer,
322 "motd": motd,
322 "motd": motd,
323 "sessionvars": sessionvars
323 "sessionvars": sessionvars
324 })
324 })
325 return tmpl
325 return tmpl
326
326
327 def archivelist(self, nodeid):
327 def archivelist(self, nodeid):
328 allowed = self.configlist("web", "allow_archive")
328 allowed = self.configlist("web", "allow_archive")
329 for i, spec in self.archive_specs.iteritems():
329 for i, spec in self.archive_specs.iteritems():
330 if i in allowed or self.configbool("web", "allow" + i):
330 if i in allowed or self.configbool("web", "allow" + i):
331 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
331 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
332
332
333 def listfilediffs(self, tmpl, files, changeset):
333 def listfilediffs(self, tmpl, files, changeset):
334 for f in files[:self.maxfiles]:
334 for f in files[:self.maxfiles]:
335 yield tmpl("filedifflink", node=hex(changeset), file=f)
335 yield tmpl("filedifflink", node=hex(changeset), file=f)
336 if len(files) > self.maxfiles:
336 if len(files) > self.maxfiles:
337 yield tmpl("fileellipses")
337 yield tmpl("fileellipses")
338
338
339 def siblings(self, siblings=[], hiderev=None, **args):
339 def siblings(self, siblings=[], hiderev=None, **args):
340 siblings = [s for s in siblings if s.node() != nullid]
340 siblings = [s for s in siblings if s.node() != nullid]
341 if len(siblings) == 1 and siblings[0].rev() == hiderev:
341 if len(siblings) == 1 and siblings[0].rev() == hiderev:
342 return
342 return
343 for s in siblings:
343 for s in siblings:
344 d = {'node': hex(s.node()), 'rev': s.rev()}
344 d = {'node': hex(s.node()), 'rev': s.rev()}
345 if hasattr(s, 'path'):
345 if hasattr(s, 'path'):
346 d['file'] = s.path()
346 d['file'] = s.path()
347 d.update(args)
347 d.update(args)
348 yield d
348 yield d
349
349
350 def renamelink(self, fl, node):
350 def renamelink(self, fl, node):
351 r = fl.renamed(node)
351 r = fl.renamed(node)
352 if r:
352 if r:
353 return [dict(file=r[0], node=hex(r[1]))]
353 return [dict(file=r[0], node=hex(r[1]))]
354 return []
354 return []
355
355
356 def nodetagsdict(self, node):
356 def nodetagsdict(self, node):
357 return [{"name": i} for i in self.repo.nodetags(node)]
357 return [{"name": i} for i in self.repo.nodetags(node)]
358
358
359 def nodebranchdict(self, ctx):
359 def nodebranchdict(self, ctx):
360 branches = []
360 branches = []
361 branch = ctx.branch()
361 branch = ctx.branch()
362 # If this is an empty repo, ctx.node() == nullid,
362 # If this is an empty repo, ctx.node() == nullid,
363 # ctx.branch() == 'default', but branchtags() is
363 # ctx.branch() == 'default', but branchtags() is
364 # an empty dict. Using dict.get avoids a traceback.
364 # an empty dict. Using dict.get avoids a traceback.
365 if self.repo.branchtags().get(branch) == ctx.node():
365 if self.repo.branchtags().get(branch) == ctx.node():
366 branches.append({"name": branch})
366 branches.append({"name": branch})
367 return branches
367 return branches
368
368
369 def showtag(self, tmpl, t1, node=nullid, **args):
369 def showtag(self, tmpl, t1, node=nullid, **args):
370 for t in self.repo.nodetags(node):
370 for t in self.repo.nodetags(node):
371 yield tmpl(t1, tag=t, **args)
371 yield tmpl(t1, tag=t, **args)
372
372
373 def diff(self, tmpl, node1, node2, files):
373 def diff(self, tmpl, node1, node2, files):
374 def filterfiles(filters, files):
374 def filterfiles(filters, files):
375 l = [x for x in files if x in filters]
375 l = [x for x in files if x in filters]
376
376
377 for t in filters:
377 for t in filters:
378 if t and t[-1] != os.sep:
378 if t and t[-1] != os.sep:
379 t += os.sep
379 t += os.sep
380 l += [x for x in files if x.startswith(t)]
380 l += [x for x in files if x.startswith(t)]
381 return l
381 return l
382
382
383 parity = paritygen(self.stripecount)
383 parity = paritygen(self.stripecount)
384 def diffblock(diff, f, fn):
384 def diffblock(diff, f, fn):
385 yield tmpl("diffblock",
385 yield tmpl("diffblock",
386 lines=prettyprintlines(diff),
386 lines=prettyprintlines(diff),
387 parity=parity.next(),
387 parity=parity.next(),
388 file=f,
388 file=f,
389 filenode=hex(fn or nullid))
389 filenode=hex(fn or nullid))
390
390
391 blockcount = countgen()
391 blockcount = countgen()
392 def prettyprintlines(diff):
392 def prettyprintlines(diff):
393 blockno = blockcount.next()
393 blockno = blockcount.next()
394 for lineno, l in enumerate(diff.splitlines(1)):
394 for lineno, l in enumerate(diff.splitlines(1)):
395 if blockno == 0:
395 if blockno == 0:
396 lineno = lineno + 1
396 lineno = lineno + 1
397 else:
397 else:
398 lineno = "%d.%d" % (blockno, lineno + 1)
398 lineno = "%d.%d" % (blockno, lineno + 1)
399 if l.startswith('+'):
399 if l.startswith('+'):
400 ltype = "difflineplus"
400 ltype = "difflineplus"
401 elif l.startswith('-'):
401 elif l.startswith('-'):
402 ltype = "difflineminus"
402 ltype = "difflineminus"
403 elif l.startswith('@'):
403 elif l.startswith('@'):
404 ltype = "difflineat"
404 ltype = "difflineat"
405 else:
405 else:
406 ltype = "diffline"
406 ltype = "diffline"
407 yield tmpl(ltype,
407 yield tmpl(ltype,
408 line=l,
408 line=l,
409 lineid="l%s" % lineno,
409 lineid="l%s" % lineno,
410 linenumber="% 8s" % lineno)
410 linenumber="% 8s" % lineno)
411
411
412 r = self.repo
412 r = self.repo
413 c1 = r.changectx(node1)
413 c1 = r.changectx(node1)
414 c2 = r.changectx(node2)
414 c2 = r.changectx(node2)
415 date1 = util.datestr(c1.date())
415 date1 = util.datestr(c1.date())
416 date2 = util.datestr(c2.date())
416 date2 = util.datestr(c2.date())
417
417
418 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
418 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
419 if files:
419 if files:
420 modified, added, removed = map(lambda x: filterfiles(files, x),
420 modified, added, removed = map(lambda x: filterfiles(files, x),
421 (modified, added, removed))
421 (modified, added, removed))
422
422
423 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
423 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
424 for f in modified:
424 for f in modified:
425 to = c1.filectx(f).data()
425 to = c1.filectx(f).data()
426 tn = c2.filectx(f).data()
426 tn = c2.filectx(f).data()
427 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
427 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
428 opts=diffopts), f, tn)
428 opts=diffopts), f, tn)
429 for f in added:
429 for f in added:
430 to = None
430 to = None
431 tn = c2.filectx(f).data()
431 tn = c2.filectx(f).data()
432 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
432 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
433 opts=diffopts), f, tn)
433 opts=diffopts), f, tn)
434 for f in removed:
434 for f in removed:
435 to = c1.filectx(f).data()
435 to = c1.filectx(f).data()
436 tn = None
436 tn = None
437 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
437 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
438 opts=diffopts), f, tn)
438 opts=diffopts), f, tn)
439
439
440 def changelog(self, tmpl, ctx, shortlog=False):
440 def changelog(self, tmpl, ctx, shortlog=False):
441 def changelist(limit=0,**map):
441 def changelist(limit=0,**map):
442 cl = self.repo.changelog
442 cl = self.repo.changelog
443 l = [] # build a list in forward order for efficiency
443 l = [] # build a list in forward order for efficiency
444 for i in xrange(start, end):
444 for i in xrange(start, end):
445 ctx = self.repo.changectx(i)
445 ctx = self.repo.changectx(i)
446 n = ctx.node()
446 n = ctx.node()
447 showtags = self.showtag(tmpl, 'changelogtag', n)
447 showtags = self.showtag(tmpl, 'changelogtag', n)
448
448
449 l.insert(0, {"parity": parity.next(),
449 l.insert(0, {"parity": parity.next(),
450 "author": ctx.user(),
450 "author": ctx.user(),
451 "parent": self.siblings(ctx.parents(), i - 1),
451 "parent": self.siblings(ctx.parents(), i - 1),
452 "child": self.siblings(ctx.children(), i + 1),
452 "child": self.siblings(ctx.children(), i + 1),
453 "changelogtag": showtags,
453 "changelogtag": showtags,
454 "desc": ctx.description(),
454 "desc": ctx.description(),
455 "date": ctx.date(),
455 "date": ctx.date(),
456 "files": self.listfilediffs(tmpl, ctx.files(), n),
456 "files": self.listfilediffs(tmpl, ctx.files(), n),
457 "rev": i,
457 "rev": i,
458 "node": hex(n),
458 "node": hex(n),
459 "tags": self.nodetagsdict(n),
459 "tags": self.nodetagsdict(n),
460 "branches": self.nodebranchdict(ctx)})
460 "branches": self.nodebranchdict(ctx)})
461
461
462 if limit > 0:
462 if limit > 0:
463 l = l[:limit]
463 l = l[:limit]
464
464
465 for e in l:
465 for e in l:
466 yield e
466 yield e
467
467
468 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
468 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
469 cl = self.repo.changelog
469 cl = self.repo.changelog
470 count = cl.count()
470 count = cl.count()
471 pos = ctx.rev()
471 pos = ctx.rev()
472 start = max(0, pos - maxchanges + 1)
472 start = max(0, pos - maxchanges + 1)
473 end = min(count, start + maxchanges)
473 end = min(count, start + maxchanges)
474 pos = end - 1
474 pos = end - 1
475 parity = paritygen(self.stripecount, offset=start-end)
475 parity = paritygen(self.stripecount, offset=start-end)
476
476
477 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
477 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
478
478
479 return tmpl(shortlog and 'shortlog' or 'changelog',
479 return tmpl(shortlog and 'shortlog' or 'changelog',
480 changenav=changenav,
480 changenav=changenav,
481 node=hex(cl.tip()),
481 node=hex(cl.tip()),
482 rev=pos, changesets=count,
482 rev=pos, changesets=count,
483 entries=lambda **x: changelist(limit=0,**x),
483 entries=lambda **x: changelist(limit=0,**x),
484 latestentry=lambda **x: changelist(limit=1,**x),
484 latestentry=lambda **x: changelist(limit=1,**x),
485 archives=self.archivelist("tip"))
485 archives=self.archivelist("tip"))
486
486
487 def search(self, tmpl, query):
487 def search(self, tmpl, query):
488
488
489 def changelist(**map):
489 def changelist(**map):
490 cl = self.repo.changelog
490 cl = self.repo.changelog
491 count = 0
491 count = 0
492 qw = query.lower().split()
492 qw = query.lower().split()
493
493
494 def revgen():
494 def revgen():
495 for i in xrange(cl.count() - 1, 0, -100):
495 for i in xrange(cl.count() - 1, 0, -100):
496 l = []
496 l = []
497 for j in xrange(max(0, i - 100), i + 1):
497 for j in xrange(max(0, i - 100), i + 1):
498 ctx = self.repo.changectx(j)
498 ctx = self.repo.changectx(j)
499 l.append(ctx)
499 l.append(ctx)
500 l.reverse()
500 l.reverse()
501 for e in l:
501 for e in l:
502 yield e
502 yield e
503
503
504 for ctx in revgen():
504 for ctx in revgen():
505 miss = 0
505 miss = 0
506 for q in qw:
506 for q in qw:
507 if not (q in ctx.user().lower() or
507 if not (q in ctx.user().lower() or
508 q in ctx.description().lower() or
508 q in ctx.description().lower() or
509 q in " ".join(ctx.files()).lower()):
509 q in " ".join(ctx.files()).lower()):
510 miss = 1
510 miss = 1
511 break
511 break
512 if miss:
512 if miss:
513 continue
513 continue
514
514
515 count += 1
515 count += 1
516 n = ctx.node()
516 n = ctx.node()
517 showtags = self.showtag(tmpl, 'changelogtag', n)
517 showtags = self.showtag(tmpl, 'changelogtag', n)
518
518
519 yield tmpl('searchentry',
519 yield tmpl('searchentry',
520 parity=parity.next(),
520 parity=parity.next(),
521 author=ctx.user(),
521 author=ctx.user(),
522 parent=self.siblings(ctx.parents()),
522 parent=self.siblings(ctx.parents()),
523 child=self.siblings(ctx.children()),
523 child=self.siblings(ctx.children()),
524 changelogtag=showtags,
524 changelogtag=showtags,
525 desc=ctx.description(),
525 desc=ctx.description(),
526 date=ctx.date(),
526 date=ctx.date(),
527 files=self.listfilediffs(tmpl, ctx.files(), n),
527 files=self.listfilediffs(tmpl, ctx.files(), n),
528 rev=ctx.rev(),
528 rev=ctx.rev(),
529 node=hex(n),
529 node=hex(n),
530 tags=self.nodetagsdict(n),
530 tags=self.nodetagsdict(n),
531 branches=self.nodebranchdict(ctx))
531 branches=self.nodebranchdict(ctx))
532
532
533 if count >= self.maxchanges:
533 if count >= self.maxchanges:
534 break
534 break
535
535
536 cl = self.repo.changelog
536 cl = self.repo.changelog
537 parity = paritygen(self.stripecount)
537 parity = paritygen(self.stripecount)
538
538
539 return tmpl('search',
539 return tmpl('search',
540 query=query,
540 query=query,
541 node=hex(cl.tip()),
541 node=hex(cl.tip()),
542 entries=changelist,
542 entries=changelist,
543 archives=self.archivelist("tip"))
543 archives=self.archivelist("tip"))
544
544
545 def changeset(self, tmpl, ctx):
545 def changeset(self, tmpl, ctx):
546 n = ctx.node()
546 n = ctx.node()
547 showtags = self.showtag(tmpl, 'changesettag', n)
547 showtags = self.showtag(tmpl, 'changesettag', n)
548 parents = ctx.parents()
548 parents = ctx.parents()
549 p1 = parents[0].node()
549 p1 = parents[0].node()
550
550
551 files = []
551 files = []
552 parity = paritygen(self.stripecount)
552 parity = paritygen(self.stripecount)
553 for f in ctx.files():
553 for f in ctx.files():
554 files.append(tmpl("filenodelink",
554 files.append(tmpl("filenodelink",
555 node=hex(n), file=f,
555 node=hex(n), file=f,
556 parity=parity.next()))
556 parity=parity.next()))
557
557
558 def diff(**map):
558 def diff(**map):
559 yield self.diff(tmpl, p1, n, None)
559 yield self.diff(tmpl, p1, n, None)
560
560
561 return tmpl('changeset',
561 return tmpl('changeset',
562 diff=diff,
562 diff=diff,
563 rev=ctx.rev(),
563 rev=ctx.rev(),
564 node=hex(n),
564 node=hex(n),
565 parent=self.siblings(parents),
565 parent=self.siblings(parents),
566 child=self.siblings(ctx.children()),
566 child=self.siblings(ctx.children()),
567 changesettag=showtags,
567 changesettag=showtags,
568 author=ctx.user(),
568 author=ctx.user(),
569 desc=ctx.description(),
569 desc=ctx.description(),
570 date=ctx.date(),
570 date=ctx.date(),
571 files=files,
571 files=files,
572 archives=self.archivelist(hex(n)),
572 archives=self.archivelist(hex(n)),
573 tags=self.nodetagsdict(n),
573 tags=self.nodetagsdict(n),
574 branches=self.nodebranchdict(ctx))
574 branches=self.nodebranchdict(ctx))
575
575
576 def filelog(self, tmpl, fctx):
576 def filelog(self, tmpl, fctx):
577 f = fctx.path()
577 f = fctx.path()
578 fl = fctx.filelog()
578 fl = fctx.filelog()
579 count = fl.count()
579 count = fl.count()
580 pagelen = self.maxshortchanges
580 pagelen = self.maxshortchanges
581 pos = fctx.filerev()
581 pos = fctx.filerev()
582 start = max(0, pos - pagelen + 1)
582 start = max(0, pos - pagelen + 1)
583 end = min(count, start + pagelen)
583 end = min(count, start + pagelen)
584 pos = end - 1
584 pos = end - 1
585 parity = paritygen(self.stripecount, offset=start-end)
585 parity = paritygen(self.stripecount, offset=start-end)
586
586
587 def entries(limit=0, **map):
587 def entries(limit=0, **map):
588 l = []
588 l = []
589
589
590 for i in xrange(start, end):
590 for i in xrange(start, end):
591 ctx = fctx.filectx(i)
591 ctx = fctx.filectx(i)
592 n = fl.node(i)
592 n = fl.node(i)
593
593
594 l.insert(0, {"parity": parity.next(),
594 l.insert(0, {"parity": parity.next(),
595 "filerev": i,
595 "filerev": i,
596 "file": f,
596 "file": f,
597 "node": hex(ctx.node()),
597 "node": hex(ctx.node()),
598 "author": ctx.user(),
598 "author": ctx.user(),
599 "date": ctx.date(),
599 "date": ctx.date(),
600 "rename": self.renamelink(fl, n),
600 "rename": self.renamelink(fl, n),
601 "parent": self.siblings(fctx.parents()),
601 "parent": self.siblings(fctx.parents()),
602 "child": self.siblings(fctx.children()),
602 "child": self.siblings(fctx.children()),
603 "desc": ctx.description()})
603 "desc": ctx.description()})
604
604
605 if limit > 0:
605 if limit > 0:
606 l = l[:limit]
606 l = l[:limit]
607
607
608 for e in l:
608 for e in l:
609 yield e
609 yield e
610
610
611 nodefunc = lambda x: fctx.filectx(fileid=x)
611 nodefunc = lambda x: fctx.filectx(fileid=x)
612 nav = revnavgen(pos, pagelen, count, nodefunc)
612 nav = revnavgen(pos, pagelen, count, nodefunc)
613 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
613 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
614 entries=lambda **x: entries(limit=0, **x),
614 entries=lambda **x: entries(limit=0, **x),
615 latestentry=lambda **x: entries(limit=1, **x))
615 latestentry=lambda **x: entries(limit=1, **x))
616
616
617 def filerevision(self, tmpl, fctx):
617 def filerevision(self, tmpl, fctx):
618 f = fctx.path()
618 f = fctx.path()
619 text = fctx.data()
619 text = fctx.data()
620 fl = fctx.filelog()
620 fl = fctx.filelog()
621 n = fctx.filenode()
621 n = fctx.filenode()
622 parity = paritygen(self.stripecount)
622 parity = paritygen(self.stripecount)
623
623
624 if util.binary(text):
624 if util.binary(text):
625 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
625 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
626 text = '(binary:%s)' % mt
626 text = '(binary:%s)' % mt
627
627
628 def lines():
628 def lines():
629 for lineno, t in enumerate(text.splitlines(1)):
629 for lineno, t in enumerate(text.splitlines(1)):
630 yield {"line": t,
630 yield {"line": t,
631 "lineid": "l%d" % (lineno + 1),
631 "lineid": "l%d" % (lineno + 1),
632 "linenumber": "% 6d" % (lineno + 1),
632 "linenumber": "% 6d" % (lineno + 1),
633 "parity": parity.next()}
633 "parity": parity.next()}
634
634
635 return tmpl("filerevision",
635 return tmpl("filerevision",
636 file=f,
636 file=f,
637 path=_up(f),
637 path=_up(f),
638 text=lines(),
638 text=lines(),
639 rev=fctx.rev(),
639 rev=fctx.rev(),
640 node=hex(fctx.node()),
640 node=hex(fctx.node()),
641 author=fctx.user(),
641 author=fctx.user(),
642 date=fctx.date(),
642 date=fctx.date(),
643 desc=fctx.description(),
643 desc=fctx.description(),
644 parent=self.siblings(fctx.parents()),
644 parent=self.siblings(fctx.parents()),
645 child=self.siblings(fctx.children()),
645 child=self.siblings(fctx.children()),
646 rename=self.renamelink(fl, n),
646 rename=self.renamelink(fl, n),
647 permissions=fctx.manifest().flags(f))
647 permissions=fctx.manifest().flags(f))
648
648
649 def fileannotate(self, tmpl, fctx):
649 def fileannotate(self, tmpl, fctx):
650 f = fctx.path()
650 f = fctx.path()
651 n = fctx.filenode()
651 n = fctx.filenode()
652 fl = fctx.filelog()
652 fl = fctx.filelog()
653 parity = paritygen(self.stripecount)
653 parity = paritygen(self.stripecount)
654
654
655 def annotate(**map):
655 def annotate(**map):
656 last = None
656 last = None
657 if util.binary(fctx.data()):
658 mt = (mimetypes.guess_type(fctx.path())[0]
659 or 'application/octet-stream')
660 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
661 '(binary:%s)' % mt)])
662 else:
657 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
663 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
658 for lineno, ((f, targetline), l) in lines:
664 for lineno, ((f, targetline), l) in lines:
659 fnode = f.filenode()
665 fnode = f.filenode()
660 name = self.repo.ui.shortuser(f.user())
666 name = self.repo.ui.shortuser(f.user())
661
667
662 if last != fnode:
668 if last != fnode:
663 last = fnode
669 last = fnode
664
670
665 yield {"parity": parity.next(),
671 yield {"parity": parity.next(),
666 "node": hex(f.node()),
672 "node": hex(f.node()),
667 "rev": f.rev(),
673 "rev": f.rev(),
668 "author": name,
674 "author": name,
669 "file": f.path(),
675 "file": f.path(),
670 "targetline": targetline,
676 "targetline": targetline,
671 "line": l,
677 "line": l,
672 "lineid": "l%d" % (lineno + 1),
678 "lineid": "l%d" % (lineno + 1),
673 "linenumber": "% 6d" % (lineno + 1)}
679 "linenumber": "% 6d" % (lineno + 1)}
674
680
675 return tmpl("fileannotate",
681 return tmpl("fileannotate",
676 file=f,
682 file=f,
677 annotate=annotate,
683 annotate=annotate,
678 path=_up(f),
684 path=_up(f),
679 rev=fctx.rev(),
685 rev=fctx.rev(),
680 node=hex(fctx.node()),
686 node=hex(fctx.node()),
681 author=fctx.user(),
687 author=fctx.user(),
682 date=fctx.date(),
688 date=fctx.date(),
683 desc=fctx.description(),
689 desc=fctx.description(),
684 rename=self.renamelink(fl, n),
690 rename=self.renamelink(fl, n),
685 parent=self.siblings(fctx.parents()),
691 parent=self.siblings(fctx.parents()),
686 child=self.siblings(fctx.children()),
692 child=self.siblings(fctx.children()),
687 permissions=fctx.manifest().flags(f))
693 permissions=fctx.manifest().flags(f))
688
694
689 def manifest(self, tmpl, ctx, path):
695 def manifest(self, tmpl, ctx, path):
690 mf = ctx.manifest()
696 mf = ctx.manifest()
691 node = ctx.node()
697 node = ctx.node()
692
698
693 files = {}
699 files = {}
694 parity = paritygen(self.stripecount)
700 parity = paritygen(self.stripecount)
695
701
696 if path and path[-1] != "/":
702 if path and path[-1] != "/":
697 path += "/"
703 path += "/"
698 l = len(path)
704 l = len(path)
699 abspath = "/" + path
705 abspath = "/" + path
700
706
701 for f, n in mf.items():
707 for f, n in mf.items():
702 if f[:l] != path:
708 if f[:l] != path:
703 continue
709 continue
704 remain = f[l:]
710 remain = f[l:]
705 if "/" in remain:
711 if "/" in remain:
706 short = remain[:remain.index("/") + 1] # bleah
712 short = remain[:remain.index("/") + 1] # bleah
707 files[short] = (f, None)
713 files[short] = (f, None)
708 else:
714 else:
709 short = os.path.basename(remain)
715 short = os.path.basename(remain)
710 files[short] = (f, n)
716 files[short] = (f, n)
711
717
712 if not files:
718 if not files:
713 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
719 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
714
720
715 def filelist(**map):
721 def filelist(**map):
716 fl = files.keys()
722 fl = files.keys()
717 fl.sort()
723 fl.sort()
718 for f in fl:
724 for f in fl:
719 full, fnode = files[f]
725 full, fnode = files[f]
720 if not fnode:
726 if not fnode:
721 continue
727 continue
722
728
723 fctx = ctx.filectx(full)
729 fctx = ctx.filectx(full)
724 yield {"file": full,
730 yield {"file": full,
725 "parity": parity.next(),
731 "parity": parity.next(),
726 "basename": f,
732 "basename": f,
727 "date": fctx.changectx().date(),
733 "date": fctx.changectx().date(),
728 "size": fctx.size(),
734 "size": fctx.size(),
729 "permissions": mf.flags(full)}
735 "permissions": mf.flags(full)}
730
736
731 def dirlist(**map):
737 def dirlist(**map):
732 fl = files.keys()
738 fl = files.keys()
733 fl.sort()
739 fl.sort()
734 for f in fl:
740 for f in fl:
735 full, fnode = files[f]
741 full, fnode = files[f]
736 if fnode:
742 if fnode:
737 continue
743 continue
738
744
739 yield {"parity": parity.next(),
745 yield {"parity": parity.next(),
740 "path": "%s%s" % (abspath, f),
746 "path": "%s%s" % (abspath, f),
741 "basename": f[:-1]}
747 "basename": f[:-1]}
742
748
743 return tmpl("manifest",
749 return tmpl("manifest",
744 rev=ctx.rev(),
750 rev=ctx.rev(),
745 node=hex(node),
751 node=hex(node),
746 path=abspath,
752 path=abspath,
747 up=_up(abspath),
753 up=_up(abspath),
748 upparity=parity.next(),
754 upparity=parity.next(),
749 fentries=filelist,
755 fentries=filelist,
750 dentries=dirlist,
756 dentries=dirlist,
751 archives=self.archivelist(hex(node)),
757 archives=self.archivelist(hex(node)),
752 tags=self.nodetagsdict(node),
758 tags=self.nodetagsdict(node),
753 branches=self.nodebranchdict(ctx))
759 branches=self.nodebranchdict(ctx))
754
760
755 def tags(self, tmpl):
761 def tags(self, tmpl):
756 i = self.repo.tagslist()
762 i = self.repo.tagslist()
757 i.reverse()
763 i.reverse()
758 parity = paritygen(self.stripecount)
764 parity = paritygen(self.stripecount)
759
765
760 def entries(notip=False,limit=0, **map):
766 def entries(notip=False,limit=0, **map):
761 count = 0
767 count = 0
762 for k, n in i:
768 for k, n in i:
763 if notip and k == "tip":
769 if notip and k == "tip":
764 continue
770 continue
765 if limit > 0 and count >= limit:
771 if limit > 0 and count >= limit:
766 continue
772 continue
767 count = count + 1
773 count = count + 1
768 yield {"parity": parity.next(),
774 yield {"parity": parity.next(),
769 "tag": k,
775 "tag": k,
770 "date": self.repo.changectx(n).date(),
776 "date": self.repo.changectx(n).date(),
771 "node": hex(n)}
777 "node": hex(n)}
772
778
773 return tmpl("tags",
779 return tmpl("tags",
774 node=hex(self.repo.changelog.tip()),
780 node=hex(self.repo.changelog.tip()),
775 entries=lambda **x: entries(False,0, **x),
781 entries=lambda **x: entries(False,0, **x),
776 entriesnotip=lambda **x: entries(True,0, **x),
782 entriesnotip=lambda **x: entries(True,0, **x),
777 latestentry=lambda **x: entries(True,1, **x))
783 latestentry=lambda **x: entries(True,1, **x))
778
784
779 def summary(self, tmpl):
785 def summary(self, tmpl):
780 i = self.repo.tagslist()
786 i = self.repo.tagslist()
781 i.reverse()
787 i.reverse()
782
788
783 def tagentries(**map):
789 def tagentries(**map):
784 parity = paritygen(self.stripecount)
790 parity = paritygen(self.stripecount)
785 count = 0
791 count = 0
786 for k, n in i:
792 for k, n in i:
787 if k == "tip": # skip tip
793 if k == "tip": # skip tip
788 continue;
794 continue;
789
795
790 count += 1
796 count += 1
791 if count > 10: # limit to 10 tags
797 if count > 10: # limit to 10 tags
792 break;
798 break;
793
799
794 yield tmpl("tagentry",
800 yield tmpl("tagentry",
795 parity=parity.next(),
801 parity=parity.next(),
796 tag=k,
802 tag=k,
797 node=hex(n),
803 node=hex(n),
798 date=self.repo.changectx(n).date())
804 date=self.repo.changectx(n).date())
799
805
800
806
801 def branches(**map):
807 def branches(**map):
802 parity = paritygen(self.stripecount)
808 parity = paritygen(self.stripecount)
803
809
804 b = self.repo.branchtags()
810 b = self.repo.branchtags()
805 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
811 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
806 l.sort()
812 l.sort()
807
813
808 for r,n,t in l:
814 for r,n,t in l:
809 ctx = self.repo.changectx(n)
815 ctx = self.repo.changectx(n)
810
816
811 yield {'parity': parity.next(),
817 yield {'parity': parity.next(),
812 'branch': t,
818 'branch': t,
813 'node': hex(n),
819 'node': hex(n),
814 'date': ctx.date()}
820 'date': ctx.date()}
815
821
816 def changelist(**map):
822 def changelist(**map):
817 parity = paritygen(self.stripecount, offset=start-end)
823 parity = paritygen(self.stripecount, offset=start-end)
818 l = [] # build a list in forward order for efficiency
824 l = [] # build a list in forward order for efficiency
819 for i in xrange(start, end):
825 for i in xrange(start, end):
820 ctx = self.repo.changectx(i)
826 ctx = self.repo.changectx(i)
821 n = ctx.node()
827 n = ctx.node()
822 hn = hex(n)
828 hn = hex(n)
823
829
824 l.insert(0, tmpl(
830 l.insert(0, tmpl(
825 'shortlogentry',
831 'shortlogentry',
826 parity=parity.next(),
832 parity=parity.next(),
827 author=ctx.user(),
833 author=ctx.user(),
828 desc=ctx.description(),
834 desc=ctx.description(),
829 date=ctx.date(),
835 date=ctx.date(),
830 rev=i,
836 rev=i,
831 node=hn,
837 node=hn,
832 tags=self.nodetagsdict(n),
838 tags=self.nodetagsdict(n),
833 branches=self.nodebranchdict(ctx)))
839 branches=self.nodebranchdict(ctx)))
834
840
835 yield l
841 yield l
836
842
837 cl = self.repo.changelog
843 cl = self.repo.changelog
838 count = cl.count()
844 count = cl.count()
839 start = max(0, count - self.maxchanges)
845 start = max(0, count - self.maxchanges)
840 end = min(count, start + self.maxchanges)
846 end = min(count, start + self.maxchanges)
841
847
842 return tmpl("summary",
848 return tmpl("summary",
843 desc=self.config("web", "description", "unknown"),
849 desc=self.config("web", "description", "unknown"),
844 owner=get_contact(self.config) or "unknown",
850 owner=get_contact(self.config) or "unknown",
845 lastchange=cl.read(cl.tip())[2],
851 lastchange=cl.read(cl.tip())[2],
846 tags=tagentries,
852 tags=tagentries,
847 branches=branches,
853 branches=branches,
848 shortlog=changelist,
854 shortlog=changelist,
849 node=hex(cl.tip()),
855 node=hex(cl.tip()),
850 archives=self.archivelist("tip"))
856 archives=self.archivelist("tip"))
851
857
852 def filediff(self, tmpl, fctx):
858 def filediff(self, tmpl, fctx):
853 n = fctx.node()
859 n = fctx.node()
854 path = fctx.path()
860 path = fctx.path()
855 parents = fctx.parents()
861 parents = fctx.parents()
856 p1 = parents and parents[0].node() or nullid
862 p1 = parents and parents[0].node() or nullid
857
863
858 def diff(**map):
864 def diff(**map):
859 yield self.diff(tmpl, p1, n, [path])
865 yield self.diff(tmpl, p1, n, [path])
860
866
861 return tmpl("filediff",
867 return tmpl("filediff",
862 file=path,
868 file=path,
863 node=hex(n),
869 node=hex(n),
864 rev=fctx.rev(),
870 rev=fctx.rev(),
865 parent=self.siblings(parents),
871 parent=self.siblings(parents),
866 child=self.siblings(fctx.children()),
872 child=self.siblings(fctx.children()),
867 diff=diff)
873 diff=diff)
868
874
869 archive_specs = {
875 archive_specs = {
870 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
876 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
871 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
877 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
872 'zip': ('application/zip', 'zip', '.zip', None),
878 'zip': ('application/zip', 'zip', '.zip', None),
873 }
879 }
874
880
875 def archive(self, tmpl, req, key, type_):
881 def archive(self, tmpl, req, key, type_):
876 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
882 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
877 cnode = self.repo.lookup(key)
883 cnode = self.repo.lookup(key)
878 arch_version = key
884 arch_version = key
879 if cnode == key or key == 'tip':
885 if cnode == key or key == 'tip':
880 arch_version = short(cnode)
886 arch_version = short(cnode)
881 name = "%s-%s" % (reponame, arch_version)
887 name = "%s-%s" % (reponame, arch_version)
882 mimetype, artype, extension, encoding = self.archive_specs[type_]
888 mimetype, artype, extension, encoding = self.archive_specs[type_]
883 headers = [
889 headers = [
884 ('Content-Type', mimetype),
890 ('Content-Type', mimetype),
885 ('Content-Disposition', 'attachment; filename=%s%s' %
891 ('Content-Disposition', 'attachment; filename=%s%s' %
886 (name, extension))
892 (name, extension))
887 ]
893 ]
888 if encoding:
894 if encoding:
889 headers.append(('Content-Encoding', encoding))
895 headers.append(('Content-Encoding', encoding))
890 req.header(headers)
896 req.header(headers)
891 req.respond(HTTP_OK)
897 req.respond(HTTP_OK)
892 archival.archive(self.repo, req, cnode, artype, prefix=name)
898 archival.archive(self.repo, req, cnode, artype, prefix=name)
893
899
894 # add tags to things
900 # add tags to things
895 # tags -> list of changesets corresponding to tags
901 # tags -> list of changesets corresponding to tags
896 # find tag, changeset, file
902 # find tag, changeset, file
897
903
898 def cleanpath(self, path):
904 def cleanpath(self, path):
899 path = path.lstrip('/')
905 path = path.lstrip('/')
900 return util.canonpath(self.repo.root, '', path)
906 return util.canonpath(self.repo.root, '', path)
901
907
902 def changectx(self, req):
908 def changectx(self, req):
903 if 'node' in req.form:
909 if 'node' in req.form:
904 changeid = req.form['node'][0]
910 changeid = req.form['node'][0]
905 elif 'manifest' in req.form:
911 elif 'manifest' in req.form:
906 changeid = req.form['manifest'][0]
912 changeid = req.form['manifest'][0]
907 else:
913 else:
908 changeid = self.repo.changelog.count() - 1
914 changeid = self.repo.changelog.count() - 1
909
915
910 try:
916 try:
911 ctx = self.repo.changectx(changeid)
917 ctx = self.repo.changectx(changeid)
912 except hg.RepoError:
918 except hg.RepoError:
913 man = self.repo.manifest
919 man = self.repo.manifest
914 mn = man.lookup(changeid)
920 mn = man.lookup(changeid)
915 ctx = self.repo.changectx(man.linkrev(mn))
921 ctx = self.repo.changectx(man.linkrev(mn))
916
922
917 return ctx
923 return ctx
918
924
919 def filectx(self, req):
925 def filectx(self, req):
920 path = self.cleanpath(req.form['file'][0])
926 path = self.cleanpath(req.form['file'][0])
921 if 'node' in req.form:
927 if 'node' in req.form:
922 changeid = req.form['node'][0]
928 changeid = req.form['node'][0]
923 else:
929 else:
924 changeid = req.form['filenode'][0]
930 changeid = req.form['filenode'][0]
925 try:
931 try:
926 ctx = self.repo.changectx(changeid)
932 ctx = self.repo.changectx(changeid)
927 fctx = ctx.filectx(path)
933 fctx = ctx.filectx(path)
928 except hg.RepoError:
934 except hg.RepoError:
929 fctx = self.repo.filectx(path, fileid=changeid)
935 fctx = self.repo.filectx(path, fileid=changeid)
930
936
931 return fctx
937 return fctx
932
938
933 def check_perm(self, req, op, default):
939 def check_perm(self, req, op, default):
934 '''check permission for operation based on user auth.
940 '''check permission for operation based on user auth.
935 return true if op allowed, else false.
941 return true if op allowed, else false.
936 default is policy to use if no config given.'''
942 default is policy to use if no config given.'''
937
943
938 user = req.env.get('REMOTE_USER')
944 user = req.env.get('REMOTE_USER')
939
945
940 deny = self.configlist('web', 'deny_' + op)
946 deny = self.configlist('web', 'deny_' + op)
941 if deny and (not user or deny == ['*'] or user in deny):
947 if deny and (not user or deny == ['*'] or user in deny):
942 return False
948 return False
943
949
944 allow = self.configlist('web', 'allow_' + op)
950 allow = self.configlist('web', 'allow_' + op)
945 return (allow and (allow == ['*'] or user in allow)) or default
951 return (allow and (allow == ['*'] or user in allow)) or default
General Comments 0
You need to be logged in to leave comments. Login now