##// END OF EJS Templates
procutil: add a shim for translating shell commands to native commands
Matt Harbison -
r38510:72286f9e default
parent child Browse files
Show More
@@ -1,283 +1,281 b''
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11 import sys
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 demandimport,
16 16 encoding,
17 17 error,
18 18 extensions,
19 19 pycompat,
20 20 util,
21 21 )
22 22 from .utils import (
23 23 procutil,
24 24 stringutil,
25 25 )
26 26
27 27 def pythonhook(ui, repo, htype, hname, funcname, args, throw):
28 28 '''call python hook. hook is callable object, looked up as
29 29 name in python module. if callable returns "true", hook
30 30 fails, else passes. if hook raises exception, treated as
31 31 hook failure. exception propagates if throw is "true".
32 32
33 33 reason for "true" meaning "hook failed" is so that
34 34 unmodified commands (e.g. mercurial.commands.update) can
35 35 be run as hooks without wrappers to convert return values.'''
36 36
37 37 if callable(funcname):
38 38 obj = funcname
39 39 funcname = pycompat.sysbytes(obj.__module__ + r"." + obj.__name__)
40 40 else:
41 41 d = funcname.rfind('.')
42 42 if d == -1:
43 43 raise error.HookLoadError(
44 44 _('%s hook is invalid: "%s" not in a module')
45 45 % (hname, funcname))
46 46 modname = funcname[:d]
47 47 oldpaths = sys.path
48 48 if procutil.mainfrozen():
49 49 # binary installs require sys.path manipulation
50 50 modpath, modfile = os.path.split(modname)
51 51 if modpath and modfile:
52 52 sys.path = sys.path[:] + [modpath]
53 53 modname = modfile
54 54 with demandimport.deactivated():
55 55 try:
56 56 obj = __import__(pycompat.sysstr(modname))
57 57 except (ImportError, SyntaxError):
58 58 e1 = sys.exc_info()
59 59 try:
60 60 # extensions are loaded with hgext_ prefix
61 61 obj = __import__(r"hgext_%s" % pycompat.sysstr(modname))
62 62 except (ImportError, SyntaxError):
63 63 e2 = sys.exc_info()
64 64 if ui.tracebackflag:
65 65 ui.warn(_('exception from first failed import '
66 66 'attempt:\n'))
67 67 ui.traceback(e1)
68 68 if ui.tracebackflag:
69 69 ui.warn(_('exception from second failed import '
70 70 'attempt:\n'))
71 71 ui.traceback(e2)
72 72
73 73 if not ui.tracebackflag:
74 74 tracebackhint = _(
75 75 'run with --traceback for stack trace')
76 76 else:
77 77 tracebackhint = None
78 78 raise error.HookLoadError(
79 79 _('%s hook is invalid: import of "%s" failed') %
80 80 (hname, modname), hint=tracebackhint)
81 81 sys.path = oldpaths
82 82 try:
83 83 for p in funcname.split('.')[1:]:
84 84 obj = getattr(obj, p)
85 85 except AttributeError:
86 86 raise error.HookLoadError(
87 87 _('%s hook is invalid: "%s" is not defined')
88 88 % (hname, funcname))
89 89 if not callable(obj):
90 90 raise error.HookLoadError(
91 91 _('%s hook is invalid: "%s" is not callable')
92 92 % (hname, funcname))
93 93
94 94 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
95 95 starttime = util.timer()
96 96
97 97 try:
98 98 r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args))
99 99 except Exception as exc:
100 100 if isinstance(exc, error.Abort):
101 101 ui.warn(_('error: %s hook failed: %s\n') %
102 102 (hname, exc.args[0]))
103 103 else:
104 104 ui.warn(_('error: %s hook raised an exception: '
105 105 '%s\n') % (hname, encoding.strtolocal(str(exc))))
106 106 if throw:
107 107 raise
108 108 if not ui.tracebackflag:
109 109 ui.warn(_('(run with --traceback for stack trace)\n'))
110 110 ui.traceback()
111 111 return True, True
112 112 finally:
113 113 duration = util.timer() - starttime
114 114 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
115 115 htype, funcname, duration)
116 116 if r:
117 117 if throw:
118 118 raise error.HookAbort(_('%s hook failed') % hname)
119 119 ui.warn(_('warning: %s hook failed\n') % hname)
120 120 return r, False
121 121
122 122 def _exthook(ui, repo, htype, name, cmd, args, throw):
123 123 starttime = util.timer()
124 124 env = {}
125 125
126 126 # make in-memory changes visible to external process
127 127 if repo is not None:
128 128 tr = repo.currenttransaction()
129 129 repo.dirstate.write(tr)
130 130 if tr and tr.writepending():
131 131 env['HG_PENDING'] = repo.root
132 132 env['HG_HOOKTYPE'] = htype
133 133 env['HG_HOOKNAME'] = name
134 134
135 135 for k, v in args.iteritems():
136 136 if callable(v):
137 137 v = v()
138 138 if isinstance(v, (dict, list)):
139 139 v = stringutil.pprint(v)
140 140 env['HG_' + k.upper()] = v
141 141
142 if pycompat.iswindows:
143 environ = procutil.shellenviron(env)
144 cmd = util.platform.shelltocmdexe(cmd, environ)
142 cmd = procutil.shelltonative(cmd, env)
145 143
146 144 ui.note(_("running hook %s: %s\n") % (name, cmd))
147 145
148 146 if repo:
149 147 cwd = repo.root
150 148 else:
151 149 cwd = pycompat.getcwd()
152 150 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,))
153 151
154 152 duration = util.timer() - starttime
155 153 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
156 154 name, cmd, duration)
157 155 if r:
158 156 desc = procutil.explainexit(r)
159 157 if throw:
160 158 raise error.HookAbort(_('%s hook %s') % (name, desc))
161 159 ui.warn(_('warning: %s hook %s\n') % (name, desc))
162 160 return r
163 161
164 162 # represent an untrusted hook command
165 163 _fromuntrusted = object()
166 164
167 165 def _allhooks(ui):
168 166 """return a list of (hook-id, cmd) pairs sorted by priority"""
169 167 hooks = _hookitems(ui)
170 168 # Be careful in this section, propagating the real commands from untrusted
171 169 # sources would create a security vulnerability, make sure anything altered
172 170 # in that section uses "_fromuntrusted" as its command.
173 171 untrustedhooks = _hookitems(ui, _untrusted=True)
174 172 for name, value in untrustedhooks.items():
175 173 trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
176 174 if value != trustedvalue:
177 175 (lp, lo, lk, lv) = trustedvalue
178 176 hooks[name] = (lp, lo, lk, _fromuntrusted)
179 177 # (end of the security sensitive section)
180 178 return [(k, v) for p, o, k, v in sorted(hooks.values())]
181 179
182 180 def _hookitems(ui, _untrusted=False):
183 181 """return all hooks items ready to be sorted"""
184 182 hooks = {}
185 183 for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
186 184 if not name.startswith('priority'):
187 185 priority = ui.configint('hooks', 'priority.%s' % name, 0)
188 186 hooks[name] = (-priority, len(hooks), name, cmd)
189 187 return hooks
190 188
191 189 _redirect = False
192 190 def redirect(state):
193 191 global _redirect
194 192 _redirect = state
195 193
196 194 def hashook(ui, htype):
197 195 """return True if a hook is configured for 'htype'"""
198 196 if not ui.callhooks:
199 197 return False
200 198 for hname, cmd in _allhooks(ui):
201 199 if hname.split('.')[0] == htype and cmd:
202 200 return True
203 201 return False
204 202
205 203 def hook(ui, repo, htype, throw=False, **args):
206 204 if not ui.callhooks:
207 205 return False
208 206
209 207 hooks = []
210 208 for hname, cmd in _allhooks(ui):
211 209 if hname.split('.')[0] == htype and cmd:
212 210 hooks.append((hname, cmd))
213 211
214 212 res = runhooks(ui, repo, htype, hooks, throw=throw, **args)
215 213 r = False
216 214 for hname, cmd in hooks:
217 215 r = res[hname][0] or r
218 216 return r
219 217
220 218 def runhooks(ui, repo, htype, hooks, throw=False, **args):
221 219 args = pycompat.byteskwargs(args)
222 220 res = {}
223 221 oldstdout = -1
224 222
225 223 try:
226 224 for hname, cmd in hooks:
227 225 if oldstdout == -1 and _redirect:
228 226 try:
229 227 stdoutno = procutil.stdout.fileno()
230 228 stderrno = procutil.stderr.fileno()
231 229 # temporarily redirect stdout to stderr, if possible
232 230 if stdoutno >= 0 and stderrno >= 0:
233 231 procutil.stdout.flush()
234 232 oldstdout = os.dup(stdoutno)
235 233 os.dup2(stderrno, stdoutno)
236 234 except (OSError, AttributeError):
237 235 # files seem to be bogus, give up on redirecting (WSGI, etc)
238 236 pass
239 237
240 238 if cmd is _fromuntrusted:
241 239 if throw:
242 240 raise error.HookAbort(
243 241 _('untrusted hook %s not executed') % hname,
244 242 hint = _("see 'hg help config.trusted'"))
245 243 ui.warn(_('warning: untrusted hook %s not executed\n') % hname)
246 244 r = 1
247 245 raised = False
248 246 elif callable(cmd):
249 247 r, raised = pythonhook(ui, repo, htype, hname, cmd, args,
250 248 throw)
251 249 elif cmd.startswith('python:'):
252 250 if cmd.count(':') >= 2:
253 251 path, cmd = cmd[7:].rsplit(':', 1)
254 252 path = util.expandpath(path)
255 253 if repo:
256 254 path = os.path.join(repo.root, path)
257 255 try:
258 256 mod = extensions.loadpath(path, 'hghook.%s' % hname)
259 257 except Exception:
260 258 ui.write(_("loading %s hook failed:\n") % hname)
261 259 raise
262 260 hookfn = getattr(mod, cmd)
263 261 else:
264 262 hookfn = cmd[7:].strip()
265 263 r, raised = pythonhook(ui, repo, htype, hname, hookfn, args,
266 264 throw)
267 265 else:
268 266 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
269 267 raised = False
270 268
271 269 res[hname] = r, raised
272 270 finally:
273 271 # The stderr is fully buffered on Windows when connected to a pipe.
274 272 # A forcible flush is required to make small stderr data in the
275 273 # remote side available to the client immediately.
276 274 procutil.stderr.flush()
277 275
278 276 if _redirect and oldstdout >= 0:
279 277 procutil.stdout.flush() # write hook output to stderr fd
280 278 os.dup2(oldstdout, stdoutno)
281 279 os.close(oldstdout)
282 280
283 281 return res
@@ -1,410 +1,417 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 276 if uin is 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 282 if uout is 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 if pycompat.iswindows:
321 def shelltonative(cmd, env):
322 return platform.shelltocmdexe(cmd, shellenviron(env))
323 else:
324 def shelltonative(cmd, env):
325 return cmd
326
320 327 def system(cmd, environ=None, cwd=None, out=None):
321 328 '''enhanced shell command execution.
322 329 run with environment maybe modified, maybe in different dir.
323 330
324 331 if out is specified, it is assumed to be a file-like object that has a
325 332 write() method. stdout and stderr will be redirected to out.'''
326 333 try:
327 334 stdout.flush()
328 335 except Exception:
329 336 pass
330 337 cmd = quotecommand(cmd)
331 338 env = shellenviron(environ)
332 339 if out is None or isstdout(out):
333 340 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
334 341 env=env, cwd=cwd)
335 342 else:
336 343 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
337 344 env=env, cwd=cwd, stdout=subprocess.PIPE,
338 345 stderr=subprocess.STDOUT)
339 346 for line in iter(proc.stdout.readline, ''):
340 347 out.write(line)
341 348 proc.wait()
342 349 rc = proc.returncode
343 350 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
344 351 rc = 0
345 352 return rc
346 353
347 354 def gui():
348 355 '''Are we running in a GUI?'''
349 356 if pycompat.isdarwin:
350 357 if 'SSH_CONNECTION' in encoding.environ:
351 358 # handle SSH access to a box where the user is logged in
352 359 return False
353 360 elif getattr(osutil, 'isgui', None):
354 361 # check if a CoreGraphics session is available
355 362 return osutil.isgui()
356 363 else:
357 364 # pure build; use a safe default
358 365 return True
359 366 else:
360 367 return pycompat.iswindows or encoding.environ.get("DISPLAY")
361 368
362 369 def hgcmd():
363 370 """Return the command used to execute current hg
364 371
365 372 This is different from hgexecutable() because on Windows we want
366 373 to avoid things opening new shell windows like batch files, so we
367 374 get either the python call or current executable.
368 375 """
369 376 if mainfrozen():
370 377 if getattr(sys, 'frozen', None) == 'macosx_app':
371 378 # Env variable set by py2app
372 379 return [encoding.environ['EXECUTABLEPATH']]
373 380 else:
374 381 return [pycompat.sysexecutable]
375 382 return _gethgcmd()
376 383
377 384 def rundetached(args, condfn):
378 385 """Execute the argument list in a detached process.
379 386
380 387 condfn is a callable which is called repeatedly and should return
381 388 True once the child process is known to have started successfully.
382 389 At this point, the child process PID is returned. If the child
383 390 process fails to start or finishes before condfn() evaluates to
384 391 True, return -1.
385 392 """
386 393 # Windows case is easier because the child process is either
387 394 # successfully starting and validating the condition or exiting
388 395 # on failure. We just poll on its PID. On Unix, if the child
389 396 # process fails to start, it will be left in a zombie state until
390 397 # the parent wait on it, which we cannot do since we expect a long
391 398 # running process on success. Instead we listen for SIGCHLD telling
392 399 # us our child process terminated.
393 400 terminated = set()
394 401 def handler(signum, frame):
395 402 terminated.add(os.wait())
396 403 prevhandler = None
397 404 SIGCHLD = getattr(signal, 'SIGCHLD', None)
398 405 if SIGCHLD is not None:
399 406 prevhandler = signal.signal(SIGCHLD, handler)
400 407 try:
401 408 pid = spawndetached(args)
402 409 while not condfn():
403 410 if ((pid in terminated or not testpid(pid))
404 411 and not condfn()):
405 412 return -1
406 413 time.sleep(0.1)
407 414 return pid
408 415 finally:
409 416 if prevhandler is not None:
410 417 signal.signal(signal.SIGCHLD, prevhandler)
General Comments 0
You need to be logged in to leave comments. Login now