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