##// END OF EJS Templates
procutil: compare fd number to see if stdio protection is needed (issue5992)...
Yuya Nishihara -
r39873:e5724be6 stable
parent child Browse files
Show More
@@ -1,450 +1,450 b''
1 # procutil.py - utility for managing processes and executable environment
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import contextlib
12 import contextlib
13 import imp
13 import imp
14 import io
14 import io
15 import os
15 import os
16 import signal
16 import signal
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import time
19 import time
20
20
21 from ..i18n import _
21 from ..i18n import _
22
22
23 from .. import (
23 from .. import (
24 encoding,
24 encoding,
25 error,
25 error,
26 policy,
26 policy,
27 pycompat,
27 pycompat,
28 )
28 )
29
29
30 osutil = policy.importmod(r'osutil')
30 osutil = policy.importmod(r'osutil')
31
31
32 stderr = pycompat.stderr
32 stderr = pycompat.stderr
33 stdin = pycompat.stdin
33 stdin = pycompat.stdin
34 stdout = pycompat.stdout
34 stdout = pycompat.stdout
35
35
36 def isatty(fp):
36 def isatty(fp):
37 try:
37 try:
38 return fp.isatty()
38 return fp.isatty()
39 except AttributeError:
39 except AttributeError:
40 return False
40 return False
41
41
42 # glibc determines buffering on first write to stdout - if we replace a TTY
42 # glibc determines buffering on first write to stdout - if we replace a TTY
43 # destined stdout with a pipe destined stdout (e.g. pager), we want line
43 # destined stdout with a pipe destined stdout (e.g. pager), we want line
44 # buffering (or unbuffered, on Windows)
44 # buffering (or unbuffered, on Windows)
45 if isatty(stdout):
45 if isatty(stdout):
46 if pycompat.iswindows:
46 if pycompat.iswindows:
47 # Windows doesn't support line buffering
47 # Windows doesn't support line buffering
48 stdout = os.fdopen(stdout.fileno(), r'wb', 0)
48 stdout = os.fdopen(stdout.fileno(), r'wb', 0)
49 else:
49 else:
50 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
50 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
51
51
52 if pycompat.iswindows:
52 if pycompat.iswindows:
53 from .. import windows as platform
53 from .. import windows as platform
54 stdout = platform.winstdout(stdout)
54 stdout = platform.winstdout(stdout)
55 else:
55 else:
56 from .. import posix as platform
56 from .. import posix as platform
57
57
58 findexe = platform.findexe
58 findexe = platform.findexe
59 _gethgcmd = platform.gethgcmd
59 _gethgcmd = platform.gethgcmd
60 getuser = platform.getuser
60 getuser = platform.getuser
61 getpid = os.getpid
61 getpid = os.getpid
62 hidewindow = platform.hidewindow
62 hidewindow = platform.hidewindow
63 quotecommand = platform.quotecommand
63 quotecommand = platform.quotecommand
64 readpipe = platform.readpipe
64 readpipe = platform.readpipe
65 setbinary = platform.setbinary
65 setbinary = platform.setbinary
66 setsignalhandler = platform.setsignalhandler
66 setsignalhandler = platform.setsignalhandler
67 shellquote = platform.shellquote
67 shellquote = platform.shellquote
68 shellsplit = platform.shellsplit
68 shellsplit = platform.shellsplit
69 spawndetached = platform.spawndetached
69 spawndetached = platform.spawndetached
70 sshargs = platform.sshargs
70 sshargs = platform.sshargs
71 testpid = platform.testpid
71 testpid = platform.testpid
72
72
73 try:
73 try:
74 setprocname = osutil.setprocname
74 setprocname = osutil.setprocname
75 except AttributeError:
75 except AttributeError:
76 pass
76 pass
77 try:
77 try:
78 unblocksignal = osutil.unblocksignal
78 unblocksignal = osutil.unblocksignal
79 except AttributeError:
79 except AttributeError:
80 pass
80 pass
81
81
82 closefds = pycompat.isposix
82 closefds = pycompat.isposix
83
83
84 def explainexit(code):
84 def explainexit(code):
85 """return a message describing a subprocess status
85 """return a message describing a subprocess status
86 (codes from kill are negative - not os.system/wait encoding)"""
86 (codes from kill are negative - not os.system/wait encoding)"""
87 if code >= 0:
87 if code >= 0:
88 return _("exited with status %d") % code
88 return _("exited with status %d") % code
89 return _("killed by signal %d") % -code
89 return _("killed by signal %d") % -code
90
90
91 class _pfile(object):
91 class _pfile(object):
92 """File-like wrapper for a stream opened by subprocess.Popen()"""
92 """File-like wrapper for a stream opened by subprocess.Popen()"""
93
93
94 def __init__(self, proc, fp):
94 def __init__(self, proc, fp):
95 self._proc = proc
95 self._proc = proc
96 self._fp = fp
96 self._fp = fp
97
97
98 def close(self):
98 def close(self):
99 # unlike os.popen(), this returns an integer in subprocess coding
99 # unlike os.popen(), this returns an integer in subprocess coding
100 self._fp.close()
100 self._fp.close()
101 return self._proc.wait()
101 return self._proc.wait()
102
102
103 def __iter__(self):
103 def __iter__(self):
104 return iter(self._fp)
104 return iter(self._fp)
105
105
106 def __getattr__(self, attr):
106 def __getattr__(self, attr):
107 return getattr(self._fp, attr)
107 return getattr(self._fp, attr)
108
108
109 def __enter__(self):
109 def __enter__(self):
110 return self
110 return self
111
111
112 def __exit__(self, exc_type, exc_value, exc_tb):
112 def __exit__(self, exc_type, exc_value, exc_tb):
113 self.close()
113 self.close()
114
114
115 def popen(cmd, mode='rb', bufsize=-1):
115 def popen(cmd, mode='rb', bufsize=-1):
116 if mode == 'rb':
116 if mode == 'rb':
117 return _popenreader(cmd, bufsize)
117 return _popenreader(cmd, bufsize)
118 elif mode == 'wb':
118 elif mode == 'wb':
119 return _popenwriter(cmd, bufsize)
119 return _popenwriter(cmd, bufsize)
120 raise error.ProgrammingError('unsupported mode: %r' % mode)
120 raise error.ProgrammingError('unsupported mode: %r' % mode)
121
121
122 def _popenreader(cmd, bufsize):
122 def _popenreader(cmd, bufsize):
123 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
123 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
124 close_fds=closefds,
124 close_fds=closefds,
125 stdout=subprocess.PIPE)
125 stdout=subprocess.PIPE)
126 return _pfile(p, p.stdout)
126 return _pfile(p, p.stdout)
127
127
128 def _popenwriter(cmd, bufsize):
128 def _popenwriter(cmd, bufsize):
129 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
129 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
130 close_fds=closefds,
130 close_fds=closefds,
131 stdin=subprocess.PIPE)
131 stdin=subprocess.PIPE)
132 return _pfile(p, p.stdin)
132 return _pfile(p, p.stdin)
133
133
134 def popen2(cmd, env=None):
134 def popen2(cmd, env=None):
135 # Setting bufsize to -1 lets the system decide the buffer size.
135 # Setting bufsize to -1 lets the system decide the buffer size.
136 # The default for bufsize is 0, meaning unbuffered. This leads to
136 # The default for bufsize is 0, meaning unbuffered. This leads to
137 # poor performance on Mac OS X: http://bugs.python.org/issue4194
137 # poor performance on Mac OS X: http://bugs.python.org/issue4194
138 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
138 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
139 close_fds=closefds,
139 close_fds=closefds,
140 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
140 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
141 env=env)
141 env=env)
142 return p.stdin, p.stdout
142 return p.stdin, p.stdout
143
143
144 def popen3(cmd, env=None):
144 def popen3(cmd, env=None):
145 stdin, stdout, stderr, p = popen4(cmd, env)
145 stdin, stdout, stderr, p = popen4(cmd, env)
146 return stdin, stdout, stderr
146 return stdin, stdout, stderr
147
147
148 def popen4(cmd, env=None, bufsize=-1):
148 def popen4(cmd, env=None, bufsize=-1):
149 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
149 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
150 close_fds=closefds,
150 close_fds=closefds,
151 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
151 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
152 stderr=subprocess.PIPE,
152 stderr=subprocess.PIPE,
153 env=env)
153 env=env)
154 return p.stdin, p.stdout, p.stderr, p
154 return p.stdin, p.stdout, p.stderr, p
155
155
156 def pipefilter(s, cmd):
156 def pipefilter(s, cmd):
157 '''filter string S through command CMD, returning its output'''
157 '''filter string S through command CMD, returning its output'''
158 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
158 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
159 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
159 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
160 pout, perr = p.communicate(s)
160 pout, perr = p.communicate(s)
161 return pout
161 return pout
162
162
163 def tempfilter(s, cmd):
163 def tempfilter(s, cmd):
164 '''filter string S through a pair of temporary files with CMD.
164 '''filter string S through a pair of temporary files with CMD.
165 CMD is used as a template to create the real command to be run,
165 CMD is used as a template to create the real command to be run,
166 with the strings INFILE and OUTFILE replaced by the real names of
166 with the strings INFILE and OUTFILE replaced by the real names of
167 the temporary files generated.'''
167 the temporary files generated.'''
168 inname, outname = None, None
168 inname, outname = None, None
169 try:
169 try:
170 infd, inname = pycompat.mkstemp(prefix='hg-filter-in-')
170 infd, inname = pycompat.mkstemp(prefix='hg-filter-in-')
171 fp = os.fdopen(infd, r'wb')
171 fp = os.fdopen(infd, r'wb')
172 fp.write(s)
172 fp.write(s)
173 fp.close()
173 fp.close()
174 outfd, outname = pycompat.mkstemp(prefix='hg-filter-out-')
174 outfd, outname = pycompat.mkstemp(prefix='hg-filter-out-')
175 os.close(outfd)
175 os.close(outfd)
176 cmd = cmd.replace('INFILE', inname)
176 cmd = cmd.replace('INFILE', inname)
177 cmd = cmd.replace('OUTFILE', outname)
177 cmd = cmd.replace('OUTFILE', outname)
178 code = system(cmd)
178 code = system(cmd)
179 if pycompat.sysplatform == 'OpenVMS' and code & 1:
179 if pycompat.sysplatform == 'OpenVMS' and code & 1:
180 code = 0
180 code = 0
181 if code:
181 if code:
182 raise error.Abort(_("command '%s' failed: %s") %
182 raise error.Abort(_("command '%s' failed: %s") %
183 (cmd, explainexit(code)))
183 (cmd, explainexit(code)))
184 with open(outname, 'rb') as fp:
184 with open(outname, 'rb') as fp:
185 return fp.read()
185 return fp.read()
186 finally:
186 finally:
187 try:
187 try:
188 if inname:
188 if inname:
189 os.unlink(inname)
189 os.unlink(inname)
190 except OSError:
190 except OSError:
191 pass
191 pass
192 try:
192 try:
193 if outname:
193 if outname:
194 os.unlink(outname)
194 os.unlink(outname)
195 except OSError:
195 except OSError:
196 pass
196 pass
197
197
198 _filtertable = {
198 _filtertable = {
199 'tempfile:': tempfilter,
199 'tempfile:': tempfilter,
200 'pipe:': pipefilter,
200 'pipe:': pipefilter,
201 }
201 }
202
202
203 def filter(s, cmd):
203 def filter(s, cmd):
204 "filter a string through a command that transforms its input to its output"
204 "filter a string through a command that transforms its input to its output"
205 for name, fn in _filtertable.iteritems():
205 for name, fn in _filtertable.iteritems():
206 if cmd.startswith(name):
206 if cmd.startswith(name):
207 return fn(s, cmd[len(name):].lstrip())
207 return fn(s, cmd[len(name):].lstrip())
208 return pipefilter(s, cmd)
208 return pipefilter(s, cmd)
209
209
210 def mainfrozen():
210 def mainfrozen():
211 """return True if we are a frozen executable.
211 """return True if we are a frozen executable.
212
212
213 The code supports py2exe (most common, Windows only) and tools/freeze
213 The code supports py2exe (most common, Windows only) and tools/freeze
214 (portable, not much used).
214 (portable, not much used).
215 """
215 """
216 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
216 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
217 pycompat.safehasattr(sys, "importers") or # old py2exe
217 pycompat.safehasattr(sys, "importers") or # old py2exe
218 imp.is_frozen(u"__main__")) # tools/freeze
218 imp.is_frozen(u"__main__")) # tools/freeze
219
219
220 _hgexecutable = None
220 _hgexecutable = None
221
221
222 def hgexecutable():
222 def hgexecutable():
223 """return location of the 'hg' executable.
223 """return location of the 'hg' executable.
224
224
225 Defaults to $HG or 'hg' in the search path.
225 Defaults to $HG or 'hg' in the search path.
226 """
226 """
227 if _hgexecutable is None:
227 if _hgexecutable is None:
228 hg = encoding.environ.get('HG')
228 hg = encoding.environ.get('HG')
229 mainmod = sys.modules[r'__main__']
229 mainmod = sys.modules[r'__main__']
230 if hg:
230 if hg:
231 _sethgexecutable(hg)
231 _sethgexecutable(hg)
232 elif mainfrozen():
232 elif mainfrozen():
233 if getattr(sys, 'frozen', None) == 'macosx_app':
233 if getattr(sys, 'frozen', None) == 'macosx_app':
234 # Env variable set by py2app
234 # Env variable set by py2app
235 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
235 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
236 else:
236 else:
237 _sethgexecutable(pycompat.sysexecutable)
237 _sethgexecutable(pycompat.sysexecutable)
238 elif (os.path.basename(
238 elif (os.path.basename(
239 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
239 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
240 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
240 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
241 else:
241 else:
242 exe = findexe('hg') or os.path.basename(sys.argv[0])
242 exe = findexe('hg') or os.path.basename(sys.argv[0])
243 _sethgexecutable(exe)
243 _sethgexecutable(exe)
244 return _hgexecutable
244 return _hgexecutable
245
245
246 def _sethgexecutable(path):
246 def _sethgexecutable(path):
247 """set location of the 'hg' executable"""
247 """set location of the 'hg' executable"""
248 global _hgexecutable
248 global _hgexecutable
249 _hgexecutable = path
249 _hgexecutable = path
250
250
251 def _testfileno(f, stdf):
251 def _testfileno(f, stdf):
252 fileno = getattr(f, 'fileno', None)
252 fileno = getattr(f, 'fileno', None)
253 try:
253 try:
254 return fileno and fileno() == stdf.fileno()
254 return fileno and fileno() == stdf.fileno()
255 except io.UnsupportedOperation:
255 except io.UnsupportedOperation:
256 return False # fileno() raised UnsupportedOperation
256 return False # fileno() raised UnsupportedOperation
257
257
258 def isstdin(f):
258 def isstdin(f):
259 return _testfileno(f, sys.__stdin__)
259 return _testfileno(f, sys.__stdin__)
260
260
261 def isstdout(f):
261 def isstdout(f):
262 return _testfileno(f, sys.__stdout__)
262 return _testfileno(f, sys.__stdout__)
263
263
264 def protectstdio(uin, uout):
264 def protectstdio(uin, uout):
265 """Duplicate streams and redirect original if (uin, uout) are stdio
265 """Duplicate streams and redirect original if (uin, uout) are stdio
266
266
267 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
267 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
268 redirected to stderr so the output is still readable.
268 redirected to stderr so the output is still readable.
269
269
270 Returns (fin, fout) which point to the original (uin, uout) fds, but
270 Returns (fin, fout) which point to the original (uin, uout) fds, but
271 may be copy of (uin, uout). The returned streams can be considered
271 may be copy of (uin, uout). The returned streams can be considered
272 "owned" in that print(), exec(), etc. never reach to them.
272 "owned" in that print(), exec(), etc. never reach to them.
273 """
273 """
274 uout.flush()
274 uout.flush()
275 fin, fout = uin, uout
275 fin, fout = uin, uout
276 if uin is stdin:
276 if _testfileno(uin, stdin):
277 newfd = os.dup(uin.fileno())
277 newfd = os.dup(uin.fileno())
278 nullfd = os.open(os.devnull, os.O_RDONLY)
278 nullfd = os.open(os.devnull, os.O_RDONLY)
279 os.dup2(nullfd, uin.fileno())
279 os.dup2(nullfd, uin.fileno())
280 os.close(nullfd)
280 os.close(nullfd)
281 fin = os.fdopen(newfd, r'rb')
281 fin = os.fdopen(newfd, r'rb')
282 if uout is stdout:
282 if _testfileno(uout, stdout):
283 newfd = os.dup(uout.fileno())
283 newfd = os.dup(uout.fileno())
284 os.dup2(stderr.fileno(), uout.fileno())
284 os.dup2(stderr.fileno(), uout.fileno())
285 fout = os.fdopen(newfd, r'wb')
285 fout = os.fdopen(newfd, r'wb')
286 return fin, fout
286 return fin, fout
287
287
288 def restorestdio(uin, uout, fin, fout):
288 def restorestdio(uin, uout, fin, fout):
289 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
289 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
290 uout.flush()
290 uout.flush()
291 for f, uif in [(fin, uin), (fout, uout)]:
291 for f, uif in [(fin, uin), (fout, uout)]:
292 if f is not uif:
292 if f is not uif:
293 os.dup2(f.fileno(), uif.fileno())
293 os.dup2(f.fileno(), uif.fileno())
294 f.close()
294 f.close()
295
295
296 @contextlib.contextmanager
296 @contextlib.contextmanager
297 def protectedstdio(uin, uout):
297 def protectedstdio(uin, uout):
298 """Run code block with protected standard streams"""
298 """Run code block with protected standard streams"""
299 fin, fout = protectstdio(uin, uout)
299 fin, fout = protectstdio(uin, uout)
300 try:
300 try:
301 yield fin, fout
301 yield fin, fout
302 finally:
302 finally:
303 restorestdio(uin, uout, fin, fout)
303 restorestdio(uin, uout, fin, fout)
304
304
305 def shellenviron(environ=None):
305 def shellenviron(environ=None):
306 """return environ with optional override, useful for shelling out"""
306 """return environ with optional override, useful for shelling out"""
307 def py2shell(val):
307 def py2shell(val):
308 'convert python object into string that is useful to shell'
308 'convert python object into string that is useful to shell'
309 if val is None or val is False:
309 if val is None or val is False:
310 return '0'
310 return '0'
311 if val is True:
311 if val is True:
312 return '1'
312 return '1'
313 return pycompat.bytestr(val)
313 return pycompat.bytestr(val)
314 env = dict(encoding.environ)
314 env = dict(encoding.environ)
315 if environ:
315 if environ:
316 env.update((k, py2shell(v)) for k, v in environ.iteritems())
316 env.update((k, py2shell(v)) for k, v in environ.iteritems())
317 env['HG'] = hgexecutable()
317 env['HG'] = hgexecutable()
318 return env
318 return env
319
319
320 if pycompat.iswindows:
320 if pycompat.iswindows:
321 def shelltonative(cmd, env):
321 def shelltonative(cmd, env):
322 return platform.shelltocmdexe(cmd, shellenviron(env))
322 return platform.shelltocmdexe(cmd, shellenviron(env))
323 else:
323 else:
324 def shelltonative(cmd, env):
324 def shelltonative(cmd, env):
325 return cmd
325 return cmd
326
326
327 def system(cmd, environ=None, cwd=None, out=None):
327 def system(cmd, environ=None, cwd=None, out=None):
328 '''enhanced shell command execution.
328 '''enhanced shell command execution.
329 run with environment maybe modified, maybe in different dir.
329 run with environment maybe modified, maybe in different dir.
330
330
331 if out is specified, it is assumed to be a file-like object that has a
331 if out is specified, it is assumed to be a file-like object that has a
332 write() method. stdout and stderr will be redirected to out.'''
332 write() method. stdout and stderr will be redirected to out.'''
333 try:
333 try:
334 stdout.flush()
334 stdout.flush()
335 except Exception:
335 except Exception:
336 pass
336 pass
337 cmd = quotecommand(cmd)
337 cmd = quotecommand(cmd)
338 env = shellenviron(environ)
338 env = shellenviron(environ)
339 if out is None or isstdout(out):
339 if out is None or isstdout(out):
340 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
340 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
341 env=env, cwd=cwd)
341 env=env, cwd=cwd)
342 else:
342 else:
343 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
343 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
344 env=env, cwd=cwd, stdout=subprocess.PIPE,
344 env=env, cwd=cwd, stdout=subprocess.PIPE,
345 stderr=subprocess.STDOUT)
345 stderr=subprocess.STDOUT)
346 for line in iter(proc.stdout.readline, ''):
346 for line in iter(proc.stdout.readline, ''):
347 out.write(line)
347 out.write(line)
348 proc.wait()
348 proc.wait()
349 rc = proc.returncode
349 rc = proc.returncode
350 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
350 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
351 rc = 0
351 rc = 0
352 return rc
352 return rc
353
353
354 def gui():
354 def gui():
355 '''Are we running in a GUI?'''
355 '''Are we running in a GUI?'''
356 if pycompat.isdarwin:
356 if pycompat.isdarwin:
357 if 'SSH_CONNECTION' in encoding.environ:
357 if 'SSH_CONNECTION' in encoding.environ:
358 # handle SSH access to a box where the user is logged in
358 # handle SSH access to a box where the user is logged in
359 return False
359 return False
360 elif getattr(osutil, 'isgui', None):
360 elif getattr(osutil, 'isgui', None):
361 # check if a CoreGraphics session is available
361 # check if a CoreGraphics session is available
362 return osutil.isgui()
362 return osutil.isgui()
363 else:
363 else:
364 # pure build; use a safe default
364 # pure build; use a safe default
365 return True
365 return True
366 else:
366 else:
367 return pycompat.iswindows or encoding.environ.get("DISPLAY")
367 return pycompat.iswindows or encoding.environ.get("DISPLAY")
368
368
369 def hgcmd():
369 def hgcmd():
370 """Return the command used to execute current hg
370 """Return the command used to execute current hg
371
371
372 This is different from hgexecutable() because on Windows we want
372 This is different from hgexecutable() because on Windows we want
373 to avoid things opening new shell windows like batch files, so we
373 to avoid things opening new shell windows like batch files, so we
374 get either the python call or current executable.
374 get either the python call or current executable.
375 """
375 """
376 if mainfrozen():
376 if mainfrozen():
377 if getattr(sys, 'frozen', None) == 'macosx_app':
377 if getattr(sys, 'frozen', None) == 'macosx_app':
378 # Env variable set by py2app
378 # Env variable set by py2app
379 return [encoding.environ['EXECUTABLEPATH']]
379 return [encoding.environ['EXECUTABLEPATH']]
380 else:
380 else:
381 return [pycompat.sysexecutable]
381 return [pycompat.sysexecutable]
382 return _gethgcmd()
382 return _gethgcmd()
383
383
384 def rundetached(args, condfn):
384 def rundetached(args, condfn):
385 """Execute the argument list in a detached process.
385 """Execute the argument list in a detached process.
386
386
387 condfn is a callable which is called repeatedly and should return
387 condfn is a callable which is called repeatedly and should return
388 True once the child process is known to have started successfully.
388 True once the child process is known to have started successfully.
389 At this point, the child process PID is returned. If the child
389 At this point, the child process PID is returned. If the child
390 process fails to start or finishes before condfn() evaluates to
390 process fails to start or finishes before condfn() evaluates to
391 True, return -1.
391 True, return -1.
392 """
392 """
393 # Windows case is easier because the child process is either
393 # Windows case is easier because the child process is either
394 # successfully starting and validating the condition or exiting
394 # successfully starting and validating the condition or exiting
395 # on failure. We just poll on its PID. On Unix, if the child
395 # on failure. We just poll on its PID. On Unix, if the child
396 # process fails to start, it will be left in a zombie state until
396 # process fails to start, it will be left in a zombie state until
397 # the parent wait on it, which we cannot do since we expect a long
397 # the parent wait on it, which we cannot do since we expect a long
398 # running process on success. Instead we listen for SIGCHLD telling
398 # running process on success. Instead we listen for SIGCHLD telling
399 # us our child process terminated.
399 # us our child process terminated.
400 terminated = set()
400 terminated = set()
401 def handler(signum, frame):
401 def handler(signum, frame):
402 terminated.add(os.wait())
402 terminated.add(os.wait())
403 prevhandler = None
403 prevhandler = None
404 SIGCHLD = getattr(signal, 'SIGCHLD', None)
404 SIGCHLD = getattr(signal, 'SIGCHLD', None)
405 if SIGCHLD is not None:
405 if SIGCHLD is not None:
406 prevhandler = signal.signal(SIGCHLD, handler)
406 prevhandler = signal.signal(SIGCHLD, handler)
407 try:
407 try:
408 pid = spawndetached(args)
408 pid = spawndetached(args)
409 while not condfn():
409 while not condfn():
410 if ((pid in terminated or not testpid(pid))
410 if ((pid in terminated or not testpid(pid))
411 and not condfn()):
411 and not condfn()):
412 return -1
412 return -1
413 time.sleep(0.1)
413 time.sleep(0.1)
414 return pid
414 return pid
415 finally:
415 finally:
416 if prevhandler is not None:
416 if prevhandler is not None:
417 signal.signal(signal.SIGCHLD, prevhandler)
417 signal.signal(signal.SIGCHLD, prevhandler)
418
418
419 @contextlib.contextmanager
419 @contextlib.contextmanager
420 def uninterruptable(warn):
420 def uninterruptable(warn):
421 """Inhibit SIGINT handling on a region of code.
421 """Inhibit SIGINT handling on a region of code.
422
422
423 Note that if this is called in a non-main thread, it turns into a no-op.
423 Note that if this is called in a non-main thread, it turns into a no-op.
424
424
425 Args:
425 Args:
426 warn: A callable which takes no arguments, and returns True if the
426 warn: A callable which takes no arguments, and returns True if the
427 previous signal handling should be restored.
427 previous signal handling should be restored.
428 """
428 """
429
429
430 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
430 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
431 shouldbail = []
431 shouldbail = []
432
432
433 def disabledsiginthandler(*args):
433 def disabledsiginthandler(*args):
434 if warn():
434 if warn():
435 signal.signal(signal.SIGINT, oldsiginthandler[0])
435 signal.signal(signal.SIGINT, oldsiginthandler[0])
436 del oldsiginthandler[0]
436 del oldsiginthandler[0]
437 shouldbail.append(True)
437 shouldbail.append(True)
438
438
439 try:
439 try:
440 try:
440 try:
441 signal.signal(signal.SIGINT, disabledsiginthandler)
441 signal.signal(signal.SIGINT, disabledsiginthandler)
442 except ValueError:
442 except ValueError:
443 # wrong thread, oh well, we tried
443 # wrong thread, oh well, we tried
444 del oldsiginthandler[0]
444 del oldsiginthandler[0]
445 yield
445 yield
446 finally:
446 finally:
447 if oldsiginthandler:
447 if oldsiginthandler:
448 signal.signal(signal.SIGINT, oldsiginthandler[0])
448 signal.signal(signal.SIGINT, oldsiginthandler[0])
449 if shouldbail:
449 if shouldbail:
450 raise KeyboardInterrupt
450 raise KeyboardInterrupt
@@ -1,665 +1,669 b''
1 #testcases sshv1 sshv2
1 #testcases sshv1 sshv2
2
2
3 #if sshv2
3 #if sshv2
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [experimental]
5 > [experimental]
6 > sshpeer.advertise-v2 = true
6 > sshpeer.advertise-v2 = true
7 > sshserver.support-v2 = true
7 > sshserver.support-v2 = true
8 > EOF
8 > EOF
9 #endif
9 #endif
10
10
11 This test tries to exercise the ssh functionality with a dummy script
11 This test tries to exercise the ssh functionality with a dummy script
12
12
13 creating 'remote' repo
13 creating 'remote' repo
14
14
15 $ hg init remote
15 $ hg init remote
16 $ cd remote
16 $ cd remote
17 $ echo this > foo
17 $ echo this > foo
18 $ echo this > fooO
18 $ echo this > fooO
19 $ hg ci -A -m "init" foo fooO
19 $ hg ci -A -m "init" foo fooO
20
20
21 insert a closed branch (issue4428)
21 insert a closed branch (issue4428)
22
22
23 $ hg up null
23 $ hg up null
24 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
24 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
25 $ hg branch closed
25 $ hg branch closed
26 marked working directory as branch closed
26 marked working directory as branch closed
27 (branches are permanent and global, did you want a bookmark?)
27 (branches are permanent and global, did you want a bookmark?)
28 $ hg ci -mc0
28 $ hg ci -mc0
29 $ hg ci --close-branch -mc1
29 $ hg ci --close-branch -mc1
30 $ hg up -q default
30 $ hg up -q default
31
31
32 configure for serving
32 configure for serving
33
33
34 $ cat <<EOF > .hg/hgrc
34 $ cat <<EOF > .hg/hgrc
35 > [server]
35 > [server]
36 > uncompressed = True
36 > uncompressed = True
37 >
37 >
38 > [hooks]
38 > [hooks]
39 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
39 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
40 > EOF
40 > EOF
41 $ cd ..
41 $ cd ..
42
42
43 repo not found error
43 repo not found error
44
44
45 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
45 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
46 remote: abort: repository nonexistent not found!
46 remote: abort: repository nonexistent not found!
47 abort: no suitable response from remote hg!
47 abort: no suitable response from remote hg!
48 [255]
48 [255]
49
49
50 non-existent absolute path
50 non-existent absolute path
51
51
52 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
52 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
53 remote: abort: repository $TESTTMP/nonexistent not found!
53 remote: abort: repository $TESTTMP/nonexistent not found!
54 abort: no suitable response from remote hg!
54 abort: no suitable response from remote hg!
55 [255]
55 [255]
56
56
57 clone remote via stream
57 clone remote via stream
58
58
59 #if no-reposimplestore
59 #if no-reposimplestore
60
60
61 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
61 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
62 streaming all changes
62 streaming all changes
63 4 files to transfer, 602 bytes of data
63 4 files to transfer, 602 bytes of data
64 transferred 602 bytes in * seconds (*) (glob)
64 transferred 602 bytes in * seconds (*) (glob)
65 searching for changes
65 searching for changes
66 no changes found
66 no changes found
67 updating to branch default
67 updating to branch default
68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 $ cd local-stream
69 $ cd local-stream
70 $ hg verify
70 $ hg verify
71 checking changesets
71 checking changesets
72 checking manifests
72 checking manifests
73 crosschecking files in changesets and manifests
73 crosschecking files in changesets and manifests
74 checking files
74 checking files
75 2 files, 3 changesets, 2 total revisions
75 2 files, 3 changesets, 2 total revisions
76 $ hg branches
76 $ hg branches
77 default 0:1160648e36ce
77 default 0:1160648e36ce
78 $ cd ..
78 $ cd ..
79
79
80 clone bookmarks via stream
80 clone bookmarks via stream
81
81
82 $ hg -R local-stream book mybook
82 $ hg -R local-stream book mybook
83 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
83 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
84 streaming all changes
84 streaming all changes
85 4 files to transfer, 602 bytes of data
85 4 files to transfer, 602 bytes of data
86 transferred 602 bytes in * seconds (*) (glob)
86 transferred 602 bytes in * seconds (*) (glob)
87 searching for changes
87 searching for changes
88 no changes found
88 no changes found
89 updating to branch default
89 updating to branch default
90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 $ cd stream2
91 $ cd stream2
92 $ hg book
92 $ hg book
93 mybook 0:1160648e36ce
93 mybook 0:1160648e36ce
94 $ cd ..
94 $ cd ..
95 $ rm -rf local-stream stream2
95 $ rm -rf local-stream stream2
96
96
97 #endif
97 #endif
98
98
99 clone remote via pull
99 clone remote via pull
100
100
101 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
101 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
102 requesting all changes
102 requesting all changes
103 adding changesets
103 adding changesets
104 adding manifests
104 adding manifests
105 adding file changes
105 adding file changes
106 added 3 changesets with 2 changes to 2 files
106 added 3 changesets with 2 changes to 2 files
107 new changesets 1160648e36ce:ad076bfb429d
107 new changesets 1160648e36ce:ad076bfb429d
108 updating to branch default
108 updating to branch default
109 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
110
110
111 verify
111 verify
112
112
113 $ cd local
113 $ cd local
114 $ hg verify
114 $ hg verify
115 checking changesets
115 checking changesets
116 checking manifests
116 checking manifests
117 crosschecking files in changesets and manifests
117 crosschecking files in changesets and manifests
118 checking files
118 checking files
119 2 files, 3 changesets, 2 total revisions
119 2 files, 3 changesets, 2 total revisions
120 $ cat >> .hg/hgrc <<EOF
120 $ cat >> .hg/hgrc <<EOF
121 > [hooks]
121 > [hooks]
122 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
122 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
123 > EOF
123 > EOF
124
124
125 empty default pull
125 empty default pull
126
126
127 $ hg paths
127 $ hg paths
128 default = ssh://user@dummy/remote
128 default = ssh://user@dummy/remote
129 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
129 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
130 pulling from ssh://user@dummy/remote
130 pulling from ssh://user@dummy/remote
131 searching for changes
131 searching for changes
132 no changes found
132 no changes found
133
133
134 pull from wrong ssh URL
134 pull from wrong ssh URL
135
135
136 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
136 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
137 pulling from ssh://user@dummy/doesnotexist
137 pulling from ssh://user@dummy/doesnotexist
138 remote: abort: repository doesnotexist not found!
138 remote: abort: repository doesnotexist not found!
139 abort: no suitable response from remote hg!
139 abort: no suitable response from remote hg!
140 [255]
140 [255]
141
141
142 local change
142 local change
143
143
144 $ echo bleah > foo
144 $ echo bleah > foo
145 $ hg ci -m "add"
145 $ hg ci -m "add"
146
146
147 updating rc
147 updating rc
148
148
149 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
149 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
150 $ echo "[ui]" >> .hg/hgrc
150 $ echo "[ui]" >> .hg/hgrc
151 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
151 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
152
152
153 find outgoing
153 find outgoing
154
154
155 $ hg out ssh://user@dummy/remote
155 $ hg out ssh://user@dummy/remote
156 comparing with ssh://user@dummy/remote
156 comparing with ssh://user@dummy/remote
157 searching for changes
157 searching for changes
158 changeset: 3:a28a9d1a809c
158 changeset: 3:a28a9d1a809c
159 tag: tip
159 tag: tip
160 parent: 0:1160648e36ce
160 parent: 0:1160648e36ce
161 user: test
161 user: test
162 date: Thu Jan 01 00:00:00 1970 +0000
162 date: Thu Jan 01 00:00:00 1970 +0000
163 summary: add
163 summary: add
164
164
165
165
166 find incoming on the remote side
166 find incoming on the remote side
167
167
168 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
168 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
169 comparing with ssh://user@dummy/local
169 comparing with ssh://user@dummy/local
170 searching for changes
170 searching for changes
171 changeset: 3:a28a9d1a809c
171 changeset: 3:a28a9d1a809c
172 tag: tip
172 tag: tip
173 parent: 0:1160648e36ce
173 parent: 0:1160648e36ce
174 user: test
174 user: test
175 date: Thu Jan 01 00:00:00 1970 +0000
175 date: Thu Jan 01 00:00:00 1970 +0000
176 summary: add
176 summary: add
177
177
178
178
179 find incoming on the remote side (using absolute path)
179 find incoming on the remote side (using absolute path)
180
180
181 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
181 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
182 comparing with ssh://user@dummy/$TESTTMP/local
182 comparing with ssh://user@dummy/$TESTTMP/local
183 searching for changes
183 searching for changes
184 changeset: 3:a28a9d1a809c
184 changeset: 3:a28a9d1a809c
185 tag: tip
185 tag: tip
186 parent: 0:1160648e36ce
186 parent: 0:1160648e36ce
187 user: test
187 user: test
188 date: Thu Jan 01 00:00:00 1970 +0000
188 date: Thu Jan 01 00:00:00 1970 +0000
189 summary: add
189 summary: add
190
190
191
191
192 push
192 push
193
193
194 $ hg push
194 $ hg push
195 pushing to ssh://user@dummy/remote
195 pushing to ssh://user@dummy/remote
196 searching for changes
196 searching for changes
197 remote: adding changesets
197 remote: adding changesets
198 remote: adding manifests
198 remote: adding manifests
199 remote: adding file changes
199 remote: adding file changes
200 remote: added 1 changesets with 1 changes to 1 files
200 remote: added 1 changesets with 1 changes to 1 files
201 $ cd ../remote
201 $ cd ../remote
202
202
203 check remote tip
203 check remote tip
204
204
205 $ hg tip
205 $ hg tip
206 changeset: 3:a28a9d1a809c
206 changeset: 3:a28a9d1a809c
207 tag: tip
207 tag: tip
208 parent: 0:1160648e36ce
208 parent: 0:1160648e36ce
209 user: test
209 user: test
210 date: Thu Jan 01 00:00:00 1970 +0000
210 date: Thu Jan 01 00:00:00 1970 +0000
211 summary: add
211 summary: add
212
212
213 $ hg verify
213 $ hg verify
214 checking changesets
214 checking changesets
215 checking manifests
215 checking manifests
216 crosschecking files in changesets and manifests
216 crosschecking files in changesets and manifests
217 checking files
217 checking files
218 2 files, 4 changesets, 3 total revisions
218 2 files, 4 changesets, 3 total revisions
219 $ hg cat -r tip foo
219 $ hg cat -r tip foo
220 bleah
220 bleah
221 $ echo z > z
221 $ echo z > z
222 $ hg ci -A -m z z
222 $ hg ci -A -m z z
223 created new head
223 created new head
224
224
225 test pushkeys and bookmarks
225 test pushkeys and bookmarks
226
226
227 $ cd ../local
227 $ cd ../local
228 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
228 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
229 bookmarks
229 bookmarks
230 namespaces
230 namespaces
231 phases
231 phases
232 $ hg book foo -r 0
232 $ hg book foo -r 0
233 $ hg out -B --config paths.default=bogus://invalid --config paths.default:pushurl=`hg paths default`
233 $ hg out -B --config paths.default=bogus://invalid --config paths.default:pushurl=`hg paths default`
234 comparing with ssh://user@dummy/remote
234 comparing with ssh://user@dummy/remote
235 searching for changed bookmarks
235 searching for changed bookmarks
236 foo 1160648e36ce
236 foo 1160648e36ce
237 $ hg push -B foo
237 $ hg push -B foo
238 pushing to ssh://user@dummy/remote
238 pushing to ssh://user@dummy/remote
239 searching for changes
239 searching for changes
240 no changes found
240 no changes found
241 exporting bookmark foo
241 exporting bookmark foo
242 [1]
242 [1]
243 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
243 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
244 foo 1160648e36cec0054048a7edc4110c6f84fde594
244 foo 1160648e36cec0054048a7edc4110c6f84fde594
245 $ hg book -f foo
245 $ hg book -f foo
246 $ hg push --traceback
246 $ hg push --traceback
247 pushing to ssh://user@dummy/remote
247 pushing to ssh://user@dummy/remote
248 searching for changes
248 searching for changes
249 no changes found
249 no changes found
250 updating bookmark foo
250 updating bookmark foo
251 [1]
251 [1]
252 $ hg book -d foo
252 $ hg book -d foo
253 $ hg in -B
253 $ hg in -B
254 comparing with ssh://user@dummy/remote
254 comparing with ssh://user@dummy/remote
255 searching for changed bookmarks
255 searching for changed bookmarks
256 foo a28a9d1a809c
256 foo a28a9d1a809c
257 $ hg book -f -r 0 foo
257 $ hg book -f -r 0 foo
258 $ hg pull -B foo
258 $ hg pull -B foo
259 pulling from ssh://user@dummy/remote
259 pulling from ssh://user@dummy/remote
260 no changes found
260 no changes found
261 updating bookmark foo
261 updating bookmark foo
262 $ hg book -d foo
262 $ hg book -d foo
263 $ hg push -B foo
263 $ hg push -B foo
264 pushing to ssh://user@dummy/remote
264 pushing to ssh://user@dummy/remote
265 searching for changes
265 searching for changes
266 no changes found
266 no changes found
267 deleting remote bookmark foo
267 deleting remote bookmark foo
268 [1]
268 [1]
269
269
270 a bad, evil hook that prints to stdout
270 a bad, evil hook that prints to stdout
271
271
272 $ cat <<EOF > $TESTTMP/badhook
272 $ cat <<EOF > $TESTTMP/badhook
273 > import sys
273 > import sys
274 > sys.stdout.write("KABOOM\n")
274 > sys.stdout.write("KABOOM\n")
275 > sys.stdout.flush()
275 > sys.stdout.flush()
276 > EOF
276 > EOF
277
277
278 $ cat <<EOF > $TESTTMP/badpyhook.py
278 $ cat <<EOF > $TESTTMP/badpyhook.py
279 > import sys
279 > import sys
280 > def hook(ui, repo, hooktype, **kwargs):
280 > def hook(ui, repo, hooktype, **kwargs):
281 > sys.stdout.write("KABOOM IN PROCESS\n")
281 > sys.stdout.write("KABOOM IN PROCESS\n")
282 > sys.stdout.flush()
282 > sys.stdout.flush()
283 > EOF
283 > EOF
284
284
285 $ cat <<EOF >> ../remote/.hg/hgrc
285 $ cat <<EOF >> ../remote/.hg/hgrc
286 > [hooks]
286 > [hooks]
287 > changegroup.stdout = $PYTHON $TESTTMP/badhook
287 > changegroup.stdout = $PYTHON $TESTTMP/badhook
288 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
288 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
289 > EOF
289 > EOF
290 $ echo r > r
290 $ echo r > r
291 $ hg ci -A -m z r
291 $ hg ci -A -m z r
292
292
293 push should succeed even though it has an unexpected response
293 push should succeed even though it has an unexpected response
294
294
295 $ hg push
295 $ hg push
296 pushing to ssh://user@dummy/remote
296 pushing to ssh://user@dummy/remote
297 searching for changes
297 searching for changes
298 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
298 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
299 remote: adding changesets
299 remote: adding changesets
300 remote: adding manifests
300 remote: adding manifests
301 remote: adding file changes
301 remote: adding file changes
302 remote: added 1 changesets with 1 changes to 1 files
302 remote: added 1 changesets with 1 changes to 1 files
303 remote: KABOOM
303 remote: KABOOM
304 remote: KABOOM IN PROCESS
304 remote: KABOOM IN PROCESS
305 $ hg -R ../remote heads
305 $ hg -R ../remote heads
306 changeset: 5:1383141674ec
306 changeset: 5:1383141674ec
307 tag: tip
307 tag: tip
308 parent: 3:a28a9d1a809c
308 parent: 3:a28a9d1a809c
309 user: test
309 user: test
310 date: Thu Jan 01 00:00:00 1970 +0000
310 date: Thu Jan 01 00:00:00 1970 +0000
311 summary: z
311 summary: z
312
312
313 changeset: 4:6c0482d977a3
313 changeset: 4:6c0482d977a3
314 parent: 0:1160648e36ce
314 parent: 0:1160648e36ce
315 user: test
315 user: test
316 date: Thu Jan 01 00:00:00 1970 +0000
316 date: Thu Jan 01 00:00:00 1970 +0000
317 summary: z
317 summary: z
318
318
319
319
320 #if chg
320 #if chg
321
321
322 try again with remote chg, which should succeed as well
322 try again with remote chg, which should succeed as well
323
323
324 $ hg rollback -R ../remote
324 $ hg rollback -R ../remote
325 repository tip rolled back to revision 4 (undo serve)
325 repository tip rolled back to revision 4 (undo serve)
326
326
327 $ hg push --config ui.remotecmd=chg
327 $ hg push --config ui.remotecmd=chg
328 pushing to ssh://user@dummy/remote
328 pushing to ssh://user@dummy/remote
329 searching for changes
329 searching for changes
330 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
330 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
331 remote: adding changesets
332 remote: adding manifests
333 remote: adding file changes
334 remote: added 1 changesets with 1 changes to 1 files
331 abort: not a Mercurial bundle
335 abort: not a Mercurial bundle
332 [255]
336 [255]
333
337
334 #endif
338 #endif
335
339
336 clone bookmarks
340 clone bookmarks
337
341
338 $ hg -R ../remote bookmark test
342 $ hg -R ../remote bookmark test
339 $ hg -R ../remote bookmarks
343 $ hg -R ../remote bookmarks
340 * test 4:6c0482d977a3
344 * test 4:6c0482d977a3
341 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
345 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
342 requesting all changes
346 requesting all changes
343 adding changesets
347 adding changesets
344 adding manifests
348 adding manifests
345 adding file changes
349 adding file changes
346 added 6 changesets with 5 changes to 4 files (+1 heads)
350 added 6 changesets with 5 changes to 4 files (+1 heads)
347 new changesets 1160648e36ce:1383141674ec
351 new changesets 1160648e36ce:1383141674ec
348 updating to branch default
352 updating to branch default
349 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
353 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
350 $ hg -R local-bookmarks bookmarks
354 $ hg -R local-bookmarks bookmarks
351 test 4:6c0482d977a3
355 test 4:6c0482d977a3
352
356
353 passwords in ssh urls are not supported
357 passwords in ssh urls are not supported
354 (we use a glob here because different Python versions give different
358 (we use a glob here because different Python versions give different
355 results here)
359 results here)
356
360
357 $ hg push ssh://user:erroneouspwd@dummy/remote
361 $ hg push ssh://user:erroneouspwd@dummy/remote
358 pushing to ssh://user:*@dummy/remote (glob)
362 pushing to ssh://user:*@dummy/remote (glob)
359 abort: password in URL not supported!
363 abort: password in URL not supported!
360 [255]
364 [255]
361
365
362 $ cd ..
366 $ cd ..
363
367
364 hide outer repo
368 hide outer repo
365 $ hg init
369 $ hg init
366
370
367 Test remote paths with spaces (issue2983):
371 Test remote paths with spaces (issue2983):
368
372
369 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
373 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
370 $ touch "$TESTTMP/a repo/test"
374 $ touch "$TESTTMP/a repo/test"
371 $ hg -R 'a repo' commit -A -m "test"
375 $ hg -R 'a repo' commit -A -m "test"
372 adding test
376 adding test
373 $ hg -R 'a repo' tag tag
377 $ hg -R 'a repo' tag tag
374 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
378 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
375 73649e48688a
379 73649e48688a
376
380
377 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
381 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
378 abort: unknown revision 'noNoNO'!
382 abort: unknown revision 'noNoNO'!
379 [255]
383 [255]
380
384
381 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
385 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
382
386
383 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
387 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
384 destination directory: a repo
388 destination directory: a repo
385 abort: destination 'a repo' is not empty
389 abort: destination 'a repo' is not empty
386 [255]
390 [255]
387
391
388 Make sure hg is really paranoid in serve --stdio mode. It used to be
392 Make sure hg is really paranoid in serve --stdio mode. It used to be
389 possible to get a debugger REPL by specifying a repo named --debugger.
393 possible to get a debugger REPL by specifying a repo named --debugger.
390 $ hg -R --debugger serve --stdio
394 $ hg -R --debugger serve --stdio
391 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
395 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
392 [255]
396 [255]
393 $ hg -R --config=ui.debugger=yes serve --stdio
397 $ hg -R --config=ui.debugger=yes serve --stdio
394 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
398 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
395 [255]
399 [255]
396 Abbreviations of 'serve' also don't work, to avoid shenanigans.
400 Abbreviations of 'serve' also don't work, to avoid shenanigans.
397 $ hg -R narf serv --stdio
401 $ hg -R narf serv --stdio
398 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
402 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
399 [255]
403 [255]
400
404
401 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
405 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
402 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
406 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
403 parameters:
407 parameters:
404
408
405 $ cat > ssh.sh << EOF
409 $ cat > ssh.sh << EOF
406 > userhost="\$1"
410 > userhost="\$1"
407 > SSH_ORIGINAL_COMMAND="\$2"
411 > SSH_ORIGINAL_COMMAND="\$2"
408 > export SSH_ORIGINAL_COMMAND
412 > export SSH_ORIGINAL_COMMAND
409 > PYTHONPATH="$PYTHONPATH"
413 > PYTHONPATH="$PYTHONPATH"
410 > export PYTHONPATH
414 > export PYTHONPATH
411 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
415 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
412 > EOF
416 > EOF
413
417
414 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
418 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
415 73649e48688a
419 73649e48688a
416
420
417 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
421 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
418 remote: Illegal repository "$TESTTMP/a'repo"
422 remote: Illegal repository "$TESTTMP/a'repo"
419 abort: no suitable response from remote hg!
423 abort: no suitable response from remote hg!
420 [255]
424 [255]
421
425
422 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
426 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
423 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
427 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
424 abort: no suitable response from remote hg!
428 abort: no suitable response from remote hg!
425 [255]
429 [255]
426
430
427 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
431 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
428 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
432 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
429 [255]
433 [255]
430
434
431 Test hg-ssh in read-only mode:
435 Test hg-ssh in read-only mode:
432
436
433 $ cat > ssh.sh << EOF
437 $ cat > ssh.sh << EOF
434 > userhost="\$1"
438 > userhost="\$1"
435 > SSH_ORIGINAL_COMMAND="\$2"
439 > SSH_ORIGINAL_COMMAND="\$2"
436 > export SSH_ORIGINAL_COMMAND
440 > export SSH_ORIGINAL_COMMAND
437 > PYTHONPATH="$PYTHONPATH"
441 > PYTHONPATH="$PYTHONPATH"
438 > export PYTHONPATH
442 > export PYTHONPATH
439 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
443 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
440 > EOF
444 > EOF
441
445
442 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
446 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
443 requesting all changes
447 requesting all changes
444 adding changesets
448 adding changesets
445 adding manifests
449 adding manifests
446 adding file changes
450 adding file changes
447 added 6 changesets with 5 changes to 4 files (+1 heads)
451 added 6 changesets with 5 changes to 4 files (+1 heads)
448 new changesets 1160648e36ce:1383141674ec
452 new changesets 1160648e36ce:1383141674ec
449 updating to branch default
453 updating to branch default
450 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
454 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
451
455
452 $ cd read-only-local
456 $ cd read-only-local
453 $ echo "baz" > bar
457 $ echo "baz" > bar
454 $ hg ci -A -m "unpushable commit" bar
458 $ hg ci -A -m "unpushable commit" bar
455 $ hg push --ssh "sh ../ssh.sh"
459 $ hg push --ssh "sh ../ssh.sh"
456 pushing to ssh://user@dummy/*/remote (glob)
460 pushing to ssh://user@dummy/*/remote (glob)
457 searching for changes
461 searching for changes
458 remote: Permission denied
462 remote: Permission denied
459 remote: pretxnopen.hg-ssh hook failed
463 remote: pretxnopen.hg-ssh hook failed
460 abort: push failed on remote
464 abort: push failed on remote
461 [255]
465 [255]
462
466
463 $ cd ..
467 $ cd ..
464
468
465 stderr from remote commands should be printed before stdout from local code (issue4336)
469 stderr from remote commands should be printed before stdout from local code (issue4336)
466
470
467 $ hg clone remote stderr-ordering
471 $ hg clone remote stderr-ordering
468 updating to branch default
472 updating to branch default
469 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
473 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
470 $ cd stderr-ordering
474 $ cd stderr-ordering
471 $ cat >> localwrite.py << EOF
475 $ cat >> localwrite.py << EOF
472 > from mercurial import exchange, extensions
476 > from mercurial import exchange, extensions
473 >
477 >
474 > def wrappedpush(orig, repo, *args, **kwargs):
478 > def wrappedpush(orig, repo, *args, **kwargs):
475 > res = orig(repo, *args, **kwargs)
479 > res = orig(repo, *args, **kwargs)
476 > repo.ui.write(b'local stdout\n')
480 > repo.ui.write(b'local stdout\n')
477 > repo.ui.flush()
481 > repo.ui.flush()
478 > return res
482 > return res
479 >
483 >
480 > def extsetup(ui):
484 > def extsetup(ui):
481 > extensions.wrapfunction(exchange, b'push', wrappedpush)
485 > extensions.wrapfunction(exchange, b'push', wrappedpush)
482 > EOF
486 > EOF
483
487
484 $ cat >> .hg/hgrc << EOF
488 $ cat >> .hg/hgrc << EOF
485 > [paths]
489 > [paths]
486 > default-push = ssh://user@dummy/remote
490 > default-push = ssh://user@dummy/remote
487 > [ui]
491 > [ui]
488 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
492 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
489 > [extensions]
493 > [extensions]
490 > localwrite = localwrite.py
494 > localwrite = localwrite.py
491 > EOF
495 > EOF
492
496
493 $ echo localwrite > foo
497 $ echo localwrite > foo
494 $ hg commit -m 'testing localwrite'
498 $ hg commit -m 'testing localwrite'
495 $ hg push
499 $ hg push
496 pushing to ssh://user@dummy/remote
500 pushing to ssh://user@dummy/remote
497 searching for changes
501 searching for changes
498 remote: adding changesets
502 remote: adding changesets
499 remote: adding manifests
503 remote: adding manifests
500 remote: adding file changes
504 remote: adding file changes
501 remote: added 1 changesets with 1 changes to 1 files
505 remote: added 1 changesets with 1 changes to 1 files
502 remote: KABOOM
506 remote: KABOOM
503 remote: KABOOM IN PROCESS
507 remote: KABOOM IN PROCESS
504 local stdout
508 local stdout
505
509
506 debug output
510 debug output
507
511
508 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
512 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
509 pulling from ssh://user@dummy/remote
513 pulling from ssh://user@dummy/remote
510 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
514 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
511 sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
515 sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
512 devel-peer-request: hello+between
516 devel-peer-request: hello+between
513 devel-peer-request: pairs: 81 bytes
517 devel-peer-request: pairs: 81 bytes
514 sending hello command
518 sending hello command
515 sending between command
519 sending between command
516 remote: 413 (sshv1 !)
520 remote: 413 (sshv1 !)
517 protocol upgraded to exp-ssh-v2-0001 (sshv2 !)
521 protocol upgraded to exp-ssh-v2-0001 (sshv2 !)
518 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
522 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
519 remote: 1 (sshv1 !)
523 remote: 1 (sshv1 !)
520 devel-peer-request: protocaps
524 devel-peer-request: protocaps
521 devel-peer-request: caps: * bytes (glob)
525 devel-peer-request: caps: * bytes (glob)
522 sending protocaps command
526 sending protocaps command
523 query 1; heads
527 query 1; heads
524 devel-peer-request: batched-content
528 devel-peer-request: batched-content
525 devel-peer-request: - heads (0 arguments)
529 devel-peer-request: - heads (0 arguments)
526 devel-peer-request: - known (1 arguments)
530 devel-peer-request: - known (1 arguments)
527 devel-peer-request: batch
531 devel-peer-request: batch
528 devel-peer-request: cmds: 141 bytes
532 devel-peer-request: cmds: 141 bytes
529 sending batch command
533 sending batch command
530 searching for changes
534 searching for changes
531 all remote heads known locally
535 all remote heads known locally
532 no changes found
536 no changes found
533 devel-peer-request: getbundle
537 devel-peer-request: getbundle
534 devel-peer-request: bookmarks: 1 bytes
538 devel-peer-request: bookmarks: 1 bytes
535 devel-peer-request: bundlecaps: 266 bytes
539 devel-peer-request: bundlecaps: 266 bytes
536 devel-peer-request: cg: 1 bytes
540 devel-peer-request: cg: 1 bytes
537 devel-peer-request: common: 122 bytes
541 devel-peer-request: common: 122 bytes
538 devel-peer-request: heads: 122 bytes
542 devel-peer-request: heads: 122 bytes
539 devel-peer-request: listkeys: 9 bytes
543 devel-peer-request: listkeys: 9 bytes
540 devel-peer-request: phases: 1 bytes
544 devel-peer-request: phases: 1 bytes
541 sending getbundle command
545 sending getbundle command
542 bundle2-input-bundle: with-transaction
546 bundle2-input-bundle: with-transaction
543 bundle2-input-part: "bookmarks" supported
547 bundle2-input-part: "bookmarks" supported
544 bundle2-input-part: total payload size 26
548 bundle2-input-part: total payload size 26
545 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
549 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
546 bundle2-input-part: total payload size 45
550 bundle2-input-part: total payload size 45
547 bundle2-input-part: "phase-heads" supported
551 bundle2-input-part: "phase-heads" supported
548 bundle2-input-part: total payload size 72
552 bundle2-input-part: total payload size 72
549 bundle2-input-bundle: 2 parts total
553 bundle2-input-bundle: 2 parts total
550 checking for updated bookmarks
554 checking for updated bookmarks
551
555
552 $ cd ..
556 $ cd ..
553
557
554 $ cat dummylog
558 $ cat dummylog
555 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
559 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
556 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
560 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
557 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
561 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
558 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio (no-reposimplestore !)
562 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio (no-reposimplestore !)
559 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
563 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
560 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
564 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
561 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
565 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
562 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
566 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
563 Got arguments 1:user@dummy 2:hg -R local serve --stdio
567 Got arguments 1:user@dummy 2:hg -R local serve --stdio
564 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
568 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
565 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
569 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
566 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
570 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
567 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
571 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
568 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
572 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
569 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
573 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
570 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
574 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
571 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
575 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
572 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
576 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
573 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
577 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
574 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
578 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
575 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
579 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
576 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
580 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
577 Got arguments 1:user@dummy 2:chg -R remote serve --stdio (chg !)
581 Got arguments 1:user@dummy 2:chg -R remote serve --stdio (chg !)
578 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 (chg !)
582 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 (chg !)
579 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
583 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
580 Got arguments 1:user@dummy 2:hg init 'a repo'
584 Got arguments 1:user@dummy 2:hg init 'a repo'
581 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
585 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
582 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
586 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
583 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
587 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
584 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
588 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
585 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
589 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
586 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
590 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
587 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
591 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
588
592
589 remote hook failure is attributed to remote
593 remote hook failure is attributed to remote
590
594
591 $ cat > $TESTTMP/failhook << EOF
595 $ cat > $TESTTMP/failhook << EOF
592 > def hook(ui, repo, **kwargs):
596 > def hook(ui, repo, **kwargs):
593 > ui.write(b'hook failure!\n')
597 > ui.write(b'hook failure!\n')
594 > ui.flush()
598 > ui.flush()
595 > return 1
599 > return 1
596 > EOF
600 > EOF
597
601
598 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
602 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
599
603
600 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
604 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
601 $ cd hookout
605 $ cd hookout
602 $ touch hookfailure
606 $ touch hookfailure
603 $ hg -q commit -A -m 'remote hook failure'
607 $ hg -q commit -A -m 'remote hook failure'
604 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
608 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
605 pushing to ssh://user@dummy/remote
609 pushing to ssh://user@dummy/remote
606 searching for changes
610 searching for changes
607 remote: adding changesets
611 remote: adding changesets
608 remote: adding manifests
612 remote: adding manifests
609 remote: adding file changes
613 remote: adding file changes
610 remote: added 1 changesets with 1 changes to 1 files
614 remote: added 1 changesets with 1 changes to 1 files
611 remote: hook failure!
615 remote: hook failure!
612 remote: transaction abort!
616 remote: transaction abort!
613 remote: rollback completed
617 remote: rollback completed
614 remote: pretxnchangegroup.fail hook failed
618 remote: pretxnchangegroup.fail hook failed
615 abort: push failed on remote
619 abort: push failed on remote
616 [255]
620 [255]
617
621
618 abort during pull is properly reported as such
622 abort during pull is properly reported as such
619
623
620 $ echo morefoo >> ../remote/foo
624 $ echo morefoo >> ../remote/foo
621 $ hg -R ../remote commit --message "more foo to be pulled"
625 $ hg -R ../remote commit --message "more foo to be pulled"
622 $ cat >> ../remote/.hg/hgrc << EOF
626 $ cat >> ../remote/.hg/hgrc << EOF
623 > [extensions]
627 > [extensions]
624 > crash = ${TESTDIR}/crashgetbundler.py
628 > crash = ${TESTDIR}/crashgetbundler.py
625 > EOF
629 > EOF
626 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
630 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
627 pulling from ssh://user@dummy/remote
631 pulling from ssh://user@dummy/remote
628 searching for changes
632 searching for changes
629 remote: abort: this is an exercise
633 remote: abort: this is an exercise
630 abort: pull failed on remote
634 abort: pull failed on remote
631 [255]
635 [255]
632
636
633 abort with no error hint when there is a ssh problem when pulling
637 abort with no error hint when there is a ssh problem when pulling
634
638
635 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
639 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
636 pulling from ssh://brokenrepository/
640 pulling from ssh://brokenrepository/
637 abort: no suitable response from remote hg!
641 abort: no suitable response from remote hg!
638 [255]
642 [255]
639
643
640 abort with configured error hint when there is a ssh problem when pulling
644 abort with configured error hint when there is a ssh problem when pulling
641
645
642 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
646 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
643 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
647 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
644 pulling from ssh://brokenrepository/
648 pulling from ssh://brokenrepository/
645 abort: no suitable response from remote hg!
649 abort: no suitable response from remote hg!
646 (Please see http://company/internalwiki/ssh.html)
650 (Please see http://company/internalwiki/ssh.html)
647 [255]
651 [255]
648
652
649 test that custom environment is passed down to ssh executable
653 test that custom environment is passed down to ssh executable
650 $ cat >>dumpenv <<EOF
654 $ cat >>dumpenv <<EOF
651 > #! /bin/sh
655 > #! /bin/sh
652 > echo \$VAR >&2
656 > echo \$VAR >&2
653 > EOF
657 > EOF
654 $ chmod +x dumpenv
658 $ chmod +x dumpenv
655 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
659 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
656 pulling from ssh://something/
660 pulling from ssh://something/
657 remote:
661 remote:
658 abort: no suitable response from remote hg!
662 abort: no suitable response from remote hg!
659 [255]
663 [255]
660 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
664 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
661 pulling from ssh://something/
665 pulling from ssh://something/
662 remote: 17
666 remote: 17
663 abort: no suitable response from remote hg!
667 abort: no suitable response from remote hg!
664 [255]
668 [255]
665
669
General Comments 0
You need to be logged in to leave comments. Login now