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