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