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