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