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