##// END OF EJS Templates
protocol: unify changegroup commands...
Matt Mackall -
r11584:1af96b09 default
parent child Browse files
Show More
@@ -1,320 +1,330
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 of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os
9 import os, zlib
10 from mercurial import ui, hg, hook, error, encoding, templater, wireproto
10 from mercurial import ui, hg, hook, error, encoding, templater, wireproto
11 from common import get_mtime, ErrorResponse, permhooks
11 from common import get_mtime, ErrorResponse, permhooks
12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 from request import wsgirequest
13 from request import wsgirequest
14 import webcommands, protocol, webutil
14 import webcommands, protocol, webutil
15
15
16 perms = {
16 perms = {
17 'changegroup': 'pull',
17 'changegroup': 'pull',
18 'changegroupsubset': 'pull',
18 'changegroupsubset': 'pull',
19 'stream_out': 'pull',
19 'stream_out': 'pull',
20 'listkeys': 'pull',
20 'listkeys': 'pull',
21 'unbundle': 'push',
21 'unbundle': 'push',
22 'pushkey': 'push',
22 'pushkey': 'push',
23 }
23 }
24
24
25 HGTYPE = 'application/mercurial-0.1'
25 class webproto(object):
26 class webproto(object):
26 def __init__(self, req):
27 def __init__(self, req):
27 self.req = req
28 self.req = req
28 self.response = ''
29 self.response = ''
29 def getargs(self, args):
30 def getargs(self, args):
30 data = {}
31 data = {}
31 keys = args.split()
32 keys = args.split()
32 for k in keys:
33 for k in keys:
33 if k == '*':
34 if k == '*':
34 star = {}
35 star = {}
35 for key in self.req.form.keys():
36 for key in self.req.form.keys():
36 if key not in keys:
37 if key not in keys:
37 star[key] = self.req.form[key][0]
38 star[key] = self.req.form[key][0]
38 data['*'] = star
39 data['*'] = star
39 else:
40 else:
40 data[k] = self.req.form[k][0]
41 data[k] = self.req.form[k][0]
41 return [data[k] for k in keys]
42 return [data[k] for k in keys]
43 def sendchangegroup(self, cg):
44 self.req.respond(HTTP_OK, HGTYPE)
45 z = zlib.compressobj()
46 while 1:
47 chunk = cg.read(4096)
48 if not chunk:
49 break
50 self.req.write(z.compress(chunk))
51 self.req.write(z.flush())
52
42 def respond(self, s):
53 def respond(self, s):
43 HGTYPE = 'application/mercurial-0.1'
44 self.req.respond(HTTP_OK, HGTYPE, length=len(s))
54 self.req.respond(HTTP_OK, HGTYPE, length=len(s))
45 self.response = s
55 self.response = s
46
56
47 def callproto(repo, req, cmd):
57 def callproto(repo, req, cmd):
48 p = webproto(req)
58 p = webproto(req)
49 r = wireproto.dispatch(repo, p, cmd)
59 r = wireproto.dispatch(repo, p, cmd)
50 yield p.response
60 yield p.response
51
61
52 class hgweb(object):
62 class hgweb(object):
53 def __init__(self, repo, name=None, baseui=None):
63 def __init__(self, repo, name=None, baseui=None):
54 if isinstance(repo, str):
64 if isinstance(repo, str):
55 if baseui:
65 if baseui:
56 u = baseui.copy()
66 u = baseui.copy()
57 else:
67 else:
58 u = ui.ui()
68 u = ui.ui()
59 self.repo = hg.repository(u, repo)
69 self.repo = hg.repository(u, repo)
60 else:
70 else:
61 self.repo = repo
71 self.repo = repo
62
72
63 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
73 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
64 self.repo.ui.setconfig('ui', 'interactive', 'off')
74 self.repo.ui.setconfig('ui', 'interactive', 'off')
65 hook.redirect(True)
75 hook.redirect(True)
66 self.mtime = -1
76 self.mtime = -1
67 self.reponame = name
77 self.reponame = name
68 self.archives = 'zip', 'gz', 'bz2'
78 self.archives = 'zip', 'gz', 'bz2'
69 self.stripecount = 1
79 self.stripecount = 1
70 # a repo owner may set web.templates in .hg/hgrc to get any file
80 # a repo owner may set web.templates in .hg/hgrc to get any file
71 # readable by the user running the CGI script
81 # readable by the user running the CGI script
72 self.templatepath = self.config('web', 'templates')
82 self.templatepath = self.config('web', 'templates')
73
83
74 # The CGI scripts are often run by a user different from the repo owner.
84 # The CGI scripts are often run by a user different from the repo owner.
75 # Trust the settings from the .hg/hgrc files by default.
85 # Trust the settings from the .hg/hgrc files by default.
76 def config(self, section, name, default=None, untrusted=True):
86 def config(self, section, name, default=None, untrusted=True):
77 return self.repo.ui.config(section, name, default,
87 return self.repo.ui.config(section, name, default,
78 untrusted=untrusted)
88 untrusted=untrusted)
79
89
80 def configbool(self, section, name, default=False, untrusted=True):
90 def configbool(self, section, name, default=False, untrusted=True):
81 return self.repo.ui.configbool(section, name, default,
91 return self.repo.ui.configbool(section, name, default,
82 untrusted=untrusted)
92 untrusted=untrusted)
83
93
84 def configlist(self, section, name, default=None, untrusted=True):
94 def configlist(self, section, name, default=None, untrusted=True):
85 return self.repo.ui.configlist(section, name, default,
95 return self.repo.ui.configlist(section, name, default,
86 untrusted=untrusted)
96 untrusted=untrusted)
87
97
88 def refresh(self, request=None):
98 def refresh(self, request=None):
89 if request:
99 if request:
90 self.repo.ui.environ = request.env
100 self.repo.ui.environ = request.env
91 mtime = get_mtime(self.repo.spath)
101 mtime = get_mtime(self.repo.spath)
92 if mtime != self.mtime:
102 if mtime != self.mtime:
93 self.mtime = mtime
103 self.mtime = mtime
94 self.repo = hg.repository(self.repo.ui, self.repo.root)
104 self.repo = hg.repository(self.repo.ui, self.repo.root)
95 self.maxchanges = int(self.config("web", "maxchanges", 10))
105 self.maxchanges = int(self.config("web", "maxchanges", 10))
96 self.stripecount = int(self.config("web", "stripes", 1))
106 self.stripecount = int(self.config("web", "stripes", 1))
97 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
107 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
98 self.maxfiles = int(self.config("web", "maxfiles", 10))
108 self.maxfiles = int(self.config("web", "maxfiles", 10))
99 self.allowpull = self.configbool("web", "allowpull", True)
109 self.allowpull = self.configbool("web", "allowpull", True)
100 encoding.encoding = self.config("web", "encoding",
110 encoding.encoding = self.config("web", "encoding",
101 encoding.encoding)
111 encoding.encoding)
102
112
103 def run(self):
113 def run(self):
104 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
114 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
105 raise RuntimeError("This function is only intended to be "
115 raise RuntimeError("This function is only intended to be "
106 "called while running as a CGI script.")
116 "called while running as a CGI script.")
107 import mercurial.hgweb.wsgicgi as wsgicgi
117 import mercurial.hgweb.wsgicgi as wsgicgi
108 wsgicgi.launch(self)
118 wsgicgi.launch(self)
109
119
110 def __call__(self, env, respond):
120 def __call__(self, env, respond):
111 req = wsgirequest(env, respond)
121 req = wsgirequest(env, respond)
112 return self.run_wsgi(req)
122 return self.run_wsgi(req)
113
123
114 def run_wsgi(self, req):
124 def run_wsgi(self, req):
115
125
116 self.refresh(req)
126 self.refresh(req)
117
127
118 # work with CGI variables to create coherent structure
128 # work with CGI variables to create coherent structure
119 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
129 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
120
130
121 req.url = req.env['SCRIPT_NAME']
131 req.url = req.env['SCRIPT_NAME']
122 if not req.url.endswith('/'):
132 if not req.url.endswith('/'):
123 req.url += '/'
133 req.url += '/'
124 if 'REPO_NAME' in req.env:
134 if 'REPO_NAME' in req.env:
125 req.url += req.env['REPO_NAME'] + '/'
135 req.url += req.env['REPO_NAME'] + '/'
126
136
127 if 'PATH_INFO' in req.env:
137 if 'PATH_INFO' in req.env:
128 parts = req.env['PATH_INFO'].strip('/').split('/')
138 parts = req.env['PATH_INFO'].strip('/').split('/')
129 repo_parts = req.env.get('REPO_NAME', '').split('/')
139 repo_parts = req.env.get('REPO_NAME', '').split('/')
130 if parts[:len(repo_parts)] == repo_parts:
140 if parts[:len(repo_parts)] == repo_parts:
131 parts = parts[len(repo_parts):]
141 parts = parts[len(repo_parts):]
132 query = '/'.join(parts)
142 query = '/'.join(parts)
133 else:
143 else:
134 query = req.env['QUERY_STRING'].split('&', 1)[0]
144 query = req.env['QUERY_STRING'].split('&', 1)[0]
135 query = query.split(';', 1)[0]
145 query = query.split(';', 1)[0]
136
146
137 # process this if it's a protocol request
147 # process this if it's a protocol request
138 # protocol bits don't need to create any URLs
148 # protocol bits don't need to create any URLs
139 # and the clients always use the old URL structure
149 # and the clients always use the old URL structure
140
150
141 cmd = req.form.get('cmd', [''])[0]
151 cmd = req.form.get('cmd', [''])[0]
142 if cmd and cmd in protocol.__all__:
152 if cmd and cmd in protocol.__all__:
143 if query:
153 if query:
144 raise ErrorResponse(HTTP_NOT_FOUND)
154 raise ErrorResponse(HTTP_NOT_FOUND)
145 try:
155 try:
146 if cmd in perms:
156 if cmd in perms:
147 try:
157 try:
148 self.check_perm(req, perms[cmd])
158 self.check_perm(req, perms[cmd])
149 except ErrorResponse, inst:
159 except ErrorResponse, inst:
150 if cmd == 'unbundle':
160 if cmd == 'unbundle':
151 req.drain()
161 req.drain()
152 raise
162 raise
153 if cmd in wireproto.commands:
163 if cmd in wireproto.commands:
154 return callproto(self.repo, req, cmd)
164 return callproto(self.repo, req, cmd)
155 method = getattr(protocol, cmd)
165 method = getattr(protocol, cmd)
156 return method(self.repo, req)
166 return method(self.repo, req)
157 except ErrorResponse, inst:
167 except ErrorResponse, inst:
158 req.respond(inst, protocol.HGTYPE)
168 req.respond(inst, protocol.HGTYPE)
159 if not inst.message:
169 if not inst.message:
160 return []
170 return []
161 return '0\n%s\n' % inst.message,
171 return '0\n%s\n' % inst.message,
162
172
163 # translate user-visible url structure to internal structure
173 # translate user-visible url structure to internal structure
164
174
165 args = query.split('/', 2)
175 args = query.split('/', 2)
166 if 'cmd' not in req.form and args and args[0]:
176 if 'cmd' not in req.form and args and args[0]:
167
177
168 cmd = args.pop(0)
178 cmd = args.pop(0)
169 style = cmd.rfind('-')
179 style = cmd.rfind('-')
170 if style != -1:
180 if style != -1:
171 req.form['style'] = [cmd[:style]]
181 req.form['style'] = [cmd[:style]]
172 cmd = cmd[style + 1:]
182 cmd = cmd[style + 1:]
173
183
174 # avoid accepting e.g. style parameter as command
184 # avoid accepting e.g. style parameter as command
175 if hasattr(webcommands, cmd):
185 if hasattr(webcommands, cmd):
176 req.form['cmd'] = [cmd]
186 req.form['cmd'] = [cmd]
177 else:
187 else:
178 cmd = ''
188 cmd = ''
179
189
180 if cmd == 'static':
190 if cmd == 'static':
181 req.form['file'] = ['/'.join(args)]
191 req.form['file'] = ['/'.join(args)]
182 else:
192 else:
183 if args and args[0]:
193 if args and args[0]:
184 node = args.pop(0)
194 node = args.pop(0)
185 req.form['node'] = [node]
195 req.form['node'] = [node]
186 if args:
196 if args:
187 req.form['file'] = args
197 req.form['file'] = args
188
198
189 ua = req.env.get('HTTP_USER_AGENT', '')
199 ua = req.env.get('HTTP_USER_AGENT', '')
190 if cmd == 'rev' and 'mercurial' in ua:
200 if cmd == 'rev' and 'mercurial' in ua:
191 req.form['style'] = ['raw']
201 req.form['style'] = ['raw']
192
202
193 if cmd == 'archive':
203 if cmd == 'archive':
194 fn = req.form['node'][0]
204 fn = req.form['node'][0]
195 for type_, spec in self.archive_specs.iteritems():
205 for type_, spec in self.archive_specs.iteritems():
196 ext = spec[2]
206 ext = spec[2]
197 if fn.endswith(ext):
207 if fn.endswith(ext):
198 req.form['node'] = [fn[:-len(ext)]]
208 req.form['node'] = [fn[:-len(ext)]]
199 req.form['type'] = [type_]
209 req.form['type'] = [type_]
200
210
201 # process the web interface request
211 # process the web interface request
202
212
203 try:
213 try:
204 tmpl = self.templater(req)
214 tmpl = self.templater(req)
205 ctype = tmpl('mimetype', encoding=encoding.encoding)
215 ctype = tmpl('mimetype', encoding=encoding.encoding)
206 ctype = templater.stringify(ctype)
216 ctype = templater.stringify(ctype)
207
217
208 # check read permissions non-static content
218 # check read permissions non-static content
209 if cmd != 'static':
219 if cmd != 'static':
210 self.check_perm(req, None)
220 self.check_perm(req, None)
211
221
212 if cmd == '':
222 if cmd == '':
213 req.form['cmd'] = [tmpl.cache['default']]
223 req.form['cmd'] = [tmpl.cache['default']]
214 cmd = req.form['cmd'][0]
224 cmd = req.form['cmd'][0]
215
225
216 if cmd not in webcommands.__all__:
226 if cmd not in webcommands.__all__:
217 msg = 'no such method: %s' % cmd
227 msg = 'no such method: %s' % cmd
218 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
228 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
219 elif cmd == 'file' and 'raw' in req.form.get('style', []):
229 elif cmd == 'file' and 'raw' in req.form.get('style', []):
220 self.ctype = ctype
230 self.ctype = ctype
221 content = webcommands.rawfile(self, req, tmpl)
231 content = webcommands.rawfile(self, req, tmpl)
222 else:
232 else:
223 content = getattr(webcommands, cmd)(self, req, tmpl)
233 content = getattr(webcommands, cmd)(self, req, tmpl)
224 req.respond(HTTP_OK, ctype)
234 req.respond(HTTP_OK, ctype)
225
235
226 return content
236 return content
227
237
228 except error.LookupError, err:
238 except error.LookupError, err:
229 req.respond(HTTP_NOT_FOUND, ctype)
239 req.respond(HTTP_NOT_FOUND, ctype)
230 msg = str(err)
240 msg = str(err)
231 if 'manifest' not in msg:
241 if 'manifest' not in msg:
232 msg = 'revision not found: %s' % err.name
242 msg = 'revision not found: %s' % err.name
233 return tmpl('error', error=msg)
243 return tmpl('error', error=msg)
234 except (error.RepoError, error.RevlogError), inst:
244 except (error.RepoError, error.RevlogError), inst:
235 req.respond(HTTP_SERVER_ERROR, ctype)
245 req.respond(HTTP_SERVER_ERROR, ctype)
236 return tmpl('error', error=str(inst))
246 return tmpl('error', error=str(inst))
237 except ErrorResponse, inst:
247 except ErrorResponse, inst:
238 req.respond(inst, ctype)
248 req.respond(inst, ctype)
239 return tmpl('error', error=inst.message)
249 return tmpl('error', error=inst.message)
240
250
241 def templater(self, req):
251 def templater(self, req):
242
252
243 # determine scheme, port and server name
253 # determine scheme, port and server name
244 # this is needed to create absolute urls
254 # this is needed to create absolute urls
245
255
246 proto = req.env.get('wsgi.url_scheme')
256 proto = req.env.get('wsgi.url_scheme')
247 if proto == 'https':
257 if proto == 'https':
248 proto = 'https'
258 proto = 'https'
249 default_port = "443"
259 default_port = "443"
250 else:
260 else:
251 proto = 'http'
261 proto = 'http'
252 default_port = "80"
262 default_port = "80"
253
263
254 port = req.env["SERVER_PORT"]
264 port = req.env["SERVER_PORT"]
255 port = port != default_port and (":" + port) or ""
265 port = port != default_port and (":" + port) or ""
256 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
266 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
257 staticurl = self.config("web", "staticurl") or req.url + 'static/'
267 staticurl = self.config("web", "staticurl") or req.url + 'static/'
258 if not staticurl.endswith('/'):
268 if not staticurl.endswith('/'):
259 staticurl += '/'
269 staticurl += '/'
260
270
261 # some functions for the templater
271 # some functions for the templater
262
272
263 def header(**map):
273 def header(**map):
264 yield tmpl('header', encoding=encoding.encoding, **map)
274 yield tmpl('header', encoding=encoding.encoding, **map)
265
275
266 def footer(**map):
276 def footer(**map):
267 yield tmpl("footer", **map)
277 yield tmpl("footer", **map)
268
278
269 def motd(**map):
279 def motd(**map):
270 yield self.config("web", "motd", "")
280 yield self.config("web", "motd", "")
271
281
272 # figure out which style to use
282 # figure out which style to use
273
283
274 vars = {}
284 vars = {}
275 styles = (
285 styles = (
276 req.form.get('style', [None])[0],
286 req.form.get('style', [None])[0],
277 self.config('web', 'style'),
287 self.config('web', 'style'),
278 'paper',
288 'paper',
279 )
289 )
280 style, mapfile = templater.stylemap(styles, self.templatepath)
290 style, mapfile = templater.stylemap(styles, self.templatepath)
281 if style == styles[0]:
291 if style == styles[0]:
282 vars['style'] = style
292 vars['style'] = style
283
293
284 start = req.url[-1] == '?' and '&' or '?'
294 start = req.url[-1] == '?' and '&' or '?'
285 sessionvars = webutil.sessionvars(vars, start)
295 sessionvars = webutil.sessionvars(vars, start)
286
296
287 if not self.reponame:
297 if not self.reponame:
288 self.reponame = (self.config("web", "name")
298 self.reponame = (self.config("web", "name")
289 or req.env.get('REPO_NAME')
299 or req.env.get('REPO_NAME')
290 or req.url.strip('/') or self.repo.root)
300 or req.url.strip('/') or self.repo.root)
291
301
292 # create the templater
302 # create the templater
293
303
294 tmpl = templater.templater(mapfile,
304 tmpl = templater.templater(mapfile,
295 defaults={"url": req.url,
305 defaults={"url": req.url,
296 "staticurl": staticurl,
306 "staticurl": staticurl,
297 "urlbase": urlbase,
307 "urlbase": urlbase,
298 "repo": self.reponame,
308 "repo": self.reponame,
299 "header": header,
309 "header": header,
300 "footer": footer,
310 "footer": footer,
301 "motd": motd,
311 "motd": motd,
302 "sessionvars": sessionvars
312 "sessionvars": sessionvars
303 })
313 })
304 return tmpl
314 return tmpl
305
315
306 def archivelist(self, nodeid):
316 def archivelist(self, nodeid):
307 allowed = self.configlist("web", "allow_archive")
317 allowed = self.configlist("web", "allow_archive")
308 for i, spec in self.archive_specs.iteritems():
318 for i, spec in self.archive_specs.iteritems():
309 if i in allowed or self.configbool("web", "allow" + i):
319 if i in allowed or self.configbool("web", "allow" + i):
310 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
320 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
311
321
312 archive_specs = {
322 archive_specs = {
313 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
323 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
314 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
324 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
315 'zip': ('application/zip', 'zip', '.zip', None),
325 'zip': ('application/zip', 'zip', '.zip', None),
316 }
326 }
317
327
318 def check_perm(self, req, op):
328 def check_perm(self, req, op):
319 for hook in permhooks:
329 for hook in permhooks:
320 hook(self, req, op)
330 hook(self, req, op)
@@ -1,161 +1,124
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import cStringIO, zlib, tempfile, errno, os, sys, urllib, copy
8 import cStringIO, zlib, tempfile, errno, os, sys, urllib, copy
9 from mercurial import util, streamclone, pushkey
9 from mercurial import util, streamclone, pushkey
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 ErrorResponse, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
12 from common import ErrorResponse, 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 'branchmap', 'pushkey', 'listkeys'
20 'branchmap', 'pushkey', 'listkeys'
21 ]
21 ]
22
22
23 HGTYPE = 'application/mercurial-0.1'
23 HGTYPE = 'application/mercurial-0.1'
24 basecaps = 'lookup changegroupsubset branchmap pushkey'.split()
24 basecaps = 'lookup changegroupsubset branchmap pushkey'.split()
25
25
26 def changegroup(repo, req):
27 req.respond(HTTP_OK, HGTYPE)
28 nodes = []
29
30 if 'roots' in req.form:
31 nodes = map(bin, req.form['roots'][0].split(" "))
32
33 z = zlib.compressobj()
34 f = repo.changegroup(nodes, 'serve')
35 while 1:
36 chunk = f.read(4096)
37 if not chunk:
38 break
39 yield z.compress(chunk)
40
41 yield z.flush()
42
43 def changegroupsubset(repo, req):
44 req.respond(HTTP_OK, HGTYPE)
45 bases = []
46 heads = []
47
48 if 'bases' in req.form:
49 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
50 if 'heads' in req.form:
51 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
52
53 z = zlib.compressobj()
54 f = repo.changegroupsubset(bases, heads, 'serve')
55 while 1:
56 chunk = f.read(4096)
57 if not chunk:
58 break
59 yield z.compress(chunk)
60
61 yield z.flush()
62
63 def capabilities(repo, req):
26 def capabilities(repo, req):
64 caps = copy.copy(basecaps)
27 caps = copy.copy(basecaps)
65 if streamclone.allowed(repo.ui):
28 if streamclone.allowed(repo.ui):
66 caps.append('stream=%d' % repo.changelog.version)
29 caps.append('stream=%d' % repo.changelog.version)
67 if changegroupmod.bundlepriority:
30 if changegroupmod.bundlepriority:
68 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
31 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
69 rsp = ' '.join(caps)
32 rsp = ' '.join(caps)
70 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
33 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
71 yield rsp
34 yield rsp
72
35
73 def unbundle(repo, req):
36 def unbundle(repo, req):
74
37
75 proto = req.env.get('wsgi.url_scheme') or 'http'
38 proto = req.env.get('wsgi.url_scheme') or 'http'
76 their_heads = req.form['heads'][0].split(' ')
39 their_heads = req.form['heads'][0].split(' ')
77
40
78 def check_heads():
41 def check_heads():
79 heads = map(hex, repo.heads())
42 heads = map(hex, repo.heads())
80 return their_heads == [hex('force')] or their_heads == heads
43 return their_heads == [hex('force')] or their_heads == heads
81
44
82 # fail early if possible
45 # fail early if possible
83 if not check_heads():
46 if not check_heads():
84 req.drain()
47 req.drain()
85 raise ErrorResponse(HTTP_OK, 'unsynced changes')
48 raise ErrorResponse(HTTP_OK, 'unsynced changes')
86
49
87 # do not lock repo until all changegroup data is
50 # do not lock repo until all changegroup data is
88 # streamed. save to temporary file.
51 # streamed. save to temporary file.
89
52
90 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
53 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
91 fp = os.fdopen(fd, 'wb+')
54 fp = os.fdopen(fd, 'wb+')
92 try:
55 try:
93 length = int(req.env['CONTENT_LENGTH'])
56 length = int(req.env['CONTENT_LENGTH'])
94 for s in util.filechunkiter(req, limit=length):
57 for s in util.filechunkiter(req, limit=length):
95 fp.write(s)
58 fp.write(s)
96
59
97 try:
60 try:
98 lock = repo.lock()
61 lock = repo.lock()
99 try:
62 try:
100 if not check_heads():
63 if not check_heads():
101 raise ErrorResponse(HTTP_OK, 'unsynced changes')
64 raise ErrorResponse(HTTP_OK, 'unsynced changes')
102
65
103 fp.seek(0)
66 fp.seek(0)
104 header = fp.read(6)
67 header = fp.read(6)
105 if header.startswith('HG') and not header.startswith('HG10'):
68 if header.startswith('HG') and not header.startswith('HG10'):
106 raise ValueError('unknown bundle version')
69 raise ValueError('unknown bundle version')
107 elif header not in changegroupmod.bundletypes:
70 elif header not in changegroupmod.bundletypes:
108 raise ValueError('unknown bundle compression type')
71 raise ValueError('unknown bundle compression type')
109 gen = changegroupmod.unbundle(header, fp)
72 gen = changegroupmod.unbundle(header, fp)
110
73
111 # send addchangegroup output to client
74 # send addchangegroup output to client
112
75
113 oldio = sys.stdout, sys.stderr
76 oldio = sys.stdout, sys.stderr
114 sys.stderr = sys.stdout = cStringIO.StringIO()
77 sys.stderr = sys.stdout = cStringIO.StringIO()
115
78
116 try:
79 try:
117 url = 'remote:%s:%s:%s' % (
80 url = 'remote:%s:%s:%s' % (
118 proto,
81 proto,
119 urllib.quote(req.env.get('REMOTE_HOST', '')),
82 urllib.quote(req.env.get('REMOTE_HOST', '')),
120 urllib.quote(req.env.get('REMOTE_USER', '')))
83 urllib.quote(req.env.get('REMOTE_USER', '')))
121 try:
84 try:
122 ret = repo.addchangegroup(gen, 'serve', url, lock=lock)
85 ret = repo.addchangegroup(gen, 'serve', url, lock=lock)
123 except util.Abort, inst:
86 except util.Abort, inst:
124 sys.stdout.write("abort: %s\n" % inst)
87 sys.stdout.write("abort: %s\n" % inst)
125 ret = 0
88 ret = 0
126 finally:
89 finally:
127 val = sys.stdout.getvalue()
90 val = sys.stdout.getvalue()
128 sys.stdout, sys.stderr = oldio
91 sys.stdout, sys.stderr = oldio
129 req.respond(HTTP_OK, HGTYPE)
92 req.respond(HTTP_OK, HGTYPE)
130 return '%d\n%s' % (ret, val),
93 return '%d\n%s' % (ret, val),
131 finally:
94 finally:
132 lock.release()
95 lock.release()
133 except ValueError, inst:
96 except ValueError, inst:
134 raise ErrorResponse(HTTP_OK, inst)
97 raise ErrorResponse(HTTP_OK, inst)
135 except (OSError, IOError), inst:
98 except (OSError, IOError), inst:
136 error = getattr(inst, 'strerror', 'Unknown error')
99 error = getattr(inst, 'strerror', 'Unknown error')
137 if not isinstance(error, str):
100 if not isinstance(error, str):
138 error = 'Error: %s' % str(error)
101 error = 'Error: %s' % str(error)
139 if inst.errno == errno.ENOENT:
102 if inst.errno == errno.ENOENT:
140 code = HTTP_NOT_FOUND
103 code = HTTP_NOT_FOUND
141 else:
104 else:
142 code = HTTP_SERVER_ERROR
105 code = HTTP_SERVER_ERROR
143 filename = getattr(inst, 'filename', '')
106 filename = getattr(inst, 'filename', '')
144 # Don't send our filesystem layout to the client
107 # Don't send our filesystem layout to the client
145 if filename and filename.startswith(repo.root):
108 if filename and filename.startswith(repo.root):
146 filename = filename[len(repo.root)+1:]
109 filename = filename[len(repo.root)+1:]
147 text = '%s: %s' % (error, filename)
110 text = '%s: %s' % (error, filename)
148 else:
111 else:
149 text = error.replace(repo.root + os.path.sep, '')
112 text = error.replace(repo.root + os.path.sep, '')
150 raise ErrorResponse(code, text)
113 raise ErrorResponse(code, text)
151 finally:
114 finally:
152 fp.close()
115 fp.close()
153 os.unlink(tempname)
116 os.unlink(tempname)
154
117
155 def stream_out(repo, req):
118 def stream_out(repo, req):
156 req.respond(HTTP_OK, HGTYPE)
119 req.respond(HTTP_OK, HGTYPE)
157 try:
120 try:
158 for chunk in streamclone.stream_out(repo):
121 for chunk in streamclone.stream_out(repo):
159 yield chunk
122 yield chunk
160 except streamclone.StreamException, inst:
123 except streamclone.StreamException, inst:
161 yield str(inst)
124 yield str(inst)
@@ -1,207 +1,188
1 # sshserver.py - ssh protocol server support for mercurial
1 # sshserver.py - ssh protocol server support for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from i18n import _
9 from i18n import _
10 from node import bin, hex
10 from node import bin, hex
11 import streamclone, util, hook, pushkey, wireproto
11 import streamclone, util, hook, pushkey, wireproto
12 import os, sys, tempfile, urllib, copy
12 import os, sys, tempfile, urllib, copy
13
13
14 class sshserver(object):
14 class sshserver(object):
15
15
16 caps = 'unbundle lookup changegroupsubset branchmap pushkey'.split()
16 caps = 'unbundle lookup changegroupsubset branchmap pushkey'.split()
17
17
18 def __init__(self, ui, repo):
18 def __init__(self, ui, repo):
19 self.ui = ui
19 self.ui = ui
20 self.repo = repo
20 self.repo = repo
21 self.lock = None
21 self.lock = None
22 self.fin = sys.stdin
22 self.fin = sys.stdin
23 self.fout = sys.stdout
23 self.fout = sys.stdout
24
24
25 hook.redirect(True)
25 hook.redirect(True)
26 sys.stdout = sys.stderr
26 sys.stdout = sys.stderr
27
27
28 # Prevent insertion/deletion of CRs
28 # Prevent insertion/deletion of CRs
29 util.set_binary(self.fin)
29 util.set_binary(self.fin)
30 util.set_binary(self.fout)
30 util.set_binary(self.fout)
31
31
32 def getargs(self, args):
32 def getargs(self, args):
33 data = {}
33 data = {}
34 keys = args.split()
34 keys = args.split()
35 count = len(keys)
35 count = len(keys)
36 for n in xrange(len(keys)):
36 for n in xrange(len(keys)):
37 argline = self.fin.readline()[:-1]
37 argline = self.fin.readline()[:-1]
38 arg, l = argline.split()
38 arg, l = argline.split()
39 val = self.fin.read(int(l))
39 val = self.fin.read(int(l))
40 if arg not in keys:
40 if arg not in keys:
41 raise util.Abort("unexpected parameter %r" % arg)
41 raise util.Abort("unexpected parameter %r" % arg)
42 if arg == '*':
42 if arg == '*':
43 star = {}
43 star = {}
44 for n in xrange(int(l)):
44 for n in xrange(int(l)):
45 arg, l = argline.split()
45 arg, l = argline.split()
46 val = self.fin.read(int(l))
46 val = self.fin.read(int(l))
47 star[arg] = val
47 star[arg] = val
48 data['*'] = star
48 data['*'] = star
49 else:
49 else:
50 data[arg] = val
50 data[arg] = val
51 return [data[k] for k in keys]
51 return [data[k] for k in keys]
52
52
53 def getarg(self, name):
53 def getarg(self, name):
54 return self.getargs(name)[0]
54 return self.getargs(name)[0]
55
55
56 def respond(self, v):
56 def respond(self, v):
57 self.fout.write("%d\n" % len(v))
57 self.fout.write("%d\n" % len(v))
58 self.fout.write(v)
58 self.fout.write(v)
59 self.fout.flush()
59 self.fout.flush()
60
60
61 def sendchangegroup(self, changegroup):
62 while True:
63 d = changegroup.read(4096)
64 if not d:
65 break
66 self.fout.write(d)
67
68 self.fout.flush()
69
61 def serve_forever(self):
70 def serve_forever(self):
62 try:
71 try:
63 while self.serve_one():
72 while self.serve_one():
64 pass
73 pass
65 finally:
74 finally:
66 if self.lock is not None:
75 if self.lock is not None:
67 self.lock.release()
76 self.lock.release()
68 sys.exit(0)
77 sys.exit(0)
69
78
70 def serve_one(self):
79 def serve_one(self):
71 cmd = self.fin.readline()[:-1]
80 cmd = self.fin.readline()[:-1]
72 if cmd and not wireproto.dispatch(self.repo, self, cmd):
81 if cmd and not wireproto.dispatch(self.repo, self, cmd):
73 impl = getattr(self, 'do_' + cmd, None)
82 impl = getattr(self, 'do_' + cmd, None)
74 if impl:
83 if impl:
75 r = impl()
84 r = impl()
76 if r is not None:
85 if r is not None:
77 self.respond(r)
86 self.respond(r)
78 else: self.respond("")
87 else: self.respond("")
79 return cmd != ''
88 return cmd != ''
80
89
81 def do_hello(self):
90 def do_hello(self):
82 '''the hello command returns a set of lines describing various
91 '''the hello command returns a set of lines describing various
83 interesting things about the server, in an RFC822-like format.
92 interesting things about the server, in an RFC822-like format.
84 Currently the only one defined is "capabilities", which
93 Currently the only one defined is "capabilities", which
85 consists of a line in the form:
94 consists of a line in the form:
86
95
87 capabilities: space separated list of tokens
96 capabilities: space separated list of tokens
88 '''
97 '''
89 caps = copy.copy(self.caps)
98 caps = copy.copy(self.caps)
90 if streamclone.allowed(self.repo.ui):
99 if streamclone.allowed(self.repo.ui):
91 caps.append('stream=%d' % self.repo.changelog.version)
100 caps.append('stream=%d' % self.repo.changelog.version)
92 return "capabilities: %s\n" % (' '.join(caps),)
101 return "capabilities: %s\n" % (' '.join(caps),)
93
102
94 def do_lock(self):
103 def do_lock(self):
95 '''DEPRECATED - allowing remote client to lock repo is not safe'''
104 '''DEPRECATED - allowing remote client to lock repo is not safe'''
96
105
97 self.lock = self.repo.lock()
106 self.lock = self.repo.lock()
98 return ""
107 return ""
99
108
100 def do_unlock(self):
109 def do_unlock(self):
101 '''DEPRECATED'''
110 '''DEPRECATED'''
102
111
103 if self.lock:
112 if self.lock:
104 self.lock.release()
113 self.lock.release()
105 self.lock = None
114 self.lock = None
106 return ""
115 return ""
107
116
108 def do_changegroup(self):
109 nodes = []
110 roots = self.getarg('roots')
111 nodes = map(bin, roots.split(" "))
112
113 cg = self.repo.changegroup(nodes, 'serve')
114 while True:
115 d = cg.read(4096)
116 if not d:
117 break
118 self.fout.write(d)
119
120 self.fout.flush()
121
122 def do_changegroupsubset(self):
123 bases, heads = self.getargs('bases heads')
124 bases = [bin(n) for n in bases.split(' ')]
125 heads = [bin(n) for n in heads.split(' ')]
126
127 cg = self.repo.changegroupsubset(bases, heads, 'serve')
128 while True:
129 d = cg.read(4096)
130 if not d:
131 break
132 self.fout.write(d)
133
134 self.fout.flush()
135
136 def do_addchangegroup(self):
117 def do_addchangegroup(self):
137 '''DEPRECATED'''
118 '''DEPRECATED'''
138
119
139 if not self.lock:
120 if not self.lock:
140 self.respond("not locked")
121 self.respond("not locked")
141 return
122 return
142
123
143 self.respond("")
124 self.respond("")
144 r = self.repo.addchangegroup(self.fin, 'serve', self.client_url(),
125 r = self.repo.addchangegroup(self.fin, 'serve', self.client_url(),
145 lock=self.lock)
126 lock=self.lock)
146 return str(r)
127 return str(r)
147
128
148 def client_url(self):
129 def client_url(self):
149 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
130 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
150 return 'remote:ssh:' + client
131 return 'remote:ssh:' + client
151
132
152 def do_unbundle(self):
133 def do_unbundle(self):
153 their_heads = self.getarg('heads').split()
134 their_heads = self.getarg('heads').split()
154
135
155 def check_heads():
136 def check_heads():
156 heads = map(hex, self.repo.heads())
137 heads = map(hex, self.repo.heads())
157 return their_heads == [hex('force')] or their_heads == heads
138 return their_heads == [hex('force')] or their_heads == heads
158
139
159 # fail early if possible
140 # fail early if possible
160 if not check_heads():
141 if not check_heads():
161 self.respond(_('unsynced changes'))
142 self.respond(_('unsynced changes'))
162 return
143 return
163
144
164 self.respond('')
145 self.respond('')
165
146
166 # write bundle data to temporary file because it can be big
147 # write bundle data to temporary file because it can be big
167 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
148 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
168 fp = os.fdopen(fd, 'wb+')
149 fp = os.fdopen(fd, 'wb+')
169 try:
150 try:
170 count = int(self.fin.readline())
151 count = int(self.fin.readline())
171 while count:
152 while count:
172 fp.write(self.fin.read(count))
153 fp.write(self.fin.read(count))
173 count = int(self.fin.readline())
154 count = int(self.fin.readline())
174
155
175 was_locked = self.lock is not None
156 was_locked = self.lock is not None
176 if not was_locked:
157 if not was_locked:
177 self.lock = self.repo.lock()
158 self.lock = self.repo.lock()
178 try:
159 try:
179 if not check_heads():
160 if not check_heads():
180 # someone else committed/pushed/unbundled while we
161 # someone else committed/pushed/unbundled while we
181 # were transferring data
162 # were transferring data
182 self.respond(_('unsynced changes'))
163 self.respond(_('unsynced changes'))
183 return
164 return
184 self.respond('')
165 self.respond('')
185
166
186 # push can proceed
167 # push can proceed
187
168
188 fp.seek(0)
169 fp.seek(0)
189 r = self.repo.addchangegroup(fp, 'serve', self.client_url(),
170 r = self.repo.addchangegroup(fp, 'serve', self.client_url(),
190 lock=self.lock)
171 lock=self.lock)
191 self.respond(str(r))
172 self.respond(str(r))
192 finally:
173 finally:
193 if not was_locked:
174 if not was_locked:
194 self.lock.release()
175 self.lock.release()
195 self.lock = None
176 self.lock = None
196 finally:
177 finally:
197 fp.close()
178 fp.close()
198 os.unlink(tempname)
179 os.unlink(tempname)
199
180
200 def do_stream_out(self):
181 def do_stream_out(self):
201 try:
182 try:
202 for chunk in streamclone.stream_out(self.repo):
183 for chunk in streamclone.stream_out(self.repo):
203 self.fout.write(chunk)
184 self.fout.write(chunk)
204 self.fout.flush()
185 self.fout.flush()
205 except streamclone.StreamException, inst:
186 except streamclone.StreamException, inst:
206 self.fout.write(str(inst))
187 self.fout.write(str(inst))
207 self.fout.flush()
188 self.fout.flush()
@@ -1,75 +1,90
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 from node import bin, hex
9 from node import bin, hex
10 import urllib
10 import urllib
11 import pushkey as pushkey_
11 import pushkey as pushkey_
12
12
13 def dispatch(repo, proto, command):
13 def dispatch(repo, proto, command):
14 if command not in commands:
14 if command not in commands:
15 return False
15 return False
16 func, spec = commands[command]
16 func, spec = commands[command]
17 args = proto.getargs(spec)
17 args = proto.getargs(spec)
18 proto.respond(func(repo, proto, *args))
18 r = func(repo, proto, *args)
19 if r != None:
20 proto.respond(r)
19 return True
21 return True
20
22
21 def between(repo, proto, pairs):
23 def between(repo, proto, pairs):
22 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
24 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
23 r = []
25 r = []
24 for b in repo.between(pairs):
26 for b in repo.between(pairs):
25 r.append(" ".join(map(hex, b)) + "\n")
27 r.append(" ".join(map(hex, b)) + "\n")
26 return "".join(r)
28 return "".join(r)
27
29
28 def branchmap(repo, proto):
30 def branchmap(repo, proto):
29 branchmap = repo.branchmap()
31 branchmap = repo.branchmap()
30 heads = []
32 heads = []
31 for branch, nodes in branchmap.iteritems():
33 for branch, nodes in branchmap.iteritems():
32 branchname = urllib.quote(branch)
34 branchname = urllib.quote(branch)
33 branchnodes = [hex(node) for node in nodes]
35 branchnodes = [hex(node) for node in nodes]
34 heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
36 heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
35 return '\n'.join(heads)
37 return '\n'.join(heads)
36
38
37 def branches(repo, proto, nodes):
39 def branches(repo, proto, nodes):
38 nodes = map(bin, nodes.split(" "))
40 nodes = map(bin, nodes.split(" "))
39 r = []
41 r = []
40 for b in repo.branches(nodes):
42 for b in repo.branches(nodes):
41 r.append(" ".join(map(hex, b)) + "\n")
43 r.append(" ".join(map(hex, b)) + "\n")
42 return "".join(r)
44 return "".join(r)
43
45
46 def changegroup(repo, proto, roots):
47 nodes = map(bin, roots.split(" "))
48 cg = repo.changegroup(nodes, 'serve')
49 proto.sendchangegroup(cg)
50
51 def changegroupsubset(repo, proto, bases, heads):
52 bases = [bin(n) for n in bases.split(' ')]
53 heads = [bin(n) for n in heads.split(' ')]
54 cg = repo.changegroupsubset(bases, heads, 'serve')
55 proto.sendchangegroup(cg)
56
44 def heads(repo, proto):
57 def heads(repo, proto):
45 h = repo.heads()
58 h = repo.heads()
46 return " ".join(map(hex, h)) + "\n"
59 return " ".join(map(hex, h)) + "\n"
47
60
48 def listkeys(repo, proto, namespace):
61 def listkeys(repo, proto, namespace):
49 d = pushkey_.list(repo, namespace).items()
62 d = pushkey_.list(repo, namespace).items()
50 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
63 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
51 v.encode('string-escape')) for k, v in d])
64 v.encode('string-escape')) for k, v in d])
52 return t
65 return t
53
66
54 def lookup(repo, proto, key):
67 def lookup(repo, proto, key):
55 try:
68 try:
56 r = hex(repo.lookup(key))
69 r = hex(repo.lookup(key))
57 success = 1
70 success = 1
58 except Exception, inst:
71 except Exception, inst:
59 r = str(inst)
72 r = str(inst)
60 success = 0
73 success = 0
61 return "%s %s\n" % (success, r)
74 return "%s %s\n" % (success, r)
62
75
63 def pushkey(repo, proto, namespace, key, old, new):
76 def pushkey(repo, proto, namespace, key, old, new):
64 r = pushkey_.push(repo, namespace, key, old, new)
77 r = pushkey_.push(repo, namespace, key, old, new)
65 return '%s\n' % int(r)
78 return '%s\n' % int(r)
66
79
67 commands = {
80 commands = {
68 'between': (between, 'pairs'),
81 'between': (between, 'pairs'),
69 'branchmap': (branchmap, ''),
82 'branchmap': (branchmap, ''),
70 'branches': (branches, 'nodes'),
83 'branches': (branches, 'nodes'),
84 'changegroup': (changegroup, 'roots'),
85 'changegroupsubset': (changegroupsubset, 'bases heads'),
71 'heads': (heads, ''),
86 'heads': (heads, ''),
72 'listkeys': (listkeys, 'namespace'),
87 'listkeys': (listkeys, 'namespace'),
73 'lookup': (lookup, 'key'),
88 'lookup': (lookup, 'key'),
74 'pushkey': (pushkey, 'namespace key old new'),
89 'pushkey': (pushkey, 'namespace key old new'),
75 }
90 }
@@ -1,62 +1,62
1 #!/bin/sh
1 #!/bin/sh
2 # This is a test of the wire protocol over CGI-based hgweb.
2 # This is a test of the wire protocol over CGI-based hgweb.
3
3
4 echo % initialize repository
4 echo % initialize repository
5 hg init test
5 hg init test
6 cd test
6 cd test
7 echo a > a
7 echo a > a
8 hg ci -Ama
8 hg ci -Ama
9 cd ..
9 cd ..
10
10
11 cat >hgweb.cgi <<HGWEB
11 cat >hgweb.cgi <<HGWEB
12 #!/usr/bin/env python
12 #!/usr/bin/env python
13 #
13 #
14 # An example CGI script to use hgweb, edit as necessary
14 # An example CGI script to use hgweb, edit as necessary
15
15
16 import cgitb
16 import cgitb
17 cgitb.enable()
17 cgitb.enable()
18
18
19 from mercurial import demandimport; demandimport.enable()
19 from mercurial import demandimport; demandimport.enable()
20 from mercurial.hgweb import hgweb
20 from mercurial.hgweb import hgweb
21 from mercurial.hgweb import wsgicgi
21 from mercurial.hgweb import wsgicgi
22
22
23 application = hgweb("test", "Empty test repository")
23 application = hgweb("test", "Empty test repository")
24 wsgicgi.launch(application)
24 wsgicgi.launch(application)
25 HGWEB
25 HGWEB
26 chmod 755 hgweb.cgi
26 chmod 755 hgweb.cgi
27
27
28 DOCUMENT_ROOT="/var/www/hg"; export DOCUMENT_ROOT
28 DOCUMENT_ROOT="/var/www/hg"; export DOCUMENT_ROOT
29 GATEWAY_INTERFACE="CGI/1.1"; export GATEWAY_INTERFACE
29 GATEWAY_INTERFACE="CGI/1.1"; export GATEWAY_INTERFACE
30 HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; export HTTP_ACCEPT
30 HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; export HTTP_ACCEPT
31 HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"; export HTTP_ACCEPT_CHARSET
31 HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"; export HTTP_ACCEPT_CHARSET
32 HTTP_ACCEPT_ENCODING="gzip,deflate"; export HTTP_ACCEPT_ENCODING
32 HTTP_ACCEPT_ENCODING="gzip,deflate"; export HTTP_ACCEPT_ENCODING
33 HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"; export HTTP_ACCEPT_LANGUAGE
33 HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"; export HTTP_ACCEPT_LANGUAGE
34 HTTP_CACHE_CONTROL="max-age=0"; export HTTP_CACHE_CONTROL
34 HTTP_CACHE_CONTROL="max-age=0"; export HTTP_CACHE_CONTROL
35 HTTP_CONNECTION="keep-alive"; export HTTP_CONNECTION
35 HTTP_CONNECTION="keep-alive"; export HTTP_CONNECTION
36 HTTP_HOST="hg.omnifarious.org"; export HTTP_HOST
36 HTTP_HOST="hg.omnifarious.org"; export HTTP_HOST
37 HTTP_KEEP_ALIVE="300"; export HTTP_KEEP_ALIVE
37 HTTP_KEEP_ALIVE="300"; export HTTP_KEEP_ALIVE
38 HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"; export HTTP_USER_AGENT
38 HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"; export HTTP_USER_AGENT
39 PATH_INFO="/"; export PATH_INFO
39 PATH_INFO="/"; export PATH_INFO
40 PATH_TRANSLATED="/var/www/hg/index.html"; export PATH_TRANSLATED
40 PATH_TRANSLATED="/var/www/hg/index.html"; export PATH_TRANSLATED
41 REMOTE_ADDR="127.0.0.2"; export REMOTE_ADDR
41 REMOTE_ADDR="127.0.0.2"; export REMOTE_ADDR
42 REMOTE_PORT="44703"; export REMOTE_PORT
42 REMOTE_PORT="44703"; export REMOTE_PORT
43 REQUEST_METHOD="GET"; export REQUEST_METHOD
43 REQUEST_METHOD="GET"; export REQUEST_METHOD
44 REQUEST_URI="/test/"; export REQUEST_URI
44 REQUEST_URI="/test/"; export REQUEST_URI
45 SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"; export SCRIPT_FILENAME
45 SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"; export SCRIPT_FILENAME
46 SCRIPT_NAME="/test"; export SCRIPT_NAME
46 SCRIPT_NAME="/test"; export SCRIPT_NAME
47 SCRIPT_URI="http://hg.omnifarious.org/test/"; export SCRIPT_URI
47 SCRIPT_URI="http://hg.omnifarious.org/test/"; export SCRIPT_URI
48 SCRIPT_URL="/test/"; export SCRIPT_URL
48 SCRIPT_URL="/test/"; export SCRIPT_URL
49 SERVER_ADDR="127.0.0.1"; export SERVER_ADDR
49 SERVER_ADDR="127.0.0.1"; export SERVER_ADDR
50 SERVER_ADMIN="eric@localhost"; export SERVER_ADMIN
50 SERVER_ADMIN="eric@localhost"; export SERVER_ADMIN
51 SERVER_NAME="hg.omnifarious.org"; export SERVER_NAME
51 SERVER_NAME="hg.omnifarious.org"; export SERVER_NAME
52 SERVER_PORT="80"; export SERVER_PORT
52 SERVER_PORT="80"; export SERVER_PORT
53 SERVER_PROTOCOL="HTTP/1.1"; export SERVER_PROTOCOL
53 SERVER_PROTOCOL="HTTP/1.1"; export SERVER_PROTOCOL
54 SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>"; export SERVER_SIGNATURE
54 SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>"; export SERVER_SIGNATURE
55 SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"; export SERVER_SOFTWARE
55 SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"; export SERVER_SOFTWARE
56
56
57 echo % try hgweb request
57 echo % try hgweb request
58 QUERY_STRING="cmd=changegroup"; export QUERY_STRING
58 QUERY_STRING="cmd=changegroup&roots=0000000000000000000000000000000000000000"; export QUERY_STRING
59 python hgweb.cgi >page1 2>&1 ; echo $?
59 python hgweb.cgi >page1 2>&1 ; echo $?
60 python "$TESTDIR/md5sum.py" page1
60 python "$TESTDIR/md5sum.py" page1
61
61
62 exit 0
62 exit 0
@@ -1,64 +1,64
1 #!/bin/sh
1 #!/bin/sh
2 # An attempt at more fully testing the hgweb web interface.
2 # An attempt at more fully testing the hgweb web interface.
3 # The following things are tested elsewhere and are therefore omitted:
3 # The following things are tested elsewhere and are therefore omitted:
4 # - archive, tested in test-archive
4 # - archive, tested in test-archive
5 # - unbundle, tested in test-push-http
5 # - unbundle, tested in test-push-http
6 # - changegroupsubset, tested in test-pull
6 # - changegroupsubset, tested in test-pull
7
7
8 echo % Set up the repo
8 echo % Set up the repo
9 hg init test
9 hg init test
10 cd test
10 cd test
11 mkdir da
11 mkdir da
12 echo foo > da/foo
12 echo foo > da/foo
13 echo foo > foo
13 echo foo > foo
14 hg ci -Ambase
14 hg ci -Ambase
15 hg tag 1.0
15 hg tag 1.0
16 echo another > foo
16 echo another > foo
17 hg branch stable
17 hg branch stable
18 hg ci -Ambranch
18 hg ci -Ambranch
19 hg serve --config server.uncompressed=False -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
19 hg serve --config server.uncompressed=False -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
20 cat hg.pid >> $DAEMON_PIDS
20 cat hg.pid >> $DAEMON_PIDS
21
21
22 echo % Logs and changes
22 echo % Logs and changes
23 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
23 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
24 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
24 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
25 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
25 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
26 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/'
26 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/'
27 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/0/'
27 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/0/'
28 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
28 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
29 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log?rev=base'
29 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log?rev=base'
30
30
31 echo % File-related
31 echo % File-related
32 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
32 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
33 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
33 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
34 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
34 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
35 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo'
35 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo'
36 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
36 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
37
37
38 echo % Overviews
38 echo % Overviews
39 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-tags'
39 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-tags'
40 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-branches'
40 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-branches'
41 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb'
41 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb'
42 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/?style=gitweb'
42 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/?style=gitweb'
43
43
44 echo % capabilities
44 echo % capabilities
45 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'
45 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'
46 echo % heads
46 echo % heads
47 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads'
47 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads'
48 echo % lookup
48 echo % lookup
49 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=lookup&key=1'
49 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=lookup&key=1'
50 echo % branches
50 echo % branches
51 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000'
51 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000'
52 echo % changegroup
52 echo % changegroup
53 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup' \
53 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000' \
54 | $TESTDIR/printrepr.py
54 | $TESTDIR/printrepr.py
55 echo % stream_out
55 echo % stream_out
56 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out'
56 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out'
57 echo % failing unbundle, requires POST request
57 echo % failing unbundle, requires POST request
58 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle'
58 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle'
59
59
60 echo % Static files
60 echo % Static files
61 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
61 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
62
62
63 echo % ERRORS ENCOUNTERED
63 echo % ERRORS ENCOUNTERED
64 cat errors.log
64 cat errors.log
General Comments 0
You need to be logged in to leave comments. Login now