##// END OF EJS Templates
windows: use upper() instead of lower() or os.path.normcase()...
FUJIWARA Katsunori -
r15671:3c5e818a stable
parent child Browse files
Show More
@@ -1,160 +1,162
1 1 # win32mbcs.py -- MBCS filename support for Mercurial
2 2 #
3 3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
4 4 #
5 5 # Version: 0.3
6 6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
7 7 #
8 8 # This software may be used and distributed according to the terms of the
9 9 # GNU General Public License version 2 or any later version.
10 10 #
11 11
12 12 '''allow the use of MBCS paths with problematic encodings
13 13
14 14 Some MBCS encodings are not good for some path operations (i.e.
15 15 splitting path, case conversion, etc.) with its encoded bytes. We call
16 16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
17 17 This extension can be used to fix the issue with those encodings by
18 18 wrapping some functions to convert to Unicode string before path
19 19 operation.
20 20
21 21 This extension is useful for:
22 22
23 23 - Japanese Windows users using shift_jis encoding.
24 24 - Chinese Windows users using big5 encoding.
25 25 - All users who use a repository with one of problematic encodings on
26 26 case-insensitive file system.
27 27
28 28 This extension is not needed for:
29 29
30 30 - Any user who use only ASCII chars in path.
31 31 - Any user who do not use any of problematic encodings.
32 32
33 33 Note that there are some limitations on using this extension:
34 34
35 35 - You should use single encoding in one repository.
36 36 - If the repository path ends with 0x5c, .hg/hgrc cannot be read.
37 37 - win32mbcs is not compatible with fixutf8 extension.
38 38
39 39 By default, win32mbcs uses encoding.encoding decided by Mercurial.
40 40 You can specify the encoding by config option::
41 41
42 42 [win32mbcs]
43 43 encoding = sjis
44 44
45 45 It is useful for the users who want to commit with UTF-8 log message.
46 46 '''
47 47
48 48 import os, sys
49 49 from mercurial.i18n import _
50 50 from mercurial import util, encoding
51 51
52 52 _encoding = None # see extsetup
53 53
54 54 def decode(arg):
55 55 if isinstance(arg, str):
56 56 uarg = arg.decode(_encoding)
57 57 if arg == uarg.encode(_encoding):
58 58 return uarg
59 59 raise UnicodeError("Not local encoding")
60 60 elif isinstance(arg, tuple):
61 61 return tuple(map(decode, arg))
62 62 elif isinstance(arg, list):
63 63 return map(decode, arg)
64 64 elif isinstance(arg, dict):
65 65 for k, v in arg.items():
66 66 arg[k] = decode(v)
67 67 return arg
68 68
69 69 def encode(arg):
70 70 if isinstance(arg, unicode):
71 71 return arg.encode(_encoding)
72 72 elif isinstance(arg, tuple):
73 73 return tuple(map(encode, arg))
74 74 elif isinstance(arg, list):
75 75 return map(encode, arg)
76 76 elif isinstance(arg, dict):
77 77 for k, v in arg.items():
78 78 arg[k] = encode(v)
79 79 return arg
80 80
81 81 def appendsep(s):
82 82 # ensure the path ends with os.sep, appending it if necessary.
83 83 try:
84 84 us = decode(s)
85 85 except UnicodeError:
86 86 us = s
87 87 if us and us[-1] not in ':/\\':
88 88 s += os.sep
89 89 return s
90 90
91 91 def wrapper(func, args, kwds):
92 92 # check argument is unicode, then call original
93 93 for arg in args:
94 94 if isinstance(arg, unicode):
95 95 return func(*args, **kwds)
96 96
97 97 try:
98 98 # convert arguments to unicode, call func, then convert back
99 99 return encode(func(*decode(args), **decode(kwds)))
100 100 except UnicodeError:
101 101 raise util.Abort(_("[win32mbcs] filename conversion failed with"
102 102 " %s encoding\n") % (_encoding))
103 103
104 104 def wrapperforlistdir(func, args, kwds):
105 105 # Ensure 'path' argument ends with os.sep to avoids
106 106 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
107 107 if args:
108 108 args = list(args)
109 109 args[0] = appendsep(args[0])
110 110 if 'path' in kwds:
111 111 kwds['path'] = appendsep(kwds['path'])
112 112 return func(*args, **kwds)
113 113
114 114 def wrapname(name, wrapper):
115 115 module, name = name.rsplit('.', 1)
116 116 module = sys.modules[module]
117 117 func = getattr(module, name)
118 118 def f(*args, **kwds):
119 119 return wrapper(func, args, kwds)
120 120 try:
121 121 f.__name__ = func.__name__ # fail with python23
122 122 except Exception:
123 123 pass
124 124 setattr(module, name, f)
125 125
126 126 # List of functions to be wrapped.
127 127 # NOTE: os.path.dirname() and os.path.basename() are safe because
128 128 # they use result of os.path.split()
129 129 funcs = '''os.path.join os.path.split os.path.splitext
130 os.path.splitunc os.path.normpath os.path.normcase os.makedirs
130 os.path.splitunc os.path.normpath os.makedirs
131 mercurial.windows.normcase
132 mercurial.util.normcase
131 133 mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
132 134 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
133 135 mercurial.util.checkwinfilename mercurial.util.checkosfilename'''
134 136
135 137 # codec and alias names of sjis and big5 to be faked.
136 138 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
137 139 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
138 140 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
139 141 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
140 142
141 143 def extsetup(ui):
142 144 # TODO: decide use of config section for this extension
143 145 if not os.path.supports_unicode_filenames:
144 146 ui.warn(_("[win32mbcs] cannot activate on this platform.\n"))
145 147 return
146 148 # determine encoding for filename
147 149 global _encoding
148 150 _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
149 151 # fake is only for relevant environment.
150 152 if _encoding.lower() in problematic_encodings.split():
151 153 for f in funcs.split():
152 154 wrapname(f, wrapper)
153 155 wrapname("mercurial.osutil.listdir", wrapperforlistdir)
154 156 # Check sys.args manually instead of using ui.debug() because
155 157 # command line options is not yet applied when
156 158 # extensions.loadall() is called.
157 159 if '--debug' in sys.argv:
158 160 ui.write("[win32mbcs] activated with encoding: %s\n"
159 161 % _encoding)
160 162
@@ -1,315 +1,316
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import osutil
10 10 import errno, msvcrt, os, re, sys
11 11
12 12 import win32
13 13 executablepath = win32.executablepath
14 14 getuser = win32.getuser
15 15 hidewindow = win32.hidewindow
16 16 lookupreg = win32.lookupreg
17 17 makedir = win32.makedir
18 18 nlinks = win32.nlinks
19 19 oslink = win32.oslink
20 20 samedevice = win32.samedevice
21 21 samefile = win32.samefile
22 22 setsignalhandler = win32.setsignalhandler
23 23 spawndetached = win32.spawndetached
24 24 termwidth = win32.termwidth
25 25 testpid = win32.testpid
26 26 unlink = win32.unlink
27 27
28 28 nulldev = 'NUL:'
29 29 umask = 002
30 30
31 31 # wrap osutil.posixfile to provide friendlier exceptions
32 32 def posixfile(name, mode='r', buffering=-1):
33 33 try:
34 34 return osutil.posixfile(name, mode, buffering)
35 35 except WindowsError, err:
36 36 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
37 37 posixfile.__doc__ = osutil.posixfile.__doc__
38 38
39 39 class winstdout(object):
40 40 '''stdout on windows misbehaves if sent through a pipe'''
41 41
42 42 def __init__(self, fp):
43 43 self.fp = fp
44 44
45 45 def __getattr__(self, key):
46 46 return getattr(self.fp, key)
47 47
48 48 def close(self):
49 49 try:
50 50 self.fp.close()
51 51 except IOError:
52 52 pass
53 53
54 54 def write(self, s):
55 55 try:
56 56 # This is workaround for "Not enough space" error on
57 57 # writing large size of data to console.
58 58 limit = 16000
59 59 l = len(s)
60 60 start = 0
61 61 self.softspace = 0
62 62 while start < l:
63 63 end = start + limit
64 64 self.fp.write(s[start:end])
65 65 start = end
66 66 except IOError, inst:
67 67 if inst.errno != 0:
68 68 raise
69 69 self.close()
70 70 raise IOError(errno.EPIPE, 'Broken pipe')
71 71
72 72 def flush(self):
73 73 try:
74 74 return self.fp.flush()
75 75 except IOError, inst:
76 76 if inst.errno != errno.EINVAL:
77 77 raise
78 78 self.close()
79 79 raise IOError(errno.EPIPE, 'Broken pipe')
80 80
81 81 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
82 82
83 83 def _is_win_9x():
84 84 '''return true if run on windows 95, 98 or me.'''
85 85 try:
86 86 return sys.getwindowsversion()[3] == 1
87 87 except AttributeError:
88 88 return 'command' in os.environ.get('comspec', '')
89 89
90 90 def openhardlinks():
91 91 return not _is_win_9x()
92 92
93 93 def parsepatchoutput(output_line):
94 94 """parses the output produced by patch and returns the filename"""
95 95 pf = output_line[14:]
96 96 if pf[0] == '`':
97 97 pf = pf[1:-1] # Remove the quotes
98 98 return pf
99 99
100 100 def sshargs(sshcmd, host, user, port):
101 101 '''Build argument list for ssh or Plink'''
102 102 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
103 103 args = user and ("%s@%s" % (user, host)) or host
104 104 return port and ("%s %s %s" % (args, pflag, port)) or args
105 105
106 106 def setflags(f, l, x):
107 107 pass
108 108
109 109 def copymode(src, dst, mode=None):
110 110 pass
111 111
112 112 def checkexec(path):
113 113 return False
114 114
115 115 def checklink(path):
116 116 return False
117 117
118 118 def setbinary(fd):
119 119 # When run without console, pipes may expose invalid
120 120 # fileno(), usually set to -1.
121 121 fno = getattr(fd, 'fileno', None)
122 122 if fno is not None and fno() >= 0:
123 123 msvcrt.setmode(fno(), os.O_BINARY)
124 124
125 125 def pconvert(path):
126 126 return '/'.join(path.split(os.sep))
127 127
128 128 def localpath(path):
129 129 return path.replace('/', '\\')
130 130
131 131 def normpath(path):
132 132 return pconvert(os.path.normpath(path))
133 133
134 normcase = os.path.normcase
134 def normcase(path):
135 return path.upper()
135 136
136 137 def realpath(path):
137 138 '''
138 139 Returns the true, canonical file system path equivalent to the given
139 140 path.
140 141 '''
141 142 # TODO: There may be a more clever way to do this that also handles other,
142 143 # less common file systems.
143 144 return os.path.normpath(normcase(os.path.realpath(path)))
144 145
145 146 def samestat(s1, s2):
146 147 return False
147 148
148 149 # A sequence of backslashes is special iff it precedes a double quote:
149 150 # - if there's an even number of backslashes, the double quote is not
150 151 # quoted (i.e. it ends the quoted region)
151 152 # - if there's an odd number of backslashes, the double quote is quoted
152 153 # - in both cases, every pair of backslashes is unquoted into a single
153 154 # backslash
154 155 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
155 156 # So, to quote a string, we must surround it in double quotes, double
156 157 # the number of backslashes that preceed double quotes and add another
157 158 # backslash before every double quote (being careful with the double
158 159 # quote we've appended to the end)
159 160 _quotere = None
160 161 def shellquote(s):
161 162 global _quotere
162 163 if _quotere is None:
163 164 _quotere = re.compile(r'(\\*)("|\\$)')
164 165 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
165 166
166 167 def quotecommand(cmd):
167 168 """Build a command string suitable for os.popen* calls."""
168 169 if sys.version_info < (2, 7, 1):
169 170 # Python versions since 2.7.1 do this extra quoting themselves
170 171 return '"' + cmd + '"'
171 172 return cmd
172 173
173 174 def popen(command, mode='r'):
174 175 # Work around "popen spawned process may not write to stdout
175 176 # under windows"
176 177 # http://bugs.python.org/issue1366
177 178 command += " 2> %s" % nulldev
178 179 return os.popen(quotecommand(command), mode)
179 180
180 181 def explainexit(code):
181 182 return _("exited with status %d") % code, code
182 183
183 184 # if you change this stub into a real check, please try to implement the
184 185 # username and groupname functions above, too.
185 186 def isowner(st):
186 187 return True
187 188
188 189 def findexe(command):
189 190 '''Find executable for command searching like cmd.exe does.
190 191 If command is a basename then PATH is searched for command.
191 192 PATH isn't searched if command is an absolute or relative path.
192 193 An extension from PATHEXT is found and added if not present.
193 194 If command isn't found None is returned.'''
194 195 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
195 196 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
196 197 if os.path.splitext(command)[1].lower() in pathexts:
197 198 pathexts = ['']
198 199
199 200 def findexisting(pathcommand):
200 201 'Will append extension (if needed) and return existing file'
201 202 for ext in pathexts:
202 203 executable = pathcommand + ext
203 204 if os.path.exists(executable):
204 205 return executable
205 206 return None
206 207
207 208 if os.sep in command:
208 209 return findexisting(command)
209 210
210 211 for path in os.environ.get('PATH', '').split(os.pathsep):
211 212 executable = findexisting(os.path.join(path, command))
212 213 if executable is not None:
213 214 return executable
214 215 return findexisting(os.path.expanduser(os.path.expandvars(command)))
215 216
216 217 def statfiles(files):
217 218 '''Stat each file in files and yield stat or None if file does not exist.
218 219 Cluster and cache stat per directory to minimize number of OS stat calls.'''
219 220 dircache = {} # dirname -> filename -> status | None if file does not exist
220 221 for nf in files:
221 222 nf = normcase(nf)
222 223 dir, base = os.path.split(nf)
223 224 if not dir:
224 225 dir = '.'
225 226 cache = dircache.get(dir, None)
226 227 if cache is None:
227 228 try:
228 229 dmap = dict([(normcase(n), s)
229 230 for n, k, s in osutil.listdir(dir, True)])
230 231 except OSError, err:
231 232 # handle directory not found in Python version prior to 2.5
232 233 # Python <= 2.4 returns native Windows code 3 in errno
233 234 # Python >= 2.5 returns ENOENT and adds winerror field
234 235 # EINVAL is raised if dir is not a directory.
235 236 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
236 237 errno.ENOTDIR):
237 238 raise
238 239 dmap = {}
239 240 cache = dircache.setdefault(dir, dmap)
240 241 yield cache.get(base, None)
241 242
242 243 def username(uid=None):
243 244 """Return the name of the user with the given uid.
244 245
245 246 If uid is None, return the name of the current user."""
246 247 return None
247 248
248 249 def groupname(gid=None):
249 250 """Return the name of the group with the given gid.
250 251
251 252 If gid is None, return the name of the current group."""
252 253 return None
253 254
254 255 def _removedirs(name):
255 256 """special version of os.removedirs that does not remove symlinked
256 257 directories or junction points if they actually contain files"""
257 258 if osutil.listdir(name):
258 259 return
259 260 os.rmdir(name)
260 261 head, tail = os.path.split(name)
261 262 if not tail:
262 263 head, tail = os.path.split(head)
263 264 while head and tail:
264 265 try:
265 266 if osutil.listdir(head):
266 267 return
267 268 os.rmdir(head)
268 269 except (ValueError, OSError):
269 270 break
270 271 head, tail = os.path.split(head)
271 272
272 273 def unlinkpath(f):
273 274 """unlink and remove the directory if it is empty"""
274 275 unlink(f)
275 276 # try removing directories that might now be empty
276 277 try:
277 278 _removedirs(os.path.dirname(f))
278 279 except OSError:
279 280 pass
280 281
281 282 def rename(src, dst):
282 283 '''atomically rename file src to dst, replacing dst if it exists'''
283 284 try:
284 285 os.rename(src, dst)
285 286 except OSError, e:
286 287 if e.errno != errno.EEXIST:
287 288 raise
288 289 unlink(dst)
289 290 os.rename(src, dst)
290 291
291 292 def gethgcmd():
292 293 return [sys.executable] + sys.argv[:1]
293 294
294 295 def termwidth():
295 296 # cmd.exe does not handle CR like a unix console, the CR is
296 297 # counted in the line length. On 80 columns consoles, if 80
297 298 # characters are written, the following CR won't apply on the
298 299 # current line but on the new one. Keep room for it.
299 300 return 79
300 301
301 302 def groupmembers(name):
302 303 # Don't support groups on Windows for now
303 304 raise KeyError()
304 305
305 306 def isexec(f):
306 307 return False
307 308
308 309 class cachestat(object):
309 310 def __init__(self, path):
310 311 pass
311 312
312 313 def cacheable(self):
313 314 return False
314 315
315 316 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now