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