##// END OF EJS Templates
hgweb: protocol functions take repo instead of web...
Dirkjan Ochtman -
r6781:b4b72611 default
parent child Browse files
Show More
@@ -1,377 +1,377 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
9 import os, mimetypes
10 from mercurial.node import hex, nullid
10 from mercurial.node import hex, nullid
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import mdiff, ui, hg, util, patch, hook
12 from mercurial import mdiff, ui, hg, util, patch, hook
13 from mercurial import revlog, templater, templatefilters
13 from mercurial import revlog, templater, templatefilters
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol, webutil
17 import webcommands, protocol, webutil
18
18
19 perms = {
19 perms = {
20 'changegroup': 'pull',
20 'changegroup': 'pull',
21 'changegroupsubset': 'pull',
21 'changegroupsubset': 'pull',
22 'unbundle': 'push',
22 'unbundle': 'push',
23 'stream_out': 'pull',
23 'stream_out': 'pull',
24 }
24 }
25
25
26 class hgweb(object):
26 class hgweb(object):
27 def __init__(self, repo, name=None):
27 def __init__(self, repo, name=None):
28 if isinstance(repo, str):
28 if isinstance(repo, str):
29 parentui = ui.ui(report_untrusted=False, interactive=False)
29 parentui = ui.ui(report_untrusted=False, interactive=False)
30 self.repo = hg.repository(parentui, repo)
30 self.repo = hg.repository(parentui, repo)
31 else:
31 else:
32 self.repo = repo
32 self.repo = repo
33
33
34 hook.redirect(True)
34 hook.redirect(True)
35 self.mtime = -1
35 self.mtime = -1
36 self.reponame = name
36 self.reponame = name
37 self.archives = 'zip', 'gz', 'bz2'
37 self.archives = 'zip', 'gz', 'bz2'
38 self.stripecount = 1
38 self.stripecount = 1
39 # a repo owner may set web.templates in .hg/hgrc to get any file
39 # a repo owner may set web.templates in .hg/hgrc to get any file
40 # readable by the user running the CGI script
40 # readable by the user running the CGI script
41 self.templatepath = self.config("web", "templates",
41 self.templatepath = self.config("web", "templates",
42 templater.templatepath(),
42 templater.templatepath(),
43 untrusted=False)
43 untrusted=False)
44
44
45 # The CGI scripts are often run by a user different from the repo owner.
45 # The CGI scripts are often run by a user different from the repo owner.
46 # Trust the settings from the .hg/hgrc files by default.
46 # Trust the settings from the .hg/hgrc files by default.
47 def config(self, section, name, default=None, untrusted=True):
47 def config(self, section, name, default=None, untrusted=True):
48 return self.repo.ui.config(section, name, default,
48 return self.repo.ui.config(section, name, default,
49 untrusted=untrusted)
49 untrusted=untrusted)
50
50
51 def configbool(self, section, name, default=False, untrusted=True):
51 def configbool(self, section, name, default=False, untrusted=True):
52 return self.repo.ui.configbool(section, name, default,
52 return self.repo.ui.configbool(section, name, default,
53 untrusted=untrusted)
53 untrusted=untrusted)
54
54
55 def configlist(self, section, name, default=None, untrusted=True):
55 def configlist(self, section, name, default=None, untrusted=True):
56 return self.repo.ui.configlist(section, name, default,
56 return self.repo.ui.configlist(section, name, default,
57 untrusted=untrusted)
57 untrusted=untrusted)
58
58
59 def refresh(self):
59 def refresh(self):
60 mtime = get_mtime(self.repo.root)
60 mtime = get_mtime(self.repo.root)
61 if mtime != self.mtime:
61 if mtime != self.mtime:
62 self.mtime = mtime
62 self.mtime = mtime
63 self.repo = hg.repository(self.repo.ui, self.repo.root)
63 self.repo = hg.repository(self.repo.ui, self.repo.root)
64 self.maxchanges = int(self.config("web", "maxchanges", 10))
64 self.maxchanges = int(self.config("web", "maxchanges", 10))
65 self.stripecount = int(self.config("web", "stripes", 1))
65 self.stripecount = int(self.config("web", "stripes", 1))
66 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
66 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
67 self.maxfiles = int(self.config("web", "maxfiles", 10))
67 self.maxfiles = int(self.config("web", "maxfiles", 10))
68 self.allowpull = self.configbool("web", "allowpull", True)
68 self.allowpull = self.configbool("web", "allowpull", True)
69 self.encoding = self.config("web", "encoding", util._encoding)
69 self.encoding = self.config("web", "encoding", util._encoding)
70
70
71 def run(self):
71 def run(self):
72 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
72 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
73 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
73 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
74 import mercurial.hgweb.wsgicgi as wsgicgi
74 import mercurial.hgweb.wsgicgi as wsgicgi
75 wsgicgi.launch(self)
75 wsgicgi.launch(self)
76
76
77 def __call__(self, env, respond):
77 def __call__(self, env, respond):
78 req = wsgirequest(env, respond)
78 req = wsgirequest(env, respond)
79 self.run_wsgi(req)
79 self.run_wsgi(req)
80 return req
80 return req
81
81
82 def run_wsgi(self, req):
82 def run_wsgi(self, req):
83
83
84 self.refresh()
84 self.refresh()
85
85
86 # process this if it's a protocol request
86 # process this if it's a protocol request
87 # protocol bits don't need to create any URLs
87 # protocol bits don't need to create any URLs
88 # and the clients always use the old URL structure
88 # and the clients always use the old URL structure
89
89
90 cmd = req.form.get('cmd', [''])[0]
90 cmd = req.form.get('cmd', [''])[0]
91 if cmd and cmd in protocol.__all__:
91 if cmd and cmd in protocol.__all__:
92 if cmd in perms and not self.check_perm(req, perms[cmd]):
92 if cmd in perms and not self.check_perm(req, perms[cmd]):
93 return
93 return
94 method = getattr(protocol, cmd)
94 method = getattr(protocol, cmd)
95 method(self, req)
95 method(self.repo, req)
96 return
96 return
97
97
98 # work with CGI variables to create coherent structure
98 # work with CGI variables to create coherent structure
99 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
99 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
100
100
101 req.url = req.env['SCRIPT_NAME']
101 req.url = req.env['SCRIPT_NAME']
102 if not req.url.endswith('/'):
102 if not req.url.endswith('/'):
103 req.url += '/'
103 req.url += '/'
104 if 'REPO_NAME' in req.env:
104 if 'REPO_NAME' in req.env:
105 req.url += req.env['REPO_NAME'] + '/'
105 req.url += req.env['REPO_NAME'] + '/'
106
106
107 if 'PATH_INFO' in req.env:
107 if 'PATH_INFO' in req.env:
108 parts = req.env['PATH_INFO'].strip('/').split('/')
108 parts = req.env['PATH_INFO'].strip('/').split('/')
109 repo_parts = req.env.get('REPO_NAME', '').split('/')
109 repo_parts = req.env.get('REPO_NAME', '').split('/')
110 if parts[:len(repo_parts)] == repo_parts:
110 if parts[:len(repo_parts)] == repo_parts:
111 parts = parts[len(repo_parts):]
111 parts = parts[len(repo_parts):]
112 query = '/'.join(parts)
112 query = '/'.join(parts)
113 else:
113 else:
114 query = req.env['QUERY_STRING'].split('&', 1)[0]
114 query = req.env['QUERY_STRING'].split('&', 1)[0]
115 query = query.split(';', 1)[0]
115 query = query.split(';', 1)[0]
116
116
117 # translate user-visible url structure to internal structure
117 # translate user-visible url structure to internal structure
118
118
119 args = query.split('/', 2)
119 args = query.split('/', 2)
120 if 'cmd' not in req.form and args and args[0]:
120 if 'cmd' not in req.form and args and args[0]:
121
121
122 cmd = args.pop(0)
122 cmd = args.pop(0)
123 style = cmd.rfind('-')
123 style = cmd.rfind('-')
124 if style != -1:
124 if style != -1:
125 req.form['style'] = [cmd[:style]]
125 req.form['style'] = [cmd[:style]]
126 cmd = cmd[style+1:]
126 cmd = cmd[style+1:]
127
127
128 # avoid accepting e.g. style parameter as command
128 # avoid accepting e.g. style parameter as command
129 if hasattr(webcommands, cmd):
129 if hasattr(webcommands, cmd):
130 req.form['cmd'] = [cmd]
130 req.form['cmd'] = [cmd]
131 else:
131 else:
132 cmd = ''
132 cmd = ''
133
133
134 if args and args[0]:
134 if args and args[0]:
135 node = args.pop(0)
135 node = args.pop(0)
136 req.form['node'] = [node]
136 req.form['node'] = [node]
137 if args:
137 if args:
138 req.form['file'] = args
138 req.form['file'] = args
139
139
140 if cmd == 'static':
140 if cmd == 'static':
141 req.form['file'] = req.form['node']
141 req.form['file'] = req.form['node']
142 elif cmd == 'archive':
142 elif cmd == 'archive':
143 fn = req.form['node'][0]
143 fn = req.form['node'][0]
144 for type_, spec in self.archive_specs.iteritems():
144 for type_, spec in self.archive_specs.iteritems():
145 ext = spec[2]
145 ext = spec[2]
146 if fn.endswith(ext):
146 if fn.endswith(ext):
147 req.form['node'] = [fn[:-len(ext)]]
147 req.form['node'] = [fn[:-len(ext)]]
148 req.form['type'] = [type_]
148 req.form['type'] = [type_]
149
149
150 # process the web interface request
150 # process the web interface request
151
151
152 try:
152 try:
153
153
154 tmpl = self.templater(req)
154 tmpl = self.templater(req)
155 ctype = tmpl('mimetype', encoding=self.encoding)
155 ctype = tmpl('mimetype', encoding=self.encoding)
156 ctype = templater.stringify(ctype)
156 ctype = templater.stringify(ctype)
157
157
158 if cmd == '':
158 if cmd == '':
159 req.form['cmd'] = [tmpl.cache['default']]
159 req.form['cmd'] = [tmpl.cache['default']]
160 cmd = req.form['cmd'][0]
160 cmd = req.form['cmd'][0]
161
161
162 if cmd not in webcommands.__all__:
162 if cmd not in webcommands.__all__:
163 msg = 'no such method: %s' % cmd
163 msg = 'no such method: %s' % cmd
164 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
164 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
165 elif cmd == 'file' and 'raw' in req.form.get('style', []):
165 elif cmd == 'file' and 'raw' in req.form.get('style', []):
166 self.ctype = ctype
166 self.ctype = ctype
167 content = webcommands.rawfile(self, req, tmpl)
167 content = webcommands.rawfile(self, req, tmpl)
168 else:
168 else:
169 content = getattr(webcommands, cmd)(self, req, tmpl)
169 content = getattr(webcommands, cmd)(self, req, tmpl)
170 req.respond(HTTP_OK, ctype)
170 req.respond(HTTP_OK, ctype)
171
171
172 req.write(content)
172 req.write(content)
173 del tmpl
173 del tmpl
174
174
175 except revlog.LookupError, err:
175 except revlog.LookupError, err:
176 req.respond(HTTP_NOT_FOUND, ctype)
176 req.respond(HTTP_NOT_FOUND, ctype)
177 msg = str(err)
177 msg = str(err)
178 if 'manifest' not in msg:
178 if 'manifest' not in msg:
179 msg = 'revision not found: %s' % err.name
179 msg = 'revision not found: %s' % err.name
180 req.write(tmpl('error', error=msg))
180 req.write(tmpl('error', error=msg))
181 except (RepoError, revlog.RevlogError), inst:
181 except (RepoError, revlog.RevlogError), inst:
182 req.respond(HTTP_SERVER_ERROR, ctype)
182 req.respond(HTTP_SERVER_ERROR, ctype)
183 req.write(tmpl('error', error=str(inst)))
183 req.write(tmpl('error', error=str(inst)))
184 except ErrorResponse, inst:
184 except ErrorResponse, inst:
185 req.respond(inst.code, ctype)
185 req.respond(inst.code, ctype)
186 req.write(tmpl('error', error=inst.message))
186 req.write(tmpl('error', error=inst.message))
187
187
188 def templater(self, req):
188 def templater(self, req):
189
189
190 # determine scheme, port and server name
190 # determine scheme, port and server name
191 # this is needed to create absolute urls
191 # this is needed to create absolute urls
192
192
193 proto = req.env.get('wsgi.url_scheme')
193 proto = req.env.get('wsgi.url_scheme')
194 if proto == 'https':
194 if proto == 'https':
195 proto = 'https'
195 proto = 'https'
196 default_port = "443"
196 default_port = "443"
197 else:
197 else:
198 proto = 'http'
198 proto = 'http'
199 default_port = "80"
199 default_port = "80"
200
200
201 port = req.env["SERVER_PORT"]
201 port = req.env["SERVER_PORT"]
202 port = port != default_port and (":" + port) or ""
202 port = port != default_port and (":" + port) or ""
203 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
203 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
204 staticurl = self.config("web", "staticurl") or req.url + 'static/'
204 staticurl = self.config("web", "staticurl") or req.url + 'static/'
205 if not staticurl.endswith('/'):
205 if not staticurl.endswith('/'):
206 staticurl += '/'
206 staticurl += '/'
207
207
208 # some functions for the templater
208 # some functions for the templater
209
209
210 def header(**map):
210 def header(**map):
211 yield tmpl('header', encoding=self.encoding, **map)
211 yield tmpl('header', encoding=self.encoding, **map)
212
212
213 def footer(**map):
213 def footer(**map):
214 yield tmpl("footer", **map)
214 yield tmpl("footer", **map)
215
215
216 def motd(**map):
216 def motd(**map):
217 yield self.config("web", "motd", "")
217 yield self.config("web", "motd", "")
218
218
219 def sessionvars(**map):
219 def sessionvars(**map):
220 fields = []
220 fields = []
221 if 'style' in req.form:
221 if 'style' in req.form:
222 style = req.form['style'][0]
222 style = req.form['style'][0]
223 if style != self.config('web', 'style', ''):
223 if style != self.config('web', 'style', ''):
224 fields.append(('style', style))
224 fields.append(('style', style))
225
225
226 separator = req.url[-1] == '?' and ';' or '?'
226 separator = req.url[-1] == '?' and ';' or '?'
227 for name, value in fields:
227 for name, value in fields:
228 yield dict(name=name, value=value, separator=separator)
228 yield dict(name=name, value=value, separator=separator)
229 separator = ';'
229 separator = ';'
230
230
231 # figure out which style to use
231 # figure out which style to use
232
232
233 style = self.config("web", "style", "")
233 style = self.config("web", "style", "")
234 if 'style' in req.form:
234 if 'style' in req.form:
235 style = req.form['style'][0]
235 style = req.form['style'][0]
236 mapfile = style_map(self.templatepath, style)
236 mapfile = style_map(self.templatepath, style)
237
237
238 if not self.reponame:
238 if not self.reponame:
239 self.reponame = (self.config("web", "name")
239 self.reponame = (self.config("web", "name")
240 or req.env.get('REPO_NAME')
240 or req.env.get('REPO_NAME')
241 or req.url.strip('/') or self.repo.root)
241 or req.url.strip('/') or self.repo.root)
242
242
243 # create the templater
243 # create the templater
244
244
245 tmpl = templater.templater(mapfile, templatefilters.filters,
245 tmpl = templater.templater(mapfile, templatefilters.filters,
246 defaults={"url": req.url,
246 defaults={"url": req.url,
247 "staticurl": staticurl,
247 "staticurl": staticurl,
248 "urlbase": urlbase,
248 "urlbase": urlbase,
249 "repo": self.reponame,
249 "repo": self.reponame,
250 "header": header,
250 "header": header,
251 "footer": footer,
251 "footer": footer,
252 "motd": motd,
252 "motd": motd,
253 "sessionvars": sessionvars
253 "sessionvars": sessionvars
254 })
254 })
255 return tmpl
255 return tmpl
256
256
257 def archivelist(self, nodeid):
257 def archivelist(self, nodeid):
258 allowed = self.configlist("web", "allow_archive")
258 allowed = self.configlist("web", "allow_archive")
259 for i, spec in self.archive_specs.iteritems():
259 for i, spec in self.archive_specs.iteritems():
260 if i in allowed or self.configbool("web", "allow" + i):
260 if i in allowed or self.configbool("web", "allow" + i):
261 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
261 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
262
262
263 def listfilediffs(self, tmpl, files, changeset):
263 def listfilediffs(self, tmpl, files, changeset):
264 for f in files[:self.maxfiles]:
264 for f in files[:self.maxfiles]:
265 yield tmpl("filedifflink", node=hex(changeset), file=f)
265 yield tmpl("filedifflink", node=hex(changeset), file=f)
266 if len(files) > self.maxfiles:
266 if len(files) > self.maxfiles:
267 yield tmpl("fileellipses")
267 yield tmpl("fileellipses")
268
268
269 def diff(self, tmpl, node1, node2, files):
269 def diff(self, tmpl, node1, node2, files):
270 def filterfiles(filters, files):
270 def filterfiles(filters, files):
271 l = [x for x in files if x in filters]
271 l = [x for x in files if x in filters]
272
272
273 for t in filters:
273 for t in filters:
274 if t and t[-1] != os.sep:
274 if t and t[-1] != os.sep:
275 t += os.sep
275 t += os.sep
276 l += [x for x in files if x.startswith(t)]
276 l += [x for x in files if x.startswith(t)]
277 return l
277 return l
278
278
279 parity = paritygen(self.stripecount)
279 parity = paritygen(self.stripecount)
280 def diffblock(diff, f, fn):
280 def diffblock(diff, f, fn):
281 yield tmpl("diffblock",
281 yield tmpl("diffblock",
282 lines=prettyprintlines(diff),
282 lines=prettyprintlines(diff),
283 parity=parity.next(),
283 parity=parity.next(),
284 file=f,
284 file=f,
285 filenode=hex(fn or nullid))
285 filenode=hex(fn or nullid))
286
286
287 blockcount = countgen()
287 blockcount = countgen()
288 def prettyprintlines(diff):
288 def prettyprintlines(diff):
289 blockno = blockcount.next()
289 blockno = blockcount.next()
290 for lineno, l in enumerate(diff.splitlines(1)):
290 for lineno, l in enumerate(diff.splitlines(1)):
291 if blockno == 0:
291 if blockno == 0:
292 lineno = lineno + 1
292 lineno = lineno + 1
293 else:
293 else:
294 lineno = "%d.%d" % (blockno, lineno + 1)
294 lineno = "%d.%d" % (blockno, lineno + 1)
295 if l.startswith('+'):
295 if l.startswith('+'):
296 ltype = "difflineplus"
296 ltype = "difflineplus"
297 elif l.startswith('-'):
297 elif l.startswith('-'):
298 ltype = "difflineminus"
298 ltype = "difflineminus"
299 elif l.startswith('@'):
299 elif l.startswith('@'):
300 ltype = "difflineat"
300 ltype = "difflineat"
301 else:
301 else:
302 ltype = "diffline"
302 ltype = "diffline"
303 yield tmpl(ltype,
303 yield tmpl(ltype,
304 line=l,
304 line=l,
305 lineid="l%s" % lineno,
305 lineid="l%s" % lineno,
306 linenumber="% 8s" % lineno)
306 linenumber="% 8s" % lineno)
307
307
308 r = self.repo
308 r = self.repo
309 c1 = r[node1]
309 c1 = r[node1]
310 c2 = r[node2]
310 c2 = r[node2]
311 date1 = util.datestr(c1.date())
311 date1 = util.datestr(c1.date())
312 date2 = util.datestr(c2.date())
312 date2 = util.datestr(c2.date())
313
313
314 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
314 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
315 if files:
315 if files:
316 modified, added, removed = map(lambda x: filterfiles(files, x),
316 modified, added, removed = map(lambda x: filterfiles(files, x),
317 (modified, added, removed))
317 (modified, added, removed))
318
318
319 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
319 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
320 for f in modified:
320 for f in modified:
321 to = c1.filectx(f).data()
321 to = c1.filectx(f).data()
322 tn = c2.filectx(f).data()
322 tn = c2.filectx(f).data()
323 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
323 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
324 opts=diffopts), f, tn)
324 opts=diffopts), f, tn)
325 for f in added:
325 for f in added:
326 to = None
326 to = None
327 tn = c2.filectx(f).data()
327 tn = c2.filectx(f).data()
328 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
328 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
329 opts=diffopts), f, tn)
329 opts=diffopts), f, tn)
330 for f in removed:
330 for f in removed:
331 to = c1.filectx(f).data()
331 to = c1.filectx(f).data()
332 tn = None
332 tn = None
333 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
333 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
334 opts=diffopts), f, tn)
334 opts=diffopts), f, tn)
335
335
336 archive_specs = {
336 archive_specs = {
337 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
337 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
338 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
338 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
339 'zip': ('application/zip', 'zip', '.zip', None),
339 'zip': ('application/zip', 'zip', '.zip', None),
340 }
340 }
341
341
342 def check_perm(self, req, op):
342 def check_perm(self, req, op):
343 '''Check permission for operation based on request data (including
343 '''Check permission for operation based on request data (including
344 authentication info. Return true if op allowed, else false.'''
344 authentication info. Return true if op allowed, else false.'''
345
345
346 def error(status, message):
346 def error(status, message):
347 req.respond(status, protocol.HGTYPE)
347 req.respond(status, protocol.HGTYPE)
348 req.write('0\n%s\n' % message)
348 req.write('0\n%s\n' % message)
349
349
350 if op == 'pull':
350 if op == 'pull':
351 return self.allowpull
351 return self.allowpull
352
352
353 # enforce that you can only push using POST requests
353 # enforce that you can only push using POST requests
354 if req.env['REQUEST_METHOD'] != 'POST':
354 if req.env['REQUEST_METHOD'] != 'POST':
355 error('405 Method Not Allowed', 'push requires POST request')
355 error('405 Method Not Allowed', 'push requires POST request')
356 return False
356 return False
357
357
358 # require ssl by default for pushing, auth info cannot be sniffed
358 # require ssl by default for pushing, auth info cannot be sniffed
359 # and replayed
359 # and replayed
360 scheme = req.env.get('wsgi.url_scheme')
360 scheme = req.env.get('wsgi.url_scheme')
361 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
361 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
362 error(HTTP_OK, 'ssl required')
362 error(HTTP_OK, 'ssl required')
363 return False
363 return False
364
364
365 user = req.env.get('REMOTE_USER')
365 user = req.env.get('REMOTE_USER')
366
366
367 deny = self.configlist('web', 'deny_push')
367 deny = self.configlist('web', 'deny_push')
368 if deny and (not user or deny == ['*'] or user in deny):
368 if deny and (not user or deny == ['*'] or user in deny):
369 error('401 Unauthorized', 'push not authorized')
369 error('401 Unauthorized', 'push not authorized')
370 return False
370 return False
371
371
372 allow = self.configlist('web', 'allow_push')
372 allow = self.configlist('web', 'allow_push')
373 result = allow and (allow == ['*'] or user in allow)
373 result = allow and (allow == ['*'] or user in allow)
374 if not result:
374 if not result:
375 error('401 Unauthorized', 'push not authorized')
375 error('401 Unauthorized', 'push not authorized')
376
376
377 return result
377 return result
@@ -1,208 +1,208 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, tempfile, errno, os, sys
8 import cStringIO, zlib, tempfile, errno, os, sys
9 from mercurial import util, streamclone
9 from mercurial import util, streamclone
10 from mercurial.node import bin, hex
10 from mercurial.node import bin, hex
11 from mercurial import changegroup as changegroupmod
11 from mercurial import changegroup as changegroupmod
12 from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
12 from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13
13
14 # __all__ is populated with the allowed commands. Be sure to add to it if
14 # __all__ is populated with the allowed commands. Be sure to add to it if
15 # you're adding a new command, or the new command won't work.
15 # you're adding a new command, or the new command won't work.
16
16
17 __all__ = [
17 __all__ = [
18 'lookup', 'heads', 'branches', 'between', 'changegroup',
18 'lookup', 'heads', 'branches', 'between', 'changegroup',
19 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
19 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
20 ]
20 ]
21
21
22 HGTYPE = 'application/mercurial-0.1'
22 HGTYPE = 'application/mercurial-0.1'
23
23
24 def lookup(web, req):
24 def lookup(repo, req):
25 try:
25 try:
26 r = hex(web.repo.lookup(req.form['key'][0]))
26 r = hex(repo.lookup(req.form['key'][0]))
27 success = 1
27 success = 1
28 except Exception,inst:
28 except Exception,inst:
29 r = str(inst)
29 r = str(inst)
30 success = 0
30 success = 0
31 resp = "%s %s\n" % (success, r)
31 resp = "%s %s\n" % (success, r)
32 req.respond(HTTP_OK, HGTYPE, length=len(resp))
32 req.respond(HTTP_OK, HGTYPE, length=len(resp))
33 req.write(resp)
33 req.write(resp)
34
34
35 def heads(web, req):
35 def heads(repo, req):
36 resp = " ".join(map(hex, web.repo.heads())) + "\n"
36 resp = " ".join(map(hex, repo.heads())) + "\n"
37 req.respond(HTTP_OK, HGTYPE, length=len(resp))
37 req.respond(HTTP_OK, HGTYPE, length=len(resp))
38 req.write(resp)
38 req.write(resp)
39
39
40 def branches(web, req):
40 def branches(repo, req):
41 nodes = []
41 nodes = []
42 if 'nodes' in req.form:
42 if 'nodes' in req.form:
43 nodes = map(bin, req.form['nodes'][0].split(" "))
43 nodes = map(bin, req.form['nodes'][0].split(" "))
44 resp = cStringIO.StringIO()
44 resp = cStringIO.StringIO()
45 for b in web.repo.branches(nodes):
45 for b in repo.branches(nodes):
46 resp.write(" ".join(map(hex, b)) + "\n")
46 resp.write(" ".join(map(hex, b)) + "\n")
47 resp = resp.getvalue()
47 resp = resp.getvalue()
48 req.respond(HTTP_OK, HGTYPE, length=len(resp))
48 req.respond(HTTP_OK, HGTYPE, length=len(resp))
49 req.write(resp)
49 req.write(resp)
50
50
51 def between(web, req):
51 def between(repo, req):
52 if 'pairs' in req.form:
52 if 'pairs' in req.form:
53 pairs = [map(bin, p.split("-"))
53 pairs = [map(bin, p.split("-"))
54 for p in req.form['pairs'][0].split(" ")]
54 for p in req.form['pairs'][0].split(" ")]
55 resp = cStringIO.StringIO()
55 resp = cStringIO.StringIO()
56 for b in web.repo.between(pairs):
56 for b in repo.between(pairs):
57 resp.write(" ".join(map(hex, b)) + "\n")
57 resp.write(" ".join(map(hex, b)) + "\n")
58 resp = resp.getvalue()
58 resp = resp.getvalue()
59 req.respond(HTTP_OK, HGTYPE, length=len(resp))
59 req.respond(HTTP_OK, HGTYPE, length=len(resp))
60 req.write(resp)
60 req.write(resp)
61
61
62 def changegroup(web, req):
62 def changegroup(repo, req):
63 req.respond(HTTP_OK, HGTYPE)
63 req.respond(HTTP_OK, HGTYPE)
64 nodes = []
64 nodes = []
65
65
66 if 'roots' in req.form:
66 if 'roots' in req.form:
67 nodes = map(bin, req.form['roots'][0].split(" "))
67 nodes = map(bin, req.form['roots'][0].split(" "))
68
68
69 z = zlib.compressobj()
69 z = zlib.compressobj()
70 f = web.repo.changegroup(nodes, 'serve')
70 f = repo.changegroup(nodes, 'serve')
71 while 1:
71 while 1:
72 chunk = f.read(4096)
72 chunk = f.read(4096)
73 if not chunk:
73 if not chunk:
74 break
74 break
75 req.write(z.compress(chunk))
75 req.write(z.compress(chunk))
76
76
77 req.write(z.flush())
77 req.write(z.flush())
78
78
79 def changegroupsubset(web, req):
79 def changegroupsubset(repo, req):
80 req.respond(HTTP_OK, HGTYPE)
80 req.respond(HTTP_OK, HGTYPE)
81 bases = []
81 bases = []
82 heads = []
82 heads = []
83
83
84 if 'bases' in req.form:
84 if 'bases' in req.form:
85 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
85 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
86 if 'heads' in req.form:
86 if 'heads' in req.form:
87 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
87 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
88
88
89 z = zlib.compressobj()
89 z = zlib.compressobj()
90 f = web.repo.changegroupsubset(bases, heads, 'serve')
90 f = repo.changegroupsubset(bases, heads, 'serve')
91 while 1:
91 while 1:
92 chunk = f.read(4096)
92 chunk = f.read(4096)
93 if not chunk:
93 if not chunk:
94 break
94 break
95 req.write(z.compress(chunk))
95 req.write(z.compress(chunk))
96
96
97 req.write(z.flush())
97 req.write(z.flush())
98
98
99 def capabilities(web, req):
99 def capabilities(repo, req):
100 caps = ['lookup', 'changegroupsubset']
100 caps = ['lookup', 'changegroupsubset']
101 if web.repo.ui.configbool('server', 'uncompressed', untrusted=True):
101 if repo.ui.configbool('server', 'uncompressed', untrusted=True):
102 caps.append('stream=%d' % web.repo.changelog.version)
102 caps.append('stream=%d' % repo.changelog.version)
103 if changegroupmod.bundlepriority:
103 if changegroupmod.bundlepriority:
104 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
104 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
105 rsp = ' '.join(caps)
105 rsp = ' '.join(caps)
106 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
106 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
107 req.write(rsp)
107 req.write(rsp)
108
108
109 def unbundle(web, req):
109 def unbundle(repo, req):
110
110
111 def bail(response, headers={}):
111 def bail(response, headers={}):
112 length = int(req.env.get('CONTENT_LENGTH', 0))
112 length = int(req.env.get('CONTENT_LENGTH', 0))
113 for s in util.filechunkiter(req, limit=length):
113 for s in util.filechunkiter(req, limit=length):
114 # drain incoming bundle, else client will not see
114 # drain incoming bundle, else client will not see
115 # response when run outside cgi script
115 # response when run outside cgi script
116 pass
116 pass
117
117
118 status = headers.pop('status', HTTP_OK)
118 status = headers.pop('status', HTTP_OK)
119 req.header(headers.items())
119 req.header(headers.items())
120 req.respond(status, HGTYPE)
120 req.respond(status, HGTYPE)
121 req.write('0\n')
121 req.write('0\n')
122 req.write(response)
122 req.write(response)
123
123
124 proto = req.env.get('wsgi.url_scheme') or 'http'
124 proto = req.env.get('wsgi.url_scheme') or 'http'
125 their_heads = req.form['heads'][0].split(' ')
125 their_heads = req.form['heads'][0].split(' ')
126
126
127 def check_heads():
127 def check_heads():
128 heads = map(hex, web.repo.heads())
128 heads = map(hex, repo.heads())
129 return their_heads == [hex('force')] or their_heads == heads
129 return their_heads == [hex('force')] or their_heads == heads
130
130
131 # fail early if possible
131 # fail early if possible
132 if not check_heads():
132 if not check_heads():
133 bail('unsynced changes\n')
133 bail('unsynced changes\n')
134 return
134 return
135
135
136 req.respond(HTTP_OK, HGTYPE)
136 req.respond(HTTP_OK, HGTYPE)
137
137
138 # do not lock repo until all changegroup data is
138 # do not lock repo until all changegroup data is
139 # streamed. save to temporary file.
139 # streamed. save to temporary file.
140
140
141 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
141 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
142 fp = os.fdopen(fd, 'wb+')
142 fp = os.fdopen(fd, 'wb+')
143 try:
143 try:
144 length = int(req.env['CONTENT_LENGTH'])
144 length = int(req.env['CONTENT_LENGTH'])
145 for s in util.filechunkiter(req, limit=length):
145 for s in util.filechunkiter(req, limit=length):
146 fp.write(s)
146 fp.write(s)
147
147
148 try:
148 try:
149 lock = web.repo.lock()
149 lock = repo.lock()
150 try:
150 try:
151 if not check_heads():
151 if not check_heads():
152 req.write('0\n')
152 req.write('0\n')
153 req.write('unsynced changes\n')
153 req.write('unsynced changes\n')
154 return
154 return
155
155
156 fp.seek(0)
156 fp.seek(0)
157 header = fp.read(6)
157 header = fp.read(6)
158 if header.startswith('HG') and not header.startswith('HG10'):
158 if header.startswith('HG') and not header.startswith('HG10'):
159 raise ValueError('unknown bundle version')
159 raise ValueError('unknown bundle version')
160 elif header not in changegroupmod.bundletypes:
160 elif header not in changegroupmod.bundletypes:
161 raise ValueError('unknown bundle compression type')
161 raise ValueError('unknown bundle compression type')
162 gen = changegroupmod.unbundle(header, fp)
162 gen = changegroupmod.unbundle(header, fp)
163
163
164 # send addchangegroup output to client
164 # send addchangegroup output to client
165
165
166 oldio = sys.stdout, sys.stderr
166 oldio = sys.stdout, sys.stderr
167 sys.stderr = sys.stdout = cStringIO.StringIO()
167 sys.stderr = sys.stdout = cStringIO.StringIO()
168
168
169 try:
169 try:
170 url = 'remote:%s:%s' % (proto,
170 url = 'remote:%s:%s' % (proto,
171 req.env.get('REMOTE_HOST', ''))
171 req.env.get('REMOTE_HOST', ''))
172 try:
172 try:
173 ret = web.repo.addchangegroup(gen, 'serve', url)
173 ret = repo.addchangegroup(gen, 'serve', url)
174 except util.Abort, inst:
174 except util.Abort, inst:
175 sys.stdout.write("abort: %s\n" % inst)
175 sys.stdout.write("abort: %s\n" % inst)
176 ret = 0
176 ret = 0
177 finally:
177 finally:
178 val = sys.stdout.getvalue()
178 val = sys.stdout.getvalue()
179 sys.stdout, sys.stderr = oldio
179 sys.stdout, sys.stderr = oldio
180 req.write('%d\n' % ret)
180 req.write('%d\n' % ret)
181 req.write(val)
181 req.write(val)
182 finally:
182 finally:
183 del lock
183 del lock
184 except ValueError, inst:
184 except ValueError, inst:
185 req.write('0\n')
185 req.write('0\n')
186 req.write(str(inst) + '\n')
186 req.write(str(inst) + '\n')
187 except (OSError, IOError), inst:
187 except (OSError, IOError), inst:
188 req.write('0\n')
188 req.write('0\n')
189 filename = getattr(inst, 'filename', '')
189 filename = getattr(inst, 'filename', '')
190 # Don't send our filesystem layout to the client
190 # Don't send our filesystem layout to the client
191 if filename.startswith(web.repo.root):
191 if filename.startswith(repo.root):
192 filename = filename[len(web.repo.root)+1:]
192 filename = filename[len(repo.root)+1:]
193 else:
193 else:
194 filename = ''
194 filename = ''
195 error = getattr(inst, 'strerror', 'Unknown error')
195 error = getattr(inst, 'strerror', 'Unknown error')
196 if inst.errno == errno.ENOENT:
196 if inst.errno == errno.ENOENT:
197 code = HTTP_NOT_FOUND
197 code = HTTP_NOT_FOUND
198 else:
198 else:
199 code = HTTP_SERVER_ERROR
199 code = HTTP_SERVER_ERROR
200 req.respond(code)
200 req.respond(code)
201 req.write('%s: %s\n' % (error, filename))
201 req.write('%s: %s\n' % (error, filename))
202 finally:
202 finally:
203 fp.close()
203 fp.close()
204 os.unlink(tempname)
204 os.unlink(tempname)
205
205
206 def stream_out(web, req):
206 def stream_out(repo, req):
207 req.respond(HTTP_OK, HGTYPE)
207 req.respond(HTTP_OK, HGTYPE)
208 streamclone.stream_out(web.repo, req, untrusted=True)
208 streamclone.stream_out(repo, req, untrusted=True)
General Comments 0
You need to be logged in to leave comments. Login now