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