##// END OF EJS Templates
procutil: assign pseudo file object if sys.stdout/stderr is missing...
Yuya Nishihara -
r46791:a04c03b0 default
parent child Browse files
Show More
@@ -1,801 +1,809 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 errno
13 import errno
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 threading
19 import threading
20 import time
20 import time
21
21
22 from ..i18n import _
22 from ..i18n import _
23 from ..pycompat import (
23 from ..pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27
27
28 from .. import (
28 from .. import (
29 encoding,
29 encoding,
30 error,
30 error,
31 policy,
31 policy,
32 pycompat,
32 pycompat,
33 )
33 )
34
34
35 # Import like this to keep import-checker happy
35 # Import like this to keep import-checker happy
36 from ..utils import resourceutil
36 from ..utils import resourceutil
37
37
38 osutil = policy.importmod('osutil')
38 osutil = policy.importmod('osutil')
39
39
40 if pycompat.iswindows:
40 if pycompat.iswindows:
41 from .. import windows as platform
41 from .. import windows as platform
42 else:
42 else:
43 from .. import posix as platform
43 from .. import posix as platform
44
44
45
45
46 def isatty(fp):
46 def isatty(fp):
47 try:
47 try:
48 return fp.isatty()
48 return fp.isatty()
49 except AttributeError:
49 except AttributeError:
50 return False
50 return False
51
51
52
52
53 class BadFile(io.RawIOBase):
53 class BadFile(io.RawIOBase):
54 """Dummy file object to simulate closed stdio behavior"""
54 """Dummy file object to simulate closed stdio behavior"""
55
55
56 def readinto(self, b):
56 def readinto(self, b):
57 raise IOError(errno.EBADF, 'Bad file descriptor')
57 raise IOError(errno.EBADF, 'Bad file descriptor')
58
58
59 def write(self, b):
59 def write(self, b):
60 raise IOError(errno.EBADF, 'Bad file descriptor')
60 raise IOError(errno.EBADF, 'Bad file descriptor')
61
61
62
62
63 class LineBufferedWrapper(object):
63 class LineBufferedWrapper(object):
64 def __init__(self, orig):
64 def __init__(self, orig):
65 self.orig = orig
65 self.orig = orig
66
66
67 def __getattr__(self, attr):
67 def __getattr__(self, attr):
68 return getattr(self.orig, attr)
68 return getattr(self.orig, attr)
69
69
70 def write(self, s):
70 def write(self, s):
71 orig = self.orig
71 orig = self.orig
72 res = orig.write(s)
72 res = orig.write(s)
73 if s.endswith(b'\n'):
73 if s.endswith(b'\n'):
74 orig.flush()
74 orig.flush()
75 return res
75 return res
76
76
77
77
78 io.BufferedIOBase.register(LineBufferedWrapper)
78 io.BufferedIOBase.register(LineBufferedWrapper)
79
79
80
80
81 def make_line_buffered(stream):
81 def make_line_buffered(stream):
82 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
82 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
83 # On Python 3, buffered streams can be expected to subclass
83 # On Python 3, buffered streams can be expected to subclass
84 # BufferedIOBase. This is definitively the case for the streams
84 # BufferedIOBase. This is definitively the case for the streams
85 # initialized by the interpreter. For unbuffered streams, we don't need
85 # initialized by the interpreter. For unbuffered streams, we don't need
86 # to emulate line buffering.
86 # to emulate line buffering.
87 return stream
87 return stream
88 if isinstance(stream, LineBufferedWrapper):
88 if isinstance(stream, LineBufferedWrapper):
89 return stream
89 return stream
90 return LineBufferedWrapper(stream)
90 return LineBufferedWrapper(stream)
91
91
92
92
93 def unwrap_line_buffered(stream):
93 def unwrap_line_buffered(stream):
94 if isinstance(stream, LineBufferedWrapper):
94 if isinstance(stream, LineBufferedWrapper):
95 assert not isinstance(stream.orig, LineBufferedWrapper)
95 assert not isinstance(stream.orig, LineBufferedWrapper)
96 return stream.orig
96 return stream.orig
97 return stream
97 return stream
98
98
99
99
100 class WriteAllWrapper(object):
100 class WriteAllWrapper(object):
101 def __init__(self, orig):
101 def __init__(self, orig):
102 self.orig = orig
102 self.orig = orig
103
103
104 def __getattr__(self, attr):
104 def __getattr__(self, attr):
105 return getattr(self.orig, attr)
105 return getattr(self.orig, attr)
106
106
107 def write(self, s):
107 def write(self, s):
108 write1 = self.orig.write
108 write1 = self.orig.write
109 m = memoryview(s)
109 m = memoryview(s)
110 total_to_write = len(s)
110 total_to_write = len(s)
111 total_written = 0
111 total_written = 0
112 while total_written < total_to_write:
112 while total_written < total_to_write:
113 total_written += write1(m[total_written:])
113 total_written += write1(m[total_written:])
114 return total_written
114 return total_written
115
115
116
116
117 io.IOBase.register(WriteAllWrapper)
117 io.IOBase.register(WriteAllWrapper)
118
118
119
119
120 def _make_write_all(stream):
120 def _make_write_all(stream):
121 assert pycompat.ispy3
121 assert pycompat.ispy3
122 if isinstance(stream, WriteAllWrapper):
122 if isinstance(stream, WriteAllWrapper):
123 return stream
123 return stream
124 if isinstance(stream, io.BufferedIOBase):
124 if isinstance(stream, io.BufferedIOBase):
125 # The io.BufferedIOBase.write() contract guarantees that all data is
125 # The io.BufferedIOBase.write() contract guarantees that all data is
126 # written.
126 # written.
127 return stream
127 return stream
128 # In general, the write() method of streams is free to write only part of
128 # In general, the write() method of streams is free to write only part of
129 # the data.
129 # the data.
130 return WriteAllWrapper(stream)
130 return WriteAllWrapper(stream)
131
131
132
132
133 if pycompat.ispy3:
133 if pycompat.ispy3:
134 # Python 3 implements its own I/O streams.
134 # Python 3 implements its own I/O streams. Unlike stdio of C library,
135 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
136
135 # TODO: .buffer might not exist if std streams were replaced; we'll need
137 # TODO: .buffer might not exist if std streams were replaced; we'll need
136 # a silly wrapper to make a bytes stream backed by a unicode one.
138 # a silly wrapper to make a bytes stream backed by a unicode one.
137
139
138 # sys.stdin can be None
140 if sys.stdin is None:
139 if sys.stdin:
141 stdin = BadFile()
142 else:
140 stdin = sys.stdin.buffer
143 stdin = sys.stdin.buffer
144 if sys.stdout is None:
145 stdout = BadFile()
141 else:
146 else:
142 stdin = BadFile()
147 stdout = _make_write_all(sys.stdout.buffer)
143 stdout = _make_write_all(sys.stdout.buffer)
148 if sys.stderr is None:
144 stderr = _make_write_all(sys.stderr.buffer)
149 stderr = BadFile()
150 else:
151 stderr = _make_write_all(sys.stderr.buffer)
152
145 if pycompat.iswindows:
153 if pycompat.iswindows:
146 # Work around Windows bugs.
154 # Work around Windows bugs.
147 stdout = platform.winstdout(stdout)
155 stdout = platform.winstdout(stdout)
148 stderr = platform.winstdout(stderr)
156 stderr = platform.winstdout(stderr)
149 if isatty(stdout):
157 if isatty(stdout):
150 # The standard library doesn't offer line-buffered binary streams.
158 # The standard library doesn't offer line-buffered binary streams.
151 stdout = make_line_buffered(stdout)
159 stdout = make_line_buffered(stdout)
152 else:
160 else:
153 # Python 2 uses the I/O streams provided by the C library.
161 # Python 2 uses the I/O streams provided by the C library.
154 stdin = sys.stdin
162 stdin = sys.stdin
155 stdout = sys.stdout
163 stdout = sys.stdout
156 stderr = sys.stderr
164 stderr = sys.stderr
157 if pycompat.iswindows:
165 if pycompat.iswindows:
158 # Work around Windows bugs.
166 # Work around Windows bugs.
159 stdout = platform.winstdout(stdout)
167 stdout = platform.winstdout(stdout)
160 stderr = platform.winstdout(stderr)
168 stderr = platform.winstdout(stderr)
161 if isatty(stdout):
169 if isatty(stdout):
162 if pycompat.iswindows:
170 if pycompat.iswindows:
163 # The Windows C runtime library doesn't support line buffering.
171 # The Windows C runtime library doesn't support line buffering.
164 stdout = make_line_buffered(stdout)
172 stdout = make_line_buffered(stdout)
165 else:
173 else:
166 # glibc determines buffering on first write to stdout - if we
174 # glibc determines buffering on first write to stdout - if we
167 # replace a TTY destined stdout with a pipe destined stdout (e.g.
175 # replace a TTY destined stdout with a pipe destined stdout (e.g.
168 # pager), we want line buffering.
176 # pager), we want line buffering.
169 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
177 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
170
178
171
179
172 findexe = platform.findexe
180 findexe = platform.findexe
173 _gethgcmd = platform.gethgcmd
181 _gethgcmd = platform.gethgcmd
174 getuser = platform.getuser
182 getuser = platform.getuser
175 getpid = os.getpid
183 getpid = os.getpid
176 hidewindow = platform.hidewindow
184 hidewindow = platform.hidewindow
177 readpipe = platform.readpipe
185 readpipe = platform.readpipe
178 setbinary = platform.setbinary
186 setbinary = platform.setbinary
179 setsignalhandler = platform.setsignalhandler
187 setsignalhandler = platform.setsignalhandler
180 shellquote = platform.shellquote
188 shellquote = platform.shellquote
181 shellsplit = platform.shellsplit
189 shellsplit = platform.shellsplit
182 spawndetached = platform.spawndetached
190 spawndetached = platform.spawndetached
183 sshargs = platform.sshargs
191 sshargs = platform.sshargs
184 testpid = platform.testpid
192 testpid = platform.testpid
185
193
186 try:
194 try:
187 setprocname = osutil.setprocname
195 setprocname = osutil.setprocname
188 except AttributeError:
196 except AttributeError:
189 pass
197 pass
190 try:
198 try:
191 unblocksignal = osutil.unblocksignal
199 unblocksignal = osutil.unblocksignal
192 except AttributeError:
200 except AttributeError:
193 pass
201 pass
194
202
195 closefds = pycompat.isposix
203 closefds = pycompat.isposix
196
204
197
205
198 def explainexit(code):
206 def explainexit(code):
199 """return a message describing a subprocess status
207 """return a message describing a subprocess status
200 (codes from kill are negative - not os.system/wait encoding)"""
208 (codes from kill are negative - not os.system/wait encoding)"""
201 if code >= 0:
209 if code >= 0:
202 return _(b"exited with status %d") % code
210 return _(b"exited with status %d") % code
203 return _(b"killed by signal %d") % -code
211 return _(b"killed by signal %d") % -code
204
212
205
213
206 class _pfile(object):
214 class _pfile(object):
207 """File-like wrapper for a stream opened by subprocess.Popen()"""
215 """File-like wrapper for a stream opened by subprocess.Popen()"""
208
216
209 def __init__(self, proc, fp):
217 def __init__(self, proc, fp):
210 self._proc = proc
218 self._proc = proc
211 self._fp = fp
219 self._fp = fp
212
220
213 def close(self):
221 def close(self):
214 # unlike os.popen(), this returns an integer in subprocess coding
222 # unlike os.popen(), this returns an integer in subprocess coding
215 self._fp.close()
223 self._fp.close()
216 return self._proc.wait()
224 return self._proc.wait()
217
225
218 def __iter__(self):
226 def __iter__(self):
219 return iter(self._fp)
227 return iter(self._fp)
220
228
221 def __getattr__(self, attr):
229 def __getattr__(self, attr):
222 return getattr(self._fp, attr)
230 return getattr(self._fp, attr)
223
231
224 def __enter__(self):
232 def __enter__(self):
225 return self
233 return self
226
234
227 def __exit__(self, exc_type, exc_value, exc_tb):
235 def __exit__(self, exc_type, exc_value, exc_tb):
228 self.close()
236 self.close()
229
237
230
238
231 def popen(cmd, mode=b'rb', bufsize=-1):
239 def popen(cmd, mode=b'rb', bufsize=-1):
232 if mode == b'rb':
240 if mode == b'rb':
233 return _popenreader(cmd, bufsize)
241 return _popenreader(cmd, bufsize)
234 elif mode == b'wb':
242 elif mode == b'wb':
235 return _popenwriter(cmd, bufsize)
243 return _popenwriter(cmd, bufsize)
236 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
244 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
237
245
238
246
239 def _popenreader(cmd, bufsize):
247 def _popenreader(cmd, bufsize):
240 p = subprocess.Popen(
248 p = subprocess.Popen(
241 tonativestr(cmd),
249 tonativestr(cmd),
242 shell=True,
250 shell=True,
243 bufsize=bufsize,
251 bufsize=bufsize,
244 close_fds=closefds,
252 close_fds=closefds,
245 stdout=subprocess.PIPE,
253 stdout=subprocess.PIPE,
246 )
254 )
247 return _pfile(p, p.stdout)
255 return _pfile(p, p.stdout)
248
256
249
257
250 def _popenwriter(cmd, bufsize):
258 def _popenwriter(cmd, bufsize):
251 p = subprocess.Popen(
259 p = subprocess.Popen(
252 tonativestr(cmd),
260 tonativestr(cmd),
253 shell=True,
261 shell=True,
254 bufsize=bufsize,
262 bufsize=bufsize,
255 close_fds=closefds,
263 close_fds=closefds,
256 stdin=subprocess.PIPE,
264 stdin=subprocess.PIPE,
257 )
265 )
258 return _pfile(p, p.stdin)
266 return _pfile(p, p.stdin)
259
267
260
268
261 def popen2(cmd, env=None):
269 def popen2(cmd, env=None):
262 # Setting bufsize to -1 lets the system decide the buffer size.
270 # Setting bufsize to -1 lets the system decide the buffer size.
263 # The default for bufsize is 0, meaning unbuffered. This leads to
271 # The default for bufsize is 0, meaning unbuffered. This leads to
264 # poor performance on Mac OS X: http://bugs.python.org/issue4194
272 # poor performance on Mac OS X: http://bugs.python.org/issue4194
265 p = subprocess.Popen(
273 p = subprocess.Popen(
266 tonativestr(cmd),
274 tonativestr(cmd),
267 shell=True,
275 shell=True,
268 bufsize=-1,
276 bufsize=-1,
269 close_fds=closefds,
277 close_fds=closefds,
270 stdin=subprocess.PIPE,
278 stdin=subprocess.PIPE,
271 stdout=subprocess.PIPE,
279 stdout=subprocess.PIPE,
272 env=tonativeenv(env),
280 env=tonativeenv(env),
273 )
281 )
274 return p.stdin, p.stdout
282 return p.stdin, p.stdout
275
283
276
284
277 def popen3(cmd, env=None):
285 def popen3(cmd, env=None):
278 stdin, stdout, stderr, p = popen4(cmd, env)
286 stdin, stdout, stderr, p = popen4(cmd, env)
279 return stdin, stdout, stderr
287 return stdin, stdout, stderr
280
288
281
289
282 def popen4(cmd, env=None, bufsize=-1):
290 def popen4(cmd, env=None, bufsize=-1):
283 p = subprocess.Popen(
291 p = subprocess.Popen(
284 tonativestr(cmd),
292 tonativestr(cmd),
285 shell=True,
293 shell=True,
286 bufsize=bufsize,
294 bufsize=bufsize,
287 close_fds=closefds,
295 close_fds=closefds,
288 stdin=subprocess.PIPE,
296 stdin=subprocess.PIPE,
289 stdout=subprocess.PIPE,
297 stdout=subprocess.PIPE,
290 stderr=subprocess.PIPE,
298 stderr=subprocess.PIPE,
291 env=tonativeenv(env),
299 env=tonativeenv(env),
292 )
300 )
293 return p.stdin, p.stdout, p.stderr, p
301 return p.stdin, p.stdout, p.stderr, p
294
302
295
303
296 def pipefilter(s, cmd):
304 def pipefilter(s, cmd):
297 '''filter string S through command CMD, returning its output'''
305 '''filter string S through command CMD, returning its output'''
298 p = subprocess.Popen(
306 p = subprocess.Popen(
299 tonativestr(cmd),
307 tonativestr(cmd),
300 shell=True,
308 shell=True,
301 close_fds=closefds,
309 close_fds=closefds,
302 stdin=subprocess.PIPE,
310 stdin=subprocess.PIPE,
303 stdout=subprocess.PIPE,
311 stdout=subprocess.PIPE,
304 )
312 )
305 pout, perr = p.communicate(s)
313 pout, perr = p.communicate(s)
306 return pout
314 return pout
307
315
308
316
309 def tempfilter(s, cmd):
317 def tempfilter(s, cmd):
310 """filter string S through a pair of temporary files with CMD.
318 """filter string S through a pair of temporary files with CMD.
311 CMD is used as a template to create the real command to be run,
319 CMD is used as a template to create the real command to be run,
312 with the strings INFILE and OUTFILE replaced by the real names of
320 with the strings INFILE and OUTFILE replaced by the real names of
313 the temporary files generated."""
321 the temporary files generated."""
314 inname, outname = None, None
322 inname, outname = None, None
315 try:
323 try:
316 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
324 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
317 fp = os.fdopen(infd, 'wb')
325 fp = os.fdopen(infd, 'wb')
318 fp.write(s)
326 fp.write(s)
319 fp.close()
327 fp.close()
320 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
328 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
321 os.close(outfd)
329 os.close(outfd)
322 cmd = cmd.replace(b'INFILE', inname)
330 cmd = cmd.replace(b'INFILE', inname)
323 cmd = cmd.replace(b'OUTFILE', outname)
331 cmd = cmd.replace(b'OUTFILE', outname)
324 code = system(cmd)
332 code = system(cmd)
325 if pycompat.sysplatform == b'OpenVMS' and code & 1:
333 if pycompat.sysplatform == b'OpenVMS' and code & 1:
326 code = 0
334 code = 0
327 if code:
335 if code:
328 raise error.Abort(
336 raise error.Abort(
329 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
337 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
330 )
338 )
331 with open(outname, b'rb') as fp:
339 with open(outname, b'rb') as fp:
332 return fp.read()
340 return fp.read()
333 finally:
341 finally:
334 try:
342 try:
335 if inname:
343 if inname:
336 os.unlink(inname)
344 os.unlink(inname)
337 except OSError:
345 except OSError:
338 pass
346 pass
339 try:
347 try:
340 if outname:
348 if outname:
341 os.unlink(outname)
349 os.unlink(outname)
342 except OSError:
350 except OSError:
343 pass
351 pass
344
352
345
353
346 _filtertable = {
354 _filtertable = {
347 b'tempfile:': tempfilter,
355 b'tempfile:': tempfilter,
348 b'pipe:': pipefilter,
356 b'pipe:': pipefilter,
349 }
357 }
350
358
351
359
352 def filter(s, cmd):
360 def filter(s, cmd):
353 """filter a string through a command that transforms its input to its
361 """filter a string through a command that transforms its input to its
354 output"""
362 output"""
355 for name, fn in pycompat.iteritems(_filtertable):
363 for name, fn in pycompat.iteritems(_filtertable):
356 if cmd.startswith(name):
364 if cmd.startswith(name):
357 return fn(s, cmd[len(name) :].lstrip())
365 return fn(s, cmd[len(name) :].lstrip())
358 return pipefilter(s, cmd)
366 return pipefilter(s, cmd)
359
367
360
368
361 _hgexecutable = None
369 _hgexecutable = None
362
370
363
371
364 def hgexecutable():
372 def hgexecutable():
365 """return location of the 'hg' executable.
373 """return location of the 'hg' executable.
366
374
367 Defaults to $HG or 'hg' in the search path.
375 Defaults to $HG or 'hg' in the search path.
368 """
376 """
369 if _hgexecutable is None:
377 if _hgexecutable is None:
370 hg = encoding.environ.get(b'HG')
378 hg = encoding.environ.get(b'HG')
371 mainmod = sys.modules['__main__']
379 mainmod = sys.modules['__main__']
372 if hg:
380 if hg:
373 _sethgexecutable(hg)
381 _sethgexecutable(hg)
374 elif resourceutil.mainfrozen():
382 elif resourceutil.mainfrozen():
375 if getattr(sys, 'frozen', None) == 'macosx_app':
383 if getattr(sys, 'frozen', None) == 'macosx_app':
376 # Env variable set by py2app
384 # Env variable set by py2app
377 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
385 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
378 else:
386 else:
379 _sethgexecutable(pycompat.sysexecutable)
387 _sethgexecutable(pycompat.sysexecutable)
380 elif (
388 elif (
381 not pycompat.iswindows
389 not pycompat.iswindows
382 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
390 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
383 ):
391 ):
384 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
392 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
385 else:
393 else:
386 _sethgexecutable(
394 _sethgexecutable(
387 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
395 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
388 )
396 )
389 return _hgexecutable
397 return _hgexecutable
390
398
391
399
392 def _sethgexecutable(path):
400 def _sethgexecutable(path):
393 """set location of the 'hg' executable"""
401 """set location of the 'hg' executable"""
394 global _hgexecutable
402 global _hgexecutable
395 _hgexecutable = path
403 _hgexecutable = path
396
404
397
405
398 def _testfileno(f, stdf):
406 def _testfileno(f, stdf):
399 fileno = getattr(f, 'fileno', None)
407 fileno = getattr(f, 'fileno', None)
400 try:
408 try:
401 return fileno and fileno() == stdf.fileno()
409 return fileno and fileno() == stdf.fileno()
402 except io.UnsupportedOperation:
410 except io.UnsupportedOperation:
403 return False # fileno() raised UnsupportedOperation
411 return False # fileno() raised UnsupportedOperation
404
412
405
413
406 def isstdin(f):
414 def isstdin(f):
407 return _testfileno(f, sys.__stdin__)
415 return _testfileno(f, sys.__stdin__)
408
416
409
417
410 def isstdout(f):
418 def isstdout(f):
411 return _testfileno(f, sys.__stdout__)
419 return _testfileno(f, sys.__stdout__)
412
420
413
421
414 def protectstdio(uin, uout):
422 def protectstdio(uin, uout):
415 """Duplicate streams and redirect original if (uin, uout) are stdio
423 """Duplicate streams and redirect original if (uin, uout) are stdio
416
424
417 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
425 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
418 redirected to stderr so the output is still readable.
426 redirected to stderr so the output is still readable.
419
427
420 Returns (fin, fout) which point to the original (uin, uout) fds, but
428 Returns (fin, fout) which point to the original (uin, uout) fds, but
421 may be copy of (uin, uout). The returned streams can be considered
429 may be copy of (uin, uout). The returned streams can be considered
422 "owned" in that print(), exec(), etc. never reach to them.
430 "owned" in that print(), exec(), etc. never reach to them.
423 """
431 """
424 uout.flush()
432 uout.flush()
425 fin, fout = uin, uout
433 fin, fout = uin, uout
426 if _testfileno(uin, stdin):
434 if _testfileno(uin, stdin):
427 newfd = os.dup(uin.fileno())
435 newfd = os.dup(uin.fileno())
428 nullfd = os.open(os.devnull, os.O_RDONLY)
436 nullfd = os.open(os.devnull, os.O_RDONLY)
429 os.dup2(nullfd, uin.fileno())
437 os.dup2(nullfd, uin.fileno())
430 os.close(nullfd)
438 os.close(nullfd)
431 fin = os.fdopen(newfd, 'rb')
439 fin = os.fdopen(newfd, 'rb')
432 if _testfileno(uout, stdout):
440 if _testfileno(uout, stdout):
433 newfd = os.dup(uout.fileno())
441 newfd = os.dup(uout.fileno())
434 os.dup2(stderr.fileno(), uout.fileno())
442 os.dup2(stderr.fileno(), uout.fileno())
435 fout = os.fdopen(newfd, 'wb')
443 fout = os.fdopen(newfd, 'wb')
436 return fin, fout
444 return fin, fout
437
445
438
446
439 def restorestdio(uin, uout, fin, fout):
447 def restorestdio(uin, uout, fin, fout):
440 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
448 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
441 uout.flush()
449 uout.flush()
442 for f, uif in [(fin, uin), (fout, uout)]:
450 for f, uif in [(fin, uin), (fout, uout)]:
443 if f is not uif:
451 if f is not uif:
444 os.dup2(f.fileno(), uif.fileno())
452 os.dup2(f.fileno(), uif.fileno())
445 f.close()
453 f.close()
446
454
447
455
448 def shellenviron(environ=None):
456 def shellenviron(environ=None):
449 """return environ with optional override, useful for shelling out"""
457 """return environ with optional override, useful for shelling out"""
450
458
451 def py2shell(val):
459 def py2shell(val):
452 """convert python object into string that is useful to shell"""
460 """convert python object into string that is useful to shell"""
453 if val is None or val is False:
461 if val is None or val is False:
454 return b'0'
462 return b'0'
455 if val is True:
463 if val is True:
456 return b'1'
464 return b'1'
457 return pycompat.bytestr(val)
465 return pycompat.bytestr(val)
458
466
459 env = dict(encoding.environ)
467 env = dict(encoding.environ)
460 if environ:
468 if environ:
461 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
469 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
462 env[b'HG'] = hgexecutable()
470 env[b'HG'] = hgexecutable()
463 return env
471 return env
464
472
465
473
466 if pycompat.iswindows:
474 if pycompat.iswindows:
467
475
468 def shelltonative(cmd, env):
476 def shelltonative(cmd, env):
469 return platform.shelltocmdexe( # pytype: disable=module-attr
477 return platform.shelltocmdexe( # pytype: disable=module-attr
470 cmd, shellenviron(env)
478 cmd, shellenviron(env)
471 )
479 )
472
480
473 tonativestr = encoding.strfromlocal
481 tonativestr = encoding.strfromlocal
474 else:
482 else:
475
483
476 def shelltonative(cmd, env):
484 def shelltonative(cmd, env):
477 return cmd
485 return cmd
478
486
479 tonativestr = pycompat.identity
487 tonativestr = pycompat.identity
480
488
481
489
482 def tonativeenv(env):
490 def tonativeenv(env):
483 """convert the environment from bytes to strings suitable for Popen(), etc."""
491 """convert the environment from bytes to strings suitable for Popen(), etc."""
484 return pycompat.rapply(tonativestr, env)
492 return pycompat.rapply(tonativestr, env)
485
493
486
494
487 def system(cmd, environ=None, cwd=None, out=None):
495 def system(cmd, environ=None, cwd=None, out=None):
488 """enhanced shell command execution.
496 """enhanced shell command execution.
489 run with environment maybe modified, maybe in different dir.
497 run with environment maybe modified, maybe in different dir.
490
498
491 if out is specified, it is assumed to be a file-like object that has a
499 if out is specified, it is assumed to be a file-like object that has a
492 write() method. stdout and stderr will be redirected to out."""
500 write() method. stdout and stderr will be redirected to out."""
493 try:
501 try:
494 stdout.flush()
502 stdout.flush()
495 except Exception:
503 except Exception:
496 pass
504 pass
497 env = shellenviron(environ)
505 env = shellenviron(environ)
498 if out is None or isstdout(out):
506 if out is None or isstdout(out):
499 rc = subprocess.call(
507 rc = subprocess.call(
500 tonativestr(cmd),
508 tonativestr(cmd),
501 shell=True,
509 shell=True,
502 close_fds=closefds,
510 close_fds=closefds,
503 env=tonativeenv(env),
511 env=tonativeenv(env),
504 cwd=pycompat.rapply(tonativestr, cwd),
512 cwd=pycompat.rapply(tonativestr, cwd),
505 )
513 )
506 else:
514 else:
507 proc = subprocess.Popen(
515 proc = subprocess.Popen(
508 tonativestr(cmd),
516 tonativestr(cmd),
509 shell=True,
517 shell=True,
510 close_fds=closefds,
518 close_fds=closefds,
511 env=tonativeenv(env),
519 env=tonativeenv(env),
512 cwd=pycompat.rapply(tonativestr, cwd),
520 cwd=pycompat.rapply(tonativestr, cwd),
513 stdout=subprocess.PIPE,
521 stdout=subprocess.PIPE,
514 stderr=subprocess.STDOUT,
522 stderr=subprocess.STDOUT,
515 )
523 )
516 for line in iter(proc.stdout.readline, b''):
524 for line in iter(proc.stdout.readline, b''):
517 out.write(line)
525 out.write(line)
518 proc.wait()
526 proc.wait()
519 rc = proc.returncode
527 rc = proc.returncode
520 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
528 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
521 rc = 0
529 rc = 0
522 return rc
530 return rc
523
531
524
532
525 _is_gui = None
533 _is_gui = None
526
534
527
535
528 def _gui():
536 def _gui():
529 '''Are we running in a GUI?'''
537 '''Are we running in a GUI?'''
530 if pycompat.isdarwin:
538 if pycompat.isdarwin:
531 if b'SSH_CONNECTION' in encoding.environ:
539 if b'SSH_CONNECTION' in encoding.environ:
532 # handle SSH access to a box where the user is logged in
540 # handle SSH access to a box where the user is logged in
533 return False
541 return False
534 elif getattr(osutil, 'isgui', None):
542 elif getattr(osutil, 'isgui', None):
535 # check if a CoreGraphics session is available
543 # check if a CoreGraphics session is available
536 return osutil.isgui()
544 return osutil.isgui()
537 else:
545 else:
538 # pure build; use a safe default
546 # pure build; use a safe default
539 return True
547 return True
540 else:
548 else:
541 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
549 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
542
550
543
551
544 def gui():
552 def gui():
545 global _is_gui
553 global _is_gui
546 if _is_gui is None:
554 if _is_gui is None:
547 _is_gui = _gui()
555 _is_gui = _gui()
548 return _is_gui
556 return _is_gui
549
557
550
558
551 def hgcmd():
559 def hgcmd():
552 """Return the command used to execute current hg
560 """Return the command used to execute current hg
553
561
554 This is different from hgexecutable() because on Windows we want
562 This is different from hgexecutable() because on Windows we want
555 to avoid things opening new shell windows like batch files, so we
563 to avoid things opening new shell windows like batch files, so we
556 get either the python call or current executable.
564 get either the python call or current executable.
557 """
565 """
558 if resourceutil.mainfrozen():
566 if resourceutil.mainfrozen():
559 if getattr(sys, 'frozen', None) == 'macosx_app':
567 if getattr(sys, 'frozen', None) == 'macosx_app':
560 # Env variable set by py2app
568 # Env variable set by py2app
561 return [encoding.environ[b'EXECUTABLEPATH']]
569 return [encoding.environ[b'EXECUTABLEPATH']]
562 else:
570 else:
563 return [pycompat.sysexecutable]
571 return [pycompat.sysexecutable]
564 return _gethgcmd()
572 return _gethgcmd()
565
573
566
574
567 def rundetached(args, condfn):
575 def rundetached(args, condfn):
568 """Execute the argument list in a detached process.
576 """Execute the argument list in a detached process.
569
577
570 condfn is a callable which is called repeatedly and should return
578 condfn is a callable which is called repeatedly and should return
571 True once the child process is known to have started successfully.
579 True once the child process is known to have started successfully.
572 At this point, the child process PID is returned. If the child
580 At this point, the child process PID is returned. If the child
573 process fails to start or finishes before condfn() evaluates to
581 process fails to start or finishes before condfn() evaluates to
574 True, return -1.
582 True, return -1.
575 """
583 """
576 # Windows case is easier because the child process is either
584 # Windows case is easier because the child process is either
577 # successfully starting and validating the condition or exiting
585 # successfully starting and validating the condition or exiting
578 # on failure. We just poll on its PID. On Unix, if the child
586 # on failure. We just poll on its PID. On Unix, if the child
579 # process fails to start, it will be left in a zombie state until
587 # process fails to start, it will be left in a zombie state until
580 # the parent wait on it, which we cannot do since we expect a long
588 # the parent wait on it, which we cannot do since we expect a long
581 # running process on success. Instead we listen for SIGCHLD telling
589 # running process on success. Instead we listen for SIGCHLD telling
582 # us our child process terminated.
590 # us our child process terminated.
583 terminated = set()
591 terminated = set()
584
592
585 def handler(signum, frame):
593 def handler(signum, frame):
586 terminated.add(os.wait())
594 terminated.add(os.wait())
587
595
588 prevhandler = None
596 prevhandler = None
589 SIGCHLD = getattr(signal, 'SIGCHLD', None)
597 SIGCHLD = getattr(signal, 'SIGCHLD', None)
590 if SIGCHLD is not None:
598 if SIGCHLD is not None:
591 prevhandler = signal.signal(SIGCHLD, handler)
599 prevhandler = signal.signal(SIGCHLD, handler)
592 try:
600 try:
593 pid = spawndetached(args)
601 pid = spawndetached(args)
594 while not condfn():
602 while not condfn():
595 if (pid in terminated or not testpid(pid)) and not condfn():
603 if (pid in terminated or not testpid(pid)) and not condfn():
596 return -1
604 return -1
597 time.sleep(0.1)
605 time.sleep(0.1)
598 return pid
606 return pid
599 finally:
607 finally:
600 if prevhandler is not None:
608 if prevhandler is not None:
601 signal.signal(signal.SIGCHLD, prevhandler)
609 signal.signal(signal.SIGCHLD, prevhandler)
602
610
603
611
604 @contextlib.contextmanager
612 @contextlib.contextmanager
605 def uninterruptible(warn):
613 def uninterruptible(warn):
606 """Inhibit SIGINT handling on a region of code.
614 """Inhibit SIGINT handling on a region of code.
607
615
608 Note that if this is called in a non-main thread, it turns into a no-op.
616 Note that if this is called in a non-main thread, it turns into a no-op.
609
617
610 Args:
618 Args:
611 warn: A callable which takes no arguments, and returns True if the
619 warn: A callable which takes no arguments, and returns True if the
612 previous signal handling should be restored.
620 previous signal handling should be restored.
613 """
621 """
614
622
615 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
623 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
616 shouldbail = []
624 shouldbail = []
617
625
618 def disabledsiginthandler(*args):
626 def disabledsiginthandler(*args):
619 if warn():
627 if warn():
620 signal.signal(signal.SIGINT, oldsiginthandler[0])
628 signal.signal(signal.SIGINT, oldsiginthandler[0])
621 del oldsiginthandler[0]
629 del oldsiginthandler[0]
622 shouldbail.append(True)
630 shouldbail.append(True)
623
631
624 try:
632 try:
625 try:
633 try:
626 signal.signal(signal.SIGINT, disabledsiginthandler)
634 signal.signal(signal.SIGINT, disabledsiginthandler)
627 except ValueError:
635 except ValueError:
628 # wrong thread, oh well, we tried
636 # wrong thread, oh well, we tried
629 del oldsiginthandler[0]
637 del oldsiginthandler[0]
630 yield
638 yield
631 finally:
639 finally:
632 if oldsiginthandler:
640 if oldsiginthandler:
633 signal.signal(signal.SIGINT, oldsiginthandler[0])
641 signal.signal(signal.SIGINT, oldsiginthandler[0])
634 if shouldbail:
642 if shouldbail:
635 raise KeyboardInterrupt
643 raise KeyboardInterrupt
636
644
637
645
638 if pycompat.iswindows:
646 if pycompat.iswindows:
639 # no fork on Windows, but we can create a detached process
647 # no fork on Windows, but we can create a detached process
640 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
648 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
641 # No stdlib constant exists for this value
649 # No stdlib constant exists for this value
642 DETACHED_PROCESS = 0x00000008
650 DETACHED_PROCESS = 0x00000008
643 # Following creation flags might create a console GUI window.
651 # Following creation flags might create a console GUI window.
644 # Using subprocess.CREATE_NEW_CONSOLE might helps.
652 # Using subprocess.CREATE_NEW_CONSOLE might helps.
645 # See https://phab.mercurial-scm.org/D1701 for discussion
653 # See https://phab.mercurial-scm.org/D1701 for discussion
646 _creationflags = (
654 _creationflags = (
647 DETACHED_PROCESS
655 DETACHED_PROCESS
648 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
656 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
649 )
657 )
650
658
651 def runbgcommand(
659 def runbgcommand(
652 script,
660 script,
653 env,
661 env,
654 shell=False,
662 shell=False,
655 stdout=None,
663 stdout=None,
656 stderr=None,
664 stderr=None,
657 ensurestart=True,
665 ensurestart=True,
658 record_wait=None,
666 record_wait=None,
659 stdin_bytes=None,
667 stdin_bytes=None,
660 ):
668 ):
661 '''Spawn a command without waiting for it to finish.'''
669 '''Spawn a command without waiting for it to finish.'''
662 # we can't use close_fds *and* redirect stdin. I'm not sure that we
670 # we can't use close_fds *and* redirect stdin. I'm not sure that we
663 # need to because the detached process has no console connection.
671 # need to because the detached process has no console connection.
664
672
665 try:
673 try:
666 stdin = None
674 stdin = None
667 if stdin_bytes is not None:
675 if stdin_bytes is not None:
668 stdin = pycompat.unnamedtempfile()
676 stdin = pycompat.unnamedtempfile()
669 stdin.write(stdin_bytes)
677 stdin.write(stdin_bytes)
670 stdin.flush()
678 stdin.flush()
671 stdin.seek(0)
679 stdin.seek(0)
672
680
673 p = subprocess.Popen(
681 p = subprocess.Popen(
674 pycompat.rapply(tonativestr, script),
682 pycompat.rapply(tonativestr, script),
675 shell=shell,
683 shell=shell,
676 env=tonativeenv(env),
684 env=tonativeenv(env),
677 close_fds=True,
685 close_fds=True,
678 creationflags=_creationflags,
686 creationflags=_creationflags,
679 stdin=stdin,
687 stdin=stdin,
680 stdout=stdout,
688 stdout=stdout,
681 stderr=stderr,
689 stderr=stderr,
682 )
690 )
683 if record_wait is not None:
691 if record_wait is not None:
684 record_wait(p.wait)
692 record_wait(p.wait)
685 finally:
693 finally:
686 if stdin is not None:
694 if stdin is not None:
687 stdin.close()
695 stdin.close()
688
696
689
697
690 else:
698 else:
691
699
692 def runbgcommand(
700 def runbgcommand(
693 cmd,
701 cmd,
694 env,
702 env,
695 shell=False,
703 shell=False,
696 stdout=None,
704 stdout=None,
697 stderr=None,
705 stderr=None,
698 ensurestart=True,
706 ensurestart=True,
699 record_wait=None,
707 record_wait=None,
700 stdin_bytes=None,
708 stdin_bytes=None,
701 ):
709 ):
702 """Spawn a command without waiting for it to finish.
710 """Spawn a command without waiting for it to finish.
703
711
704
712
705 When `record_wait` is not None, the spawned process will not be fully
713 When `record_wait` is not None, the spawned process will not be fully
706 detached and the `record_wait` argument will be called with a the
714 detached and the `record_wait` argument will be called with a the
707 `Subprocess.wait` function for the spawned process. This is mostly
715 `Subprocess.wait` function for the spawned process. This is mostly
708 useful for developers that need to make sure the spawned process
716 useful for developers that need to make sure the spawned process
709 finished before a certain point. (eg: writing test)"""
717 finished before a certain point. (eg: writing test)"""
710 if pycompat.isdarwin:
718 if pycompat.isdarwin:
711 # avoid crash in CoreFoundation in case another thread
719 # avoid crash in CoreFoundation in case another thread
712 # calls gui() while we're calling fork().
720 # calls gui() while we're calling fork().
713 gui()
721 gui()
714
722
715 # double-fork to completely detach from the parent process
723 # double-fork to completely detach from the parent process
716 # based on http://code.activestate.com/recipes/278731
724 # based on http://code.activestate.com/recipes/278731
717 if record_wait is None:
725 if record_wait is None:
718 pid = os.fork()
726 pid = os.fork()
719 if pid:
727 if pid:
720 if not ensurestart:
728 if not ensurestart:
721 # Even though we're not waiting on the child process,
729 # Even though we're not waiting on the child process,
722 # we still must call waitpid() on it at some point so
730 # we still must call waitpid() on it at some point so
723 # it's not a zombie/defunct. This is especially relevant for
731 # it's not a zombie/defunct. This is especially relevant for
724 # chg since the parent process won't die anytime soon.
732 # chg since the parent process won't die anytime soon.
725 # We use a thread to make the overhead tiny.
733 # We use a thread to make the overhead tiny.
726 def _do_wait():
734 def _do_wait():
727 os.waitpid(pid, 0)
735 os.waitpid(pid, 0)
728
736
729 t = threading.Thread(target=_do_wait)
737 t = threading.Thread(target=_do_wait)
730 t.daemon = True
738 t.daemon = True
731 t.start()
739 t.start()
732 return
740 return
733 # Parent process
741 # Parent process
734 (_pid, status) = os.waitpid(pid, 0)
742 (_pid, status) = os.waitpid(pid, 0)
735 if os.WIFEXITED(status):
743 if os.WIFEXITED(status):
736 returncode = os.WEXITSTATUS(status)
744 returncode = os.WEXITSTATUS(status)
737 else:
745 else:
738 returncode = -(os.WTERMSIG(status))
746 returncode = -(os.WTERMSIG(status))
739 if returncode != 0:
747 if returncode != 0:
740 # The child process's return code is 0 on success, an errno
748 # The child process's return code is 0 on success, an errno
741 # value on failure, or 255 if we don't have a valid errno
749 # value on failure, or 255 if we don't have a valid errno
742 # value.
750 # value.
743 #
751 #
744 # (It would be slightly nicer to return the full exception info
752 # (It would be slightly nicer to return the full exception info
745 # over a pipe as the subprocess module does. For now it
753 # over a pipe as the subprocess module does. For now it
746 # doesn't seem worth adding that complexity here, though.)
754 # doesn't seem worth adding that complexity here, though.)
747 if returncode == 255:
755 if returncode == 255:
748 returncode = errno.EINVAL
756 returncode = errno.EINVAL
749 raise OSError(
757 raise OSError(
750 returncode,
758 returncode,
751 b'error running %r: %s'
759 b'error running %r: %s'
752 % (cmd, os.strerror(returncode)),
760 % (cmd, os.strerror(returncode)),
753 )
761 )
754 return
762 return
755
763
756 returncode = 255
764 returncode = 255
757 try:
765 try:
758 if record_wait is None:
766 if record_wait is None:
759 # Start a new session
767 # Start a new session
760 os.setsid()
768 os.setsid()
761 # connect stdin to devnull to make sure the subprocess can't
769 # connect stdin to devnull to make sure the subprocess can't
762 # muck up that stream for mercurial.
770 # muck up that stream for mercurial.
763 if stdin_bytes is None:
771 if stdin_bytes is None:
764 stdin = open(os.devnull, b'r')
772 stdin = open(os.devnull, b'r')
765 else:
773 else:
766 stdin = pycompat.unnamedtempfile()
774 stdin = pycompat.unnamedtempfile()
767 stdin.write(stdin_bytes)
775 stdin.write(stdin_bytes)
768 stdin.flush()
776 stdin.flush()
769 stdin.seek(0)
777 stdin.seek(0)
770
778
771 if stdout is None:
779 if stdout is None:
772 stdout = open(os.devnull, b'w')
780 stdout = open(os.devnull, b'w')
773 if stderr is None:
781 if stderr is None:
774 stderr = open(os.devnull, b'w')
782 stderr = open(os.devnull, b'w')
775
783
776 p = subprocess.Popen(
784 p = subprocess.Popen(
777 cmd,
785 cmd,
778 shell=shell,
786 shell=shell,
779 env=env,
787 env=env,
780 close_fds=True,
788 close_fds=True,
781 stdin=stdin,
789 stdin=stdin,
782 stdout=stdout,
790 stdout=stdout,
783 stderr=stderr,
791 stderr=stderr,
784 )
792 )
785 if record_wait is not None:
793 if record_wait is not None:
786 record_wait(p.wait)
794 record_wait(p.wait)
787 returncode = 0
795 returncode = 0
788 except EnvironmentError as ex:
796 except EnvironmentError as ex:
789 returncode = ex.errno & 0xFF
797 returncode = ex.errno & 0xFF
790 if returncode == 0:
798 if returncode == 0:
791 # This shouldn't happen, but just in case make sure the
799 # This shouldn't happen, but just in case make sure the
792 # return code is never 0 here.
800 # return code is never 0 here.
793 returncode = 255
801 returncode = 255
794 except Exception:
802 except Exception:
795 returncode = 255
803 returncode = 255
796 finally:
804 finally:
797 # mission accomplished, this child needs to exit and not
805 # mission accomplished, this child needs to exit and not
798 # continue the hg process here.
806 # continue the hg process here.
799 stdin.close()
807 stdin.close()
800 if record_wait is None:
808 if record_wait is None:
801 os._exit(returncode)
809 os._exit(returncode)
@@ -1,229 +1,254 b''
1 Create a repository:
1 Create a repository:
2
2
3 #if no-extraextensions
3 #if no-extraextensions
4 $ hg config
4 $ hg config
5 devel.all-warnings=true
5 devel.all-warnings=true
6 devel.default-date=0 0
6 devel.default-date=0 0
7 extensions.fsmonitor= (fsmonitor !)
7 extensions.fsmonitor= (fsmonitor !)
8 largefiles.usercache=$TESTTMP/.cache/largefiles
8 largefiles.usercache=$TESTTMP/.cache/largefiles
9 lfs.usercache=$TESTTMP/.cache/lfs
9 lfs.usercache=$TESTTMP/.cache/lfs
10 ui.slash=True
10 ui.slash=True
11 ui.interactive=False
11 ui.interactive=False
12 ui.detailed-exit-code=True
12 ui.detailed-exit-code=True
13 ui.merge=internal:merge
13 ui.merge=internal:merge
14 ui.mergemarkers=detailed
14 ui.mergemarkers=detailed
15 ui.promptecho=True
15 ui.promptecho=True
16 ui.timeout.warn=15
16 ui.timeout.warn=15
17 web.address=localhost
17 web.address=localhost
18 web\.ipv6=(?:True|False) (re)
18 web\.ipv6=(?:True|False) (re)
19 web.server-header=testing stub value
19 web.server-header=testing stub value
20 #endif
20 #endif
21
21
22 $ hg init t
22 $ hg init t
23 $ cd t
23 $ cd t
24
24
25 Prepare a changeset:
25 Prepare a changeset:
26
26
27 $ echo a > a
27 $ echo a > a
28 $ hg add a
28 $ hg add a
29
29
30 $ hg status
30 $ hg status
31 A a
31 A a
32
32
33 Writes to stdio succeed and fail appropriately
33 Writes to stdio succeed and fail appropriately
34
34
35 #if devfull
35 #if devfull
36 $ hg status 2>/dev/full
36 $ hg status 2>/dev/full
37 A a
37 A a
38
38
39 $ hg status >/dev/full
39 $ hg status >/dev/full
40 abort: No space left on device
40 abort: No space left on device
41 [255]
41 [255]
42 #endif
42 #endif
43
43
44 #if devfull
44 #if devfull
45 $ hg status >/dev/full 2>&1
45 $ hg status >/dev/full 2>&1
46 [255]
46 [255]
47
47
48 $ hg status ENOENT 2>/dev/full
48 $ hg status ENOENT 2>/dev/full
49 [255]
49 [255]
50 #endif
50 #endif
51
51
52 On Python 3, stdio may be None:
53
54 $ hg debuguiprompt --config ui.interactive=true 0<&-
55 abort: Bad file descriptor
56 [255]
57 $ hg version -q 0<&-
58 Mercurial Distributed SCM * (glob)
59
60 #if py3
61 $ hg version -q 1>&-
62 abort: Bad file descriptor
63 [255]
64 #else
65 $ hg version -q 1>&-
66 #endif
67 $ hg unknown -q 1>&-
68 hg: unknown command 'unknown'
69 (did you mean debugknown?)
70 [255]
71
72 $ hg version -q 2>&-
73 Mercurial Distributed SCM * (glob)
74 $ hg unknown -q 2>&-
75 [255]
76
52 $ hg commit -m test
77 $ hg commit -m test
53
78
54 This command is ancient:
79 This command is ancient:
55
80
56 $ hg history
81 $ hg history
57 changeset: 0:acb14030fe0a
82 changeset: 0:acb14030fe0a
58 tag: tip
83 tag: tip
59 user: test
84 user: test
60 date: Thu Jan 01 00:00:00 1970 +0000
85 date: Thu Jan 01 00:00:00 1970 +0000
61 summary: test
86 summary: test
62
87
63
88
64 Verify that updating to revision 0 via commands.update() works properly
89 Verify that updating to revision 0 via commands.update() works properly
65
90
66 $ cat <<EOF > update_to_rev0.py
91 $ cat <<EOF > update_to_rev0.py
67 > from mercurial import commands, hg, ui as uimod
92 > from mercurial import commands, hg, ui as uimod
68 > myui = uimod.ui.load()
93 > myui = uimod.ui.load()
69 > repo = hg.repository(myui, path=b'.')
94 > repo = hg.repository(myui, path=b'.')
70 > commands.update(myui, repo, rev=b"0")
95 > commands.update(myui, repo, rev=b"0")
71 > EOF
96 > EOF
72 $ hg up null
97 $ hg up null
73 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
98 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
74 $ "$PYTHON" ./update_to_rev0.py
99 $ "$PYTHON" ./update_to_rev0.py
75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 $ hg identify -n
101 $ hg identify -n
77 0
102 0
78
103
79
104
80 Poke around at hashes:
105 Poke around at hashes:
81
106
82 $ hg manifest --debug
107 $ hg manifest --debug
83 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
108 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
84
109
85 $ hg cat a
110 $ hg cat a
86 a
111 a
87
112
88 Verify should succeed:
113 Verify should succeed:
89
114
90 $ hg verify
115 $ hg verify
91 checking changesets
116 checking changesets
92 checking manifests
117 checking manifests
93 crosschecking files in changesets and manifests
118 crosschecking files in changesets and manifests
94 checking files
119 checking files
95 checked 1 changesets with 1 changes to 1 files
120 checked 1 changesets with 1 changes to 1 files
96
121
97 Repository root:
122 Repository root:
98
123
99 $ hg root
124 $ hg root
100 $TESTTMP/t
125 $TESTTMP/t
101 $ hg log -l1 -T '{reporoot}\n'
126 $ hg log -l1 -T '{reporoot}\n'
102 $TESTTMP/t
127 $TESTTMP/t
103 $ hg root -Tjson | sed 's|\\\\|\\|g'
128 $ hg root -Tjson | sed 's|\\\\|\\|g'
104 [
129 [
105 {
130 {
106 "hgpath": "$TESTTMP/t/.hg",
131 "hgpath": "$TESTTMP/t/.hg",
107 "reporoot": "$TESTTMP/t",
132 "reporoot": "$TESTTMP/t",
108 "storepath": "$TESTTMP/t/.hg/store"
133 "storepath": "$TESTTMP/t/.hg/store"
109 }
134 }
110 ]
135 ]
111
136
112 At the end...
137 At the end...
113
138
114 $ cd ..
139 $ cd ..
115
140
116 Status message redirection:
141 Status message redirection:
117
142
118 $ hg init empty
143 $ hg init empty
119
144
120 status messages are sent to stdout by default:
145 status messages are sent to stdout by default:
121
146
122 $ hg outgoing -R t empty -Tjson 2>/dev/null
147 $ hg outgoing -R t empty -Tjson 2>/dev/null
123 comparing with empty
148 comparing with empty
124 searching for changes
149 searching for changes
125 [
150 [
126 {
151 {
127 "bookmarks": [],
152 "bookmarks": [],
128 "branch": "default",
153 "branch": "default",
129 "date": [0, 0],
154 "date": [0, 0],
130 "desc": "test",
155 "desc": "test",
131 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
156 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
132 "parents": ["0000000000000000000000000000000000000000"],
157 "parents": ["0000000000000000000000000000000000000000"],
133 "phase": "draft",
158 "phase": "draft",
134 "rev": 0,
159 "rev": 0,
135 "tags": ["tip"],
160 "tags": ["tip"],
136 "user": "test"
161 "user": "test"
137 }
162 }
138 ]
163 ]
139
164
140 which can be configured to send to stderr, so the output wouldn't be
165 which can be configured to send to stderr, so the output wouldn't be
141 interleaved:
166 interleaved:
142
167
143 $ cat <<'EOF' >> "$HGRCPATH"
168 $ cat <<'EOF' >> "$HGRCPATH"
144 > [ui]
169 > [ui]
145 > message-output = stderr
170 > message-output = stderr
146 > EOF
171 > EOF
147 $ hg outgoing -R t empty -Tjson 2>/dev/null
172 $ hg outgoing -R t empty -Tjson 2>/dev/null
148 [
173 [
149 {
174 {
150 "bookmarks": [],
175 "bookmarks": [],
151 "branch": "default",
176 "branch": "default",
152 "date": [0, 0],
177 "date": [0, 0],
153 "desc": "test",
178 "desc": "test",
154 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
179 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
155 "parents": ["0000000000000000000000000000000000000000"],
180 "parents": ["0000000000000000000000000000000000000000"],
156 "phase": "draft",
181 "phase": "draft",
157 "rev": 0,
182 "rev": 0,
158 "tags": ["tip"],
183 "tags": ["tip"],
159 "user": "test"
184 "user": "test"
160 }
185 }
161 ]
186 ]
162 $ hg outgoing -R t empty -Tjson >/dev/null
187 $ hg outgoing -R t empty -Tjson >/dev/null
163 comparing with empty
188 comparing with empty
164 searching for changes
189 searching for changes
165
190
166 this option should be turned off by HGPLAIN= since it may break scripting use:
191 this option should be turned off by HGPLAIN= since it may break scripting use:
167
192
168 $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null
193 $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null
169 comparing with empty
194 comparing with empty
170 searching for changes
195 searching for changes
171 [
196 [
172 {
197 {
173 "bookmarks": [],
198 "bookmarks": [],
174 "branch": "default",
199 "branch": "default",
175 "date": [0, 0],
200 "date": [0, 0],
176 "desc": "test",
201 "desc": "test",
177 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
202 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
178 "parents": ["0000000000000000000000000000000000000000"],
203 "parents": ["0000000000000000000000000000000000000000"],
179 "phase": "draft",
204 "phase": "draft",
180 "rev": 0,
205 "rev": 0,
181 "tags": ["tip"],
206 "tags": ["tip"],
182 "user": "test"
207 "user": "test"
183 }
208 }
184 ]
209 ]
185
210
186 but still overridden by --config:
211 but still overridden by --config:
187
212
188 $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \
213 $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \
189 > 2>/dev/null
214 > 2>/dev/null
190 [
215 [
191 {
216 {
192 "bookmarks": [],
217 "bookmarks": [],
193 "branch": "default",
218 "branch": "default",
194 "date": [0, 0],
219 "date": [0, 0],
195 "desc": "test",
220 "desc": "test",
196 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
221 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
197 "parents": ["0000000000000000000000000000000000000000"],
222 "parents": ["0000000000000000000000000000000000000000"],
198 "phase": "draft",
223 "phase": "draft",
199 "rev": 0,
224 "rev": 0,
200 "tags": ["tip"],
225 "tags": ["tip"],
201 "user": "test"
226 "user": "test"
202 }
227 }
203 ]
228 ]
204
229
205 Invalid ui.message-output option:
230 Invalid ui.message-output option:
206
231
207 $ hg log -R t --config ui.message-output=bad
232 $ hg log -R t --config ui.message-output=bad
208 abort: invalid ui.message-output destination: bad
233 abort: invalid ui.message-output destination: bad
209 [255]
234 [255]
210
235
211 Underlying message streams should be updated when ui.fout/ferr are set:
236 Underlying message streams should be updated when ui.fout/ferr are set:
212
237
213 $ cat <<'EOF' > capui.py
238 $ cat <<'EOF' > capui.py
214 > from mercurial import pycompat, registrar
239 > from mercurial import pycompat, registrar
215 > cmdtable = {}
240 > cmdtable = {}
216 > command = registrar.command(cmdtable)
241 > command = registrar.command(cmdtable)
217 > @command(b'capui', norepo=True)
242 > @command(b'capui', norepo=True)
218 > def capui(ui):
243 > def capui(ui):
219 > out = ui.fout
244 > out = ui.fout
220 > ui.fout = pycompat.bytesio()
245 > ui.fout = pycompat.bytesio()
221 > ui.status(b'status\n')
246 > ui.status(b'status\n')
222 > ui.ferr = pycompat.bytesio()
247 > ui.ferr = pycompat.bytesio()
223 > ui.warn(b'warn\n')
248 > ui.warn(b'warn\n')
224 > out.write(b'stdout: %s' % ui.fout.getvalue())
249 > out.write(b'stdout: %s' % ui.fout.getvalue())
225 > out.write(b'stderr: %s' % ui.ferr.getvalue())
250 > out.write(b'stderr: %s' % ui.ferr.getvalue())
226 > EOF
251 > EOF
227 $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui
252 $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui
228 stdout: status
253 stdout: status
229 stderr: warn
254 stderr: warn
General Comments 0
You need to be logged in to leave comments. Login now