##// END OF EJS Templates
http: support sending hgargs via POST body instead of in GET or headers...
Augie Fackler -
r28530:fd2acc50 default
parent child Browse files
Show More
@@ -1,110 +1,115
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cStringIO
10 import cStringIO
11 import cgi
11 import cgi
12 import urllib
12 import urllib
13 import zlib
13 import zlib
14
14
15 from .common import (
15 from .common import (
16 HTTP_OK,
16 HTTP_OK,
17 )
17 )
18
18
19 from .. import (
19 from .. import (
20 util,
20 util,
21 wireproto,
21 wireproto,
22 )
22 )
23
23
24 HGTYPE = 'application/mercurial-0.1'
24 HGTYPE = 'application/mercurial-0.1'
25 HGERRTYPE = 'application/hg-error'
25 HGERRTYPE = 'application/hg-error'
26
26
27 class webproto(wireproto.abstractserverproto):
27 class webproto(wireproto.abstractserverproto):
28 def __init__(self, req, ui):
28 def __init__(self, req, ui):
29 self.req = req
29 self.req = req
30 self.response = ''
30 self.response = ''
31 self.ui = ui
31 self.ui = ui
32 def getargs(self, args):
32 def getargs(self, args):
33 knownargs = self._args()
33 knownargs = self._args()
34 data = {}
34 data = {}
35 keys = args.split()
35 keys = args.split()
36 for k in keys:
36 for k in keys:
37 if k == '*':
37 if k == '*':
38 star = {}
38 star = {}
39 for key in knownargs.keys():
39 for key in knownargs.keys():
40 if key != 'cmd' and key not in keys:
40 if key != 'cmd' and key not in keys:
41 star[key] = knownargs[key][0]
41 star[key] = knownargs[key][0]
42 data['*'] = star
42 data['*'] = star
43 else:
43 else:
44 data[k] = knownargs[k][0]
44 data[k] = knownargs[k][0]
45 return [data[k] for k in keys]
45 return [data[k] for k in keys]
46 def _args(self):
46 def _args(self):
47 args = self.req.form.copy()
47 args = self.req.form.copy()
48 postlen = int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
49 if postlen:
50 args.update(cgi.parse_qs(
51 self.req.read(postlen), keep_blank_values=True))
52 return args
48 chunks = []
53 chunks = []
49 i = 1
54 i = 1
50 while True:
55 while True:
51 h = self.req.env.get('HTTP_X_HGARG_' + str(i))
56 h = self.req.env.get('HTTP_X_HGARG_' + str(i))
52 if h is None:
57 if h is None:
53 break
58 break
54 chunks += [h]
59 chunks += [h]
55 i += 1
60 i += 1
56 args.update(cgi.parse_qs(''.join(chunks), keep_blank_values=True))
61 args.update(cgi.parse_qs(''.join(chunks), keep_blank_values=True))
57 return args
62 return args
58 def getfile(self, fp):
63 def getfile(self, fp):
59 length = int(self.req.env['CONTENT_LENGTH'])
64 length = int(self.req.env['CONTENT_LENGTH'])
60 for s in util.filechunkiter(self.req, limit=length):
65 for s in util.filechunkiter(self.req, limit=length):
61 fp.write(s)
66 fp.write(s)
62 def redirect(self):
67 def redirect(self):
63 self.oldio = self.ui.fout, self.ui.ferr
68 self.oldio = self.ui.fout, self.ui.ferr
64 self.ui.ferr = self.ui.fout = cStringIO.StringIO()
69 self.ui.ferr = self.ui.fout = cStringIO.StringIO()
65 def restore(self):
70 def restore(self):
66 val = self.ui.fout.getvalue()
71 val = self.ui.fout.getvalue()
67 self.ui.ferr, self.ui.fout = self.oldio
72 self.ui.ferr, self.ui.fout = self.oldio
68 return val
73 return val
69 def groupchunks(self, cg):
74 def groupchunks(self, cg):
70 z = zlib.compressobj()
75 z = zlib.compressobj()
71 while True:
76 while True:
72 chunk = cg.read(4096)
77 chunk = cg.read(4096)
73 if not chunk:
78 if not chunk:
74 break
79 break
75 yield z.compress(chunk)
80 yield z.compress(chunk)
76 yield z.flush()
81 yield z.flush()
77 def _client(self):
82 def _client(self):
78 return 'remote:%s:%s:%s' % (
83 return 'remote:%s:%s:%s' % (
79 self.req.env.get('wsgi.url_scheme') or 'http',
84 self.req.env.get('wsgi.url_scheme') or 'http',
80 urllib.quote(self.req.env.get('REMOTE_HOST', '')),
85 urllib.quote(self.req.env.get('REMOTE_HOST', '')),
81 urllib.quote(self.req.env.get('REMOTE_USER', '')))
86 urllib.quote(self.req.env.get('REMOTE_USER', '')))
82
87
83 def iscmd(cmd):
88 def iscmd(cmd):
84 return cmd in wireproto.commands
89 return cmd in wireproto.commands
85
90
86 def call(repo, req, cmd):
91 def call(repo, req, cmd):
87 p = webproto(req, repo.ui)
92 p = webproto(req, repo.ui)
88 rsp = wireproto.dispatch(repo, p, cmd)
93 rsp = wireproto.dispatch(repo, p, cmd)
89 if isinstance(rsp, str):
94 if isinstance(rsp, str):
90 req.respond(HTTP_OK, HGTYPE, body=rsp)
95 req.respond(HTTP_OK, HGTYPE, body=rsp)
91 return []
96 return []
92 elif isinstance(rsp, wireproto.streamres):
97 elif isinstance(rsp, wireproto.streamres):
93 req.respond(HTTP_OK, HGTYPE)
98 req.respond(HTTP_OK, HGTYPE)
94 return rsp.gen
99 return rsp.gen
95 elif isinstance(rsp, wireproto.pushres):
100 elif isinstance(rsp, wireproto.pushres):
96 val = p.restore()
101 val = p.restore()
97 rsp = '%d\n%s' % (rsp.res, val)
102 rsp = '%d\n%s' % (rsp.res, val)
98 req.respond(HTTP_OK, HGTYPE, body=rsp)
103 req.respond(HTTP_OK, HGTYPE, body=rsp)
99 return []
104 return []
100 elif isinstance(rsp, wireproto.pusherr):
105 elif isinstance(rsp, wireproto.pusherr):
101 # drain the incoming bundle
106 # drain the incoming bundle
102 req.drain()
107 req.drain()
103 p.restore()
108 p.restore()
104 rsp = '0\n%s\n' % rsp.res
109 rsp = '0\n%s\n' % rsp.res
105 req.respond(HTTP_OK, HGTYPE, body=rsp)
110 req.respond(HTTP_OK, HGTYPE, body=rsp)
106 return []
111 return []
107 elif isinstance(rsp, wireproto.ooberror):
112 elif isinstance(rsp, wireproto.ooberror):
108 rsp = rsp.message
113 rsp = rsp.message
109 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
114 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
110 return []
115 return []
@@ -1,292 +1,307
1 # httppeer.py - HTTP repository proxy classes for mercurial
1 # httppeer.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import httplib
12 import httplib
13 import os
13 import os
14 import socket
14 import socket
15 import tempfile
15 import tempfile
16 import urllib
16 import urllib
17 import urllib2
17 import urllib2
18 import zlib
18 import zlib
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22 from . import (
22 from . import (
23 changegroup,
23 changegroup,
24 error,
24 error,
25 httpconnection,
25 httpconnection,
26 statichttprepo,
26 statichttprepo,
27 url,
27 url,
28 util,
28 util,
29 wireproto,
29 wireproto,
30 )
30 )
31
31
32 def zgenerator(f):
32 def zgenerator(f):
33 zd = zlib.decompressobj()
33 zd = zlib.decompressobj()
34 try:
34 try:
35 for chunk in util.filechunkiter(f):
35 for chunk in util.filechunkiter(f):
36 while chunk:
36 while chunk:
37 yield zd.decompress(chunk, 2**18)
37 yield zd.decompress(chunk, 2**18)
38 chunk = zd.unconsumed_tail
38 chunk = zd.unconsumed_tail
39 except httplib.HTTPException:
39 except httplib.HTTPException:
40 raise IOError(None, _('connection ended unexpectedly'))
40 raise IOError(None, _('connection ended unexpectedly'))
41 yield zd.flush()
41 yield zd.flush()
42
42
43 class httppeer(wireproto.wirepeer):
43 class httppeer(wireproto.wirepeer):
44 def __init__(self, ui, path):
44 def __init__(self, ui, path):
45 self.path = path
45 self.path = path
46 self.caps = None
46 self.caps = None
47 self.handler = None
47 self.handler = None
48 self.urlopener = None
48 self.urlopener = None
49 self.requestbuilder = None
49 self.requestbuilder = None
50 u = util.url(path)
50 u = util.url(path)
51 if u.query or u.fragment:
51 if u.query or u.fragment:
52 raise error.Abort(_('unsupported URL component: "%s"') %
52 raise error.Abort(_('unsupported URL component: "%s"') %
53 (u.query or u.fragment))
53 (u.query or u.fragment))
54
54
55 # urllib cannot handle URLs with embedded user or passwd
55 # urllib cannot handle URLs with embedded user or passwd
56 self._url, authinfo = u.authinfo()
56 self._url, authinfo = u.authinfo()
57
57
58 self.ui = ui
58 self.ui = ui
59 self.ui.debug('using %s\n' % self._url)
59 self.ui.debug('using %s\n' % self._url)
60
60
61 self.urlopener = url.opener(ui, authinfo)
61 self.urlopener = url.opener(ui, authinfo)
62 self.requestbuilder = urllib2.Request
62 self.requestbuilder = urllib2.Request
63
63
64 def __del__(self):
64 def __del__(self):
65 if self.urlopener:
65 if self.urlopener:
66 for h in self.urlopener.handlers:
66 for h in self.urlopener.handlers:
67 h.close()
67 h.close()
68 getattr(h, "close_all", lambda : None)()
68 getattr(h, "close_all", lambda : None)()
69
69
70 def url(self):
70 def url(self):
71 return self.path
71 return self.path
72
72
73 # look up capabilities only when needed
73 # look up capabilities only when needed
74
74
75 def _fetchcaps(self):
75 def _fetchcaps(self):
76 self.caps = set(self._call('capabilities').split())
76 self.caps = set(self._call('capabilities').split())
77
77
78 def _capabilities(self):
78 def _capabilities(self):
79 if self.caps is None:
79 if self.caps is None:
80 try:
80 try:
81 self._fetchcaps()
81 self._fetchcaps()
82 except error.RepoError:
82 except error.RepoError:
83 self.caps = set()
83 self.caps = set()
84 self.ui.debug('capabilities: %s\n' %
84 self.ui.debug('capabilities: %s\n' %
85 (' '.join(self.caps or ['none'])))
85 (' '.join(self.caps or ['none'])))
86 return self.caps
86 return self.caps
87
87
88 def lock(self):
88 def lock(self):
89 raise error.Abort(_('operation not supported over http'))
89 raise error.Abort(_('operation not supported over http'))
90
90
91 def _callstream(self, cmd, **args):
91 def _callstream(self, cmd, **args):
92 if cmd == 'pushkey':
92 if cmd == 'pushkey':
93 args['data'] = ''
93 args['data'] = ''
94 data = args.pop('data', None)
94 data = args.pop('data', None)
95 headers = args.pop('headers', {})
95 headers = args.pop('headers', {})
96
96
97 self.ui.debug("sending %s command\n" % cmd)
97 self.ui.debug("sending %s command\n" % cmd)
98 q = [('cmd', cmd)]
98 q = [('cmd', cmd)]
99 headersize = 0
99 headersize = 0
100 if True:
100 # Important: don't use self.capable() here or else you end up
101 # with infinite recursion when trying to look up capabilities
102 # for the first time.
103 postargsok = self.caps is not None and 'httppostargs' in self.caps
104 # TODO: support for httppostargs when data is a file-like
105 # object rather than a basestring
106 canmungedata = not data or isinstance(data, basestring)
107 if postargsok and canmungedata:
108 strargs = urllib.urlencode(sorted(args.items()))
109 if strargs:
110 if not data:
111 data = strargs
112 elif isinstance(data, basestring):
113 data = strargs + data
114 headers['X-HgArgs-Post'] = len(strargs)
115 else:
101 if len(args) > 0:
116 if len(args) > 0:
102 httpheader = self.capable('httpheader')
117 httpheader = self.capable('httpheader')
103 if httpheader:
118 if httpheader:
104 headersize = int(httpheader.split(',', 1)[0])
119 headersize = int(httpheader.split(',', 1)[0])
105 if headersize > 0:
120 if headersize > 0:
106 # The headers can typically carry more data than the URL.
121 # The headers can typically carry more data than the URL.
107 encargs = urllib.urlencode(sorted(args.items()))
122 encargs = urllib.urlencode(sorted(args.items()))
108 headerfmt = 'X-HgArg-%s'
123 headerfmt = 'X-HgArg-%s'
109 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
124 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
110 headernum = 0
125 headernum = 0
111 varyheaders = []
126 varyheaders = []
112 for i in xrange(0, len(encargs), contentlen):
127 for i in xrange(0, len(encargs), contentlen):
113 headernum += 1
128 headernum += 1
114 header = headerfmt % str(headernum)
129 header = headerfmt % str(headernum)
115 headers[header] = encargs[i:i + contentlen]
130 headers[header] = encargs[i:i + contentlen]
116 varyheaders.append(header)
131 varyheaders.append(header)
117 headers['Vary'] = ','.join(varyheaders)
132 headers['Vary'] = ','.join(varyheaders)
118 else:
133 else:
119 q += sorted(args.items())
134 q += sorted(args.items())
120 qs = '?%s' % urllib.urlencode(q)
135 qs = '?%s' % urllib.urlencode(q)
121 cu = "%s%s" % (self._url, qs)
136 cu = "%s%s" % (self._url, qs)
122 size = 0
137 size = 0
123 if util.safehasattr(data, 'length'):
138 if util.safehasattr(data, 'length'):
124 size = data.length
139 size = data.length
125 elif data is not None:
140 elif data is not None:
126 size = len(data)
141 size = len(data)
127 if size and self.ui.configbool('ui', 'usehttp2', False):
142 if size and self.ui.configbool('ui', 'usehttp2', False):
128 headers['Expect'] = '100-Continue'
143 headers['Expect'] = '100-Continue'
129 headers['X-HgHttp2'] = '1'
144 headers['X-HgHttp2'] = '1'
130 if data is not None and 'Content-Type' not in headers:
145 if data is not None and 'Content-Type' not in headers:
131 headers['Content-Type'] = 'application/mercurial-0.1'
146 headers['Content-Type'] = 'application/mercurial-0.1'
132 req = self.requestbuilder(cu, data, headers)
147 req = self.requestbuilder(cu, data, headers)
133 if data is not None:
148 if data is not None:
134 self.ui.debug("sending %s bytes\n" % size)
149 self.ui.debug("sending %s bytes\n" % size)
135 req.add_unredirected_header('Content-Length', '%d' % size)
150 req.add_unredirected_header('Content-Length', '%d' % size)
136 try:
151 try:
137 resp = self.urlopener.open(req)
152 resp = self.urlopener.open(req)
138 except urllib2.HTTPError as inst:
153 except urllib2.HTTPError as inst:
139 if inst.code == 401:
154 if inst.code == 401:
140 raise error.Abort(_('authorization failed'))
155 raise error.Abort(_('authorization failed'))
141 raise
156 raise
142 except httplib.HTTPException as inst:
157 except httplib.HTTPException as inst:
143 self.ui.debug('http error while sending %s command\n' % cmd)
158 self.ui.debug('http error while sending %s command\n' % cmd)
144 self.ui.traceback()
159 self.ui.traceback()
145 raise IOError(None, inst)
160 raise IOError(None, inst)
146 except IndexError:
161 except IndexError:
147 # this only happens with Python 2.3, later versions raise URLError
162 # this only happens with Python 2.3, later versions raise URLError
148 raise error.Abort(_('http error, possibly caused by proxy setting'))
163 raise error.Abort(_('http error, possibly caused by proxy setting'))
149 # record the url we got redirected to
164 # record the url we got redirected to
150 resp_url = resp.geturl()
165 resp_url = resp.geturl()
151 if resp_url.endswith(qs):
166 if resp_url.endswith(qs):
152 resp_url = resp_url[:-len(qs)]
167 resp_url = resp_url[:-len(qs)]
153 if self._url.rstrip('/') != resp_url.rstrip('/'):
168 if self._url.rstrip('/') != resp_url.rstrip('/'):
154 if not self.ui.quiet:
169 if not self.ui.quiet:
155 self.ui.warn(_('real URL is %s\n') % resp_url)
170 self.ui.warn(_('real URL is %s\n') % resp_url)
156 self._url = resp_url
171 self._url = resp_url
157 try:
172 try:
158 proto = resp.getheader('content-type')
173 proto = resp.getheader('content-type')
159 except AttributeError:
174 except AttributeError:
160 proto = resp.headers.get('content-type', '')
175 proto = resp.headers.get('content-type', '')
161
176
162 safeurl = util.hidepassword(self._url)
177 safeurl = util.hidepassword(self._url)
163 if proto.startswith('application/hg-error'):
178 if proto.startswith('application/hg-error'):
164 raise error.OutOfBandError(resp.read())
179 raise error.OutOfBandError(resp.read())
165 # accept old "text/plain" and "application/hg-changegroup" for now
180 # accept old "text/plain" and "application/hg-changegroup" for now
166 if not (proto.startswith('application/mercurial-') or
181 if not (proto.startswith('application/mercurial-') or
167 (proto.startswith('text/plain')
182 (proto.startswith('text/plain')
168 and not resp.headers.get('content-length')) or
183 and not resp.headers.get('content-length')) or
169 proto.startswith('application/hg-changegroup')):
184 proto.startswith('application/hg-changegroup')):
170 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
185 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
171 raise error.RepoError(
186 raise error.RepoError(
172 _("'%s' does not appear to be an hg repository:\n"
187 _("'%s' does not appear to be an hg repository:\n"
173 "---%%<--- (%s)\n%s\n---%%<---\n")
188 "---%%<--- (%s)\n%s\n---%%<---\n")
174 % (safeurl, proto or 'no content-type', resp.read(1024)))
189 % (safeurl, proto or 'no content-type', resp.read(1024)))
175
190
176 if proto.startswith('application/mercurial-'):
191 if proto.startswith('application/mercurial-'):
177 try:
192 try:
178 version = proto.split('-', 1)[1]
193 version = proto.split('-', 1)[1]
179 version_info = tuple([int(n) for n in version.split('.')])
194 version_info = tuple([int(n) for n in version.split('.')])
180 except ValueError:
195 except ValueError:
181 raise error.RepoError(_("'%s' sent a broken Content-Type "
196 raise error.RepoError(_("'%s' sent a broken Content-Type "
182 "header (%s)") % (safeurl, proto))
197 "header (%s)") % (safeurl, proto))
183 if version_info > (0, 1):
198 if version_info > (0, 1):
184 raise error.RepoError(_("'%s' uses newer protocol %s") %
199 raise error.RepoError(_("'%s' uses newer protocol %s") %
185 (safeurl, version))
200 (safeurl, version))
186
201
187 return resp
202 return resp
188
203
189 def _call(self, cmd, **args):
204 def _call(self, cmd, **args):
190 fp = self._callstream(cmd, **args)
205 fp = self._callstream(cmd, **args)
191 try:
206 try:
192 return fp.read()
207 return fp.read()
193 finally:
208 finally:
194 # if using keepalive, allow connection to be reused
209 # if using keepalive, allow connection to be reused
195 fp.close()
210 fp.close()
196
211
197 def _callpush(self, cmd, cg, **args):
212 def _callpush(self, cmd, cg, **args):
198 # have to stream bundle to a temp file because we do not have
213 # have to stream bundle to a temp file because we do not have
199 # http 1.1 chunked transfer.
214 # http 1.1 chunked transfer.
200
215
201 types = self.capable('unbundle')
216 types = self.capable('unbundle')
202 try:
217 try:
203 types = types.split(',')
218 types = types.split(',')
204 except AttributeError:
219 except AttributeError:
205 # servers older than d1b16a746db6 will send 'unbundle' as a
220 # servers older than d1b16a746db6 will send 'unbundle' as a
206 # boolean capability. They only support headerless/uncompressed
221 # boolean capability. They only support headerless/uncompressed
207 # bundles.
222 # bundles.
208 types = [""]
223 types = [""]
209 for x in types:
224 for x in types:
210 if x in changegroup.bundletypes:
225 if x in changegroup.bundletypes:
211 type = x
226 type = x
212 break
227 break
213
228
214 tempname = changegroup.writebundle(self.ui, cg, None, type)
229 tempname = changegroup.writebundle(self.ui, cg, None, type)
215 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
230 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
216 headers = {'Content-Type': 'application/mercurial-0.1'}
231 headers = {'Content-Type': 'application/mercurial-0.1'}
217
232
218 try:
233 try:
219 r = self._call(cmd, data=fp, headers=headers, **args)
234 r = self._call(cmd, data=fp, headers=headers, **args)
220 vals = r.split('\n', 1)
235 vals = r.split('\n', 1)
221 if len(vals) < 2:
236 if len(vals) < 2:
222 raise error.ResponseError(_("unexpected response:"), r)
237 raise error.ResponseError(_("unexpected response:"), r)
223 return vals
238 return vals
224 except socket.error as err:
239 except socket.error as err:
225 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
240 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
226 raise error.Abort(_('push failed: %s') % err.args[1])
241 raise error.Abort(_('push failed: %s') % err.args[1])
227 raise error.Abort(err.args[1])
242 raise error.Abort(err.args[1])
228 finally:
243 finally:
229 fp.close()
244 fp.close()
230 os.unlink(tempname)
245 os.unlink(tempname)
231
246
232 def _calltwowaystream(self, cmd, fp, **args):
247 def _calltwowaystream(self, cmd, fp, **args):
233 fh = None
248 fh = None
234 fp_ = None
249 fp_ = None
235 filename = None
250 filename = None
236 try:
251 try:
237 # dump bundle to disk
252 # dump bundle to disk
238 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
253 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
239 fh = os.fdopen(fd, "wb")
254 fh = os.fdopen(fd, "wb")
240 d = fp.read(4096)
255 d = fp.read(4096)
241 while d:
256 while d:
242 fh.write(d)
257 fh.write(d)
243 d = fp.read(4096)
258 d = fp.read(4096)
244 fh.close()
259 fh.close()
245 # start http push
260 # start http push
246 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
261 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
247 headers = {'Content-Type': 'application/mercurial-0.1'}
262 headers = {'Content-Type': 'application/mercurial-0.1'}
248 return self._callstream(cmd, data=fp_, headers=headers, **args)
263 return self._callstream(cmd, data=fp_, headers=headers, **args)
249 finally:
264 finally:
250 if fp_ is not None:
265 if fp_ is not None:
251 fp_.close()
266 fp_.close()
252 if fh is not None:
267 if fh is not None:
253 fh.close()
268 fh.close()
254 os.unlink(filename)
269 os.unlink(filename)
255
270
256 def _callcompressable(self, cmd, **args):
271 def _callcompressable(self, cmd, **args):
257 stream = self._callstream(cmd, **args)
272 stream = self._callstream(cmd, **args)
258 return util.chunkbuffer(zgenerator(stream))
273 return util.chunkbuffer(zgenerator(stream))
259
274
260 def _abort(self, exception):
275 def _abort(self, exception):
261 raise exception
276 raise exception
262
277
263 class httpspeer(httppeer):
278 class httpspeer(httppeer):
264 def __init__(self, ui, path):
279 def __init__(self, ui, path):
265 if not url.has_https:
280 if not url.has_https:
266 raise error.Abort(_('Python support for SSL and HTTPS '
281 raise error.Abort(_('Python support for SSL and HTTPS '
267 'is not installed'))
282 'is not installed'))
268 httppeer.__init__(self, ui, path)
283 httppeer.__init__(self, ui, path)
269
284
270 def instance(ui, path, create):
285 def instance(ui, path, create):
271 if create:
286 if create:
272 raise error.Abort(_('cannot create new http repository'))
287 raise error.Abort(_('cannot create new http repository'))
273 try:
288 try:
274 if path.startswith('https:'):
289 if path.startswith('https:'):
275 inst = httpspeer(ui, path)
290 inst = httpspeer(ui, path)
276 else:
291 else:
277 inst = httppeer(ui, path)
292 inst = httppeer(ui, path)
278 try:
293 try:
279 # Try to do useful work when checking compatibility.
294 # Try to do useful work when checking compatibility.
280 # Usually saves a roundtrip since we want the caps anyway.
295 # Usually saves a roundtrip since we want the caps anyway.
281 inst._fetchcaps()
296 inst._fetchcaps()
282 except error.RepoError:
297 except error.RepoError:
283 # No luck, try older compatibility check.
298 # No luck, try older compatibility check.
284 inst.between([(nullid, nullid)])
299 inst.between([(nullid, nullid)])
285 return inst
300 return inst
286 except error.RepoError as httpexception:
301 except error.RepoError as httpexception:
287 try:
302 try:
288 r = statichttprepo.instance(ui, "static-" + path, create)
303 r = statichttprepo.instance(ui, "static-" + path, create)
289 ui.note('(falling back to static-http)\n')
304 ui.note('(falling back to static-http)\n')
290 return r
305 return r
291 except error.RepoError:
306 except error.RepoError:
292 raise httpexception # use the original http RepoError instead
307 raise httpexception # use the original http RepoError instead
@@ -1,932 +1,934
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12 import sys
12 import sys
13 import tempfile
13 import tempfile
14 import urllib
14 import urllib
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 bundle2,
23 bundle2,
24 changegroup as changegroupmod,
24 changegroup as changegroupmod,
25 encoding,
25 encoding,
26 error,
26 error,
27 exchange,
27 exchange,
28 peer,
28 peer,
29 pushkey as pushkeymod,
29 pushkey as pushkeymod,
30 streamclone,
30 streamclone,
31 util,
31 util,
32 )
32 )
33
33
34 bundle2required = _(
34 bundle2required = _(
35 'incompatible Mercurial client; bundle2 required\n'
35 'incompatible Mercurial client; bundle2 required\n'
36 '(see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n')
36 '(see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n')
37
37
38 class abstractserverproto(object):
38 class abstractserverproto(object):
39 """abstract class that summarizes the protocol API
39 """abstract class that summarizes the protocol API
40
40
41 Used as reference and documentation.
41 Used as reference and documentation.
42 """
42 """
43
43
44 def getargs(self, args):
44 def getargs(self, args):
45 """return the value for arguments in <args>
45 """return the value for arguments in <args>
46
46
47 returns a list of values (same order as <args>)"""
47 returns a list of values (same order as <args>)"""
48 raise NotImplementedError()
48 raise NotImplementedError()
49
49
50 def getfile(self, fp):
50 def getfile(self, fp):
51 """write the whole content of a file into a file like object
51 """write the whole content of a file into a file like object
52
52
53 The file is in the form::
53 The file is in the form::
54
54
55 (<chunk-size>\n<chunk>)+0\n
55 (<chunk-size>\n<chunk>)+0\n
56
56
57 chunk size is the ascii version of the int.
57 chunk size is the ascii version of the int.
58 """
58 """
59 raise NotImplementedError()
59 raise NotImplementedError()
60
60
61 def redirect(self):
61 def redirect(self):
62 """may setup interception for stdout and stderr
62 """may setup interception for stdout and stderr
63
63
64 See also the `restore` method."""
64 See also the `restore` method."""
65 raise NotImplementedError()
65 raise NotImplementedError()
66
66
67 # If the `redirect` function does install interception, the `restore`
67 # If the `redirect` function does install interception, the `restore`
68 # function MUST be defined. If interception is not used, this function
68 # function MUST be defined. If interception is not used, this function
69 # MUST NOT be defined.
69 # MUST NOT be defined.
70 #
70 #
71 # left commented here on purpose
71 # left commented here on purpose
72 #
72 #
73 #def restore(self):
73 #def restore(self):
74 # """reinstall previous stdout and stderr and return intercepted stdout
74 # """reinstall previous stdout and stderr and return intercepted stdout
75 # """
75 # """
76 # raise NotImplementedError()
76 # raise NotImplementedError()
77
77
78 def groupchunks(self, cg):
78 def groupchunks(self, cg):
79 """return 4096 chunks from a changegroup object
79 """return 4096 chunks from a changegroup object
80
80
81 Some protocols may have compressed the contents."""
81 Some protocols may have compressed the contents."""
82 raise NotImplementedError()
82 raise NotImplementedError()
83
83
84 class remotebatch(peer.batcher):
84 class remotebatch(peer.batcher):
85 '''batches the queued calls; uses as few roundtrips as possible'''
85 '''batches the queued calls; uses as few roundtrips as possible'''
86 def __init__(self, remote):
86 def __init__(self, remote):
87 '''remote must support _submitbatch(encbatch) and
87 '''remote must support _submitbatch(encbatch) and
88 _submitone(op, encargs)'''
88 _submitone(op, encargs)'''
89 peer.batcher.__init__(self)
89 peer.batcher.__init__(self)
90 self.remote = remote
90 self.remote = remote
91 def submit(self):
91 def submit(self):
92 req, rsp = [], []
92 req, rsp = [], []
93 for name, args, opts, resref in self.calls:
93 for name, args, opts, resref in self.calls:
94 mtd = getattr(self.remote, name)
94 mtd = getattr(self.remote, name)
95 batchablefn = getattr(mtd, 'batchable', None)
95 batchablefn = getattr(mtd, 'batchable', None)
96 if batchablefn is not None:
96 if batchablefn is not None:
97 batchable = batchablefn(mtd.im_self, *args, **opts)
97 batchable = batchablefn(mtd.im_self, *args, **opts)
98 encargsorres, encresref = batchable.next()
98 encargsorres, encresref = batchable.next()
99 if encresref:
99 if encresref:
100 req.append((name, encargsorres,))
100 req.append((name, encargsorres,))
101 rsp.append((batchable, encresref, resref,))
101 rsp.append((batchable, encresref, resref,))
102 else:
102 else:
103 resref.set(encargsorres)
103 resref.set(encargsorres)
104 else:
104 else:
105 if req:
105 if req:
106 self._submitreq(req, rsp)
106 self._submitreq(req, rsp)
107 req, rsp = [], []
107 req, rsp = [], []
108 resref.set(mtd(*args, **opts))
108 resref.set(mtd(*args, **opts))
109 if req:
109 if req:
110 self._submitreq(req, rsp)
110 self._submitreq(req, rsp)
111 def _submitreq(self, req, rsp):
111 def _submitreq(self, req, rsp):
112 encresults = self.remote._submitbatch(req)
112 encresults = self.remote._submitbatch(req)
113 for encres, r in zip(encresults, rsp):
113 for encres, r in zip(encresults, rsp):
114 batchable, encresref, resref = r
114 batchable, encresref, resref = r
115 encresref.set(encres)
115 encresref.set(encres)
116 resref.set(batchable.next())
116 resref.set(batchable.next())
117
117
118 class remoteiterbatcher(peer.iterbatcher):
118 class remoteiterbatcher(peer.iterbatcher):
119 def __init__(self, remote):
119 def __init__(self, remote):
120 super(remoteiterbatcher, self).__init__()
120 super(remoteiterbatcher, self).__init__()
121 self._remote = remote
121 self._remote = remote
122
122
123 def __getattr__(self, name):
123 def __getattr__(self, name):
124 if not getattr(self._remote, name, False):
124 if not getattr(self._remote, name, False):
125 raise AttributeError(
125 raise AttributeError(
126 'Attempted to iterbatch non-batchable call to %r' % name)
126 'Attempted to iterbatch non-batchable call to %r' % name)
127 return super(remoteiterbatcher, self).__getattr__(name)
127 return super(remoteiterbatcher, self).__getattr__(name)
128
128
129 def submit(self):
129 def submit(self):
130 """Break the batch request into many patch calls and pipeline them.
130 """Break the batch request into many patch calls and pipeline them.
131
131
132 This is mostly valuable over http where request sizes can be
132 This is mostly valuable over http where request sizes can be
133 limited, but can be used in other places as well.
133 limited, but can be used in other places as well.
134 """
134 """
135 req, rsp = [], []
135 req, rsp = [], []
136 for name, args, opts, resref in self.calls:
136 for name, args, opts, resref in self.calls:
137 mtd = getattr(self._remote, name)
137 mtd = getattr(self._remote, name)
138 batchable = mtd.batchable(mtd.im_self, *args, **opts)
138 batchable = mtd.batchable(mtd.im_self, *args, **opts)
139 encargsorres, encresref = batchable.next()
139 encargsorres, encresref = batchable.next()
140 assert encresref
140 assert encresref
141 req.append((name, encargsorres))
141 req.append((name, encargsorres))
142 rsp.append((batchable, encresref))
142 rsp.append((batchable, encresref))
143 if req:
143 if req:
144 self._resultiter = self._remote._submitbatch(req)
144 self._resultiter = self._remote._submitbatch(req)
145 self._rsp = rsp
145 self._rsp = rsp
146
146
147 def results(self):
147 def results(self):
148 for (batchable, encresref), encres in itertools.izip(
148 for (batchable, encresref), encres in itertools.izip(
149 self._rsp, self._resultiter):
149 self._rsp, self._resultiter):
150 encresref.set(encres)
150 encresref.set(encres)
151 yield batchable.next()
151 yield batchable.next()
152
152
153 # Forward a couple of names from peer to make wireproto interactions
153 # Forward a couple of names from peer to make wireproto interactions
154 # slightly more sensible.
154 # slightly more sensible.
155 batchable = peer.batchable
155 batchable = peer.batchable
156 future = peer.future
156 future = peer.future
157
157
158 # list of nodes encoding / decoding
158 # list of nodes encoding / decoding
159
159
160 def decodelist(l, sep=' '):
160 def decodelist(l, sep=' '):
161 if l:
161 if l:
162 return map(bin, l.split(sep))
162 return map(bin, l.split(sep))
163 return []
163 return []
164
164
165 def encodelist(l, sep=' '):
165 def encodelist(l, sep=' '):
166 try:
166 try:
167 return sep.join(map(hex, l))
167 return sep.join(map(hex, l))
168 except TypeError:
168 except TypeError:
169 raise
169 raise
170
170
171 # batched call argument encoding
171 # batched call argument encoding
172
172
173 def escapearg(plain):
173 def escapearg(plain):
174 return (plain
174 return (plain
175 .replace(':', ':c')
175 .replace(':', ':c')
176 .replace(',', ':o')
176 .replace(',', ':o')
177 .replace(';', ':s')
177 .replace(';', ':s')
178 .replace('=', ':e'))
178 .replace('=', ':e'))
179
179
180 def unescapearg(escaped):
180 def unescapearg(escaped):
181 return (escaped
181 return (escaped
182 .replace(':e', '=')
182 .replace(':e', '=')
183 .replace(':s', ';')
183 .replace(':s', ';')
184 .replace(':o', ',')
184 .replace(':o', ',')
185 .replace(':c', ':'))
185 .replace(':c', ':'))
186
186
187 # mapping of options accepted by getbundle and their types
187 # mapping of options accepted by getbundle and their types
188 #
188 #
189 # Meant to be extended by extensions. It is extensions responsibility to ensure
189 # Meant to be extended by extensions. It is extensions responsibility to ensure
190 # such options are properly processed in exchange.getbundle.
190 # such options are properly processed in exchange.getbundle.
191 #
191 #
192 # supported types are:
192 # supported types are:
193 #
193 #
194 # :nodes: list of binary nodes
194 # :nodes: list of binary nodes
195 # :csv: list of comma-separated values
195 # :csv: list of comma-separated values
196 # :scsv: list of comma-separated values return as set
196 # :scsv: list of comma-separated values return as set
197 # :plain: string with no transformation needed.
197 # :plain: string with no transformation needed.
198 gboptsmap = {'heads': 'nodes',
198 gboptsmap = {'heads': 'nodes',
199 'common': 'nodes',
199 'common': 'nodes',
200 'obsmarkers': 'boolean',
200 'obsmarkers': 'boolean',
201 'bundlecaps': 'scsv',
201 'bundlecaps': 'scsv',
202 'listkeys': 'csv',
202 'listkeys': 'csv',
203 'cg': 'boolean',
203 'cg': 'boolean',
204 'cbattempted': 'boolean'}
204 'cbattempted': 'boolean'}
205
205
206 # client side
206 # client side
207
207
208 class wirepeer(peer.peerrepository):
208 class wirepeer(peer.peerrepository):
209 """Client-side interface for communicating with a peer repository.
209 """Client-side interface for communicating with a peer repository.
210
210
211 Methods commonly call wire protocol commands of the same name.
211 Methods commonly call wire protocol commands of the same name.
212
212
213 See also httppeer.py and sshpeer.py for protocol-specific
213 See also httppeer.py and sshpeer.py for protocol-specific
214 implementations of this interface.
214 implementations of this interface.
215 """
215 """
216 def batch(self):
216 def batch(self):
217 if self.capable('batch'):
217 if self.capable('batch'):
218 return remotebatch(self)
218 return remotebatch(self)
219 else:
219 else:
220 return peer.localbatch(self)
220 return peer.localbatch(self)
221 def _submitbatch(self, req):
221 def _submitbatch(self, req):
222 """run batch request <req> on the server
222 """run batch request <req> on the server
223
223
224 Returns an iterator of the raw responses from the server.
224 Returns an iterator of the raw responses from the server.
225 """
225 """
226 cmds = []
226 cmds = []
227 for op, argsdict in req:
227 for op, argsdict in req:
228 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
228 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
229 for k, v in argsdict.iteritems())
229 for k, v in argsdict.iteritems())
230 cmds.append('%s %s' % (op, args))
230 cmds.append('%s %s' % (op, args))
231 rsp = self._callstream("batch", cmds=';'.join(cmds))
231 rsp = self._callstream("batch", cmds=';'.join(cmds))
232 # TODO this response parsing is probably suboptimal for large
232 # TODO this response parsing is probably suboptimal for large
233 # batches with large responses.
233 # batches with large responses.
234 work = rsp.read(1024)
234 work = rsp.read(1024)
235 chunk = work
235 chunk = work
236 while chunk:
236 while chunk:
237 while ';' in work:
237 while ';' in work:
238 one, work = work.split(';', 1)
238 one, work = work.split(';', 1)
239 yield unescapearg(one)
239 yield unescapearg(one)
240 chunk = rsp.read(1024)
240 chunk = rsp.read(1024)
241 work += chunk
241 work += chunk
242 yield unescapearg(work)
242 yield unescapearg(work)
243
243
244 def _submitone(self, op, args):
244 def _submitone(self, op, args):
245 return self._call(op, **args)
245 return self._call(op, **args)
246
246
247 def iterbatch(self):
247 def iterbatch(self):
248 return remoteiterbatcher(self)
248 return remoteiterbatcher(self)
249
249
250 @batchable
250 @batchable
251 def lookup(self, key):
251 def lookup(self, key):
252 self.requirecap('lookup', _('look up remote revision'))
252 self.requirecap('lookup', _('look up remote revision'))
253 f = future()
253 f = future()
254 yield {'key': encoding.fromlocal(key)}, f
254 yield {'key': encoding.fromlocal(key)}, f
255 d = f.value
255 d = f.value
256 success, data = d[:-1].split(" ", 1)
256 success, data = d[:-1].split(" ", 1)
257 if int(success):
257 if int(success):
258 yield bin(data)
258 yield bin(data)
259 self._abort(error.RepoError(data))
259 self._abort(error.RepoError(data))
260
260
261 @batchable
261 @batchable
262 def heads(self):
262 def heads(self):
263 f = future()
263 f = future()
264 yield {}, f
264 yield {}, f
265 d = f.value
265 d = f.value
266 try:
266 try:
267 yield decodelist(d[:-1])
267 yield decodelist(d[:-1])
268 except ValueError:
268 except ValueError:
269 self._abort(error.ResponseError(_("unexpected response:"), d))
269 self._abort(error.ResponseError(_("unexpected response:"), d))
270
270
271 @batchable
271 @batchable
272 def known(self, nodes):
272 def known(self, nodes):
273 f = future()
273 f = future()
274 yield {'nodes': encodelist(nodes)}, f
274 yield {'nodes': encodelist(nodes)}, f
275 d = f.value
275 d = f.value
276 try:
276 try:
277 yield [bool(int(b)) for b in d]
277 yield [bool(int(b)) for b in d]
278 except ValueError:
278 except ValueError:
279 self._abort(error.ResponseError(_("unexpected response:"), d))
279 self._abort(error.ResponseError(_("unexpected response:"), d))
280
280
281 @batchable
281 @batchable
282 def branchmap(self):
282 def branchmap(self):
283 f = future()
283 f = future()
284 yield {}, f
284 yield {}, f
285 d = f.value
285 d = f.value
286 try:
286 try:
287 branchmap = {}
287 branchmap = {}
288 for branchpart in d.splitlines():
288 for branchpart in d.splitlines():
289 branchname, branchheads = branchpart.split(' ', 1)
289 branchname, branchheads = branchpart.split(' ', 1)
290 branchname = encoding.tolocal(urllib.unquote(branchname))
290 branchname = encoding.tolocal(urllib.unquote(branchname))
291 branchheads = decodelist(branchheads)
291 branchheads = decodelist(branchheads)
292 branchmap[branchname] = branchheads
292 branchmap[branchname] = branchheads
293 yield branchmap
293 yield branchmap
294 except TypeError:
294 except TypeError:
295 self._abort(error.ResponseError(_("unexpected response:"), d))
295 self._abort(error.ResponseError(_("unexpected response:"), d))
296
296
297 def branches(self, nodes):
297 def branches(self, nodes):
298 n = encodelist(nodes)
298 n = encodelist(nodes)
299 d = self._call("branches", nodes=n)
299 d = self._call("branches", nodes=n)
300 try:
300 try:
301 br = [tuple(decodelist(b)) for b in d.splitlines()]
301 br = [tuple(decodelist(b)) for b in d.splitlines()]
302 return br
302 return br
303 except ValueError:
303 except ValueError:
304 self._abort(error.ResponseError(_("unexpected response:"), d))
304 self._abort(error.ResponseError(_("unexpected response:"), d))
305
305
306 def between(self, pairs):
306 def between(self, pairs):
307 batch = 8 # avoid giant requests
307 batch = 8 # avoid giant requests
308 r = []
308 r = []
309 for i in xrange(0, len(pairs), batch):
309 for i in xrange(0, len(pairs), batch):
310 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
310 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
311 d = self._call("between", pairs=n)
311 d = self._call("between", pairs=n)
312 try:
312 try:
313 r.extend(l and decodelist(l) or [] for l in d.splitlines())
313 r.extend(l and decodelist(l) or [] for l in d.splitlines())
314 except ValueError:
314 except ValueError:
315 self._abort(error.ResponseError(_("unexpected response:"), d))
315 self._abort(error.ResponseError(_("unexpected response:"), d))
316 return r
316 return r
317
317
318 @batchable
318 @batchable
319 def pushkey(self, namespace, key, old, new):
319 def pushkey(self, namespace, key, old, new):
320 if not self.capable('pushkey'):
320 if not self.capable('pushkey'):
321 yield False, None
321 yield False, None
322 f = future()
322 f = future()
323 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
323 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
324 yield {'namespace': encoding.fromlocal(namespace),
324 yield {'namespace': encoding.fromlocal(namespace),
325 'key': encoding.fromlocal(key),
325 'key': encoding.fromlocal(key),
326 'old': encoding.fromlocal(old),
326 'old': encoding.fromlocal(old),
327 'new': encoding.fromlocal(new)}, f
327 'new': encoding.fromlocal(new)}, f
328 d = f.value
328 d = f.value
329 d, output = d.split('\n', 1)
329 d, output = d.split('\n', 1)
330 try:
330 try:
331 d = bool(int(d))
331 d = bool(int(d))
332 except ValueError:
332 except ValueError:
333 raise error.ResponseError(
333 raise error.ResponseError(
334 _('push failed (unexpected response):'), d)
334 _('push failed (unexpected response):'), d)
335 for l in output.splitlines(True):
335 for l in output.splitlines(True):
336 self.ui.status(_('remote: '), l)
336 self.ui.status(_('remote: '), l)
337 yield d
337 yield d
338
338
339 @batchable
339 @batchable
340 def listkeys(self, namespace):
340 def listkeys(self, namespace):
341 if not self.capable('pushkey'):
341 if not self.capable('pushkey'):
342 yield {}, None
342 yield {}, None
343 f = future()
343 f = future()
344 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
344 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
345 yield {'namespace': encoding.fromlocal(namespace)}, f
345 yield {'namespace': encoding.fromlocal(namespace)}, f
346 d = f.value
346 d = f.value
347 self.ui.debug('received listkey for "%s": %i bytes\n'
347 self.ui.debug('received listkey for "%s": %i bytes\n'
348 % (namespace, len(d)))
348 % (namespace, len(d)))
349 yield pushkeymod.decodekeys(d)
349 yield pushkeymod.decodekeys(d)
350
350
351 def stream_out(self):
351 def stream_out(self):
352 return self._callstream('stream_out')
352 return self._callstream('stream_out')
353
353
354 def changegroup(self, nodes, kind):
354 def changegroup(self, nodes, kind):
355 n = encodelist(nodes)
355 n = encodelist(nodes)
356 f = self._callcompressable("changegroup", roots=n)
356 f = self._callcompressable("changegroup", roots=n)
357 return changegroupmod.cg1unpacker(f, 'UN')
357 return changegroupmod.cg1unpacker(f, 'UN')
358
358
359 def changegroupsubset(self, bases, heads, kind):
359 def changegroupsubset(self, bases, heads, kind):
360 self.requirecap('changegroupsubset', _('look up remote changes'))
360 self.requirecap('changegroupsubset', _('look up remote changes'))
361 bases = encodelist(bases)
361 bases = encodelist(bases)
362 heads = encodelist(heads)
362 heads = encodelist(heads)
363 f = self._callcompressable("changegroupsubset",
363 f = self._callcompressable("changegroupsubset",
364 bases=bases, heads=heads)
364 bases=bases, heads=heads)
365 return changegroupmod.cg1unpacker(f, 'UN')
365 return changegroupmod.cg1unpacker(f, 'UN')
366
366
367 def getbundle(self, source, **kwargs):
367 def getbundle(self, source, **kwargs):
368 self.requirecap('getbundle', _('look up remote changes'))
368 self.requirecap('getbundle', _('look up remote changes'))
369 opts = {}
369 opts = {}
370 bundlecaps = kwargs.get('bundlecaps')
370 bundlecaps = kwargs.get('bundlecaps')
371 if bundlecaps is not None:
371 if bundlecaps is not None:
372 kwargs['bundlecaps'] = sorted(bundlecaps)
372 kwargs['bundlecaps'] = sorted(bundlecaps)
373 else:
373 else:
374 bundlecaps = () # kwargs could have it to None
374 bundlecaps = () # kwargs could have it to None
375 for key, value in kwargs.iteritems():
375 for key, value in kwargs.iteritems():
376 if value is None:
376 if value is None:
377 continue
377 continue
378 keytype = gboptsmap.get(key)
378 keytype = gboptsmap.get(key)
379 if keytype is None:
379 if keytype is None:
380 assert False, 'unexpected'
380 assert False, 'unexpected'
381 elif keytype == 'nodes':
381 elif keytype == 'nodes':
382 value = encodelist(value)
382 value = encodelist(value)
383 elif keytype in ('csv', 'scsv'):
383 elif keytype in ('csv', 'scsv'):
384 value = ','.join(value)
384 value = ','.join(value)
385 elif keytype == 'boolean':
385 elif keytype == 'boolean':
386 value = '%i' % bool(value)
386 value = '%i' % bool(value)
387 elif keytype != 'plain':
387 elif keytype != 'plain':
388 raise KeyError('unknown getbundle option type %s'
388 raise KeyError('unknown getbundle option type %s'
389 % keytype)
389 % keytype)
390 opts[key] = value
390 opts[key] = value
391 f = self._callcompressable("getbundle", **opts)
391 f = self._callcompressable("getbundle", **opts)
392 if any((cap.startswith('HG2') for cap in bundlecaps)):
392 if any((cap.startswith('HG2') for cap in bundlecaps)):
393 return bundle2.getunbundler(self.ui, f)
393 return bundle2.getunbundler(self.ui, f)
394 else:
394 else:
395 return changegroupmod.cg1unpacker(f, 'UN')
395 return changegroupmod.cg1unpacker(f, 'UN')
396
396
397 def unbundle(self, cg, heads, source):
397 def unbundle(self, cg, heads, source):
398 '''Send cg (a readable file-like object representing the
398 '''Send cg (a readable file-like object representing the
399 changegroup to push, typically a chunkbuffer object) to the
399 changegroup to push, typically a chunkbuffer object) to the
400 remote server as a bundle.
400 remote server as a bundle.
401
401
402 When pushing a bundle10 stream, return an integer indicating the
402 When pushing a bundle10 stream, return an integer indicating the
403 result of the push (see localrepository.addchangegroup()).
403 result of the push (see localrepository.addchangegroup()).
404
404
405 When pushing a bundle20 stream, return a bundle20 stream.'''
405 When pushing a bundle20 stream, return a bundle20 stream.'''
406
406
407 if heads != ['force'] and self.capable('unbundlehash'):
407 if heads != ['force'] and self.capable('unbundlehash'):
408 heads = encodelist(['hashed',
408 heads = encodelist(['hashed',
409 util.sha1(''.join(sorted(heads))).digest()])
409 util.sha1(''.join(sorted(heads))).digest()])
410 else:
410 else:
411 heads = encodelist(heads)
411 heads = encodelist(heads)
412
412
413 if util.safehasattr(cg, 'deltaheader'):
413 if util.safehasattr(cg, 'deltaheader'):
414 # this a bundle10, do the old style call sequence
414 # this a bundle10, do the old style call sequence
415 ret, output = self._callpush("unbundle", cg, heads=heads)
415 ret, output = self._callpush("unbundle", cg, heads=heads)
416 if ret == "":
416 if ret == "":
417 raise error.ResponseError(
417 raise error.ResponseError(
418 _('push failed:'), output)
418 _('push failed:'), output)
419 try:
419 try:
420 ret = int(ret)
420 ret = int(ret)
421 except ValueError:
421 except ValueError:
422 raise error.ResponseError(
422 raise error.ResponseError(
423 _('push failed (unexpected response):'), ret)
423 _('push failed (unexpected response):'), ret)
424
424
425 for l in output.splitlines(True):
425 for l in output.splitlines(True):
426 self.ui.status(_('remote: '), l)
426 self.ui.status(_('remote: '), l)
427 else:
427 else:
428 # bundle2 push. Send a stream, fetch a stream.
428 # bundle2 push. Send a stream, fetch a stream.
429 stream = self._calltwowaystream('unbundle', cg, heads=heads)
429 stream = self._calltwowaystream('unbundle', cg, heads=heads)
430 ret = bundle2.getunbundler(self.ui, stream)
430 ret = bundle2.getunbundler(self.ui, stream)
431 return ret
431 return ret
432
432
433 def debugwireargs(self, one, two, three=None, four=None, five=None):
433 def debugwireargs(self, one, two, three=None, four=None, five=None):
434 # don't pass optional arguments left at their default value
434 # don't pass optional arguments left at their default value
435 opts = {}
435 opts = {}
436 if three is not None:
436 if three is not None:
437 opts['three'] = three
437 opts['three'] = three
438 if four is not None:
438 if four is not None:
439 opts['four'] = four
439 opts['four'] = four
440 return self._call('debugwireargs', one=one, two=two, **opts)
440 return self._call('debugwireargs', one=one, two=two, **opts)
441
441
442 def _call(self, cmd, **args):
442 def _call(self, cmd, **args):
443 """execute <cmd> on the server
443 """execute <cmd> on the server
444
444
445 The command is expected to return a simple string.
445 The command is expected to return a simple string.
446
446
447 returns the server reply as a string."""
447 returns the server reply as a string."""
448 raise NotImplementedError()
448 raise NotImplementedError()
449
449
450 def _callstream(self, cmd, **args):
450 def _callstream(self, cmd, **args):
451 """execute <cmd> on the server
451 """execute <cmd> on the server
452
452
453 The command is expected to return a stream. Note that if the
453 The command is expected to return a stream. Note that if the
454 command doesn't return a stream, _callstream behaves
454 command doesn't return a stream, _callstream behaves
455 differently for ssh and http peers.
455 differently for ssh and http peers.
456
456
457 returns the server reply as a file like object.
457 returns the server reply as a file like object.
458 """
458 """
459 raise NotImplementedError()
459 raise NotImplementedError()
460
460
461 def _callcompressable(self, cmd, **args):
461 def _callcompressable(self, cmd, **args):
462 """execute <cmd> on the server
462 """execute <cmd> on the server
463
463
464 The command is expected to return a stream.
464 The command is expected to return a stream.
465
465
466 The stream may have been compressed in some implementations. This
466 The stream may have been compressed in some implementations. This
467 function takes care of the decompression. This is the only difference
467 function takes care of the decompression. This is the only difference
468 with _callstream.
468 with _callstream.
469
469
470 returns the server reply as a file like object.
470 returns the server reply as a file like object.
471 """
471 """
472 raise NotImplementedError()
472 raise NotImplementedError()
473
473
474 def _callpush(self, cmd, fp, **args):
474 def _callpush(self, cmd, fp, **args):
475 """execute a <cmd> on server
475 """execute a <cmd> on server
476
476
477 The command is expected to be related to a push. Push has a special
477 The command is expected to be related to a push. Push has a special
478 return method.
478 return method.
479
479
480 returns the server reply as a (ret, output) tuple. ret is either
480 returns the server reply as a (ret, output) tuple. ret is either
481 empty (error) or a stringified int.
481 empty (error) or a stringified int.
482 """
482 """
483 raise NotImplementedError()
483 raise NotImplementedError()
484
484
485 def _calltwowaystream(self, cmd, fp, **args):
485 def _calltwowaystream(self, cmd, fp, **args):
486 """execute <cmd> on server
486 """execute <cmd> on server
487
487
488 The command will send a stream to the server and get a stream in reply.
488 The command will send a stream to the server and get a stream in reply.
489 """
489 """
490 raise NotImplementedError()
490 raise NotImplementedError()
491
491
492 def _abort(self, exception):
492 def _abort(self, exception):
493 """clearly abort the wire protocol connection and raise the exception
493 """clearly abort the wire protocol connection and raise the exception
494 """
494 """
495 raise NotImplementedError()
495 raise NotImplementedError()
496
496
497 # server side
497 # server side
498
498
499 # wire protocol command can either return a string or one of these classes.
499 # wire protocol command can either return a string or one of these classes.
500 class streamres(object):
500 class streamres(object):
501 """wireproto reply: binary stream
501 """wireproto reply: binary stream
502
502
503 The call was successful and the result is a stream.
503 The call was successful and the result is a stream.
504 Iterate on the `self.gen` attribute to retrieve chunks.
504 Iterate on the `self.gen` attribute to retrieve chunks.
505 """
505 """
506 def __init__(self, gen):
506 def __init__(self, gen):
507 self.gen = gen
507 self.gen = gen
508
508
509 class pushres(object):
509 class pushres(object):
510 """wireproto reply: success with simple integer return
510 """wireproto reply: success with simple integer return
511
511
512 The call was successful and returned an integer contained in `self.res`.
512 The call was successful and returned an integer contained in `self.res`.
513 """
513 """
514 def __init__(self, res):
514 def __init__(self, res):
515 self.res = res
515 self.res = res
516
516
517 class pusherr(object):
517 class pusherr(object):
518 """wireproto reply: failure
518 """wireproto reply: failure
519
519
520 The call failed. The `self.res` attribute contains the error message.
520 The call failed. The `self.res` attribute contains the error message.
521 """
521 """
522 def __init__(self, res):
522 def __init__(self, res):
523 self.res = res
523 self.res = res
524
524
525 class ooberror(object):
525 class ooberror(object):
526 """wireproto reply: failure of a batch of operation
526 """wireproto reply: failure of a batch of operation
527
527
528 Something failed during a batch call. The error message is stored in
528 Something failed during a batch call. The error message is stored in
529 `self.message`.
529 `self.message`.
530 """
530 """
531 def __init__(self, message):
531 def __init__(self, message):
532 self.message = message
532 self.message = message
533
533
534 def dispatch(repo, proto, command):
534 def dispatch(repo, proto, command):
535 repo = repo.filtered("served")
535 repo = repo.filtered("served")
536 func, spec = commands[command]
536 func, spec = commands[command]
537 args = proto.getargs(spec)
537 args = proto.getargs(spec)
538 return func(repo, proto, *args)
538 return func(repo, proto, *args)
539
539
540 def options(cmd, keys, others):
540 def options(cmd, keys, others):
541 opts = {}
541 opts = {}
542 for k in keys:
542 for k in keys:
543 if k in others:
543 if k in others:
544 opts[k] = others[k]
544 opts[k] = others[k]
545 del others[k]
545 del others[k]
546 if others:
546 if others:
547 sys.stderr.write("warning: %s ignored unexpected arguments %s\n"
547 sys.stderr.write("warning: %s ignored unexpected arguments %s\n"
548 % (cmd, ",".join(others)))
548 % (cmd, ",".join(others)))
549 return opts
549 return opts
550
550
551 def bundle1allowed(repo, action):
551 def bundle1allowed(repo, action):
552 """Whether a bundle1 operation is allowed from the server.
552 """Whether a bundle1 operation is allowed from the server.
553
553
554 Priority is:
554 Priority is:
555
555
556 1. server.bundle1gd.<action> (if generaldelta active)
556 1. server.bundle1gd.<action> (if generaldelta active)
557 2. server.bundle1.<action>
557 2. server.bundle1.<action>
558 3. server.bundle1gd (if generaldelta active)
558 3. server.bundle1gd (if generaldelta active)
559 4. server.bundle1
559 4. server.bundle1
560 """
560 """
561 ui = repo.ui
561 ui = repo.ui
562 gd = 'generaldelta' in repo.requirements
562 gd = 'generaldelta' in repo.requirements
563
563
564 if gd:
564 if gd:
565 v = ui.configbool('server', 'bundle1gd.%s' % action, None)
565 v = ui.configbool('server', 'bundle1gd.%s' % action, None)
566 if v is not None:
566 if v is not None:
567 return v
567 return v
568
568
569 v = ui.configbool('server', 'bundle1.%s' % action, None)
569 v = ui.configbool('server', 'bundle1.%s' % action, None)
570 if v is not None:
570 if v is not None:
571 return v
571 return v
572
572
573 if gd:
573 if gd:
574 v = ui.configbool('server', 'bundle1gd', None)
574 v = ui.configbool('server', 'bundle1gd', None)
575 if v is not None:
575 if v is not None:
576 return v
576 return v
577
577
578 return ui.configbool('server', 'bundle1', True)
578 return ui.configbool('server', 'bundle1', True)
579
579
580 # list of commands
580 # list of commands
581 commands = {}
581 commands = {}
582
582
583 def wireprotocommand(name, args=''):
583 def wireprotocommand(name, args=''):
584 """decorator for wire protocol command"""
584 """decorator for wire protocol command"""
585 def register(func):
585 def register(func):
586 commands[name] = (func, args)
586 commands[name] = (func, args)
587 return func
587 return func
588 return register
588 return register
589
589
590 @wireprotocommand('batch', 'cmds *')
590 @wireprotocommand('batch', 'cmds *')
591 def batch(repo, proto, cmds, others):
591 def batch(repo, proto, cmds, others):
592 repo = repo.filtered("served")
592 repo = repo.filtered("served")
593 res = []
593 res = []
594 for pair in cmds.split(';'):
594 for pair in cmds.split(';'):
595 op, args = pair.split(' ', 1)
595 op, args = pair.split(' ', 1)
596 vals = {}
596 vals = {}
597 for a in args.split(','):
597 for a in args.split(','):
598 if a:
598 if a:
599 n, v = a.split('=')
599 n, v = a.split('=')
600 vals[n] = unescapearg(v)
600 vals[n] = unescapearg(v)
601 func, spec = commands[op]
601 func, spec = commands[op]
602 if spec:
602 if spec:
603 keys = spec.split()
603 keys = spec.split()
604 data = {}
604 data = {}
605 for k in keys:
605 for k in keys:
606 if k == '*':
606 if k == '*':
607 star = {}
607 star = {}
608 for key in vals.keys():
608 for key in vals.keys():
609 if key not in keys:
609 if key not in keys:
610 star[key] = vals[key]
610 star[key] = vals[key]
611 data['*'] = star
611 data['*'] = star
612 else:
612 else:
613 data[k] = vals[k]
613 data[k] = vals[k]
614 result = func(repo, proto, *[data[k] for k in keys])
614 result = func(repo, proto, *[data[k] for k in keys])
615 else:
615 else:
616 result = func(repo, proto)
616 result = func(repo, proto)
617 if isinstance(result, ooberror):
617 if isinstance(result, ooberror):
618 return result
618 return result
619 res.append(escapearg(result))
619 res.append(escapearg(result))
620 return ';'.join(res)
620 return ';'.join(res)
621
621
622 @wireprotocommand('between', 'pairs')
622 @wireprotocommand('between', 'pairs')
623 def between(repo, proto, pairs):
623 def between(repo, proto, pairs):
624 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
624 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
625 r = []
625 r = []
626 for b in repo.between(pairs):
626 for b in repo.between(pairs):
627 r.append(encodelist(b) + "\n")
627 r.append(encodelist(b) + "\n")
628 return "".join(r)
628 return "".join(r)
629
629
630 @wireprotocommand('branchmap')
630 @wireprotocommand('branchmap')
631 def branchmap(repo, proto):
631 def branchmap(repo, proto):
632 branchmap = repo.branchmap()
632 branchmap = repo.branchmap()
633 heads = []
633 heads = []
634 for branch, nodes in branchmap.iteritems():
634 for branch, nodes in branchmap.iteritems():
635 branchname = urllib.quote(encoding.fromlocal(branch))
635 branchname = urllib.quote(encoding.fromlocal(branch))
636 branchnodes = encodelist(nodes)
636 branchnodes = encodelist(nodes)
637 heads.append('%s %s' % (branchname, branchnodes))
637 heads.append('%s %s' % (branchname, branchnodes))
638 return '\n'.join(heads)
638 return '\n'.join(heads)
639
639
640 @wireprotocommand('branches', 'nodes')
640 @wireprotocommand('branches', 'nodes')
641 def branches(repo, proto, nodes):
641 def branches(repo, proto, nodes):
642 nodes = decodelist(nodes)
642 nodes = decodelist(nodes)
643 r = []
643 r = []
644 for b in repo.branches(nodes):
644 for b in repo.branches(nodes):
645 r.append(encodelist(b) + "\n")
645 r.append(encodelist(b) + "\n")
646 return "".join(r)
646 return "".join(r)
647
647
648 @wireprotocommand('clonebundles', '')
648 @wireprotocommand('clonebundles', '')
649 def clonebundles(repo, proto):
649 def clonebundles(repo, proto):
650 """Server command for returning info for available bundles to seed clones.
650 """Server command for returning info for available bundles to seed clones.
651
651
652 Clients will parse this response and determine what bundle to fetch.
652 Clients will parse this response and determine what bundle to fetch.
653
653
654 Extensions may wrap this command to filter or dynamically emit data
654 Extensions may wrap this command to filter or dynamically emit data
655 depending on the request. e.g. you could advertise URLs for the closest
655 depending on the request. e.g. you could advertise URLs for the closest
656 data center given the client's IP address.
656 data center given the client's IP address.
657 """
657 """
658 return repo.opener.tryread('clonebundles.manifest')
658 return repo.opener.tryread('clonebundles.manifest')
659
659
660 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
660 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
661 'known', 'getbundle', 'unbundlehash', 'batch']
661 'known', 'getbundle', 'unbundlehash', 'batch']
662
662
663 def _capabilities(repo, proto):
663 def _capabilities(repo, proto):
664 """return a list of capabilities for a repo
664 """return a list of capabilities for a repo
665
665
666 This function exists to allow extensions to easily wrap capabilities
666 This function exists to allow extensions to easily wrap capabilities
667 computation
667 computation
668
668
669 - returns a lists: easy to alter
669 - returns a lists: easy to alter
670 - change done here will be propagated to both `capabilities` and `hello`
670 - change done here will be propagated to both `capabilities` and `hello`
671 command without any other action needed.
671 command without any other action needed.
672 """
672 """
673 # copy to prevent modification of the global list
673 # copy to prevent modification of the global list
674 caps = list(wireprotocaps)
674 caps = list(wireprotocaps)
675 if streamclone.allowservergeneration(repo.ui):
675 if streamclone.allowservergeneration(repo.ui):
676 if repo.ui.configbool('server', 'preferuncompressed', False):
676 if repo.ui.configbool('server', 'preferuncompressed', False):
677 caps.append('stream-preferred')
677 caps.append('stream-preferred')
678 requiredformats = repo.requirements & repo.supportedformats
678 requiredformats = repo.requirements & repo.supportedformats
679 # if our local revlogs are just revlogv1, add 'stream' cap
679 # if our local revlogs are just revlogv1, add 'stream' cap
680 if not requiredformats - set(('revlogv1',)):
680 if not requiredformats - set(('revlogv1',)):
681 caps.append('stream')
681 caps.append('stream')
682 # otherwise, add 'streamreqs' detailing our local revlog format
682 # otherwise, add 'streamreqs' detailing our local revlog format
683 else:
683 else:
684 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
684 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
685 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
685 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
686 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
686 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
687 caps.append('bundle2=' + urllib.quote(capsblob))
687 caps.append('bundle2=' + urllib.quote(capsblob))
688 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
688 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
689 caps.append(
689 caps.append(
690 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
690 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
691 if repo.ui.configbool('experimental', 'httppostargs', False):
692 caps.append('httppostargs')
691 return caps
693 return caps
692
694
693 # If you are writing an extension and consider wrapping this function. Wrap
695 # If you are writing an extension and consider wrapping this function. Wrap
694 # `_capabilities` instead.
696 # `_capabilities` instead.
695 @wireprotocommand('capabilities')
697 @wireprotocommand('capabilities')
696 def capabilities(repo, proto):
698 def capabilities(repo, proto):
697 return ' '.join(_capabilities(repo, proto))
699 return ' '.join(_capabilities(repo, proto))
698
700
699 @wireprotocommand('changegroup', 'roots')
701 @wireprotocommand('changegroup', 'roots')
700 def changegroup(repo, proto, roots):
702 def changegroup(repo, proto, roots):
701 nodes = decodelist(roots)
703 nodes = decodelist(roots)
702 cg = changegroupmod.changegroup(repo, nodes, 'serve')
704 cg = changegroupmod.changegroup(repo, nodes, 'serve')
703 return streamres(proto.groupchunks(cg))
705 return streamres(proto.groupchunks(cg))
704
706
705 @wireprotocommand('changegroupsubset', 'bases heads')
707 @wireprotocommand('changegroupsubset', 'bases heads')
706 def changegroupsubset(repo, proto, bases, heads):
708 def changegroupsubset(repo, proto, bases, heads):
707 bases = decodelist(bases)
709 bases = decodelist(bases)
708 heads = decodelist(heads)
710 heads = decodelist(heads)
709 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
711 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
710 return streamres(proto.groupchunks(cg))
712 return streamres(proto.groupchunks(cg))
711
713
712 @wireprotocommand('debugwireargs', 'one two *')
714 @wireprotocommand('debugwireargs', 'one two *')
713 def debugwireargs(repo, proto, one, two, others):
715 def debugwireargs(repo, proto, one, two, others):
714 # only accept optional args from the known set
716 # only accept optional args from the known set
715 opts = options('debugwireargs', ['three', 'four'], others)
717 opts = options('debugwireargs', ['three', 'four'], others)
716 return repo.debugwireargs(one, two, **opts)
718 return repo.debugwireargs(one, two, **opts)
717
719
718 # List of options accepted by getbundle.
720 # List of options accepted by getbundle.
719 #
721 #
720 # Meant to be extended by extensions. It is the extension's responsibility to
722 # Meant to be extended by extensions. It is the extension's responsibility to
721 # ensure such options are properly processed in exchange.getbundle.
723 # ensure such options are properly processed in exchange.getbundle.
722 gboptslist = ['heads', 'common', 'bundlecaps']
724 gboptslist = ['heads', 'common', 'bundlecaps']
723
725
724 @wireprotocommand('getbundle', '*')
726 @wireprotocommand('getbundle', '*')
725 def getbundle(repo, proto, others):
727 def getbundle(repo, proto, others):
726 opts = options('getbundle', gboptsmap.keys(), others)
728 opts = options('getbundle', gboptsmap.keys(), others)
727 for k, v in opts.iteritems():
729 for k, v in opts.iteritems():
728 keytype = gboptsmap[k]
730 keytype = gboptsmap[k]
729 if keytype == 'nodes':
731 if keytype == 'nodes':
730 opts[k] = decodelist(v)
732 opts[k] = decodelist(v)
731 elif keytype == 'csv':
733 elif keytype == 'csv':
732 opts[k] = list(v.split(','))
734 opts[k] = list(v.split(','))
733 elif keytype == 'scsv':
735 elif keytype == 'scsv':
734 opts[k] = set(v.split(','))
736 opts[k] = set(v.split(','))
735 elif keytype == 'boolean':
737 elif keytype == 'boolean':
736 # Client should serialize False as '0', which is a non-empty string
738 # Client should serialize False as '0', which is a non-empty string
737 # so it evaluates as a True bool.
739 # so it evaluates as a True bool.
738 if v == '0':
740 if v == '0':
739 opts[k] = False
741 opts[k] = False
740 else:
742 else:
741 opts[k] = bool(v)
743 opts[k] = bool(v)
742 elif keytype != 'plain':
744 elif keytype != 'plain':
743 raise KeyError('unknown getbundle option type %s'
745 raise KeyError('unknown getbundle option type %s'
744 % keytype)
746 % keytype)
745
747
746 if not bundle1allowed(repo, 'pull'):
748 if not bundle1allowed(repo, 'pull'):
747 if not exchange.bundle2requested(opts.get('bundlecaps')):
749 if not exchange.bundle2requested(opts.get('bundlecaps')):
748 return ooberror(bundle2required)
750 return ooberror(bundle2required)
749
751
750 cg = exchange.getbundle(repo, 'serve', **opts)
752 cg = exchange.getbundle(repo, 'serve', **opts)
751 return streamres(proto.groupchunks(cg))
753 return streamres(proto.groupchunks(cg))
752
754
753 @wireprotocommand('heads')
755 @wireprotocommand('heads')
754 def heads(repo, proto):
756 def heads(repo, proto):
755 h = repo.heads()
757 h = repo.heads()
756 return encodelist(h) + "\n"
758 return encodelist(h) + "\n"
757
759
758 @wireprotocommand('hello')
760 @wireprotocommand('hello')
759 def hello(repo, proto):
761 def hello(repo, proto):
760 '''the hello command returns a set of lines describing various
762 '''the hello command returns a set of lines describing various
761 interesting things about the server, in an RFC822-like format.
763 interesting things about the server, in an RFC822-like format.
762 Currently the only one defined is "capabilities", which
764 Currently the only one defined is "capabilities", which
763 consists of a line in the form:
765 consists of a line in the form:
764
766
765 capabilities: space separated list of tokens
767 capabilities: space separated list of tokens
766 '''
768 '''
767 return "capabilities: %s\n" % (capabilities(repo, proto))
769 return "capabilities: %s\n" % (capabilities(repo, proto))
768
770
769 @wireprotocommand('listkeys', 'namespace')
771 @wireprotocommand('listkeys', 'namespace')
770 def listkeys(repo, proto, namespace):
772 def listkeys(repo, proto, namespace):
771 d = repo.listkeys(encoding.tolocal(namespace)).items()
773 d = repo.listkeys(encoding.tolocal(namespace)).items()
772 return pushkeymod.encodekeys(d)
774 return pushkeymod.encodekeys(d)
773
775
774 @wireprotocommand('lookup', 'key')
776 @wireprotocommand('lookup', 'key')
775 def lookup(repo, proto, key):
777 def lookup(repo, proto, key):
776 try:
778 try:
777 k = encoding.tolocal(key)
779 k = encoding.tolocal(key)
778 c = repo[k]
780 c = repo[k]
779 r = c.hex()
781 r = c.hex()
780 success = 1
782 success = 1
781 except Exception as inst:
783 except Exception as inst:
782 r = str(inst)
784 r = str(inst)
783 success = 0
785 success = 0
784 return "%s %s\n" % (success, r)
786 return "%s %s\n" % (success, r)
785
787
786 @wireprotocommand('known', 'nodes *')
788 @wireprotocommand('known', 'nodes *')
787 def known(repo, proto, nodes, others):
789 def known(repo, proto, nodes, others):
788 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
790 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
789
791
790 @wireprotocommand('pushkey', 'namespace key old new')
792 @wireprotocommand('pushkey', 'namespace key old new')
791 def pushkey(repo, proto, namespace, key, old, new):
793 def pushkey(repo, proto, namespace, key, old, new):
792 # compatibility with pre-1.8 clients which were accidentally
794 # compatibility with pre-1.8 clients which were accidentally
793 # sending raw binary nodes rather than utf-8-encoded hex
795 # sending raw binary nodes rather than utf-8-encoded hex
794 if len(new) == 20 and new.encode('string-escape') != new:
796 if len(new) == 20 and new.encode('string-escape') != new:
795 # looks like it could be a binary node
797 # looks like it could be a binary node
796 try:
798 try:
797 new.decode('utf-8')
799 new.decode('utf-8')
798 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
800 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
799 except UnicodeDecodeError:
801 except UnicodeDecodeError:
800 pass # binary, leave unmodified
802 pass # binary, leave unmodified
801 else:
803 else:
802 new = encoding.tolocal(new) # normal path
804 new = encoding.tolocal(new) # normal path
803
805
804 if util.safehasattr(proto, 'restore'):
806 if util.safehasattr(proto, 'restore'):
805
807
806 proto.redirect()
808 proto.redirect()
807
809
808 try:
810 try:
809 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
811 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
810 encoding.tolocal(old), new) or False
812 encoding.tolocal(old), new) or False
811 except error.Abort:
813 except error.Abort:
812 r = False
814 r = False
813
815
814 output = proto.restore()
816 output = proto.restore()
815
817
816 return '%s\n%s' % (int(r), output)
818 return '%s\n%s' % (int(r), output)
817
819
818 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
820 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
819 encoding.tolocal(old), new)
821 encoding.tolocal(old), new)
820 return '%s\n' % int(r)
822 return '%s\n' % int(r)
821
823
822 @wireprotocommand('stream_out')
824 @wireprotocommand('stream_out')
823 def stream(repo, proto):
825 def stream(repo, proto):
824 '''If the server supports streaming clone, it advertises the "stream"
826 '''If the server supports streaming clone, it advertises the "stream"
825 capability with a value representing the version and flags of the repo
827 capability with a value representing the version and flags of the repo
826 it is serving. Client checks to see if it understands the format.
828 it is serving. Client checks to see if it understands the format.
827 '''
829 '''
828 if not streamclone.allowservergeneration(repo.ui):
830 if not streamclone.allowservergeneration(repo.ui):
829 return '1\n'
831 return '1\n'
830
832
831 def getstream(it):
833 def getstream(it):
832 yield '0\n'
834 yield '0\n'
833 for chunk in it:
835 for chunk in it:
834 yield chunk
836 yield chunk
835
837
836 try:
838 try:
837 # LockError may be raised before the first result is yielded. Don't
839 # LockError may be raised before the first result is yielded. Don't
838 # emit output until we're sure we got the lock successfully.
840 # emit output until we're sure we got the lock successfully.
839 it = streamclone.generatev1wireproto(repo)
841 it = streamclone.generatev1wireproto(repo)
840 return streamres(getstream(it))
842 return streamres(getstream(it))
841 except error.LockError:
843 except error.LockError:
842 return '2\n'
844 return '2\n'
843
845
844 @wireprotocommand('unbundle', 'heads')
846 @wireprotocommand('unbundle', 'heads')
845 def unbundle(repo, proto, heads):
847 def unbundle(repo, proto, heads):
846 their_heads = decodelist(heads)
848 their_heads = decodelist(heads)
847
849
848 try:
850 try:
849 proto.redirect()
851 proto.redirect()
850
852
851 exchange.check_heads(repo, their_heads, 'preparing changes')
853 exchange.check_heads(repo, their_heads, 'preparing changes')
852
854
853 # write bundle data to temporary file because it can be big
855 # write bundle data to temporary file because it can be big
854 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
856 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
855 fp = os.fdopen(fd, 'wb+')
857 fp = os.fdopen(fd, 'wb+')
856 r = 0
858 r = 0
857 try:
859 try:
858 proto.getfile(fp)
860 proto.getfile(fp)
859 fp.seek(0)
861 fp.seek(0)
860 gen = exchange.readbundle(repo.ui, fp, None)
862 gen = exchange.readbundle(repo.ui, fp, None)
861 if (isinstance(gen, changegroupmod.cg1unpacker)
863 if (isinstance(gen, changegroupmod.cg1unpacker)
862 and not bundle1allowed(repo, 'push')):
864 and not bundle1allowed(repo, 'push')):
863 return ooberror(bundle2required)
865 return ooberror(bundle2required)
864
866
865 r = exchange.unbundle(repo, gen, their_heads, 'serve',
867 r = exchange.unbundle(repo, gen, their_heads, 'serve',
866 proto._client())
868 proto._client())
867 if util.safehasattr(r, 'addpart'):
869 if util.safehasattr(r, 'addpart'):
868 # The return looks streamable, we are in the bundle2 case and
870 # The return looks streamable, we are in the bundle2 case and
869 # should return a stream.
871 # should return a stream.
870 return streamres(r.getchunks())
872 return streamres(r.getchunks())
871 return pushres(r)
873 return pushres(r)
872
874
873 finally:
875 finally:
874 fp.close()
876 fp.close()
875 os.unlink(tempname)
877 os.unlink(tempname)
876
878
877 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
879 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
878 # handle non-bundle2 case first
880 # handle non-bundle2 case first
879 if not getattr(exc, 'duringunbundle2', False):
881 if not getattr(exc, 'duringunbundle2', False):
880 try:
882 try:
881 raise
883 raise
882 except error.Abort:
884 except error.Abort:
883 # The old code we moved used sys.stderr directly.
885 # The old code we moved used sys.stderr directly.
884 # We did not change it to minimise code change.
886 # We did not change it to minimise code change.
885 # This need to be moved to something proper.
887 # This need to be moved to something proper.
886 # Feel free to do it.
888 # Feel free to do it.
887 sys.stderr.write("abort: %s\n" % exc)
889 sys.stderr.write("abort: %s\n" % exc)
888 return pushres(0)
890 return pushres(0)
889 except error.PushRaced:
891 except error.PushRaced:
890 return pusherr(str(exc))
892 return pusherr(str(exc))
891
893
892 bundler = bundle2.bundle20(repo.ui)
894 bundler = bundle2.bundle20(repo.ui)
893 for out in getattr(exc, '_bundle2salvagedoutput', ()):
895 for out in getattr(exc, '_bundle2salvagedoutput', ()):
894 bundler.addpart(out)
896 bundler.addpart(out)
895 try:
897 try:
896 try:
898 try:
897 raise
899 raise
898 except error.PushkeyFailed as exc:
900 except error.PushkeyFailed as exc:
899 # check client caps
901 # check client caps
900 remotecaps = getattr(exc, '_replycaps', None)
902 remotecaps = getattr(exc, '_replycaps', None)
901 if (remotecaps is not None
903 if (remotecaps is not None
902 and 'pushkey' not in remotecaps.get('error', ())):
904 and 'pushkey' not in remotecaps.get('error', ())):
903 # no support remote side, fallback to Abort handler.
905 # no support remote side, fallback to Abort handler.
904 raise
906 raise
905 part = bundler.newpart('error:pushkey')
907 part = bundler.newpart('error:pushkey')
906 part.addparam('in-reply-to', exc.partid)
908 part.addparam('in-reply-to', exc.partid)
907 if exc.namespace is not None:
909 if exc.namespace is not None:
908 part.addparam('namespace', exc.namespace, mandatory=False)
910 part.addparam('namespace', exc.namespace, mandatory=False)
909 if exc.key is not None:
911 if exc.key is not None:
910 part.addparam('key', exc.key, mandatory=False)
912 part.addparam('key', exc.key, mandatory=False)
911 if exc.new is not None:
913 if exc.new is not None:
912 part.addparam('new', exc.new, mandatory=False)
914 part.addparam('new', exc.new, mandatory=False)
913 if exc.old is not None:
915 if exc.old is not None:
914 part.addparam('old', exc.old, mandatory=False)
916 part.addparam('old', exc.old, mandatory=False)
915 if exc.ret is not None:
917 if exc.ret is not None:
916 part.addparam('ret', exc.ret, mandatory=False)
918 part.addparam('ret', exc.ret, mandatory=False)
917 except error.BundleValueError as exc:
919 except error.BundleValueError as exc:
918 errpart = bundler.newpart('error:unsupportedcontent')
920 errpart = bundler.newpart('error:unsupportedcontent')
919 if exc.parttype is not None:
921 if exc.parttype is not None:
920 errpart.addparam('parttype', exc.parttype)
922 errpart.addparam('parttype', exc.parttype)
921 if exc.params:
923 if exc.params:
922 errpart.addparam('params', '\0'.join(exc.params))
924 errpart.addparam('params', '\0'.join(exc.params))
923 except error.Abort as exc:
925 except error.Abort as exc:
924 manargs = [('message', str(exc))]
926 manargs = [('message', str(exc))]
925 advargs = []
927 advargs = []
926 if exc.hint is not None:
928 if exc.hint is not None:
927 advargs.append(('hint', exc.hint))
929 advargs.append(('hint', exc.hint))
928 bundler.addpart(bundle2.bundlepart('error:abort',
930 bundler.addpart(bundle2.bundlepart('error:abort',
929 manargs, advargs))
931 manargs, advargs))
930 except error.PushRaced as exc:
932 except error.PushRaced as exc:
931 bundler.newpart('error:pushraced', [('message', str(exc))])
933 bundler.newpart('error:pushraced', [('message', str(exc))])
932 return streamres(bundler.getchunks())
934 return streamres(bundler.getchunks())
@@ -1,105 +1,162
1 #require killdaemons
1 #require killdaemons
2
2
3 Test wire protocol argument passing
3 Test wire protocol argument passing
4
4
5 Setup repo:
5 Setup repo:
6
6
7 $ hg init repo
7 $ hg init repo
8
8
9 Local:
9 Local:
10
10
11 $ hg debugwireargs repo eins zwei --three drei --four vier
11 $ hg debugwireargs repo eins zwei --three drei --four vier
12 eins zwei drei vier None
12 eins zwei drei vier None
13 $ hg debugwireargs repo eins zwei --four vier
13 $ hg debugwireargs repo eins zwei --four vier
14 eins zwei None vier None
14 eins zwei None vier None
15 $ hg debugwireargs repo eins zwei
15 $ hg debugwireargs repo eins zwei
16 eins zwei None None None
16 eins zwei None None None
17 $ hg debugwireargs repo eins zwei --five fuenf
17 $ hg debugwireargs repo eins zwei --five fuenf
18 eins zwei None None fuenf
18 eins zwei None None fuenf
19
19
20 HTTP:
20 HTTP:
21
21
22 $ hg serve -R repo -p $HGPORT -d --pid-file=hg1.pid -E error.log -A access.log
22 $ hg serve -R repo -p $HGPORT -d --pid-file=hg1.pid \
23 > -E error.log -A access.log \
24 > --config experimental.httppostargs=yes
23 $ cat hg1.pid >> $DAEMON_PIDS
25 $ cat hg1.pid >> $DAEMON_PIDS
24
26
25 $ hg debugwireargs http://localhost:$HGPORT/ un deux trois quatre
27 $ hg debugwireargs http://localhost:$HGPORT/ un deux trois quatre
26 un deux trois quatre None
28 un deux trois quatre None
27 $ hg debugwireargs http://localhost:$HGPORT/ \ un deux trois\ qu\ \ atre
29 $ hg debugwireargs http://localhost:$HGPORT/ \ un deux trois\ qu\ \ atre
28 un deux trois qu atre None
30 un deux trois qu atre None
29 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --four vier
31 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --four vier
30 eins zwei None vier None
32 eins zwei None vier None
31 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei
33 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei
32 eins zwei None None None
34 eins zwei None None None
33 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --five fuenf
35 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --five fuenf
34 eins zwei None None None
36 eins zwei None None None
35 $ hg debugwireargs http://localhost:$HGPORT/ un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
37 $ hg debugwireargs http://localhost:$HGPORT/ un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
36 un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx None
38 un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx None
37 $ cat error.log
39 $ cat error.log
38 $ cat access.log
40 $ cat access.log
39 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
41 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
42 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:39 (glob)
43 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:39 (glob)
44 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
45 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:43 (glob)
46 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:43 (glob)
47 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
48 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:27 (glob)
49 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:27 (glob)
50 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
51 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
52 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
53 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
54 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
55 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
56 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
57 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:1033 (glob)
58 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:1033 (glob)
59
60 HTTP without args-in-POST:
61 $ hg serve -R repo -p $HGPORT1 -d --pid-file=hg1.pid -E error.log -A access.log
62 $ cat hg1.pid >> $DAEMON_PIDS
63
64 $ hg debugwireargs http://localhost:$HGPORT1/ un deux trois quatre
65 un deux trois quatre None
66 $ hg debugwireargs http://localhost:$HGPORT1/ \ un deux trois\ qu\ \ atre
67 un deux trois qu atre None
68 $ hg debugwireargs http://localhost:$HGPORT1/ eins zwei --four vier
69 eins zwei None vier None
70 $ hg debugwireargs http://localhost:$HGPORT1/ eins zwei
71 eins zwei None None None
72 $ hg debugwireargs http://localhost:$HGPORT1/ eins zwei --five fuenf
73 eins zwei None None None
74 $ hg debugwireargs http://localhost:$HGPORT1/ un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
75 un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx None
76 $ cat error.log
77 $ cat access.log
78 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
79 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:39 (glob)
80 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:39 (glob)
81 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
82 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:43 (glob)
83 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:43 (glob)
84 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
85 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:27 (glob)
86 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:27 (glob)
87 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
88 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
89 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
90 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
91 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
92 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:17 (glob)
93 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
94 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:1033 (glob)
95 * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:1033 (glob)
96 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
40 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux (glob)
97 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux (glob)
41 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux (glob)
98 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux (glob)
42 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
99 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
43 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux (glob)
100 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux (glob)
44 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux (glob)
101 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux (glob)
45 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
102 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
46 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei (glob)
103 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei (glob)
47 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei (glob)
104 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei (glob)
48 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
105 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
49 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
106 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
50 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
107 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
51 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
108 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
52 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
109 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
53 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
110 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob)
54 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
111 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
55 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux (glob)
112 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux (glob)
56 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux (glob)
113 * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux (glob)
57
114
58 HTTP without the httpheader capability:
115 HTTP without the httpheader capability:
59
116
60 $ HGRCPATH="`pwd`/repo/.hgrc"
117 $ HGRCPATH="`pwd`/repo/.hgrc"
61 $ export HGRCPATH
118 $ export HGRCPATH
62 $ CAP=httpheader
119 $ CAP=httpheader
63 $ . "$TESTDIR/notcapable"
120 $ . "$TESTDIR/notcapable"
64
121
65 $ hg serve -R repo -p $HGPORT2 -d --pid-file=hg2.pid -E error2.log -A access2.log
122 $ hg serve -R repo -p $HGPORT2 -d --pid-file=hg2.pid -E error2.log -A access2.log
66 $ cat hg2.pid >> $DAEMON_PIDS
123 $ cat hg2.pid >> $DAEMON_PIDS
67
124
68 $ hg debugwireargs http://localhost:$HGPORT2/ un deux trois quatre
125 $ hg debugwireargs http://localhost:$HGPORT2/ un deux trois quatre
69 un deux trois quatre None
126 un deux trois quatre None
70 $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei --four vier
127 $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei --four vier
71 eins zwei None vier None
128 eins zwei None vier None
72 $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei
129 $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei
73 eins zwei None None None
130 eins zwei None None None
74 $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei --five fuenf
131 $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei --five fuenf
75 eins zwei None None None
132 eins zwei None None None
76 $ cat error2.log
133 $ cat error2.log
77 $ cat access2.log
134 $ cat access2.log
78 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
135 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
79 * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
136 * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
80 * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
137 * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
81 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
138 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
82 * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
139 * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
83 * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
140 * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
84 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
141 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
85 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
142 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
86 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
143 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
87 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
144 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
88 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
145 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
89 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
146 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
90
147
91 SSH (try to exercise the ssh functionality with a dummy script):
148 SSH (try to exercise the ssh functionality with a dummy script):
92
149
93 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo uno due tre quattro
150 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo uno due tre quattro
94 uno due tre quattro None
151 uno due tre quattro None
95 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --four vier
152 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --four vier
96 eins zwei None vier None
153 eins zwei None vier None
97 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei
154 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei
98 eins zwei None None None
155 eins zwei None None None
99 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --five fuenf
156 $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --five fuenf
100 eins zwei None None None
157 eins zwei None None None
101
158
102 Explicitly kill daemons to let the test exit on Windows
159 Explicitly kill daemons to let the test exit on Windows
103
160
104 $ killdaemons.py
161 $ killdaemons.py
105
162
General Comments 0
You need to be logged in to leave comments. Login now