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