##// END OF EJS Templates
windows: add a method to convert Unix style command lines to Windows style...
Matt Harbison -
r38502:3efadf23 default
parent child Browse files
Show More
@@ -1,485 +1,588 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import msvcrt
11 import msvcrt
12 import os
12 import os
13 import re
13 import re
14 import stat
14 import stat
15 import string
15 import sys
16 import sys
16
17
17 from .i18n import _
18 from .i18n import _
18 from . import (
19 from . import (
19 encoding,
20 encoding,
20 error,
21 error,
21 policy,
22 policy,
22 pycompat,
23 pycompat,
23 win32,
24 win32,
24 )
25 )
25
26
26 try:
27 try:
27 import _winreg as winreg
28 import _winreg as winreg
28 winreg.CloseKey
29 winreg.CloseKey
29 except ImportError:
30 except ImportError:
30 import winreg
31 import winreg
31
32
32 osutil = policy.importmod(r'osutil')
33 osutil = policy.importmod(r'osutil')
33
34
34 getfsmountpoint = win32.getvolumename
35 getfsmountpoint = win32.getvolumename
35 getfstype = win32.getfstype
36 getfstype = win32.getfstype
36 getuser = win32.getuser
37 getuser = win32.getuser
37 hidewindow = win32.hidewindow
38 hidewindow = win32.hidewindow
38 makedir = win32.makedir
39 makedir = win32.makedir
39 nlinks = win32.nlinks
40 nlinks = win32.nlinks
40 oslink = win32.oslink
41 oslink = win32.oslink
41 samedevice = win32.samedevice
42 samedevice = win32.samedevice
42 samefile = win32.samefile
43 samefile = win32.samefile
43 setsignalhandler = win32.setsignalhandler
44 setsignalhandler = win32.setsignalhandler
44 spawndetached = win32.spawndetached
45 spawndetached = win32.spawndetached
45 split = os.path.split
46 split = os.path.split
46 testpid = win32.testpid
47 testpid = win32.testpid
47 unlink = win32.unlink
48 unlink = win32.unlink
48
49
49 umask = 0o022
50 umask = 0o022
50
51
51 class mixedfilemodewrapper(object):
52 class mixedfilemodewrapper(object):
52 """Wraps a file handle when it is opened in read/write mode.
53 """Wraps a file handle when it is opened in read/write mode.
53
54
54 fopen() and fdopen() on Windows have a specific-to-Windows requirement
55 fopen() and fdopen() on Windows have a specific-to-Windows requirement
55 that files opened with mode r+, w+, or a+ make a call to a file positioning
56 that files opened with mode r+, w+, or a+ make a call to a file positioning
56 function when switching between reads and writes. Without this extra call,
57 function when switching between reads and writes. Without this extra call,
57 Python will raise a not very intuitive "IOError: [Errno 0] Error."
58 Python will raise a not very intuitive "IOError: [Errno 0] Error."
58
59
59 This class wraps posixfile instances when the file is opened in read/write
60 This class wraps posixfile instances when the file is opened in read/write
60 mode and automatically adds checks or inserts appropriate file positioning
61 mode and automatically adds checks or inserts appropriate file positioning
61 calls when necessary.
62 calls when necessary.
62 """
63 """
63 OPNONE = 0
64 OPNONE = 0
64 OPREAD = 1
65 OPREAD = 1
65 OPWRITE = 2
66 OPWRITE = 2
66
67
67 def __init__(self, fp):
68 def __init__(self, fp):
68 object.__setattr__(self, r'_fp', fp)
69 object.__setattr__(self, r'_fp', fp)
69 object.__setattr__(self, r'_lastop', 0)
70 object.__setattr__(self, r'_lastop', 0)
70
71
71 def __enter__(self):
72 def __enter__(self):
72 return self._fp.__enter__()
73 return self._fp.__enter__()
73
74
74 def __exit__(self, exc_type, exc_val, exc_tb):
75 def __exit__(self, exc_type, exc_val, exc_tb):
75 self._fp.__exit__(exc_type, exc_val, exc_tb)
76 self._fp.__exit__(exc_type, exc_val, exc_tb)
76
77
77 def __getattr__(self, name):
78 def __getattr__(self, name):
78 return getattr(self._fp, name)
79 return getattr(self._fp, name)
79
80
80 def __setattr__(self, name, value):
81 def __setattr__(self, name, value):
81 return self._fp.__setattr__(name, value)
82 return self._fp.__setattr__(name, value)
82
83
83 def _noopseek(self):
84 def _noopseek(self):
84 self._fp.seek(0, os.SEEK_CUR)
85 self._fp.seek(0, os.SEEK_CUR)
85
86
86 def seek(self, *args, **kwargs):
87 def seek(self, *args, **kwargs):
87 object.__setattr__(self, r'_lastop', self.OPNONE)
88 object.__setattr__(self, r'_lastop', self.OPNONE)
88 return self._fp.seek(*args, **kwargs)
89 return self._fp.seek(*args, **kwargs)
89
90
90 def write(self, d):
91 def write(self, d):
91 if self._lastop == self.OPREAD:
92 if self._lastop == self.OPREAD:
92 self._noopseek()
93 self._noopseek()
93
94
94 object.__setattr__(self, r'_lastop', self.OPWRITE)
95 object.__setattr__(self, r'_lastop', self.OPWRITE)
95 return self._fp.write(d)
96 return self._fp.write(d)
96
97
97 def writelines(self, *args, **kwargs):
98 def writelines(self, *args, **kwargs):
98 if self._lastop == self.OPREAD:
99 if self._lastop == self.OPREAD:
99 self._noopeseek()
100 self._noopeseek()
100
101
101 object.__setattr__(self, r'_lastop', self.OPWRITE)
102 object.__setattr__(self, r'_lastop', self.OPWRITE)
102 return self._fp.writelines(*args, **kwargs)
103 return self._fp.writelines(*args, **kwargs)
103
104
104 def read(self, *args, **kwargs):
105 def read(self, *args, **kwargs):
105 if self._lastop == self.OPWRITE:
106 if self._lastop == self.OPWRITE:
106 self._noopseek()
107 self._noopseek()
107
108
108 object.__setattr__(self, r'_lastop', self.OPREAD)
109 object.__setattr__(self, r'_lastop', self.OPREAD)
109 return self._fp.read(*args, **kwargs)
110 return self._fp.read(*args, **kwargs)
110
111
111 def readline(self, *args, **kwargs):
112 def readline(self, *args, **kwargs):
112 if self._lastop == self.OPWRITE:
113 if self._lastop == self.OPWRITE:
113 self._noopseek()
114 self._noopseek()
114
115
115 object.__setattr__(self, r'_lastop', self.OPREAD)
116 object.__setattr__(self, r'_lastop', self.OPREAD)
116 return self._fp.readline(*args, **kwargs)
117 return self._fp.readline(*args, **kwargs)
117
118
118 def readlines(self, *args, **kwargs):
119 def readlines(self, *args, **kwargs):
119 if self._lastop == self.OPWRITE:
120 if self._lastop == self.OPWRITE:
120 self._noopseek()
121 self._noopseek()
121
122
122 object.__setattr__(self, r'_lastop', self.OPREAD)
123 object.__setattr__(self, r'_lastop', self.OPREAD)
123 return self._fp.readlines(*args, **kwargs)
124 return self._fp.readlines(*args, **kwargs)
124
125
125 def posixfile(name, mode='r', buffering=-1):
126 def posixfile(name, mode='r', buffering=-1):
126 '''Open a file with even more POSIX-like semantics'''
127 '''Open a file with even more POSIX-like semantics'''
127 try:
128 try:
128 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
129 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
129
130
130 # The position when opening in append mode is implementation defined, so
131 # The position when opening in append mode is implementation defined, so
131 # make it consistent with other platforms, which position at EOF.
132 # make it consistent with other platforms, which position at EOF.
132 if 'a' in mode:
133 if 'a' in mode:
133 fp.seek(0, os.SEEK_END)
134 fp.seek(0, os.SEEK_END)
134
135
135 if '+' in mode:
136 if '+' in mode:
136 return mixedfilemodewrapper(fp)
137 return mixedfilemodewrapper(fp)
137
138
138 return fp
139 return fp
139 except WindowsError as err:
140 except WindowsError as err:
140 # convert to a friendlier exception
141 # convert to a friendlier exception
141 raise IOError(err.errno, '%s: %s' % (
142 raise IOError(err.errno, '%s: %s' % (
142 name, encoding.strtolocal(err.strerror)))
143 name, encoding.strtolocal(err.strerror)))
143
144
144 # may be wrapped by win32mbcs extension
145 # may be wrapped by win32mbcs extension
145 listdir = osutil.listdir
146 listdir = osutil.listdir
146
147
147 class winstdout(object):
148 class winstdout(object):
148 '''stdout on windows misbehaves if sent through a pipe'''
149 '''stdout on windows misbehaves if sent through a pipe'''
149
150
150 def __init__(self, fp):
151 def __init__(self, fp):
151 self.fp = fp
152 self.fp = fp
152
153
153 def __getattr__(self, key):
154 def __getattr__(self, key):
154 return getattr(self.fp, key)
155 return getattr(self.fp, key)
155
156
156 def close(self):
157 def close(self):
157 try:
158 try:
158 self.fp.close()
159 self.fp.close()
159 except IOError:
160 except IOError:
160 pass
161 pass
161
162
162 def write(self, s):
163 def write(self, s):
163 try:
164 try:
164 # This is workaround for "Not enough space" error on
165 # This is workaround for "Not enough space" error on
165 # writing large size of data to console.
166 # writing large size of data to console.
166 limit = 16000
167 limit = 16000
167 l = len(s)
168 l = len(s)
168 start = 0
169 start = 0
169 self.softspace = 0
170 self.softspace = 0
170 while start < l:
171 while start < l:
171 end = start + limit
172 end = start + limit
172 self.fp.write(s[start:end])
173 self.fp.write(s[start:end])
173 start = end
174 start = end
174 except IOError as inst:
175 except IOError as inst:
175 if inst.errno != 0:
176 if inst.errno != 0:
176 raise
177 raise
177 self.close()
178 self.close()
178 raise IOError(errno.EPIPE, 'Broken pipe')
179 raise IOError(errno.EPIPE, 'Broken pipe')
179
180
180 def flush(self):
181 def flush(self):
181 try:
182 try:
182 return self.fp.flush()
183 return self.fp.flush()
183 except IOError as inst:
184 except IOError as inst:
184 if inst.errno != errno.EINVAL:
185 if inst.errno != errno.EINVAL:
185 raise
186 raise
186 raise IOError(errno.EPIPE, 'Broken pipe')
187 raise IOError(errno.EPIPE, 'Broken pipe')
187
188
188 def _is_win_9x():
189 def _is_win_9x():
189 '''return true if run on windows 95, 98 or me.'''
190 '''return true if run on windows 95, 98 or me.'''
190 try:
191 try:
191 return sys.getwindowsversion()[3] == 1
192 return sys.getwindowsversion()[3] == 1
192 except AttributeError:
193 except AttributeError:
193 return 'command' in encoding.environ.get('comspec', '')
194 return 'command' in encoding.environ.get('comspec', '')
194
195
195 def openhardlinks():
196 def openhardlinks():
196 return not _is_win_9x()
197 return not _is_win_9x()
197
198
198 def parsepatchoutput(output_line):
199 def parsepatchoutput(output_line):
199 """parses the output produced by patch and returns the filename"""
200 """parses the output produced by patch and returns the filename"""
200 pf = output_line[14:]
201 pf = output_line[14:]
201 if pf[0] == '`':
202 if pf[0] == '`':
202 pf = pf[1:-1] # Remove the quotes
203 pf = pf[1:-1] # Remove the quotes
203 return pf
204 return pf
204
205
205 def sshargs(sshcmd, host, user, port):
206 def sshargs(sshcmd, host, user, port):
206 '''Build argument list for ssh or Plink'''
207 '''Build argument list for ssh or Plink'''
207 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
208 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
208 args = user and ("%s@%s" % (user, host)) or host
209 args = user and ("%s@%s" % (user, host)) or host
209 if args.startswith('-') or args.startswith('/'):
210 if args.startswith('-') or args.startswith('/'):
210 raise error.Abort(
211 raise error.Abort(
211 _('illegal ssh hostname or username starting with - or /: %s') %
212 _('illegal ssh hostname or username starting with - or /: %s') %
212 args)
213 args)
213 args = shellquote(args)
214 args = shellquote(args)
214 if port:
215 if port:
215 args = '%s %s %s' % (pflag, shellquote(port), args)
216 args = '%s %s %s' % (pflag, shellquote(port), args)
216 return args
217 return args
217
218
218 def setflags(f, l, x):
219 def setflags(f, l, x):
219 pass
220 pass
220
221
221 def copymode(src, dst, mode=None):
222 def copymode(src, dst, mode=None):
222 pass
223 pass
223
224
224 def checkexec(path):
225 def checkexec(path):
225 return False
226 return False
226
227
227 def checklink(path):
228 def checklink(path):
228 return False
229 return False
229
230
230 def setbinary(fd):
231 def setbinary(fd):
231 # When run without console, pipes may expose invalid
232 # When run without console, pipes may expose invalid
232 # fileno(), usually set to -1.
233 # fileno(), usually set to -1.
233 fno = getattr(fd, 'fileno', None)
234 fno = getattr(fd, 'fileno', None)
234 if fno is not None and fno() >= 0:
235 if fno is not None and fno() >= 0:
235 msvcrt.setmode(fno(), os.O_BINARY)
236 msvcrt.setmode(fno(), os.O_BINARY)
236
237
237 def pconvert(path):
238 def pconvert(path):
238 return path.replace(pycompat.ossep, '/')
239 return path.replace(pycompat.ossep, '/')
239
240
240 def localpath(path):
241 def localpath(path):
241 return path.replace('/', '\\')
242 return path.replace('/', '\\')
242
243
243 def normpath(path):
244 def normpath(path):
244 return pconvert(os.path.normpath(path))
245 return pconvert(os.path.normpath(path))
245
246
246 def normcase(path):
247 def normcase(path):
247 return encoding.upper(path) # NTFS compares via upper()
248 return encoding.upper(path) # NTFS compares via upper()
248
249
249 # see posix.py for definitions
250 # see posix.py for definitions
250 normcasespec = encoding.normcasespecs.upper
251 normcasespec = encoding.normcasespecs.upper
251 normcasefallback = encoding.upperfallback
252 normcasefallback = encoding.upperfallback
252
253
253 def samestat(s1, s2):
254 def samestat(s1, s2):
254 return False
255 return False
255
256
257 def shelltocmdexe(path, env):
258 r"""Convert shell variables in the form $var and ${var} inside ``path``
259 to %var% form. Existing Windows style variables are left unchanged.
260
261 The variables are limited to the given environment. Unknown variables are
262 left unchanged.
263
264 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
265 >>> # Only valid values are expanded
266 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
267 ... e)
268 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
269 >>> # Single quote prevents expansion, as does \$ escaping
270 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
271 "cmd '$var1 ${var2} %var3%' $var1 ${var2} \\"
272 >>> # $$ -> $, %% is not special, but can be the end and start of variables
273 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
274 'cmd $ %% %var1%%var2%'
275 >>> # No double substitution
276 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
277 '%var1% %var1%'
278 """
279 if b'$' not in path:
280 return path
281
282 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
283
284 res = b''
285 index = 0
286 pathlen = len(path)
287 while index < pathlen:
288 c = path[index]
289 if c == b'\'': # no expansion within single quotes
290 path = path[index + 1:]
291 pathlen = len(path)
292 try:
293 index = path.index(b'\'')
294 res += b'\'' + path[:index + 1]
295 except ValueError:
296 res += c + path
297 index = pathlen - 1
298 elif c == b'%': # variable
299 path = path[index + 1:]
300 pathlen = len(path)
301 try:
302 index = path.index(b'%')
303 except ValueError:
304 res += b'%' + path
305 index = pathlen - 1
306 else:
307 var = path[:index]
308 res += b'%' + var + b'%'
309 elif c == b'$': # variable or '$$'
310 if path[index + 1:index + 2] == b'$':
311 res += c
312 index += 1
313 elif path[index + 1:index + 2] == b'{':
314 path = path[index + 2:]
315 pathlen = len(path)
316 try:
317 index = path.index(b'}')
318 var = path[:index]
319
320 # See below for why empty variables are handled specially
321 if env.get(var, '') != '':
322 res += b'%' + var + b'%'
323 else:
324 res += b'${' + var + b'}'
325 except ValueError:
326 res += b'${' + path
327 index = pathlen - 1
328 else:
329 var = b''
330 index += 1
331 c = path[index:index + 1]
332 while c != b'' and c in varchars:
333 var += c
334 index += 1
335 c = path[index:index + 1]
336 # Some variables (like HG_OLDNODE) may be defined, but have an
337 # empty value. Those need to be skipped because when spawning
338 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
339 # VAR, and that really confuses things like revset expressions.
340 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
341 # will substitute to an empty string, and everything is happy.
342 if env.get(var, '') != '':
343 res += b'%' + var + b'%'
344 else:
345 res += b'$' + var
346
347 if c != '':
348 index -= 1
349 elif c == b'\\' and index + 1 < pathlen and path[index + 1] == b'$':
350 # Skip '\', but only if it is escaping $
351 res += b'$'
352 index += 1
353 else:
354 res += c
355
356 index += 1
357 return res
358
256 # A sequence of backslashes is special iff it precedes a double quote:
359 # A sequence of backslashes is special iff it precedes a double quote:
257 # - if there's an even number of backslashes, the double quote is not
360 # - if there's an even number of backslashes, the double quote is not
258 # quoted (i.e. it ends the quoted region)
361 # quoted (i.e. it ends the quoted region)
259 # - if there's an odd number of backslashes, the double quote is quoted
362 # - if there's an odd number of backslashes, the double quote is quoted
260 # - in both cases, every pair of backslashes is unquoted into a single
363 # - in both cases, every pair of backslashes is unquoted into a single
261 # backslash
364 # backslash
262 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
365 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
263 # So, to quote a string, we must surround it in double quotes, double
366 # So, to quote a string, we must surround it in double quotes, double
264 # the number of backslashes that precede double quotes and add another
367 # the number of backslashes that precede double quotes and add another
265 # backslash before every double quote (being careful with the double
368 # backslash before every double quote (being careful with the double
266 # quote we've appended to the end)
369 # quote we've appended to the end)
267 _quotere = None
370 _quotere = None
268 _needsshellquote = None
371 _needsshellquote = None
269 def shellquote(s):
372 def shellquote(s):
270 r"""
373 r"""
271 >>> shellquote(br'C:\Users\xyz')
374 >>> shellquote(br'C:\Users\xyz')
272 '"C:\\Users\\xyz"'
375 '"C:\\Users\\xyz"'
273 >>> shellquote(br'C:\Users\xyz/mixed')
376 >>> shellquote(br'C:\Users\xyz/mixed')
274 '"C:\\Users\\xyz/mixed"'
377 '"C:\\Users\\xyz/mixed"'
275 >>> # Would be safe not to quote too, since it is all double backslashes
378 >>> # Would be safe not to quote too, since it is all double backslashes
276 >>> shellquote(br'C:\\Users\\xyz')
379 >>> shellquote(br'C:\\Users\\xyz')
277 '"C:\\\\Users\\\\xyz"'
380 '"C:\\\\Users\\\\xyz"'
278 >>> # But this must be quoted
381 >>> # But this must be quoted
279 >>> shellquote(br'C:\\Users\\xyz/abc')
382 >>> shellquote(br'C:\\Users\\xyz/abc')
280 '"C:\\\\Users\\\\xyz/abc"'
383 '"C:\\\\Users\\\\xyz/abc"'
281 """
384 """
282 global _quotere
385 global _quotere
283 if _quotere is None:
386 if _quotere is None:
284 _quotere = re.compile(r'(\\*)("|\\$)')
387 _quotere = re.compile(r'(\\*)("|\\$)')
285 global _needsshellquote
388 global _needsshellquote
286 if _needsshellquote is None:
389 if _needsshellquote is None:
287 # ":" is also treated as "safe character", because it is used as a part
390 # ":" is also treated as "safe character", because it is used as a part
288 # of path name on Windows. "\" is also part of a path name, but isn't
391 # of path name on Windows. "\" is also part of a path name, but isn't
289 # safe because shlex.split() (kind of) treats it as an escape char and
392 # safe because shlex.split() (kind of) treats it as an escape char and
290 # drops it. It will leave the next character, even if it is another
393 # drops it. It will leave the next character, even if it is another
291 # "\".
394 # "\".
292 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
395 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
293 if s and not _needsshellquote(s) and not _quotere.search(s):
396 if s and not _needsshellquote(s) and not _quotere.search(s):
294 # "s" shouldn't have to be quoted
397 # "s" shouldn't have to be quoted
295 return s
398 return s
296 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
399 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
297
400
298 def _unquote(s):
401 def _unquote(s):
299 if s.startswith(b'"') and s.endswith(b'"'):
402 if s.startswith(b'"') and s.endswith(b'"'):
300 return s[1:-1]
403 return s[1:-1]
301 return s
404 return s
302
405
303 def shellsplit(s):
406 def shellsplit(s):
304 """Parse a command string in cmd.exe way (best-effort)"""
407 """Parse a command string in cmd.exe way (best-effort)"""
305 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
408 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
306
409
307 def quotecommand(cmd):
410 def quotecommand(cmd):
308 """Build a command string suitable for os.popen* calls."""
411 """Build a command string suitable for os.popen* calls."""
309 if sys.version_info < (2, 7, 1):
412 if sys.version_info < (2, 7, 1):
310 # Python versions since 2.7.1 do this extra quoting themselves
413 # Python versions since 2.7.1 do this extra quoting themselves
311 return '"' + cmd + '"'
414 return '"' + cmd + '"'
312 return cmd
415 return cmd
313
416
314 # if you change this stub into a real check, please try to implement the
417 # if you change this stub into a real check, please try to implement the
315 # username and groupname functions above, too.
418 # username and groupname functions above, too.
316 def isowner(st):
419 def isowner(st):
317 return True
420 return True
318
421
319 def findexe(command):
422 def findexe(command):
320 '''Find executable for command searching like cmd.exe does.
423 '''Find executable for command searching like cmd.exe does.
321 If command is a basename then PATH is searched for command.
424 If command is a basename then PATH is searched for command.
322 PATH isn't searched if command is an absolute or relative path.
425 PATH isn't searched if command is an absolute or relative path.
323 An extension from PATHEXT is found and added if not present.
426 An extension from PATHEXT is found and added if not present.
324 If command isn't found None is returned.'''
427 If command isn't found None is returned.'''
325 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
428 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
326 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
429 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
327 if os.path.splitext(command)[1].lower() in pathexts:
430 if os.path.splitext(command)[1].lower() in pathexts:
328 pathexts = ['']
431 pathexts = ['']
329
432
330 def findexisting(pathcommand):
433 def findexisting(pathcommand):
331 'Will append extension (if needed) and return existing file'
434 'Will append extension (if needed) and return existing file'
332 for ext in pathexts:
435 for ext in pathexts:
333 executable = pathcommand + ext
436 executable = pathcommand + ext
334 if os.path.exists(executable):
437 if os.path.exists(executable):
335 return executable
438 return executable
336 return None
439 return None
337
440
338 if pycompat.ossep in command:
441 if pycompat.ossep in command:
339 return findexisting(command)
442 return findexisting(command)
340
443
341 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
444 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
342 executable = findexisting(os.path.join(path, command))
445 executable = findexisting(os.path.join(path, command))
343 if executable is not None:
446 if executable is not None:
344 return executable
447 return executable
345 return findexisting(os.path.expanduser(os.path.expandvars(command)))
448 return findexisting(os.path.expanduser(os.path.expandvars(command)))
346
449
347 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
450 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
348
451
349 def statfiles(files):
452 def statfiles(files):
350 '''Stat each file in files. Yield each stat, or None if a file
453 '''Stat each file in files. Yield each stat, or None if a file
351 does not exist or has a type we don't care about.
454 does not exist or has a type we don't care about.
352
455
353 Cluster and cache stat per directory to minimize number of OS stat calls.'''
456 Cluster and cache stat per directory to minimize number of OS stat calls.'''
354 dircache = {} # dirname -> filename -> status | None if file does not exist
457 dircache = {} # dirname -> filename -> status | None if file does not exist
355 getkind = stat.S_IFMT
458 getkind = stat.S_IFMT
356 for nf in files:
459 for nf in files:
357 nf = normcase(nf)
460 nf = normcase(nf)
358 dir, base = os.path.split(nf)
461 dir, base = os.path.split(nf)
359 if not dir:
462 if not dir:
360 dir = '.'
463 dir = '.'
361 cache = dircache.get(dir, None)
464 cache = dircache.get(dir, None)
362 if cache is None:
465 if cache is None:
363 try:
466 try:
364 dmap = dict([(normcase(n), s)
467 dmap = dict([(normcase(n), s)
365 for n, k, s in listdir(dir, True)
468 for n, k, s in listdir(dir, True)
366 if getkind(s.st_mode) in _wantedkinds])
469 if getkind(s.st_mode) in _wantedkinds])
367 except OSError as err:
470 except OSError as err:
368 # Python >= 2.5 returns ENOENT and adds winerror field
471 # Python >= 2.5 returns ENOENT and adds winerror field
369 # EINVAL is raised if dir is not a directory.
472 # EINVAL is raised if dir is not a directory.
370 if err.errno not in (errno.ENOENT, errno.EINVAL,
473 if err.errno not in (errno.ENOENT, errno.EINVAL,
371 errno.ENOTDIR):
474 errno.ENOTDIR):
372 raise
475 raise
373 dmap = {}
476 dmap = {}
374 cache = dircache.setdefault(dir, dmap)
477 cache = dircache.setdefault(dir, dmap)
375 yield cache.get(base, None)
478 yield cache.get(base, None)
376
479
377 def username(uid=None):
480 def username(uid=None):
378 """Return the name of the user with the given uid.
481 """Return the name of the user with the given uid.
379
482
380 If uid is None, return the name of the current user."""
483 If uid is None, return the name of the current user."""
381 return None
484 return None
382
485
383 def groupname(gid=None):
486 def groupname(gid=None):
384 """Return the name of the group with the given gid.
487 """Return the name of the group with the given gid.
385
488
386 If gid is None, return the name of the current group."""
489 If gid is None, return the name of the current group."""
387 return None
490 return None
388
491
389 def removedirs(name):
492 def removedirs(name):
390 """special version of os.removedirs that does not remove symlinked
493 """special version of os.removedirs that does not remove symlinked
391 directories or junction points if they actually contain files"""
494 directories or junction points if they actually contain files"""
392 if listdir(name):
495 if listdir(name):
393 return
496 return
394 os.rmdir(name)
497 os.rmdir(name)
395 head, tail = os.path.split(name)
498 head, tail = os.path.split(name)
396 if not tail:
499 if not tail:
397 head, tail = os.path.split(head)
500 head, tail = os.path.split(head)
398 while head and tail:
501 while head and tail:
399 try:
502 try:
400 if listdir(head):
503 if listdir(head):
401 return
504 return
402 os.rmdir(head)
505 os.rmdir(head)
403 except (ValueError, OSError):
506 except (ValueError, OSError):
404 break
507 break
405 head, tail = os.path.split(head)
508 head, tail = os.path.split(head)
406
509
407 def rename(src, dst):
510 def rename(src, dst):
408 '''atomically rename file src to dst, replacing dst if it exists'''
511 '''atomically rename file src to dst, replacing dst if it exists'''
409 try:
512 try:
410 os.rename(src, dst)
513 os.rename(src, dst)
411 except OSError as e:
514 except OSError as e:
412 if e.errno != errno.EEXIST:
515 if e.errno != errno.EEXIST:
413 raise
516 raise
414 unlink(dst)
517 unlink(dst)
415 os.rename(src, dst)
518 os.rename(src, dst)
416
519
417 def gethgcmd():
520 def gethgcmd():
418 return [sys.executable] + sys.argv[:1]
521 return [sys.executable] + sys.argv[:1]
419
522
420 def groupmembers(name):
523 def groupmembers(name):
421 # Don't support groups on Windows for now
524 # Don't support groups on Windows for now
422 raise KeyError
525 raise KeyError
423
526
424 def isexec(f):
527 def isexec(f):
425 return False
528 return False
426
529
427 class cachestat(object):
530 class cachestat(object):
428 def __init__(self, path):
531 def __init__(self, path):
429 pass
532 pass
430
533
431 def cacheable(self):
534 def cacheable(self):
432 return False
535 return False
433
536
434 def lookupreg(key, valname=None, scope=None):
537 def lookupreg(key, valname=None, scope=None):
435 ''' Look up a key/value name in the Windows registry.
538 ''' Look up a key/value name in the Windows registry.
436
539
437 valname: value name. If unspecified, the default value for the key
540 valname: value name. If unspecified, the default value for the key
438 is used.
541 is used.
439 scope: optionally specify scope for registry lookup, this can be
542 scope: optionally specify scope for registry lookup, this can be
440 a sequence of scopes to look up in order. Default (CURRENT_USER,
543 a sequence of scopes to look up in order. Default (CURRENT_USER,
441 LOCAL_MACHINE).
544 LOCAL_MACHINE).
442 '''
545 '''
443 if scope is None:
546 if scope is None:
444 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
547 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
445 elif not isinstance(scope, (list, tuple)):
548 elif not isinstance(scope, (list, tuple)):
446 scope = (scope,)
549 scope = (scope,)
447 for s in scope:
550 for s in scope:
448 try:
551 try:
449 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
552 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
450 # never let a Unicode string escape into the wild
553 # never let a Unicode string escape into the wild
451 return encoding.unitolocal(val)
554 return encoding.unitolocal(val)
452 except EnvironmentError:
555 except EnvironmentError:
453 pass
556 pass
454
557
455 expandglobs = True
558 expandglobs = True
456
559
457 def statislink(st):
560 def statislink(st):
458 '''check whether a stat result is a symlink'''
561 '''check whether a stat result is a symlink'''
459 return False
562 return False
460
563
461 def statisexec(st):
564 def statisexec(st):
462 '''check whether a stat result is an executable file'''
565 '''check whether a stat result is an executable file'''
463 return False
566 return False
464
567
465 def poll(fds):
568 def poll(fds):
466 # see posix.py for description
569 # see posix.py for description
467 raise NotImplementedError()
570 raise NotImplementedError()
468
571
469 def readpipe(pipe):
572 def readpipe(pipe):
470 """Read all available data from a pipe."""
573 """Read all available data from a pipe."""
471 chunks = []
574 chunks = []
472 while True:
575 while True:
473 size = win32.peekpipe(pipe)
576 size = win32.peekpipe(pipe)
474 if not size:
577 if not size:
475 break
578 break
476
579
477 s = pipe.read(size)
580 s = pipe.read(size)
478 if not s:
581 if not s:
479 break
582 break
480 chunks.append(s)
583 chunks.append(s)
481
584
482 return ''.join(chunks)
585 return ''.join(chunks)
483
586
484 def bindunixsocket(sock, path):
587 def bindunixsocket(sock, path):
485 raise NotImplementedError('unsupported platform')
588 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now