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