##// END OF EJS Templates
windows: define normcase spec and fallback...
Siddharth Agarwal -
r24598:22f49c7d default
parent child Browse files
Show More
@@ -1,371 +1,375 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 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
145 normcasespec = encoding.normcasespecs.upper
146 normcasefallback = encoding.upperfallback
147
144 def samestat(s1, s2):
148 def samestat(s1, s2):
145 return False
149 return False
146
150
147 # 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:
148 # - 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
149 # quoted (i.e. it ends the quoted region)
153 # quoted (i.e. it ends the quoted region)
150 # - 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
151 # - 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
152 # backslash
156 # backslash
153 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
157 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
154 # 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
155 # the number of backslashes that precede double quotes and add another
159 # the number of backslashes that precede double quotes and add another
156 # backslash before every double quote (being careful with the double
160 # backslash before every double quote (being careful with the double
157 # quote we've appended to the end)
161 # quote we've appended to the end)
158 _quotere = None
162 _quotere = None
159 _needsshellquote = None
163 _needsshellquote = None
160 def shellquote(s):
164 def shellquote(s):
161 global _quotere
165 global _quotere
162 if _quotere is None:
166 if _quotere is None:
163 _quotere = re.compile(r'(\\*)("|\\$)')
167 _quotere = re.compile(r'(\\*)("|\\$)')
164 global _needsshellquote
168 global _needsshellquote
165 if _needsshellquote is None:
169 if _needsshellquote is None:
166 # ":" and "\\" are also treated as "safe character", because
170 # ":" and "\\" are also treated as "safe character", because
167 # they are used as a part of path name (and the latter doesn't
171 # they are used as a part of path name (and the latter doesn't
168 # work as "escape character", like one on posix) on Windows
172 # work as "escape character", like one on posix) on Windows
169 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/\\-]').search
173 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/\\-]').search
170 if s and not _needsshellquote(s) and not _quotere.search(s):
174 if s and not _needsshellquote(s) and not _quotere.search(s):
171 # "s" shouldn't have to be quoted
175 # "s" shouldn't have to be quoted
172 return s
176 return s
173 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
177 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
174
178
175 def quotecommand(cmd):
179 def quotecommand(cmd):
176 """Build a command string suitable for os.popen* calls."""
180 """Build a command string suitable for os.popen* calls."""
177 if sys.version_info < (2, 7, 1):
181 if sys.version_info < (2, 7, 1):
178 # Python versions since 2.7.1 do this extra quoting themselves
182 # Python versions since 2.7.1 do this extra quoting themselves
179 return '"' + cmd + '"'
183 return '"' + cmd + '"'
180 return cmd
184 return cmd
181
185
182 def popen(command, mode='r'):
186 def popen(command, mode='r'):
183 # Work around "popen spawned process may not write to stdout
187 # Work around "popen spawned process may not write to stdout
184 # under windows"
188 # under windows"
185 # http://bugs.python.org/issue1366
189 # http://bugs.python.org/issue1366
186 command += " 2> %s" % os.devnull
190 command += " 2> %s" % os.devnull
187 return os.popen(quotecommand(command), mode)
191 return os.popen(quotecommand(command), mode)
188
192
189 def explainexit(code):
193 def explainexit(code):
190 return _("exited with status %d") % code, code
194 return _("exited with status %d") % code, code
191
195
192 # if you change this stub into a real check, please try to implement the
196 # if you change this stub into a real check, please try to implement the
193 # username and groupname functions above, too.
197 # username and groupname functions above, too.
194 def isowner(st):
198 def isowner(st):
195 return True
199 return True
196
200
197 def findexe(command):
201 def findexe(command):
198 '''Find executable for command searching like cmd.exe does.
202 '''Find executable for command searching like cmd.exe does.
199 If command is a basename then PATH is searched for command.
203 If command is a basename then PATH is searched for command.
200 PATH isn't searched if command is an absolute or relative path.
204 PATH isn't searched if command is an absolute or relative path.
201 An extension from PATHEXT is found and added if not present.
205 An extension from PATHEXT is found and added if not present.
202 If command isn't found None is returned.'''
206 If command isn't found None is returned.'''
203 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
207 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
204 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
208 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
205 if os.path.splitext(command)[1].lower() in pathexts:
209 if os.path.splitext(command)[1].lower() in pathexts:
206 pathexts = ['']
210 pathexts = ['']
207
211
208 def findexisting(pathcommand):
212 def findexisting(pathcommand):
209 'Will append extension (if needed) and return existing file'
213 'Will append extension (if needed) and return existing file'
210 for ext in pathexts:
214 for ext in pathexts:
211 executable = pathcommand + ext
215 executable = pathcommand + ext
212 if os.path.exists(executable):
216 if os.path.exists(executable):
213 return executable
217 return executable
214 return None
218 return None
215
219
216 if os.sep in command:
220 if os.sep in command:
217 return findexisting(command)
221 return findexisting(command)
218
222
219 for path in os.environ.get('PATH', '').split(os.pathsep):
223 for path in os.environ.get('PATH', '').split(os.pathsep):
220 executable = findexisting(os.path.join(path, command))
224 executable = findexisting(os.path.join(path, command))
221 if executable is not None:
225 if executable is not None:
222 return executable
226 return executable
223 return findexisting(os.path.expanduser(os.path.expandvars(command)))
227 return findexisting(os.path.expanduser(os.path.expandvars(command)))
224
228
225 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
229 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
226
230
227 def statfiles(files):
231 def statfiles(files):
228 '''Stat each file in files. Yield each stat, or None if a file
232 '''Stat each file in files. Yield each stat, or None if a file
229 does not exist or has a type we don't care about.
233 does not exist or has a type we don't care about.
230
234
231 Cluster and cache stat per directory to minimize number of OS stat calls.'''
235 Cluster and cache stat per directory to minimize number of OS stat calls.'''
232 dircache = {} # dirname -> filename -> status | None if file does not exist
236 dircache = {} # dirname -> filename -> status | None if file does not exist
233 getkind = stat.S_IFMT
237 getkind = stat.S_IFMT
234 for nf in files:
238 for nf in files:
235 nf = normcase(nf)
239 nf = normcase(nf)
236 dir, base = os.path.split(nf)
240 dir, base = os.path.split(nf)
237 if not dir:
241 if not dir:
238 dir = '.'
242 dir = '.'
239 cache = dircache.get(dir, None)
243 cache = dircache.get(dir, None)
240 if cache is None:
244 if cache is None:
241 try:
245 try:
242 dmap = dict([(normcase(n), s)
246 dmap = dict([(normcase(n), s)
243 for n, k, s in osutil.listdir(dir, True)
247 for n, k, s in osutil.listdir(dir, True)
244 if getkind(s.st_mode) in _wantedkinds])
248 if getkind(s.st_mode) in _wantedkinds])
245 except OSError, err:
249 except OSError, err:
246 # handle directory not found in Python version prior to 2.5
250 # handle directory not found in Python version prior to 2.5
247 # Python <= 2.4 returns native Windows code 3 in errno
251 # Python <= 2.4 returns native Windows code 3 in errno
248 # Python >= 2.5 returns ENOENT and adds winerror field
252 # Python >= 2.5 returns ENOENT and adds winerror field
249 # EINVAL is raised if dir is not a directory.
253 # EINVAL is raised if dir is not a directory.
250 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
254 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
251 errno.ENOTDIR):
255 errno.ENOTDIR):
252 raise
256 raise
253 dmap = {}
257 dmap = {}
254 cache = dircache.setdefault(dir, dmap)
258 cache = dircache.setdefault(dir, dmap)
255 yield cache.get(base, None)
259 yield cache.get(base, None)
256
260
257 def username(uid=None):
261 def username(uid=None):
258 """Return the name of the user with the given uid.
262 """Return the name of the user with the given uid.
259
263
260 If uid is None, return the name of the current user."""
264 If uid is None, return the name of the current user."""
261 return None
265 return None
262
266
263 def groupname(gid=None):
267 def groupname(gid=None):
264 """Return the name of the group with the given gid.
268 """Return the name of the group with the given gid.
265
269
266 If gid is None, return the name of the current group."""
270 If gid is None, return the name of the current group."""
267 return None
271 return None
268
272
269 def _removedirs(name):
273 def _removedirs(name):
270 """special version of os.removedirs that does not remove symlinked
274 """special version of os.removedirs that does not remove symlinked
271 directories or junction points if they actually contain files"""
275 directories or junction points if they actually contain files"""
272 if osutil.listdir(name):
276 if osutil.listdir(name):
273 return
277 return
274 os.rmdir(name)
278 os.rmdir(name)
275 head, tail = os.path.split(name)
279 head, tail = os.path.split(name)
276 if not tail:
280 if not tail:
277 head, tail = os.path.split(head)
281 head, tail = os.path.split(head)
278 while head and tail:
282 while head and tail:
279 try:
283 try:
280 if osutil.listdir(head):
284 if osutil.listdir(head):
281 return
285 return
282 os.rmdir(head)
286 os.rmdir(head)
283 except (ValueError, OSError):
287 except (ValueError, OSError):
284 break
288 break
285 head, tail = os.path.split(head)
289 head, tail = os.path.split(head)
286
290
287 def unlinkpath(f, ignoremissing=False):
291 def unlinkpath(f, ignoremissing=False):
288 """unlink and remove the directory if it is empty"""
292 """unlink and remove the directory if it is empty"""
289 try:
293 try:
290 unlink(f)
294 unlink(f)
291 except OSError, e:
295 except OSError, e:
292 if not (ignoremissing and e.errno == errno.ENOENT):
296 if not (ignoremissing and e.errno == errno.ENOENT):
293 raise
297 raise
294 # try removing directories that might now be empty
298 # try removing directories that might now be empty
295 try:
299 try:
296 _removedirs(os.path.dirname(f))
300 _removedirs(os.path.dirname(f))
297 except OSError:
301 except OSError:
298 pass
302 pass
299
303
300 def rename(src, dst):
304 def rename(src, dst):
301 '''atomically rename file src to dst, replacing dst if it exists'''
305 '''atomically rename file src to dst, replacing dst if it exists'''
302 try:
306 try:
303 os.rename(src, dst)
307 os.rename(src, dst)
304 except OSError, e:
308 except OSError, e:
305 if e.errno != errno.EEXIST:
309 if e.errno != errno.EEXIST:
306 raise
310 raise
307 unlink(dst)
311 unlink(dst)
308 os.rename(src, dst)
312 os.rename(src, dst)
309
313
310 def gethgcmd():
314 def gethgcmd():
311 return [sys.executable] + sys.argv[:1]
315 return [sys.executable] + sys.argv[:1]
312
316
313 def groupmembers(name):
317 def groupmembers(name):
314 # Don't support groups on Windows for now
318 # Don't support groups on Windows for now
315 raise KeyError
319 raise KeyError
316
320
317 def isexec(f):
321 def isexec(f):
318 return False
322 return False
319
323
320 class cachestat(object):
324 class cachestat(object):
321 def __init__(self, path):
325 def __init__(self, path):
322 pass
326 pass
323
327
324 def cacheable(self):
328 def cacheable(self):
325 return False
329 return False
326
330
327 def lookupreg(key, valname=None, scope=None):
331 def lookupreg(key, valname=None, scope=None):
328 ''' Look up a key/value name in the Windows registry.
332 ''' Look up a key/value name in the Windows registry.
329
333
330 valname: value name. If unspecified, the default value for the key
334 valname: value name. If unspecified, the default value for the key
331 is used.
335 is used.
332 scope: optionally specify scope for registry lookup, this can be
336 scope: optionally specify scope for registry lookup, this can be
333 a sequence of scopes to look up in order. Default (CURRENT_USER,
337 a sequence of scopes to look up in order. Default (CURRENT_USER,
334 LOCAL_MACHINE).
338 LOCAL_MACHINE).
335 '''
339 '''
336 if scope is None:
340 if scope is None:
337 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
341 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
338 elif not isinstance(scope, (list, tuple)):
342 elif not isinstance(scope, (list, tuple)):
339 scope = (scope,)
343 scope = (scope,)
340 for s in scope:
344 for s in scope:
341 try:
345 try:
342 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
346 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
343 # never let a Unicode string escape into the wild
347 # never let a Unicode string escape into the wild
344 return encoding.tolocal(val.encode('UTF-8'))
348 return encoding.tolocal(val.encode('UTF-8'))
345 except EnvironmentError:
349 except EnvironmentError:
346 pass
350 pass
347
351
348 expandglobs = True
352 expandglobs = True
349
353
350 def statislink(st):
354 def statislink(st):
351 '''check whether a stat result is a symlink'''
355 '''check whether a stat result is a symlink'''
352 return False
356 return False
353
357
354 def statisexec(st):
358 def statisexec(st):
355 '''check whether a stat result is an executable file'''
359 '''check whether a stat result is an executable file'''
356 return False
360 return False
357
361
358 def readpipe(pipe):
362 def readpipe(pipe):
359 """Read all available data from a pipe."""
363 """Read all available data from a pipe."""
360 chunks = []
364 chunks = []
361 while True:
365 while True:
362 size = os.fstat(pipe.fileno()).st_size
366 size = os.fstat(pipe.fileno()).st_size
363 if not size:
367 if not size:
364 break
368 break
365
369
366 s = pipe.read(size)
370 s = pipe.read(size)
367 if not s:
371 if not s:
368 break
372 break
369 chunks.append(s)
373 chunks.append(s)
370
374
371 return ''.join(chunks)
375 return ''.join(chunks)
General Comments 0
You need to be logged in to leave comments. Login now