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