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