##// END OF EJS Templates
cleanup: remove compatibility code for Python < 2.7.1...
Manuel Jacob -
r45402:5258bffd default
parent child Browse files
Show More
@@ -1,683 +1,680
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 openhardlinks():
231 def openhardlinks():
232 return True
232 return True
233
233
234
234
235 def parsepatchoutput(output_line):
235 def parsepatchoutput(output_line):
236 """parses the output produced by patch and returns the filename"""
236 """parses the output produced by patch and returns the filename"""
237 pf = output_line[14:]
237 pf = output_line[14:]
238 if pf[0] == b'`':
238 if pf[0] == b'`':
239 pf = pf[1:-1] # Remove the quotes
239 pf = pf[1:-1] # Remove the quotes
240 return pf
240 return pf
241
241
242
242
243 def sshargs(sshcmd, host, user, port):
243 def sshargs(sshcmd, host, user, port):
244 '''Build argument list for ssh or Plink'''
244 '''Build argument list for ssh or Plink'''
245 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'
246 args = user and (b"%s@%s" % (user, host)) or host
246 args = user and (b"%s@%s" % (user, host)) or host
247 if args.startswith(b'-') or args.startswith(b'/'):
247 if args.startswith(b'-') or args.startswith(b'/'):
248 raise error.Abort(
248 raise error.Abort(
249 _(b'illegal ssh hostname or username starting with - or /: %s')
249 _(b'illegal ssh hostname or username starting with - or /: %s')
250 % args
250 % args
251 )
251 )
252 args = shellquote(args)
252 args = shellquote(args)
253 if port:
253 if port:
254 args = b'%s %s %s' % (pflag, shellquote(port), args)
254 args = b'%s %s %s' % (pflag, shellquote(port), args)
255 return args
255 return args
256
256
257
257
258 def setflags(f, l, x):
258 def setflags(f, l, x):
259 pass
259 pass
260
260
261
261
262 def copymode(src, dst, mode=None, enforcewritable=False):
262 def copymode(src, dst, mode=None, enforcewritable=False):
263 pass
263 pass
264
264
265
265
266 def checkexec(path):
266 def checkexec(path):
267 return False
267 return False
268
268
269
269
270 def checklink(path):
270 def checklink(path):
271 return False
271 return False
272
272
273
273
274 def setbinary(fd):
274 def setbinary(fd):
275 # When run without console, pipes may expose invalid
275 # When run without console, pipes may expose invalid
276 # fileno(), usually set to -1.
276 # fileno(), usually set to -1.
277 fno = getattr(fd, 'fileno', None)
277 fno = getattr(fd, 'fileno', None)
278 if fno is not None and fno() >= 0:
278 if fno is not None and fno() >= 0:
279 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
279 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
280
280
281
281
282 def pconvert(path):
282 def pconvert(path):
283 return path.replace(pycompat.ossep, b'/')
283 return path.replace(pycompat.ossep, b'/')
284
284
285
285
286 def localpath(path):
286 def localpath(path):
287 return path.replace(b'/', b'\\')
287 return path.replace(b'/', b'\\')
288
288
289
289
290 def normpath(path):
290 def normpath(path):
291 return pconvert(os.path.normpath(path))
291 return pconvert(os.path.normpath(path))
292
292
293
293
294 def normcase(path):
294 def normcase(path):
295 return encoding.upper(path) # NTFS compares via upper()
295 return encoding.upper(path) # NTFS compares via upper()
296
296
297
297
298 # see posix.py for definitions
298 # see posix.py for definitions
299 normcasespec = encoding.normcasespecs.upper
299 normcasespec = encoding.normcasespecs.upper
300 normcasefallback = encoding.upperfallback
300 normcasefallback = encoding.upperfallback
301
301
302
302
303 def samestat(s1, s2):
303 def samestat(s1, s2):
304 return False
304 return False
305
305
306
306
307 def shelltocmdexe(path, env):
307 def shelltocmdexe(path, env):
308 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``
309 to %var% form. Existing Windows style variables are left unchanged.
309 to %var% form. Existing Windows style variables are left unchanged.
310
310
311 The variables are limited to the given environment. Unknown variables are
311 The variables are limited to the given environment. Unknown variables are
312 left unchanged.
312 left unchanged.
313
313
314 >>> 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'}
315 >>> # Only valid values are expanded
315 >>> # Only valid values are expanded
316 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
316 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
317 ... e)
317 ... e)
318 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
318 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
319 >>> # Single quote prevents expansion, as does \$ escaping
319 >>> # Single quote prevents expansion, as does \$ escaping
320 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
320 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
321 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
321 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
322 >>> # $$ 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
323 >>> # start of consecutive variables
323 >>> # start of consecutive variables
324 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
324 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
325 'cmd $$ %% %var1%%var2%'
325 'cmd $$ %% %var1%%var2%'
326 >>> # No double substitution
326 >>> # No double substitution
327 >>> 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'})
328 '%var1% %var1%'
328 '%var1% %var1%'
329 >>> # Tilde expansion
329 >>> # Tilde expansion
330 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
330 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
331 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
331 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
332 """
332 """
333 if not any(c in path for c in b"$'~"):
333 if not any(c in path for c in b"$'~"):
334 return path
334 return path
335
335
336 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
336 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
337
337
338 res = b''
338 res = b''
339 index = 0
339 index = 0
340 pathlen = len(path)
340 pathlen = len(path)
341 while index < pathlen:
341 while index < pathlen:
342 c = path[index : index + 1]
342 c = path[index : index + 1]
343 if c == b'\'': # no expansion within single quotes
343 if c == b'\'': # no expansion within single quotes
344 path = path[index + 1 :]
344 path = path[index + 1 :]
345 pathlen = len(path)
345 pathlen = len(path)
346 try:
346 try:
347 index = path.index(b'\'')
347 index = path.index(b'\'')
348 res += b'"' + path[:index] + b'"'
348 res += b'"' + path[:index] + b'"'
349 except ValueError:
349 except ValueError:
350 res += c + path
350 res += c + path
351 index = pathlen - 1
351 index = pathlen - 1
352 elif c == b'%': # variable
352 elif c == b'%': # variable
353 path = path[index + 1 :]
353 path = path[index + 1 :]
354 pathlen = len(path)
354 pathlen = len(path)
355 try:
355 try:
356 index = path.index(b'%')
356 index = path.index(b'%')
357 except ValueError:
357 except ValueError:
358 res += b'%' + path
358 res += b'%' + path
359 index = pathlen - 1
359 index = pathlen - 1
360 else:
360 else:
361 var = path[:index]
361 var = path[:index]
362 res += b'%' + var + b'%'
362 res += b'%' + var + b'%'
363 elif c == b'$': # variable
363 elif c == b'$': # variable
364 if path[index + 1 : index + 2] == b'{':
364 if path[index + 1 : index + 2] == b'{':
365 path = path[index + 2 :]
365 path = path[index + 2 :]
366 pathlen = len(path)
366 pathlen = len(path)
367 try:
367 try:
368 index = path.index(b'}')
368 index = path.index(b'}')
369 var = path[:index]
369 var = path[:index]
370
370
371 # See below for why empty variables are handled specially
371 # See below for why empty variables are handled specially
372 if env.get(var, b'') != b'':
372 if env.get(var, b'') != b'':
373 res += b'%' + var + b'%'
373 res += b'%' + var + b'%'
374 else:
374 else:
375 res += b'${' + var + b'}'
375 res += b'${' + var + b'}'
376 except ValueError:
376 except ValueError:
377 res += b'${' + path
377 res += b'${' + path
378 index = pathlen - 1
378 index = pathlen - 1
379 else:
379 else:
380 var = b''
380 var = b''
381 index += 1
381 index += 1
382 c = path[index : index + 1]
382 c = path[index : index + 1]
383 while c != b'' and c in varchars:
383 while c != b'' and c in varchars:
384 var += c
384 var += c
385 index += 1
385 index += 1
386 c = path[index : index + 1]
386 c = path[index : index + 1]
387 # Some variables (like HG_OLDNODE) may be defined, but have an
387 # Some variables (like HG_OLDNODE) may be defined, but have an
388 # empty value. Those need to be skipped because when spawning
388 # empty value. Those need to be skipped because when spawning
389 # 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
390 # VAR, and that really confuses things like revset expressions.
390 # VAR, and that really confuses things like revset expressions.
391 # 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
392 # will substitute to an empty string, and everything is happy.
392 # will substitute to an empty string, and everything is happy.
393 if env.get(var, b'') != b'':
393 if env.get(var, b'') != b'':
394 res += b'%' + var + b'%'
394 res += b'%' + var + b'%'
395 else:
395 else:
396 res += b'$' + var
396 res += b'$' + var
397
397
398 if c != b'':
398 if c != b'':
399 index -= 1
399 index -= 1
400 elif (
400 elif (
401 c == b'~'
401 c == b'~'
402 and index + 1 < pathlen
402 and index + 1 < pathlen
403 and path[index + 1 : index + 2] in (b'\\', b'/')
403 and path[index + 1 : index + 2] in (b'\\', b'/')
404 ):
404 ):
405 res += b"%USERPROFILE%"
405 res += b"%USERPROFILE%"
406 elif (
406 elif (
407 c == b'\\'
407 c == b'\\'
408 and index + 1 < pathlen
408 and index + 1 < pathlen
409 and path[index + 1 : index + 2] in (b'$', b'~')
409 and path[index + 1 : index + 2] in (b'$', b'~')
410 ):
410 ):
411 # Skip '\', but only if it is escaping $ or ~
411 # Skip '\', but only if it is escaping $ or ~
412 res += path[index + 1 : index + 2]
412 res += path[index + 1 : index + 2]
413 index += 1
413 index += 1
414 else:
414 else:
415 res += c
415 res += c
416
416
417 index += 1
417 index += 1
418 return res
418 return res
419
419
420
420
421 # 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:
422 # - 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
423 # quoted (i.e. it ends the quoted region)
423 # quoted (i.e. it ends the quoted region)
424 # - 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
425 # - 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
426 # backslash
426 # backslash
427 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
427 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
428 # 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
429 # the number of backslashes that precede double quotes and add another
429 # the number of backslashes that precede double quotes and add another
430 # backslash before every double quote (being careful with the double
430 # backslash before every double quote (being careful with the double
431 # quote we've appended to the end)
431 # quote we've appended to the end)
432 _quotere = None
432 _quotere = None
433 _needsshellquote = None
433 _needsshellquote = None
434
434
435
435
436 def shellquote(s):
436 def shellquote(s):
437 r"""
437 r"""
438 >>> shellquote(br'C:\Users\xyz')
438 >>> shellquote(br'C:\Users\xyz')
439 '"C:\\Users\\xyz"'
439 '"C:\\Users\\xyz"'
440 >>> shellquote(br'C:\Users\xyz/mixed')
440 >>> shellquote(br'C:\Users\xyz/mixed')
441 '"C:\\Users\\xyz/mixed"'
441 '"C:\\Users\\xyz/mixed"'
442 >>> # 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
443 >>> shellquote(br'C:\\Users\\xyz')
443 >>> shellquote(br'C:\\Users\\xyz')
444 '"C:\\\\Users\\\\xyz"'
444 '"C:\\\\Users\\\\xyz"'
445 >>> # But this must be quoted
445 >>> # But this must be quoted
446 >>> shellquote(br'C:\\Users\\xyz/abc')
446 >>> shellquote(br'C:\\Users\\xyz/abc')
447 '"C:\\\\Users\\\\xyz/abc"'
447 '"C:\\\\Users\\\\xyz/abc"'
448 """
448 """
449 global _quotere
449 global _quotere
450 if _quotere is None:
450 if _quotere is None:
451 _quotere = re.compile(br'(\\*)("|\\$)')
451 _quotere = re.compile(br'(\\*)("|\\$)')
452 global _needsshellquote
452 global _needsshellquote
453 if _needsshellquote is None:
453 if _needsshellquote is None:
454 # ":" 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
455 # 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
456 # 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
457 # 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
458 # "\".
458 # "\".
459 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
459 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
460 if s and not _needsshellquote(s) and not _quotere.search(s):
460 if s and not _needsshellquote(s) and not _quotere.search(s):
461 # "s" shouldn't have to be quoted
461 # "s" shouldn't have to be quoted
462 return s
462 return s
463 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
463 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
464
464
465
465
466 def _unquote(s):
466 def _unquote(s):
467 if s.startswith(b'"') and s.endswith(b'"'):
467 if s.startswith(b'"') and s.endswith(b'"'):
468 return s[1:-1]
468 return s[1:-1]
469 return s
469 return s
470
470
471
471
472 def shellsplit(s):
472 def shellsplit(s):
473 """Parse a command string in cmd.exe way (best-effort)"""
473 """Parse a command string in cmd.exe way (best-effort)"""
474 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
474 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
475
475
476
476
477 def quotecommand(cmd):
477 def quotecommand(cmd):
478 """Build a command string suitable for os.popen* calls."""
478 """Build a command string suitable for os.popen* calls."""
479 if sys.version_info < (2, 7, 1):
480 # Python versions since 2.7.1 do this extra quoting themselves
481 return b'"' + cmd + b'"'
482 return cmd
479 return cmd
483
480
484
481
485 # if you change this stub into a real check, please try to implement the
482 # if you change this stub into a real check, please try to implement the
486 # username and groupname functions above, too.
483 # username and groupname functions above, too.
487 def isowner(st):
484 def isowner(st):
488 return True
485 return True
489
486
490
487
491 def findexe(command):
488 def findexe(command):
492 '''Find executable for command searching like cmd.exe does.
489 '''Find executable for command searching like cmd.exe does.
493 If command is a basename then PATH is searched for command.
490 If command is a basename then PATH is searched for command.
494 PATH isn't searched if command is an absolute or relative path.
491 PATH isn't searched if command is an absolute or relative path.
495 An extension from PATHEXT is found and added if not present.
492 An extension from PATHEXT is found and added if not present.
496 If command isn't found None is returned.'''
493 If command isn't found None is returned.'''
497 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
494 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
498 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
495 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
499 if os.path.splitext(command)[1].lower() in pathexts:
496 if os.path.splitext(command)[1].lower() in pathexts:
500 pathexts = [b'']
497 pathexts = [b'']
501
498
502 def findexisting(pathcommand):
499 def findexisting(pathcommand):
503 """Will append extension (if needed) and return existing file"""
500 """Will append extension (if needed) and return existing file"""
504 for ext in pathexts:
501 for ext in pathexts:
505 executable = pathcommand + ext
502 executable = pathcommand + ext
506 if os.path.exists(executable):
503 if os.path.exists(executable):
507 return executable
504 return executable
508 return None
505 return None
509
506
510 if pycompat.ossep in command:
507 if pycompat.ossep in command:
511 return findexisting(command)
508 return findexisting(command)
512
509
513 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
510 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
514 executable = findexisting(os.path.join(path, command))
511 executable = findexisting(os.path.join(path, command))
515 if executable is not None:
512 if executable is not None:
516 return executable
513 return executable
517 return findexisting(os.path.expanduser(os.path.expandvars(command)))
514 return findexisting(os.path.expanduser(os.path.expandvars(command)))
518
515
519
516
520 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
517 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
521
518
522
519
523 def statfiles(files):
520 def statfiles(files):
524 '''Stat each file in files. Yield each stat, or None if a file
521 '''Stat each file in files. Yield each stat, or None if a file
525 does not exist or has a type we don't care about.
522 does not exist or has a type we don't care about.
526
523
527 Cluster and cache stat per directory to minimize number of OS stat calls.'''
524 Cluster and cache stat per directory to minimize number of OS stat calls.'''
528 dircache = {} # dirname -> filename -> status | None if file does not exist
525 dircache = {} # dirname -> filename -> status | None if file does not exist
529 getkind = stat.S_IFMT
526 getkind = stat.S_IFMT
530 for nf in files:
527 for nf in files:
531 nf = normcase(nf)
528 nf = normcase(nf)
532 dir, base = os.path.split(nf)
529 dir, base = os.path.split(nf)
533 if not dir:
530 if not dir:
534 dir = b'.'
531 dir = b'.'
535 cache = dircache.get(dir, None)
532 cache = dircache.get(dir, None)
536 if cache is None:
533 if cache is None:
537 try:
534 try:
538 dmap = {
535 dmap = {
539 normcase(n): s
536 normcase(n): s
540 for n, k, s in listdir(dir, True)
537 for n, k, s in listdir(dir, True)
541 if getkind(s.st_mode) in _wantedkinds
538 if getkind(s.st_mode) in _wantedkinds
542 }
539 }
543 except OSError as err:
540 except OSError as err:
544 # Python >= 2.5 returns ENOENT and adds winerror field
541 # Python >= 2.5 returns ENOENT and adds winerror field
545 # EINVAL is raised if dir is not a directory.
542 # EINVAL is raised if dir is not a directory.
546 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
543 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
547 raise
544 raise
548 dmap = {}
545 dmap = {}
549 cache = dircache.setdefault(dir, dmap)
546 cache = dircache.setdefault(dir, dmap)
550 yield cache.get(base, None)
547 yield cache.get(base, None)
551
548
552
549
553 def username(uid=None):
550 def username(uid=None):
554 """Return the name of the user with the given uid.
551 """Return the name of the user with the given uid.
555
552
556 If uid is None, return the name of the current user."""
553 If uid is None, return the name of the current user."""
557 if not uid:
554 if not uid:
558 return pycompat.fsencode(getpass.getuser())
555 return pycompat.fsencode(getpass.getuser())
559 return None
556 return None
560
557
561
558
562 def groupname(gid=None):
559 def groupname(gid=None):
563 """Return the name of the group with the given gid.
560 """Return the name of the group with the given gid.
564
561
565 If gid is None, return the name of the current group."""
562 If gid is None, return the name of the current group."""
566 return None
563 return None
567
564
568
565
569 def readlink(pathname):
566 def readlink(pathname):
570 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
567 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
571
568
572
569
573 def removedirs(name):
570 def removedirs(name):
574 """special version of os.removedirs that does not remove symlinked
571 """special version of os.removedirs that does not remove symlinked
575 directories or junction points if they actually contain files"""
572 directories or junction points if they actually contain files"""
576 if listdir(name):
573 if listdir(name):
577 return
574 return
578 os.rmdir(name)
575 os.rmdir(name)
579 head, tail = os.path.split(name)
576 head, tail = os.path.split(name)
580 if not tail:
577 if not tail:
581 head, tail = os.path.split(head)
578 head, tail = os.path.split(head)
582 while head and tail:
579 while head and tail:
583 try:
580 try:
584 if listdir(head):
581 if listdir(head):
585 return
582 return
586 os.rmdir(head)
583 os.rmdir(head)
587 except (ValueError, OSError):
584 except (ValueError, OSError):
588 break
585 break
589 head, tail = os.path.split(head)
586 head, tail = os.path.split(head)
590
587
591
588
592 def rename(src, dst):
589 def rename(src, dst):
593 '''atomically rename file src to dst, replacing dst if it exists'''
590 '''atomically rename file src to dst, replacing dst if it exists'''
594 try:
591 try:
595 os.rename(src, dst)
592 os.rename(src, dst)
596 except OSError as e:
593 except OSError as e:
597 if e.errno != errno.EEXIST:
594 if e.errno != errno.EEXIST:
598 raise
595 raise
599 unlink(dst)
596 unlink(dst)
600 os.rename(src, dst)
597 os.rename(src, dst)
601
598
602
599
603 def gethgcmd():
600 def gethgcmd():
604 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
601 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
605
602
606
603
607 def groupmembers(name):
604 def groupmembers(name):
608 # Don't support groups on Windows for now
605 # Don't support groups on Windows for now
609 raise KeyError
606 raise KeyError
610
607
611
608
612 def isexec(f):
609 def isexec(f):
613 return False
610 return False
614
611
615
612
616 class cachestat(object):
613 class cachestat(object):
617 def __init__(self, path):
614 def __init__(self, path):
618 pass
615 pass
619
616
620 def cacheable(self):
617 def cacheable(self):
621 return False
618 return False
622
619
623
620
624 def lookupreg(key, valname=None, scope=None):
621 def lookupreg(key, valname=None, scope=None):
625 ''' Look up a key/value name in the Windows registry.
622 ''' Look up a key/value name in the Windows registry.
626
623
627 valname: value name. If unspecified, the default value for the key
624 valname: value name. If unspecified, the default value for the key
628 is used.
625 is used.
629 scope: optionally specify scope for registry lookup, this can be
626 scope: optionally specify scope for registry lookup, this can be
630 a sequence of scopes to look up in order. Default (CURRENT_USER,
627 a sequence of scopes to look up in order. Default (CURRENT_USER,
631 LOCAL_MACHINE).
628 LOCAL_MACHINE).
632 '''
629 '''
633 if scope is None:
630 if scope is None:
634 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
631 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
635 elif not isinstance(scope, (list, tuple)):
632 elif not isinstance(scope, (list, tuple)):
636 scope = (scope,)
633 scope = (scope,)
637 for s in scope:
634 for s in scope:
638 try:
635 try:
639 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
636 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
640 name = valname and encoding.strfromlocal(valname) or valname
637 name = valname and encoding.strfromlocal(valname) or valname
641 val = winreg.QueryValueEx(hkey, name)[0]
638 val = winreg.QueryValueEx(hkey, name)[0]
642 # never let a Unicode string escape into the wild
639 # never let a Unicode string escape into the wild
643 return encoding.unitolocal(val)
640 return encoding.unitolocal(val)
644 except EnvironmentError:
641 except EnvironmentError:
645 pass
642 pass
646
643
647
644
648 expandglobs = True
645 expandglobs = True
649
646
650
647
651 def statislink(st):
648 def statislink(st):
652 '''check whether a stat result is a symlink'''
649 '''check whether a stat result is a symlink'''
653 return False
650 return False
654
651
655
652
656 def statisexec(st):
653 def statisexec(st):
657 '''check whether a stat result is an executable file'''
654 '''check whether a stat result is an executable file'''
658 return False
655 return False
659
656
660
657
661 def poll(fds):
658 def poll(fds):
662 # see posix.py for description
659 # see posix.py for description
663 raise NotImplementedError()
660 raise NotImplementedError()
664
661
665
662
666 def readpipe(pipe):
663 def readpipe(pipe):
667 """Read all available data from a pipe."""
664 """Read all available data from a pipe."""
668 chunks = []
665 chunks = []
669 while True:
666 while True:
670 size = win32.peekpipe(pipe)
667 size = win32.peekpipe(pipe)
671 if not size:
668 if not size:
672 break
669 break
673
670
674 s = pipe.read(size)
671 s = pipe.read(size)
675 if not s:
672 if not s:
676 break
673 break
677 chunks.append(s)
674 chunks.append(s)
678
675
679 return b''.join(chunks)
676 return b''.join(chunks)
680
677
681
678
682 def bindunixsocket(sock, path):
679 def bindunixsocket(sock, path):
683 raise NotImplementedError('unsupported platform')
680 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now