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