##// END OF EJS Templates
windows: add doctest for shellquote()...
Matt Harbison -
r24908:30b910fe default
parent child Browse files
Show More
@@ -1,377 +1,389
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 i18n import _
8 from i18n import _
9 import osutil, encoding
9 import osutil, encoding
10 import errno, msvcrt, os, re, stat, sys, _winreg
10 import errno, msvcrt, os, re, stat, sys, _winreg
11
11
12 import win32
12 import win32
13 executablepath = win32.executablepath
13 executablepath = win32.executablepath
14 getuser = win32.getuser
14 getuser = win32.getuser
15 hidewindow = win32.hidewindow
15 hidewindow = win32.hidewindow
16 makedir = win32.makedir
16 makedir = win32.makedir
17 nlinks = win32.nlinks
17 nlinks = win32.nlinks
18 oslink = win32.oslink
18 oslink = win32.oslink
19 samedevice = win32.samedevice
19 samedevice = win32.samedevice
20 samefile = win32.samefile
20 samefile = win32.samefile
21 setsignalhandler = win32.setsignalhandler
21 setsignalhandler = win32.setsignalhandler
22 spawndetached = win32.spawndetached
22 spawndetached = win32.spawndetached
23 split = os.path.split
23 split = os.path.split
24 termwidth = win32.termwidth
24 termwidth = win32.termwidth
25 testpid = win32.testpid
25 testpid = win32.testpid
26 unlink = win32.unlink
26 unlink = win32.unlink
27
27
28 umask = 0022
28 umask = 0022
29 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
29 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
30
30
31 def posixfile(name, mode='r', buffering=-1):
31 def posixfile(name, mode='r', buffering=-1):
32 '''Open a file with even more POSIX-like semantics'''
32 '''Open a file with even more POSIX-like semantics'''
33 try:
33 try:
34 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
34 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
35
35
36 # The position when opening in append mode is implementation defined, so
36 # The position when opening in append mode is implementation defined, so
37 # make it consistent with other platforms, which position at EOF.
37 # make it consistent with other platforms, which position at EOF.
38 if 'a' in mode:
38 if 'a' in mode:
39 fp.seek(0, _SEEK_END)
39 fp.seek(0, _SEEK_END)
40
40
41 return fp
41 return fp
42 except WindowsError, err:
42 except WindowsError, err:
43 # convert to a friendlier exception
43 # convert to a friendlier exception
44 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
44 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
45
45
46 class winstdout(object):
46 class winstdout(object):
47 '''stdout on windows misbehaves if sent through a pipe'''
47 '''stdout on windows misbehaves if sent through a pipe'''
48
48
49 def __init__(self, fp):
49 def __init__(self, fp):
50 self.fp = fp
50 self.fp = fp
51
51
52 def __getattr__(self, key):
52 def __getattr__(self, key):
53 return getattr(self.fp, key)
53 return getattr(self.fp, key)
54
54
55 def close(self):
55 def close(self):
56 try:
56 try:
57 self.fp.close()
57 self.fp.close()
58 except IOError:
58 except IOError:
59 pass
59 pass
60
60
61 def write(self, s):
61 def write(self, s):
62 try:
62 try:
63 # This is workaround for "Not enough space" error on
63 # This is workaround for "Not enough space" error on
64 # writing large size of data to console.
64 # writing large size of data to console.
65 limit = 16000
65 limit = 16000
66 l = len(s)
66 l = len(s)
67 start = 0
67 start = 0
68 self.softspace = 0
68 self.softspace = 0
69 while start < l:
69 while start < l:
70 end = start + limit
70 end = start + limit
71 self.fp.write(s[start:end])
71 self.fp.write(s[start:end])
72 start = end
72 start = end
73 except IOError, inst:
73 except IOError, inst:
74 if inst.errno != 0:
74 if inst.errno != 0:
75 raise
75 raise
76 self.close()
76 self.close()
77 raise IOError(errno.EPIPE, 'Broken pipe')
77 raise IOError(errno.EPIPE, 'Broken pipe')
78
78
79 def flush(self):
79 def flush(self):
80 try:
80 try:
81 return self.fp.flush()
81 return self.fp.flush()
82 except IOError, inst:
82 except IOError, inst:
83 if inst.errno != errno.EINVAL:
83 if inst.errno != errno.EINVAL:
84 raise
84 raise
85 self.close()
85 self.close()
86 raise IOError(errno.EPIPE, 'Broken pipe')
86 raise IOError(errno.EPIPE, 'Broken pipe')
87
87
88 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
88 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
89
89
90 def _is_win_9x():
90 def _is_win_9x():
91 '''return true if run on windows 95, 98 or me.'''
91 '''return true if run on windows 95, 98 or me.'''
92 try:
92 try:
93 return sys.getwindowsversion()[3] == 1
93 return sys.getwindowsversion()[3] == 1
94 except AttributeError:
94 except AttributeError:
95 return 'command' in os.environ.get('comspec', '')
95 return 'command' in os.environ.get('comspec', '')
96
96
97 def openhardlinks():
97 def openhardlinks():
98 return not _is_win_9x()
98 return not _is_win_9x()
99
99
100 def parsepatchoutput(output_line):
100 def parsepatchoutput(output_line):
101 """parses the output produced by patch and returns the filename"""
101 """parses the output produced by patch and returns the filename"""
102 pf = output_line[14:]
102 pf = output_line[14:]
103 if pf[0] == '`':
103 if pf[0] == '`':
104 pf = pf[1:-1] # Remove the quotes
104 pf = pf[1:-1] # Remove the quotes
105 return pf
105 return pf
106
106
107 def sshargs(sshcmd, host, user, port):
107 def sshargs(sshcmd, host, user, port):
108 '''Build argument list for ssh or Plink'''
108 '''Build argument list for ssh or Plink'''
109 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
109 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
110 args = user and ("%s@%s" % (user, host)) or host
110 args = user and ("%s@%s" % (user, host)) or host
111 return port and ("%s %s %s" % (args, pflag, port)) or args
111 return port and ("%s %s %s" % (args, pflag, port)) or args
112
112
113 def setflags(f, l, x):
113 def setflags(f, l, x):
114 pass
114 pass
115
115
116 def copymode(src, dst, mode=None):
116 def copymode(src, dst, mode=None):
117 pass
117 pass
118
118
119 def checkexec(path):
119 def checkexec(path):
120 return False
120 return False
121
121
122 def checklink(path):
122 def checklink(path):
123 return False
123 return False
124
124
125 def setbinary(fd):
125 def setbinary(fd):
126 # When run without console, pipes may expose invalid
126 # When run without console, pipes may expose invalid
127 # fileno(), usually set to -1.
127 # fileno(), usually set to -1.
128 fno = getattr(fd, 'fileno', None)
128 fno = getattr(fd, 'fileno', None)
129 if fno is not None and fno() >= 0:
129 if fno is not None and fno() >= 0:
130 msvcrt.setmode(fno(), os.O_BINARY)
130 msvcrt.setmode(fno(), os.O_BINARY)
131
131
132 def pconvert(path):
132 def pconvert(path):
133 return path.replace(os.sep, '/')
133 return path.replace(os.sep, '/')
134
134
135 def localpath(path):
135 def localpath(path):
136 return path.replace('/', '\\')
136 return path.replace('/', '\\')
137
137
138 def normpath(path):
138 def normpath(path):
139 return pconvert(os.path.normpath(path))
139 return pconvert(os.path.normpath(path))
140
140
141 def normcase(path):
141 def normcase(path):
142 return encoding.upper(path)
142 return encoding.upper(path)
143
143
144 # see posix.py for definitions
144 # see posix.py for definitions
145 normcasespec = encoding.normcasespecs.upper
145 normcasespec = encoding.normcasespecs.upper
146 normcasefallback = encoding.upperfallback
146 normcasefallback = encoding.upperfallback
147
147
148 def samestat(s1, s2):
148 def samestat(s1, s2):
149 return False
149 return False
150
150
151 # A sequence of backslashes is special iff it precedes a double quote:
151 # A sequence of backslashes is special iff it precedes a double quote:
152 # - if there's an even number of backslashes, the double quote is not
152 # - if there's an even number of backslashes, the double quote is not
153 # quoted (i.e. it ends the quoted region)
153 # quoted (i.e. it ends the quoted region)
154 # - if there's an odd number of backslashes, the double quote is quoted
154 # - if there's an odd number of backslashes, the double quote is quoted
155 # - in both cases, every pair of backslashes is unquoted into a single
155 # - in both cases, every pair of backslashes is unquoted into a single
156 # backslash
156 # backslash
157 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
157 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
158 # So, to quote a string, we must surround it in double quotes, double
158 # So, to quote a string, we must surround it in double quotes, double
159 # the number of backslashes that precede double quotes and add another
159 # the number of backslashes that precede double quotes and add another
160 # backslash before every double quote (being careful with the double
160 # backslash before every double quote (being careful with the double
161 # quote we've appended to the end)
161 # quote we've appended to the end)
162 _quotere = None
162 _quotere = None
163 _needsshellquote = None
163 _needsshellquote = None
164 def shellquote(s):
164 def shellquote(s):
165 r"""
166 >>> shellquote(r'C:\Users\xyz')
167 '"C:\\Users\\xyz"'
168 >>> shellquote(r'C:\Users\xyz/mixed')
169 '"C:\\Users\\xyz/mixed"'
170 >>> # Would be safe not to quote too, since it is all double backslashes
171 >>> shellquote(r'C:\\Users\\xyz')
172 '"C:\\\\Users\\\\xyz"'
173 >>> # But this must be quoted
174 >>> shellquote(r'C:\\Users\\xyz/abc')
175 '"C:\\\\Users\\\\xyz/abc"'
176 """
165 global _quotere
177 global _quotere
166 if _quotere is None:
178 if _quotere is None:
167 _quotere = re.compile(r'(\\*)("|\\$)')
179 _quotere = re.compile(r'(\\*)("|\\$)')
168 global _needsshellquote
180 global _needsshellquote
169 if _needsshellquote is None:
181 if _needsshellquote is None:
170 # ":" is also treated as "safe character", because it is used as a part
182 # ":" is also treated as "safe character", because it is used as a part
171 # of path name on Windows. "\" is also part of a path name, but isn't
183 # of path name on Windows. "\" is also part of a path name, but isn't
172 # safe because shlex.split() (kind of) treats it as an escape char and
184 # safe because shlex.split() (kind of) treats it as an escape char and
173 # drops it. It will leave the next character, even if it is another
185 # drops it. It will leave the next character, even if it is another
174 # "\".
186 # "\".
175 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
187 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
176 if s and not _needsshellquote(s) and not _quotere.search(s):
188 if s and not _needsshellquote(s) and not _quotere.search(s):
177 # "s" shouldn't have to be quoted
189 # "s" shouldn't have to be quoted
178 return s
190 return s
179 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
191 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
180
192
181 def quotecommand(cmd):
193 def quotecommand(cmd):
182 """Build a command string suitable for os.popen* calls."""
194 """Build a command string suitable for os.popen* calls."""
183 if sys.version_info < (2, 7, 1):
195 if sys.version_info < (2, 7, 1):
184 # Python versions since 2.7.1 do this extra quoting themselves
196 # Python versions since 2.7.1 do this extra quoting themselves
185 return '"' + cmd + '"'
197 return '"' + cmd + '"'
186 return cmd
198 return cmd
187
199
188 def popen(command, mode='r'):
200 def popen(command, mode='r'):
189 # Work around "popen spawned process may not write to stdout
201 # Work around "popen spawned process may not write to stdout
190 # under windows"
202 # under windows"
191 # http://bugs.python.org/issue1366
203 # http://bugs.python.org/issue1366
192 command += " 2> %s" % os.devnull
204 command += " 2> %s" % os.devnull
193 return os.popen(quotecommand(command), mode)
205 return os.popen(quotecommand(command), mode)
194
206
195 def explainexit(code):
207 def explainexit(code):
196 return _("exited with status %d") % code, code
208 return _("exited with status %d") % code, code
197
209
198 # if you change this stub into a real check, please try to implement the
210 # if you change this stub into a real check, please try to implement the
199 # username and groupname functions above, too.
211 # username and groupname functions above, too.
200 def isowner(st):
212 def isowner(st):
201 return True
213 return True
202
214
203 def findexe(command):
215 def findexe(command):
204 '''Find executable for command searching like cmd.exe does.
216 '''Find executable for command searching like cmd.exe does.
205 If command is a basename then PATH is searched for command.
217 If command is a basename then PATH is searched for command.
206 PATH isn't searched if command is an absolute or relative path.
218 PATH isn't searched if command is an absolute or relative path.
207 An extension from PATHEXT is found and added if not present.
219 An extension from PATHEXT is found and added if not present.
208 If command isn't found None is returned.'''
220 If command isn't found None is returned.'''
209 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
221 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
210 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
222 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
211 if os.path.splitext(command)[1].lower() in pathexts:
223 if os.path.splitext(command)[1].lower() in pathexts:
212 pathexts = ['']
224 pathexts = ['']
213
225
214 def findexisting(pathcommand):
226 def findexisting(pathcommand):
215 'Will append extension (if needed) and return existing file'
227 'Will append extension (if needed) and return existing file'
216 for ext in pathexts:
228 for ext in pathexts:
217 executable = pathcommand + ext
229 executable = pathcommand + ext
218 if os.path.exists(executable):
230 if os.path.exists(executable):
219 return executable
231 return executable
220 return None
232 return None
221
233
222 if os.sep in command:
234 if os.sep in command:
223 return findexisting(command)
235 return findexisting(command)
224
236
225 for path in os.environ.get('PATH', '').split(os.pathsep):
237 for path in os.environ.get('PATH', '').split(os.pathsep):
226 executable = findexisting(os.path.join(path, command))
238 executable = findexisting(os.path.join(path, command))
227 if executable is not None:
239 if executable is not None:
228 return executable
240 return executable
229 return findexisting(os.path.expanduser(os.path.expandvars(command)))
241 return findexisting(os.path.expanduser(os.path.expandvars(command)))
230
242
231 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
243 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
232
244
233 def statfiles(files):
245 def statfiles(files):
234 '''Stat each file in files. Yield each stat, or None if a file
246 '''Stat each file in files. Yield each stat, or None if a file
235 does not exist or has a type we don't care about.
247 does not exist or has a type we don't care about.
236
248
237 Cluster and cache stat per directory to minimize number of OS stat calls.'''
249 Cluster and cache stat per directory to minimize number of OS stat calls.'''
238 dircache = {} # dirname -> filename -> status | None if file does not exist
250 dircache = {} # dirname -> filename -> status | None if file does not exist
239 getkind = stat.S_IFMT
251 getkind = stat.S_IFMT
240 for nf in files:
252 for nf in files:
241 nf = normcase(nf)
253 nf = normcase(nf)
242 dir, base = os.path.split(nf)
254 dir, base = os.path.split(nf)
243 if not dir:
255 if not dir:
244 dir = '.'
256 dir = '.'
245 cache = dircache.get(dir, None)
257 cache = dircache.get(dir, None)
246 if cache is None:
258 if cache is None:
247 try:
259 try:
248 dmap = dict([(normcase(n), s)
260 dmap = dict([(normcase(n), s)
249 for n, k, s in osutil.listdir(dir, True)
261 for n, k, s in osutil.listdir(dir, True)
250 if getkind(s.st_mode) in _wantedkinds])
262 if getkind(s.st_mode) in _wantedkinds])
251 except OSError, err:
263 except OSError, err:
252 # handle directory not found in Python version prior to 2.5
264 # handle directory not found in Python version prior to 2.5
253 # Python <= 2.4 returns native Windows code 3 in errno
265 # Python <= 2.4 returns native Windows code 3 in errno
254 # Python >= 2.5 returns ENOENT and adds winerror field
266 # Python >= 2.5 returns ENOENT and adds winerror field
255 # EINVAL is raised if dir is not a directory.
267 # EINVAL is raised if dir is not a directory.
256 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
268 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
257 errno.ENOTDIR):
269 errno.ENOTDIR):
258 raise
270 raise
259 dmap = {}
271 dmap = {}
260 cache = dircache.setdefault(dir, dmap)
272 cache = dircache.setdefault(dir, dmap)
261 yield cache.get(base, None)
273 yield cache.get(base, None)
262
274
263 def username(uid=None):
275 def username(uid=None):
264 """Return the name of the user with the given uid.
276 """Return the name of the user with the given uid.
265
277
266 If uid is None, return the name of the current user."""
278 If uid is None, return the name of the current user."""
267 return None
279 return None
268
280
269 def groupname(gid=None):
281 def groupname(gid=None):
270 """Return the name of the group with the given gid.
282 """Return the name of the group with the given gid.
271
283
272 If gid is None, return the name of the current group."""
284 If gid is None, return the name of the current group."""
273 return None
285 return None
274
286
275 def removedirs(name):
287 def removedirs(name):
276 """special version of os.removedirs that does not remove symlinked
288 """special version of os.removedirs that does not remove symlinked
277 directories or junction points if they actually contain files"""
289 directories or junction points if they actually contain files"""
278 if osutil.listdir(name):
290 if osutil.listdir(name):
279 return
291 return
280 os.rmdir(name)
292 os.rmdir(name)
281 head, tail = os.path.split(name)
293 head, tail = os.path.split(name)
282 if not tail:
294 if not tail:
283 head, tail = os.path.split(head)
295 head, tail = os.path.split(head)
284 while head and tail:
296 while head and tail:
285 try:
297 try:
286 if osutil.listdir(head):
298 if osutil.listdir(head):
287 return
299 return
288 os.rmdir(head)
300 os.rmdir(head)
289 except (ValueError, OSError):
301 except (ValueError, OSError):
290 break
302 break
291 head, tail = os.path.split(head)
303 head, tail = os.path.split(head)
292
304
293 def unlinkpath(f, ignoremissing=False):
305 def unlinkpath(f, ignoremissing=False):
294 """unlink and remove the directory if it is empty"""
306 """unlink and remove the directory if it is empty"""
295 try:
307 try:
296 unlink(f)
308 unlink(f)
297 except OSError, e:
309 except OSError, e:
298 if not (ignoremissing and e.errno == errno.ENOENT):
310 if not (ignoremissing and e.errno == errno.ENOENT):
299 raise
311 raise
300 # try removing directories that might now be empty
312 # try removing directories that might now be empty
301 try:
313 try:
302 removedirs(os.path.dirname(f))
314 removedirs(os.path.dirname(f))
303 except OSError:
315 except OSError:
304 pass
316 pass
305
317
306 def rename(src, dst):
318 def rename(src, dst):
307 '''atomically rename file src to dst, replacing dst if it exists'''
319 '''atomically rename file src to dst, replacing dst if it exists'''
308 try:
320 try:
309 os.rename(src, dst)
321 os.rename(src, dst)
310 except OSError, e:
322 except OSError, e:
311 if e.errno != errno.EEXIST:
323 if e.errno != errno.EEXIST:
312 raise
324 raise
313 unlink(dst)
325 unlink(dst)
314 os.rename(src, dst)
326 os.rename(src, dst)
315
327
316 def gethgcmd():
328 def gethgcmd():
317 return [sys.executable] + sys.argv[:1]
329 return [sys.executable] + sys.argv[:1]
318
330
319 def groupmembers(name):
331 def groupmembers(name):
320 # Don't support groups on Windows for now
332 # Don't support groups on Windows for now
321 raise KeyError
333 raise KeyError
322
334
323 def isexec(f):
335 def isexec(f):
324 return False
336 return False
325
337
326 class cachestat(object):
338 class cachestat(object):
327 def __init__(self, path):
339 def __init__(self, path):
328 pass
340 pass
329
341
330 def cacheable(self):
342 def cacheable(self):
331 return False
343 return False
332
344
333 def lookupreg(key, valname=None, scope=None):
345 def lookupreg(key, valname=None, scope=None):
334 ''' Look up a key/value name in the Windows registry.
346 ''' Look up a key/value name in the Windows registry.
335
347
336 valname: value name. If unspecified, the default value for the key
348 valname: value name. If unspecified, the default value for the key
337 is used.
349 is used.
338 scope: optionally specify scope for registry lookup, this can be
350 scope: optionally specify scope for registry lookup, this can be
339 a sequence of scopes to look up in order. Default (CURRENT_USER,
351 a sequence of scopes to look up in order. Default (CURRENT_USER,
340 LOCAL_MACHINE).
352 LOCAL_MACHINE).
341 '''
353 '''
342 if scope is None:
354 if scope is None:
343 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
355 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
344 elif not isinstance(scope, (list, tuple)):
356 elif not isinstance(scope, (list, tuple)):
345 scope = (scope,)
357 scope = (scope,)
346 for s in scope:
358 for s in scope:
347 try:
359 try:
348 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
360 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
349 # never let a Unicode string escape into the wild
361 # never let a Unicode string escape into the wild
350 return encoding.tolocal(val.encode('UTF-8'))
362 return encoding.tolocal(val.encode('UTF-8'))
351 except EnvironmentError:
363 except EnvironmentError:
352 pass
364 pass
353
365
354 expandglobs = True
366 expandglobs = True
355
367
356 def statislink(st):
368 def statislink(st):
357 '''check whether a stat result is a symlink'''
369 '''check whether a stat result is a symlink'''
358 return False
370 return False
359
371
360 def statisexec(st):
372 def statisexec(st):
361 '''check whether a stat result is an executable file'''
373 '''check whether a stat result is an executable file'''
362 return False
374 return False
363
375
364 def readpipe(pipe):
376 def readpipe(pipe):
365 """Read all available data from a pipe."""
377 """Read all available data from a pipe."""
366 chunks = []
378 chunks = []
367 while True:
379 while True:
368 size = win32.peekpipe(pipe)
380 size = win32.peekpipe(pipe)
369 if not size:
381 if not size:
370 break
382 break
371
383
372 s = pipe.read(size)
384 s = pipe.read(size)
373 if not s:
385 if not s:
374 break
386 break
375 chunks.append(s)
387 chunks.append(s)
376
388
377 return ''.join(chunks)
389 return ''.join(chunks)
General Comments 0
You need to be logged in to leave comments. Login now