##// END OF EJS Templates
sshpeer: add support for request tracing...
Boris Feld -
r35717:f7ef49e4 default
parent child Browse files
Show More
@@ -1,362 +1,373 b''
1 # sshpeer.py - ssh repository proxy class for mercurial
1 # sshpeer.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 pycompat,
15 pycompat,
16 util,
16 util,
17 wireproto,
17 wireproto,
18 )
18 )
19
19
20 def _serverquote(s):
20 def _serverquote(s):
21 """quote a string for the remote shell ... which we assume is sh"""
21 """quote a string for the remote shell ... which we assume is sh"""
22 if not s:
22 if not s:
23 return s
23 return s
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
25 return s
25 return s
26 return "'%s'" % s.replace("'", "'\\''")
26 return "'%s'" % s.replace("'", "'\\''")
27
27
28 def _forwardoutput(ui, pipe):
28 def _forwardoutput(ui, pipe):
29 """display all data currently available on pipe as remote output.
29 """display all data currently available on pipe as remote output.
30
30
31 This is non blocking."""
31 This is non blocking."""
32 s = util.readpipe(pipe)
32 s = util.readpipe(pipe)
33 if s:
33 if s:
34 for l in s.splitlines():
34 for l in s.splitlines():
35 ui.status(_("remote: "), l, '\n')
35 ui.status(_("remote: "), l, '\n')
36
36
37 class doublepipe(object):
37 class doublepipe(object):
38 """Operate a side-channel pipe in addition of a main one
38 """Operate a side-channel pipe in addition of a main one
39
39
40 The side-channel pipe contains server output to be forwarded to the user
40 The side-channel pipe contains server output to be forwarded to the user
41 input. The double pipe will behave as the "main" pipe, but will ensure the
41 input. The double pipe will behave as the "main" pipe, but will ensure the
42 content of the "side" pipe is properly processed while we wait for blocking
42 content of the "side" pipe is properly processed while we wait for blocking
43 call on the "main" pipe.
43 call on the "main" pipe.
44
44
45 If large amounts of data are read from "main", the forward will cease after
45 If large amounts of data are read from "main", the forward will cease after
46 the first bytes start to appear. This simplifies the implementation
46 the first bytes start to appear. This simplifies the implementation
47 without affecting actual output of sshpeer too much as we rarely issue
47 without affecting actual output of sshpeer too much as we rarely issue
48 large read for data not yet emitted by the server.
48 large read for data not yet emitted by the server.
49
49
50 The main pipe is expected to be a 'bufferedinputpipe' from the util module
50 The main pipe is expected to be a 'bufferedinputpipe' from the util module
51 that handle all the os specific bits. This class lives in this module
51 that handle all the os specific bits. This class lives in this module
52 because it focus on behavior specific to the ssh protocol."""
52 because it focus on behavior specific to the ssh protocol."""
53
53
54 def __init__(self, ui, main, side):
54 def __init__(self, ui, main, side):
55 self._ui = ui
55 self._ui = ui
56 self._main = main
56 self._main = main
57 self._side = side
57 self._side = side
58
58
59 def _wait(self):
59 def _wait(self):
60 """wait until some data are available on main or side
60 """wait until some data are available on main or side
61
61
62 return a pair of boolean (ismainready, issideready)
62 return a pair of boolean (ismainready, issideready)
63
63
64 (This will only wait for data if the setup is supported by `util.poll`)
64 (This will only wait for data if the setup is supported by `util.poll`)
65 """
65 """
66 if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe
66 if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe
67 return (True, True) # main has data, assume side is worth poking at.
67 return (True, True) # main has data, assume side is worth poking at.
68 fds = [self._main.fileno(), self._side.fileno()]
68 fds = [self._main.fileno(), self._side.fileno()]
69 try:
69 try:
70 act = util.poll(fds)
70 act = util.poll(fds)
71 except NotImplementedError:
71 except NotImplementedError:
72 # non supported yet case, assume all have data.
72 # non supported yet case, assume all have data.
73 act = fds
73 act = fds
74 return (self._main.fileno() in act, self._side.fileno() in act)
74 return (self._main.fileno() in act, self._side.fileno() in act)
75
75
76 def write(self, data):
76 def write(self, data):
77 return self._call('write', data)
77 return self._call('write', data)
78
78
79 def read(self, size):
79 def read(self, size):
80 r = self._call('read', size)
80 r = self._call('read', size)
81 if size != 0 and not r:
81 if size != 0 and not r:
82 # We've observed a condition that indicates the
82 # We've observed a condition that indicates the
83 # stdout closed unexpectedly. Check stderr one
83 # stdout closed unexpectedly. Check stderr one
84 # more time and snag anything that's there before
84 # more time and snag anything that's there before
85 # letting anyone know the main part of the pipe
85 # letting anyone know the main part of the pipe
86 # closed prematurely.
86 # closed prematurely.
87 _forwardoutput(self._ui, self._side)
87 _forwardoutput(self._ui, self._side)
88 return r
88 return r
89
89
90 def readline(self):
90 def readline(self):
91 return self._call('readline')
91 return self._call('readline')
92
92
93 def _call(self, methname, data=None):
93 def _call(self, methname, data=None):
94 """call <methname> on "main", forward output of "side" while blocking
94 """call <methname> on "main", forward output of "side" while blocking
95 """
95 """
96 # data can be '' or 0
96 # data can be '' or 0
97 if (data is not None and not data) or self._main.closed:
97 if (data is not None and not data) or self._main.closed:
98 _forwardoutput(self._ui, self._side)
98 _forwardoutput(self._ui, self._side)
99 return ''
99 return ''
100 while True:
100 while True:
101 mainready, sideready = self._wait()
101 mainready, sideready = self._wait()
102 if sideready:
102 if sideready:
103 _forwardoutput(self._ui, self._side)
103 _forwardoutput(self._ui, self._side)
104 if mainready:
104 if mainready:
105 meth = getattr(self._main, methname)
105 meth = getattr(self._main, methname)
106 if data is None:
106 if data is None:
107 return meth()
107 return meth()
108 else:
108 else:
109 return meth(data)
109 return meth(data)
110
110
111 def close(self):
111 def close(self):
112 return self._main.close()
112 return self._main.close()
113
113
114 def flush(self):
114 def flush(self):
115 return self._main.flush()
115 return self._main.flush()
116
116
117 class sshpeer(wireproto.wirepeer):
117 class sshpeer(wireproto.wirepeer):
118 def __init__(self, ui, path, create=False):
118 def __init__(self, ui, path, create=False):
119 self._url = path
119 self._url = path
120 self._ui = ui
120 self._ui = ui
121 self._pipeo = self._pipei = self._pipee = None
121 self._pipeo = self._pipei = self._pipee = None
122
122
123 u = util.url(path, parsequery=False, parsefragment=False)
123 u = util.url(path, parsequery=False, parsefragment=False)
124 if u.scheme != 'ssh' or not u.host or u.path is None:
124 if u.scheme != 'ssh' or not u.host or u.path is None:
125 self._abort(error.RepoError(_("couldn't parse location %s") % path))
125 self._abort(error.RepoError(_("couldn't parse location %s") % path))
126
126
127 util.checksafessh(path)
127 util.checksafessh(path)
128
128
129 if u.passwd is not None:
129 if u.passwd is not None:
130 self._abort(error.RepoError(_("password in URL not supported")))
130 self._abort(error.RepoError(_("password in URL not supported")))
131
131
132 self._user = u.user
132 self._user = u.user
133 self._host = u.host
133 self._host = u.host
134 self._port = u.port
134 self._port = u.port
135 self._path = u.path or '.'
135 self._path = u.path or '.'
136
136
137 sshcmd = self.ui.config("ui", "ssh")
137 sshcmd = self.ui.config("ui", "ssh")
138 remotecmd = self.ui.config("ui", "remotecmd")
138 remotecmd = self.ui.config("ui", "remotecmd")
139 sshaddenv = dict(self.ui.configitems("sshenv"))
139 sshaddenv = dict(self.ui.configitems("sshenv"))
140 sshenv = util.shellenviron(sshaddenv)
140 sshenv = util.shellenviron(sshaddenv)
141
141
142 args = util.sshargs(sshcmd, self._host, self._user, self._port)
142 args = util.sshargs(sshcmd, self._host, self._user, self._port)
143
143
144 if create:
144 if create:
145 cmd = '%s %s %s' % (sshcmd, args,
145 cmd = '%s %s %s' % (sshcmd, args,
146 util.shellquote("%s init %s" %
146 util.shellquote("%s init %s" %
147 (_serverquote(remotecmd), _serverquote(self._path))))
147 (_serverquote(remotecmd), _serverquote(self._path))))
148 ui.debug('running %s\n' % cmd)
148 ui.debug('running %s\n' % cmd)
149 res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv)
149 res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv)
150 if res != 0:
150 if res != 0:
151 self._abort(error.RepoError(_("could not create remote repo")))
151 self._abort(error.RepoError(_("could not create remote repo")))
152
152
153 self._validaterepo(sshcmd, args, remotecmd, sshenv)
153 self._validaterepo(sshcmd, args, remotecmd, sshenv)
154
154
155 # Begin of _basepeer interface.
155 # Begin of _basepeer interface.
156
156
157 @util.propertycache
157 @util.propertycache
158 def ui(self):
158 def ui(self):
159 return self._ui
159 return self._ui
160
160
161 def url(self):
161 def url(self):
162 return self._url
162 return self._url
163
163
164 def local(self):
164 def local(self):
165 return None
165 return None
166
166
167 def peer(self):
167 def peer(self):
168 return self
168 return self
169
169
170 def canpush(self):
170 def canpush(self):
171 return True
171 return True
172
172
173 def close(self):
173 def close(self):
174 pass
174 pass
175
175
176 # End of _basepeer interface.
176 # End of _basepeer interface.
177
177
178 # Begin of _basewirecommands interface.
178 # Begin of _basewirecommands interface.
179
179
180 def capabilities(self):
180 def capabilities(self):
181 return self._caps
181 return self._caps
182
182
183 # End of _basewirecommands interface.
183 # End of _basewirecommands interface.
184
184
185 def _validaterepo(self, sshcmd, args, remotecmd, sshenv=None):
185 def _validaterepo(self, sshcmd, args, remotecmd, sshenv=None):
186 # cleanup up previous run
186 # cleanup up previous run
187 self._cleanup()
187 self._cleanup()
188
188
189 cmd = '%s %s %s' % (sshcmd, args,
189 cmd = '%s %s %s' % (sshcmd, args,
190 util.shellquote("%s -R %s serve --stdio" %
190 util.shellquote("%s -R %s serve --stdio" %
191 (_serverquote(remotecmd), _serverquote(self._path))))
191 (_serverquote(remotecmd), _serverquote(self._path))))
192 self.ui.debug('running %s\n' % cmd)
192 self.ui.debug('running %s\n' % cmd)
193 cmd = util.quotecommand(cmd)
193 cmd = util.quotecommand(cmd)
194
194
195 # while self._subprocess isn't used, having it allows the subprocess to
195 # while self._subprocess isn't used, having it allows the subprocess to
196 # to clean up correctly later
196 # to clean up correctly later
197 #
197 #
198 # no buffer allow the use of 'select'
198 # no buffer allow the use of 'select'
199 # feel free to remove buffering and select usage when we ultimately
199 # feel free to remove buffering and select usage when we ultimately
200 # move to threading.
200 # move to threading.
201 sub = util.popen4(cmd, bufsize=0, env=sshenv)
201 sub = util.popen4(cmd, bufsize=0, env=sshenv)
202 self._pipeo, self._pipei, self._pipee, self._subprocess = sub
202 self._pipeo, self._pipei, self._pipee, self._subprocess = sub
203
203
204 self._pipei = util.bufferedinputpipe(self._pipei)
204 self._pipei = util.bufferedinputpipe(self._pipei)
205 self._pipei = doublepipe(self.ui, self._pipei, self._pipee)
205 self._pipei = doublepipe(self.ui, self._pipei, self._pipee)
206 self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
206 self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
207
207
208 def badresponse():
208 def badresponse():
209 msg = _("no suitable response from remote hg")
209 msg = _("no suitable response from remote hg")
210 hint = self.ui.config("ui", "ssherrorhint")
210 hint = self.ui.config("ui", "ssherrorhint")
211 self._abort(error.RepoError(msg, hint=hint))
211 self._abort(error.RepoError(msg, hint=hint))
212
212
213 try:
213 try:
214 # skip any noise generated by remote shell
214 # skip any noise generated by remote shell
215 self._callstream("hello")
215 self._callstream("hello")
216 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
216 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
217 except IOError:
217 except IOError:
218 badresponse()
218 badresponse()
219
219
220 lines = ["", "dummy"]
220 lines = ["", "dummy"]
221 max_noise = 500
221 max_noise = 500
222 while lines[-1] and max_noise:
222 while lines[-1] and max_noise:
223 try:
223 try:
224 l = r.readline()
224 l = r.readline()
225 self._readerr()
225 self._readerr()
226 if lines[-1] == "1\n" and l == "\n":
226 if lines[-1] == "1\n" and l == "\n":
227 break
227 break
228 if l:
228 if l:
229 self.ui.debug("remote: ", l)
229 self.ui.debug("remote: ", l)
230 lines.append(l)
230 lines.append(l)
231 max_noise -= 1
231 max_noise -= 1
232 except IOError:
232 except IOError:
233 badresponse()
233 badresponse()
234 else:
234 else:
235 badresponse()
235 badresponse()
236
236
237 self._caps = set()
237 self._caps = set()
238 for l in reversed(lines):
238 for l in reversed(lines):
239 if l.startswith("capabilities:"):
239 if l.startswith("capabilities:"):
240 self._caps.update(l[:-1].split(":")[1].split())
240 self._caps.update(l[:-1].split(":")[1].split())
241 break
241 break
242
242
243 def _readerr(self):
243 def _readerr(self):
244 _forwardoutput(self.ui, self._pipee)
244 _forwardoutput(self.ui, self._pipee)
245
245
246 def _abort(self, exception):
246 def _abort(self, exception):
247 self._cleanup()
247 self._cleanup()
248 raise exception
248 raise exception
249
249
250 def _cleanup(self):
250 def _cleanup(self):
251 if self._pipeo is None:
251 if self._pipeo is None:
252 return
252 return
253 self._pipeo.close()
253 self._pipeo.close()
254 self._pipei.close()
254 self._pipei.close()
255 try:
255 try:
256 # read the error descriptor until EOF
256 # read the error descriptor until EOF
257 for l in self._pipee:
257 for l in self._pipee:
258 self.ui.status(_("remote: "), l)
258 self.ui.status(_("remote: "), l)
259 except (IOError, ValueError):
259 except (IOError, ValueError):
260 pass
260 pass
261 self._pipee.close()
261 self._pipee.close()
262
262
263 __del__ = _cleanup
263 __del__ = _cleanup
264
264
265 def _submitbatch(self, req):
265 def _submitbatch(self, req):
266 rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
266 rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
267 available = self._getamount()
267 available = self._getamount()
268 # TODO this response parsing is probably suboptimal for large
268 # TODO this response parsing is probably suboptimal for large
269 # batches with large responses.
269 # batches with large responses.
270 toread = min(available, 1024)
270 toread = min(available, 1024)
271 work = rsp.read(toread)
271 work = rsp.read(toread)
272 available -= toread
272 available -= toread
273 chunk = work
273 chunk = work
274 while chunk:
274 while chunk:
275 while ';' in work:
275 while ';' in work:
276 one, work = work.split(';', 1)
276 one, work = work.split(';', 1)
277 yield wireproto.unescapearg(one)
277 yield wireproto.unescapearg(one)
278 toread = min(available, 1024)
278 toread = min(available, 1024)
279 chunk = rsp.read(toread)
279 chunk = rsp.read(toread)
280 available -= toread
280 available -= toread
281 work += chunk
281 work += chunk
282 yield wireproto.unescapearg(work)
282 yield wireproto.unescapearg(work)
283
283
284 def _callstream(self, cmd, **args):
284 def _callstream(self, cmd, **args):
285 args = pycompat.byteskwargs(args)
285 args = pycompat.byteskwargs(args)
286 if (self.ui.debugflag
287 and self.ui.configbool('devel', 'debug.peer-request')):
288 dbg = self.ui.debug
289 line = 'devel-peer-request: %s\n'
290 dbg(line % cmd)
291 for key, value in sorted(args.items()):
292 if not isinstance(value, dict):
293 dbg(line % ' %s: %d bytes' % (key, len(value)))
294 else:
295 for dk, dv in sorted(value.items()):
296 dbg(line % ' %s-%s: %d' % (key, dk, len(dv)))
286 self.ui.debug("sending %s command\n" % cmd)
297 self.ui.debug("sending %s command\n" % cmd)
287 self._pipeo.write("%s\n" % cmd)
298 self._pipeo.write("%s\n" % cmd)
288 _func, names = wireproto.commands[cmd]
299 _func, names = wireproto.commands[cmd]
289 keys = names.split()
300 keys = names.split()
290 wireargs = {}
301 wireargs = {}
291 for k in keys:
302 for k in keys:
292 if k == '*':
303 if k == '*':
293 wireargs['*'] = args
304 wireargs['*'] = args
294 break
305 break
295 else:
306 else:
296 wireargs[k] = args[k]
307 wireargs[k] = args[k]
297 del args[k]
308 del args[k]
298 for k, v in sorted(wireargs.iteritems()):
309 for k, v in sorted(wireargs.iteritems()):
299 self._pipeo.write("%s %d\n" % (k, len(v)))
310 self._pipeo.write("%s %d\n" % (k, len(v)))
300 if isinstance(v, dict):
311 if isinstance(v, dict):
301 for dk, dv in v.iteritems():
312 for dk, dv in v.iteritems():
302 self._pipeo.write("%s %d\n" % (dk, len(dv)))
313 self._pipeo.write("%s %d\n" % (dk, len(dv)))
303 self._pipeo.write(dv)
314 self._pipeo.write(dv)
304 else:
315 else:
305 self._pipeo.write(v)
316 self._pipeo.write(v)
306 self._pipeo.flush()
317 self._pipeo.flush()
307
318
308 return self._pipei
319 return self._pipei
309
320
310 def _callcompressable(self, cmd, **args):
321 def _callcompressable(self, cmd, **args):
311 return self._callstream(cmd, **args)
322 return self._callstream(cmd, **args)
312
323
313 def _call(self, cmd, **args):
324 def _call(self, cmd, **args):
314 self._callstream(cmd, **args)
325 self._callstream(cmd, **args)
315 return self._recv()
326 return self._recv()
316
327
317 def _callpush(self, cmd, fp, **args):
328 def _callpush(self, cmd, fp, **args):
318 r = self._call(cmd, **args)
329 r = self._call(cmd, **args)
319 if r:
330 if r:
320 return '', r
331 return '', r
321 for d in iter(lambda: fp.read(4096), ''):
332 for d in iter(lambda: fp.read(4096), ''):
322 self._send(d)
333 self._send(d)
323 self._send("", flush=True)
334 self._send("", flush=True)
324 r = self._recv()
335 r = self._recv()
325 if r:
336 if r:
326 return '', r
337 return '', r
327 return self._recv(), ''
338 return self._recv(), ''
328
339
329 def _calltwowaystream(self, cmd, fp, **args):
340 def _calltwowaystream(self, cmd, fp, **args):
330 r = self._call(cmd, **args)
341 r = self._call(cmd, **args)
331 if r:
342 if r:
332 # XXX needs to be made better
343 # XXX needs to be made better
333 raise error.Abort(_('unexpected remote reply: %s') % r)
344 raise error.Abort(_('unexpected remote reply: %s') % r)
334 for d in iter(lambda: fp.read(4096), ''):
345 for d in iter(lambda: fp.read(4096), ''):
335 self._send(d)
346 self._send(d)
336 self._send("", flush=True)
347 self._send("", flush=True)
337 return self._pipei
348 return self._pipei
338
349
339 def _getamount(self):
350 def _getamount(self):
340 l = self._pipei.readline()
351 l = self._pipei.readline()
341 if l == '\n':
352 if l == '\n':
342 self._readerr()
353 self._readerr()
343 msg = _('check previous remote output')
354 msg = _('check previous remote output')
344 self._abort(error.OutOfBandError(hint=msg))
355 self._abort(error.OutOfBandError(hint=msg))
345 self._readerr()
356 self._readerr()
346 try:
357 try:
347 return int(l)
358 return int(l)
348 except ValueError:
359 except ValueError:
349 self._abort(error.ResponseError(_("unexpected response:"), l))
360 self._abort(error.ResponseError(_("unexpected response:"), l))
350
361
351 def _recv(self):
362 def _recv(self):
352 return self._pipei.read(self._getamount())
363 return self._pipei.read(self._getamount())
353
364
354 def _send(self, data, flush=False):
365 def _send(self, data, flush=False):
355 self._pipeo.write("%d\n" % len(data))
366 self._pipeo.write("%d\n" % len(data))
356 if data:
367 if data:
357 self._pipeo.write(data)
368 self._pipeo.write(data)
358 if flush:
369 if flush:
359 self._pipeo.flush()
370 self._pipeo.flush()
360 self._readerr()
371 self._readerr()
361
372
362 instance = sshpeer
373 instance = sshpeer
@@ -1,616 +1,629 b''
1
1
2 This test tries to exercise the ssh functionality with a dummy script
2 This test tries to exercise the ssh functionality with a dummy script
3
3
4 $ cat <<EOF >> $HGRCPATH
4 $ cat <<EOF >> $HGRCPATH
5 > [format]
5 > [format]
6 > usegeneraldelta=yes
6 > usegeneraldelta=yes
7 > EOF
7 > EOF
8
8
9 creating 'remote' repo
9 creating 'remote' repo
10
10
11 $ hg init remote
11 $ hg init remote
12 $ cd remote
12 $ cd remote
13 $ echo this > foo
13 $ echo this > foo
14 $ echo this > fooO
14 $ echo this > fooO
15 $ hg ci -A -m "init" foo fooO
15 $ hg ci -A -m "init" foo fooO
16
16
17 insert a closed branch (issue4428)
17 insert a closed branch (issue4428)
18
18
19 $ hg up null
19 $ hg up null
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
20 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
21 $ hg branch closed
21 $ hg branch closed
22 marked working directory as branch closed
22 marked working directory as branch closed
23 (branches are permanent and global, did you want a bookmark?)
23 (branches are permanent and global, did you want a bookmark?)
24 $ hg ci -mc0
24 $ hg ci -mc0
25 $ hg ci --close-branch -mc1
25 $ hg ci --close-branch -mc1
26 $ hg up -q default
26 $ hg up -q default
27
27
28 configure for serving
28 configure for serving
29
29
30 $ cat <<EOF > .hg/hgrc
30 $ cat <<EOF > .hg/hgrc
31 > [server]
31 > [server]
32 > uncompressed = True
32 > uncompressed = True
33 >
33 >
34 > [hooks]
34 > [hooks]
35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
35 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
36 > EOF
36 > EOF
37 $ cd ..
37 $ cd ..
38
38
39 repo not found error
39 repo not found error
40
40
41 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
41 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
42 remote: abort: repository nonexistent not found!
42 remote: abort: repository nonexistent not found!
43 abort: no suitable response from remote hg!
43 abort: no suitable response from remote hg!
44 [255]
44 [255]
45
45
46 non-existent absolute path
46 non-existent absolute path
47
47
48 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
48 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
49 remote: abort: repository $TESTTMP/nonexistent not found!
49 remote: abort: repository $TESTTMP/nonexistent not found!
50 abort: no suitable response from remote hg!
50 abort: no suitable response from remote hg!
51 [255]
51 [255]
52
52
53 clone remote via stream
53 clone remote via stream
54
54
55 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
55 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
56 streaming all changes
56 streaming all changes
57 4 files to transfer, 602 bytes of data
57 4 files to transfer, 602 bytes of data
58 transferred 602 bytes in * seconds (*) (glob)
58 transferred 602 bytes in * seconds (*) (glob)
59 searching for changes
59 searching for changes
60 no changes found
60 no changes found
61 updating to branch default
61 updating to branch default
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 $ cd local-stream
63 $ cd local-stream
64 $ hg verify
64 $ hg verify
65 checking changesets
65 checking changesets
66 checking manifests
66 checking manifests
67 crosschecking files in changesets and manifests
67 crosschecking files in changesets and manifests
68 checking files
68 checking files
69 2 files, 3 changesets, 2 total revisions
69 2 files, 3 changesets, 2 total revisions
70 $ hg branches
70 $ hg branches
71 default 0:1160648e36ce
71 default 0:1160648e36ce
72 $ cd ..
72 $ cd ..
73
73
74 clone bookmarks via stream
74 clone bookmarks via stream
75
75
76 $ hg -R local-stream book mybook
76 $ hg -R local-stream book mybook
77 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
77 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
78 streaming all changes
78 streaming all changes
79 4 files to transfer, 602 bytes of data
79 4 files to transfer, 602 bytes of data
80 transferred 602 bytes in * seconds (*) (glob)
80 transferred 602 bytes in * seconds (*) (glob)
81 searching for changes
81 searching for changes
82 no changes found
82 no changes found
83 updating to branch default
83 updating to branch default
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 $ cd stream2
85 $ cd stream2
86 $ hg book
86 $ hg book
87 mybook 0:1160648e36ce
87 mybook 0:1160648e36ce
88 $ cd ..
88 $ cd ..
89 $ rm -rf local-stream stream2
89 $ rm -rf local-stream stream2
90
90
91 clone remote via pull
91 clone remote via pull
92
92
93 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
93 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
94 requesting all changes
94 requesting all changes
95 adding changesets
95 adding changesets
96 adding manifests
96 adding manifests
97 adding file changes
97 adding file changes
98 added 3 changesets with 2 changes to 2 files
98 added 3 changesets with 2 changes to 2 files
99 new changesets 1160648e36ce:ad076bfb429d
99 new changesets 1160648e36ce:ad076bfb429d
100 updating to branch default
100 updating to branch default
101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
102
102
103 verify
103 verify
104
104
105 $ cd local
105 $ cd local
106 $ hg verify
106 $ hg verify
107 checking changesets
107 checking changesets
108 checking manifests
108 checking manifests
109 crosschecking files in changesets and manifests
109 crosschecking files in changesets and manifests
110 checking files
110 checking files
111 2 files, 3 changesets, 2 total revisions
111 2 files, 3 changesets, 2 total revisions
112 $ cat >> .hg/hgrc <<EOF
112 $ cat >> .hg/hgrc <<EOF
113 > [hooks]
113 > [hooks]
114 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
114 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
115 > EOF
115 > EOF
116
116
117 empty default pull
117 empty default pull
118
118
119 $ hg paths
119 $ hg paths
120 default = ssh://user@dummy/remote
120 default = ssh://user@dummy/remote
121 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
121 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
122 pulling from ssh://user@dummy/remote
122 pulling from ssh://user@dummy/remote
123 searching for changes
123 searching for changes
124 no changes found
124 no changes found
125
125
126 pull from wrong ssh URL
126 pull from wrong ssh URL
127
127
128 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
128 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
129 pulling from ssh://user@dummy/doesnotexist
129 pulling from ssh://user@dummy/doesnotexist
130 remote: abort: repository doesnotexist not found!
130 remote: abort: repository doesnotexist not found!
131 abort: no suitable response from remote hg!
131 abort: no suitable response from remote hg!
132 [255]
132 [255]
133
133
134 local change
134 local change
135
135
136 $ echo bleah > foo
136 $ echo bleah > foo
137 $ hg ci -m "add"
137 $ hg ci -m "add"
138
138
139 updating rc
139 updating rc
140
140
141 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
141 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
142 $ echo "[ui]" >> .hg/hgrc
142 $ echo "[ui]" >> .hg/hgrc
143 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
143 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
144
144
145 find outgoing
145 find outgoing
146
146
147 $ hg out ssh://user@dummy/remote
147 $ hg out ssh://user@dummy/remote
148 comparing with ssh://user@dummy/remote
148 comparing with ssh://user@dummy/remote
149 searching for changes
149 searching for changes
150 changeset: 3:a28a9d1a809c
150 changeset: 3:a28a9d1a809c
151 tag: tip
151 tag: tip
152 parent: 0:1160648e36ce
152 parent: 0:1160648e36ce
153 user: test
153 user: test
154 date: Thu Jan 01 00:00:00 1970 +0000
154 date: Thu Jan 01 00:00:00 1970 +0000
155 summary: add
155 summary: add
156
156
157
157
158 find incoming on the remote side
158 find incoming on the remote side
159
159
160 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
160 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
161 comparing with ssh://user@dummy/local
161 comparing with ssh://user@dummy/local
162 searching for changes
162 searching for changes
163 changeset: 3:a28a9d1a809c
163 changeset: 3:a28a9d1a809c
164 tag: tip
164 tag: tip
165 parent: 0:1160648e36ce
165 parent: 0:1160648e36ce
166 user: test
166 user: test
167 date: Thu Jan 01 00:00:00 1970 +0000
167 date: Thu Jan 01 00:00:00 1970 +0000
168 summary: add
168 summary: add
169
169
170
170
171 find incoming on the remote side (using absolute path)
171 find incoming on the remote side (using absolute path)
172
172
173 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
173 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
174 comparing with ssh://user@dummy/$TESTTMP/local
174 comparing with ssh://user@dummy/$TESTTMP/local
175 searching for changes
175 searching for changes
176 changeset: 3:a28a9d1a809c
176 changeset: 3:a28a9d1a809c
177 tag: tip
177 tag: tip
178 parent: 0:1160648e36ce
178 parent: 0:1160648e36ce
179 user: test
179 user: test
180 date: Thu Jan 01 00:00:00 1970 +0000
180 date: Thu Jan 01 00:00:00 1970 +0000
181 summary: add
181 summary: add
182
182
183
183
184 push
184 push
185
185
186 $ hg push
186 $ hg push
187 pushing to ssh://user@dummy/remote
187 pushing to ssh://user@dummy/remote
188 searching for changes
188 searching for changes
189 remote: adding changesets
189 remote: adding changesets
190 remote: adding manifests
190 remote: adding manifests
191 remote: adding file changes
191 remote: adding file changes
192 remote: added 1 changesets with 1 changes to 1 files
192 remote: added 1 changesets with 1 changes to 1 files
193 $ cd ../remote
193 $ cd ../remote
194
194
195 check remote tip
195 check remote tip
196
196
197 $ hg tip
197 $ hg tip
198 changeset: 3:a28a9d1a809c
198 changeset: 3:a28a9d1a809c
199 tag: tip
199 tag: tip
200 parent: 0:1160648e36ce
200 parent: 0:1160648e36ce
201 user: test
201 user: test
202 date: Thu Jan 01 00:00:00 1970 +0000
202 date: Thu Jan 01 00:00:00 1970 +0000
203 summary: add
203 summary: add
204
204
205 $ hg verify
205 $ hg verify
206 checking changesets
206 checking changesets
207 checking manifests
207 checking manifests
208 crosschecking files in changesets and manifests
208 crosschecking files in changesets and manifests
209 checking files
209 checking files
210 2 files, 4 changesets, 3 total revisions
210 2 files, 4 changesets, 3 total revisions
211 $ hg cat -r tip foo
211 $ hg cat -r tip foo
212 bleah
212 bleah
213 $ echo z > z
213 $ echo z > z
214 $ hg ci -A -m z z
214 $ hg ci -A -m z z
215 created new head
215 created new head
216
216
217 test pushkeys and bookmarks
217 test pushkeys and bookmarks
218
218
219 $ cd ../local
219 $ cd ../local
220 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
220 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
221 bookmarks
221 bookmarks
222 namespaces
222 namespaces
223 phases
223 phases
224 $ hg book foo -r 0
224 $ hg book foo -r 0
225 $ hg out -B
225 $ hg out -B
226 comparing with ssh://user@dummy/remote
226 comparing with ssh://user@dummy/remote
227 searching for changed bookmarks
227 searching for changed bookmarks
228 foo 1160648e36ce
228 foo 1160648e36ce
229 $ hg push -B foo
229 $ hg push -B foo
230 pushing to ssh://user@dummy/remote
230 pushing to ssh://user@dummy/remote
231 searching for changes
231 searching for changes
232 no changes found
232 no changes found
233 exporting bookmark foo
233 exporting bookmark foo
234 [1]
234 [1]
235 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
235 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
236 foo 1160648e36cec0054048a7edc4110c6f84fde594
236 foo 1160648e36cec0054048a7edc4110c6f84fde594
237 $ hg book -f foo
237 $ hg book -f foo
238 $ hg push --traceback
238 $ hg push --traceback
239 pushing to ssh://user@dummy/remote
239 pushing to ssh://user@dummy/remote
240 searching for changes
240 searching for changes
241 no changes found
241 no changes found
242 updating bookmark foo
242 updating bookmark foo
243 [1]
243 [1]
244 $ hg book -d foo
244 $ hg book -d foo
245 $ hg in -B
245 $ hg in -B
246 comparing with ssh://user@dummy/remote
246 comparing with ssh://user@dummy/remote
247 searching for changed bookmarks
247 searching for changed bookmarks
248 foo a28a9d1a809c
248 foo a28a9d1a809c
249 $ hg book -f -r 0 foo
249 $ hg book -f -r 0 foo
250 $ hg pull -B foo
250 $ hg pull -B foo
251 pulling from ssh://user@dummy/remote
251 pulling from ssh://user@dummy/remote
252 no changes found
252 no changes found
253 updating bookmark foo
253 updating bookmark foo
254 $ hg book -d foo
254 $ hg book -d foo
255 $ hg push -B foo
255 $ hg push -B foo
256 pushing to ssh://user@dummy/remote
256 pushing to ssh://user@dummy/remote
257 searching for changes
257 searching for changes
258 no changes found
258 no changes found
259 deleting remote bookmark foo
259 deleting remote bookmark foo
260 [1]
260 [1]
261
261
262 a bad, evil hook that prints to stdout
262 a bad, evil hook that prints to stdout
263
263
264 $ cat <<EOF > $TESTTMP/badhook
264 $ cat <<EOF > $TESTTMP/badhook
265 > import sys
265 > import sys
266 > sys.stdout.write("KABOOM\n")
266 > sys.stdout.write("KABOOM\n")
267 > EOF
267 > EOF
268
268
269 $ cat <<EOF > $TESTTMP/badpyhook.py
269 $ cat <<EOF > $TESTTMP/badpyhook.py
270 > import sys
270 > import sys
271 > def hook(ui, repo, hooktype, **kwargs):
271 > def hook(ui, repo, hooktype, **kwargs):
272 > sys.stdout.write("KABOOM IN PROCESS\n")
272 > sys.stdout.write("KABOOM IN PROCESS\n")
273 > EOF
273 > EOF
274
274
275 $ cat <<EOF >> ../remote/.hg/hgrc
275 $ cat <<EOF >> ../remote/.hg/hgrc
276 > [hooks]
276 > [hooks]
277 > changegroup.stdout = $PYTHON $TESTTMP/badhook
277 > changegroup.stdout = $PYTHON $TESTTMP/badhook
278 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
278 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
279 > EOF
279 > EOF
280 $ echo r > r
280 $ echo r > r
281 $ hg ci -A -m z r
281 $ hg ci -A -m z r
282
282
283 push should succeed even though it has an unexpected response
283 push should succeed even though it has an unexpected response
284
284
285 $ hg push
285 $ hg push
286 pushing to ssh://user@dummy/remote
286 pushing to ssh://user@dummy/remote
287 searching for changes
287 searching for changes
288 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
288 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
289 remote: adding changesets
289 remote: adding changesets
290 remote: adding manifests
290 remote: adding manifests
291 remote: adding file changes
291 remote: adding file changes
292 remote: added 1 changesets with 1 changes to 1 files
292 remote: added 1 changesets with 1 changes to 1 files
293 remote: KABOOM
293 remote: KABOOM
294 remote: KABOOM IN PROCESS
294 remote: KABOOM IN PROCESS
295 $ hg -R ../remote heads
295 $ hg -R ../remote heads
296 changeset: 5:1383141674ec
296 changeset: 5:1383141674ec
297 tag: tip
297 tag: tip
298 parent: 3:a28a9d1a809c
298 parent: 3:a28a9d1a809c
299 user: test
299 user: test
300 date: Thu Jan 01 00:00:00 1970 +0000
300 date: Thu Jan 01 00:00:00 1970 +0000
301 summary: z
301 summary: z
302
302
303 changeset: 4:6c0482d977a3
303 changeset: 4:6c0482d977a3
304 parent: 0:1160648e36ce
304 parent: 0:1160648e36ce
305 user: test
305 user: test
306 date: Thu Jan 01 00:00:00 1970 +0000
306 date: Thu Jan 01 00:00:00 1970 +0000
307 summary: z
307 summary: z
308
308
309
309
310 clone bookmarks
310 clone bookmarks
311
311
312 $ hg -R ../remote bookmark test
312 $ hg -R ../remote bookmark test
313 $ hg -R ../remote bookmarks
313 $ hg -R ../remote bookmarks
314 * test 4:6c0482d977a3
314 * test 4:6c0482d977a3
315 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
315 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
316 requesting all changes
316 requesting all changes
317 adding changesets
317 adding changesets
318 adding manifests
318 adding manifests
319 adding file changes
319 adding file changes
320 added 6 changesets with 5 changes to 4 files (+1 heads)
320 added 6 changesets with 5 changes to 4 files (+1 heads)
321 new changesets 1160648e36ce:1383141674ec
321 new changesets 1160648e36ce:1383141674ec
322 updating to branch default
322 updating to branch default
323 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
323 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
324 $ hg -R local-bookmarks bookmarks
324 $ hg -R local-bookmarks bookmarks
325 test 4:6c0482d977a3
325 test 4:6c0482d977a3
326
326
327 passwords in ssh urls are not supported
327 passwords in ssh urls are not supported
328 (we use a glob here because different Python versions give different
328 (we use a glob here because different Python versions give different
329 results here)
329 results here)
330
330
331 $ hg push ssh://user:erroneouspwd@dummy/remote
331 $ hg push ssh://user:erroneouspwd@dummy/remote
332 pushing to ssh://user:*@dummy/remote (glob)
332 pushing to ssh://user:*@dummy/remote (glob)
333 abort: password in URL not supported!
333 abort: password in URL not supported!
334 [255]
334 [255]
335
335
336 $ cd ..
336 $ cd ..
337
337
338 hide outer repo
338 hide outer repo
339 $ hg init
339 $ hg init
340
340
341 Test remote paths with spaces (issue2983):
341 Test remote paths with spaces (issue2983):
342
342
343 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
343 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
344 $ touch "$TESTTMP/a repo/test"
344 $ touch "$TESTTMP/a repo/test"
345 $ hg -R 'a repo' commit -A -m "test"
345 $ hg -R 'a repo' commit -A -m "test"
346 adding test
346 adding test
347 $ hg -R 'a repo' tag tag
347 $ hg -R 'a repo' tag tag
348 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
348 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
349 73649e48688a
349 73649e48688a
350
350
351 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
351 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
352 abort: unknown revision 'noNoNO'!
352 abort: unknown revision 'noNoNO'!
353 [255]
353 [255]
354
354
355 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
355 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
356
356
357 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
357 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
358 destination directory: a repo
358 destination directory: a repo
359 abort: destination 'a repo' is not empty
359 abort: destination 'a repo' is not empty
360 [255]
360 [255]
361
361
362 Make sure hg is really paranoid in serve --stdio mode. It used to be
362 Make sure hg is really paranoid in serve --stdio mode. It used to be
363 possible to get a debugger REPL by specifying a repo named --debugger.
363 possible to get a debugger REPL by specifying a repo named --debugger.
364 $ hg -R --debugger serve --stdio
364 $ hg -R --debugger serve --stdio
365 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
365 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
366 [255]
366 [255]
367 $ hg -R --config=ui.debugger=yes serve --stdio
367 $ hg -R --config=ui.debugger=yes serve --stdio
368 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
368 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
369 [255]
369 [255]
370 Abbreviations of 'serve' also don't work, to avoid shenanigans.
370 Abbreviations of 'serve' also don't work, to avoid shenanigans.
371 $ hg -R narf serv --stdio
371 $ hg -R narf serv --stdio
372 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
372 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
373 [255]
373 [255]
374
374
375 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
375 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
376 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
376 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
377 parameters:
377 parameters:
378
378
379 $ cat > ssh.sh << EOF
379 $ cat > ssh.sh << EOF
380 > userhost="\$1"
380 > userhost="\$1"
381 > SSH_ORIGINAL_COMMAND="\$2"
381 > SSH_ORIGINAL_COMMAND="\$2"
382 > export SSH_ORIGINAL_COMMAND
382 > export SSH_ORIGINAL_COMMAND
383 > PYTHONPATH="$PYTHONPATH"
383 > PYTHONPATH="$PYTHONPATH"
384 > export PYTHONPATH
384 > export PYTHONPATH
385 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
385 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
386 > EOF
386 > EOF
387
387
388 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
388 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
389 73649e48688a
389 73649e48688a
390
390
391 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
391 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
392 remote: Illegal repository "$TESTTMP/a'repo"
392 remote: Illegal repository "$TESTTMP/a'repo"
393 abort: no suitable response from remote hg!
393 abort: no suitable response from remote hg!
394 [255]
394 [255]
395
395
396 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
396 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
397 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
397 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
398 abort: no suitable response from remote hg!
398 abort: no suitable response from remote hg!
399 [255]
399 [255]
400
400
401 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
401 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
402 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
402 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
403 [255]
403 [255]
404
404
405 Test hg-ssh in read-only mode:
405 Test hg-ssh in read-only mode:
406
406
407 $ cat > ssh.sh << EOF
407 $ cat > ssh.sh << EOF
408 > userhost="\$1"
408 > userhost="\$1"
409 > SSH_ORIGINAL_COMMAND="\$2"
409 > SSH_ORIGINAL_COMMAND="\$2"
410 > export SSH_ORIGINAL_COMMAND
410 > export SSH_ORIGINAL_COMMAND
411 > PYTHONPATH="$PYTHONPATH"
411 > PYTHONPATH="$PYTHONPATH"
412 > export PYTHONPATH
412 > export PYTHONPATH
413 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
413 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
414 > EOF
414 > EOF
415
415
416 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
416 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
417 requesting all changes
417 requesting all changes
418 adding changesets
418 adding changesets
419 adding manifests
419 adding manifests
420 adding file changes
420 adding file changes
421 added 6 changesets with 5 changes to 4 files (+1 heads)
421 added 6 changesets with 5 changes to 4 files (+1 heads)
422 new changesets 1160648e36ce:1383141674ec
422 new changesets 1160648e36ce:1383141674ec
423 updating to branch default
423 updating to branch default
424 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
424 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
425
425
426 $ cd read-only-local
426 $ cd read-only-local
427 $ echo "baz" > bar
427 $ echo "baz" > bar
428 $ hg ci -A -m "unpushable commit" bar
428 $ hg ci -A -m "unpushable commit" bar
429 $ hg push --ssh "sh ../ssh.sh"
429 $ hg push --ssh "sh ../ssh.sh"
430 pushing to ssh://user@dummy/*/remote (glob)
430 pushing to ssh://user@dummy/*/remote (glob)
431 searching for changes
431 searching for changes
432 remote: Permission denied
432 remote: Permission denied
433 remote: pretxnopen.hg-ssh hook failed
433 remote: pretxnopen.hg-ssh hook failed
434 abort: push failed on remote
434 abort: push failed on remote
435 [255]
435 [255]
436
436
437 $ cd ..
437 $ cd ..
438
438
439 stderr from remote commands should be printed before stdout from local code (issue4336)
439 stderr from remote commands should be printed before stdout from local code (issue4336)
440
440
441 $ hg clone remote stderr-ordering
441 $ hg clone remote stderr-ordering
442 updating to branch default
442 updating to branch default
443 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
444 $ cd stderr-ordering
444 $ cd stderr-ordering
445 $ cat >> localwrite.py << EOF
445 $ cat >> localwrite.py << EOF
446 > from mercurial import exchange, extensions
446 > from mercurial import exchange, extensions
447 >
447 >
448 > def wrappedpush(orig, repo, *args, **kwargs):
448 > def wrappedpush(orig, repo, *args, **kwargs):
449 > res = orig(repo, *args, **kwargs)
449 > res = orig(repo, *args, **kwargs)
450 > repo.ui.write('local stdout\n')
450 > repo.ui.write('local stdout\n')
451 > return res
451 > return res
452 >
452 >
453 > def extsetup(ui):
453 > def extsetup(ui):
454 > extensions.wrapfunction(exchange, 'push', wrappedpush)
454 > extensions.wrapfunction(exchange, 'push', wrappedpush)
455 > EOF
455 > EOF
456
456
457 $ cat >> .hg/hgrc << EOF
457 $ cat >> .hg/hgrc << EOF
458 > [paths]
458 > [paths]
459 > default-push = ssh://user@dummy/remote
459 > default-push = ssh://user@dummy/remote
460 > [ui]
460 > [ui]
461 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
461 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
462 > [extensions]
462 > [extensions]
463 > localwrite = localwrite.py
463 > localwrite = localwrite.py
464 > EOF
464 > EOF
465
465
466 $ echo localwrite > foo
466 $ echo localwrite > foo
467 $ hg commit -m 'testing localwrite'
467 $ hg commit -m 'testing localwrite'
468 $ hg push
468 $ hg push
469 pushing to ssh://user@dummy/remote
469 pushing to ssh://user@dummy/remote
470 searching for changes
470 searching for changes
471 remote: adding changesets
471 remote: adding changesets
472 remote: adding manifests
472 remote: adding manifests
473 remote: adding file changes
473 remote: adding file changes
474 remote: added 1 changesets with 1 changes to 1 files
474 remote: added 1 changesets with 1 changes to 1 files
475 remote: KABOOM
475 remote: KABOOM
476 remote: KABOOM IN PROCESS
476 remote: KABOOM IN PROCESS
477 local stdout
477 local stdout
478
478
479 debug output
479 debug output
480
480
481 $ hg pull --debug ssh://user@dummy/remote
481 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
482 pulling from ssh://user@dummy/remote
482 pulling from ssh://user@dummy/remote
483 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
483 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
484 devel-peer-request: hello
484 sending hello command
485 sending hello command
486 devel-peer-request: between
487 devel-peer-request: pairs: 81 bytes
485 sending between command
488 sending between command
486 remote: 384
489 remote: 384
487 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS$ unbundle=HG10GZ,HG10BZ,HG10UN
490 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS$ unbundle=HG10GZ,HG10BZ,HG10UN
488 remote: 1
491 remote: 1
489 query 1; heads
492 query 1; heads
493 devel-peer-request: batch
494 devel-peer-request: cmds: 141 bytes
490 sending batch command
495 sending batch command
491 searching for changes
496 searching for changes
492 all remote heads known locally
497 all remote heads known locally
493 no changes found
498 no changes found
499 devel-peer-request: getbundle
500 devel-peer-request: bookmarks: 1 bytes
501 devel-peer-request: bundlecaps: 233 bytes
502 devel-peer-request: cg: 1 bytes
503 devel-peer-request: common: 122 bytes
504 devel-peer-request: heads: 122 bytes
505 devel-peer-request: listkeys: 9 bytes
506 devel-peer-request: phases: 1 bytes
494 sending getbundle command
507 sending getbundle command
495 bundle2-input-bundle: with-transaction
508 bundle2-input-bundle: with-transaction
496 bundle2-input-part: "bookmarks" supported
509 bundle2-input-part: "bookmarks" supported
497 bundle2-input-part: total payload size 26
510 bundle2-input-part: total payload size 26
498 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
511 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
499 bundle2-input-part: total payload size 45
512 bundle2-input-part: total payload size 45
500 bundle2-input-part: "phase-heads" supported
513 bundle2-input-part: "phase-heads" supported
501 bundle2-input-part: total payload size 72
514 bundle2-input-part: total payload size 72
502 bundle2-input-bundle: 2 parts total
515 bundle2-input-bundle: 2 parts total
503 checking for updated bookmarks
516 checking for updated bookmarks
504
517
505 $ cd ..
518 $ cd ..
506
519
507 $ cat dummylog
520 $ cat dummylog
508 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
521 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
509 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
522 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
510 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
523 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
511 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
524 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
512 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
525 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
513 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
526 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
514 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
527 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
515 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
528 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
516 Got arguments 1:user@dummy 2:hg -R local serve --stdio
529 Got arguments 1:user@dummy 2:hg -R local serve --stdio
517 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
530 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
531 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
519 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
532 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
533 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
521 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
534 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
522 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
535 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
523 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
536 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
524 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
537 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
525 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
538 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
526 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
539 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
527 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
540 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
528 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
541 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
529 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
542 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
530 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
543 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
531 Got arguments 1:user@dummy 2:hg init 'a repo'
544 Got arguments 1:user@dummy 2:hg init 'a repo'
532 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
545 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
533 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
546 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
534 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
547 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
535 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
548 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
536 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
549 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
537 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
550 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
538 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
551 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
539
552
540 remote hook failure is attributed to remote
553 remote hook failure is attributed to remote
541
554
542 $ cat > $TESTTMP/failhook << EOF
555 $ cat > $TESTTMP/failhook << EOF
543 > def hook(ui, repo, **kwargs):
556 > def hook(ui, repo, **kwargs):
544 > ui.write('hook failure!\n')
557 > ui.write('hook failure!\n')
545 > ui.flush()
558 > ui.flush()
546 > return 1
559 > return 1
547 > EOF
560 > EOF
548
561
549 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
562 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
550
563
551 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
564 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
552 $ cd hookout
565 $ cd hookout
553 $ touch hookfailure
566 $ touch hookfailure
554 $ hg -q commit -A -m 'remote hook failure'
567 $ hg -q commit -A -m 'remote hook failure'
555 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
568 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
556 pushing to ssh://user@dummy/remote
569 pushing to ssh://user@dummy/remote
557 searching for changes
570 searching for changes
558 remote: adding changesets
571 remote: adding changesets
559 remote: adding manifests
572 remote: adding manifests
560 remote: adding file changes
573 remote: adding file changes
561 remote: added 1 changesets with 1 changes to 1 files
574 remote: added 1 changesets with 1 changes to 1 files
562 remote: hook failure!
575 remote: hook failure!
563 remote: transaction abort!
576 remote: transaction abort!
564 remote: rollback completed
577 remote: rollback completed
565 remote: pretxnchangegroup.fail hook failed
578 remote: pretxnchangegroup.fail hook failed
566 abort: push failed on remote
579 abort: push failed on remote
567 [255]
580 [255]
568
581
569 abort during pull is properly reported as such
582 abort during pull is properly reported as such
570
583
571 $ echo morefoo >> ../remote/foo
584 $ echo morefoo >> ../remote/foo
572 $ hg -R ../remote commit --message "more foo to be pulled"
585 $ hg -R ../remote commit --message "more foo to be pulled"
573 $ cat >> ../remote/.hg/hgrc << EOF
586 $ cat >> ../remote/.hg/hgrc << EOF
574 > [extensions]
587 > [extensions]
575 > crash = ${TESTDIR}/crashgetbundler.py
588 > crash = ${TESTDIR}/crashgetbundler.py
576 > EOF
589 > EOF
577 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
590 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
578 pulling from ssh://user@dummy/remote
591 pulling from ssh://user@dummy/remote
579 searching for changes
592 searching for changes
580 remote: abort: this is an exercise
593 remote: abort: this is an exercise
581 abort: pull failed on remote
594 abort: pull failed on remote
582 [255]
595 [255]
583
596
584 abort with no error hint when there is a ssh problem when pulling
597 abort with no error hint when there is a ssh problem when pulling
585
598
586 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
599 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
587 pulling from ssh://brokenrepository/
600 pulling from ssh://brokenrepository/
588 abort: no suitable response from remote hg!
601 abort: no suitable response from remote hg!
589 [255]
602 [255]
590
603
591 abort with configured error hint when there is a ssh problem when pulling
604 abort with configured error hint when there is a ssh problem when pulling
592
605
593 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
606 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
594 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
607 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
595 pulling from ssh://brokenrepository/
608 pulling from ssh://brokenrepository/
596 abort: no suitable response from remote hg!
609 abort: no suitable response from remote hg!
597 (Please see http://company/internalwiki/ssh.html)
610 (Please see http://company/internalwiki/ssh.html)
598 [255]
611 [255]
599
612
600 test that custom environment is passed down to ssh executable
613 test that custom environment is passed down to ssh executable
601 $ cat >>dumpenv <<EOF
614 $ cat >>dumpenv <<EOF
602 > #! /bin/sh
615 > #! /bin/sh
603 > echo \$VAR >&2
616 > echo \$VAR >&2
604 > EOF
617 > EOF
605 $ chmod +x dumpenv
618 $ chmod +x dumpenv
606 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
619 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
607 pulling from ssh://something/
620 pulling from ssh://something/
608 remote:
621 remote:
609 abort: no suitable response from remote hg!
622 abort: no suitable response from remote hg!
610 [255]
623 [255]
611 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
624 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
612 pulling from ssh://something/
625 pulling from ssh://something/
613 remote: 17
626 remote: 17
614 abort: no suitable response from remote hg!
627 abort: no suitable response from remote hg!
615 [255]
628 [255]
616
629
General Comments 0
You need to be logged in to leave comments. Login now