##// END OF EJS Templates
darwin: omit ignorable codepoints when normcase()ing a file path...
Augie Fackler -
r23597:7a5bcd47 stable
parent child Browse files
Show More
@@ -1,596 +1,599
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix 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 encoding
9 import encoding
10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
11 import fcntl
11 import fcntl
12
12
13 posixfile = open
13 posixfile = open
14 normpath = os.path.normpath
14 normpath = os.path.normpath
15 samestat = os.path.samestat
15 samestat = os.path.samestat
16 oslink = os.link
16 oslink = os.link
17 unlink = os.unlink
17 unlink = os.unlink
18 rename = os.rename
18 rename = os.rename
19 expandglobs = False
19 expandglobs = False
20
20
21 umask = os.umask(0)
21 umask = os.umask(0)
22 os.umask(umask)
22 os.umask(umask)
23
23
24 def split(p):
24 def split(p):
25 '''Same as posixpath.split, but faster
25 '''Same as posixpath.split, but faster
26
26
27 >>> import posixpath
27 >>> import posixpath
28 >>> for f in ['/absolute/path/to/file',
28 >>> for f in ['/absolute/path/to/file',
29 ... 'relative/path/to/file',
29 ... 'relative/path/to/file',
30 ... 'file_alone',
30 ... 'file_alone',
31 ... 'path/to/directory/',
31 ... 'path/to/directory/',
32 ... '/multiple/path//separators',
32 ... '/multiple/path//separators',
33 ... '/file_at_root',
33 ... '/file_at_root',
34 ... '///multiple_leading_separators_at_root',
34 ... '///multiple_leading_separators_at_root',
35 ... '']:
35 ... '']:
36 ... assert split(f) == posixpath.split(f), f
36 ... assert split(f) == posixpath.split(f), f
37 '''
37 '''
38 ht = p.rsplit('/', 1)
38 ht = p.rsplit('/', 1)
39 if len(ht) == 1:
39 if len(ht) == 1:
40 return '', p
40 return '', p
41 nh = ht[0].rstrip('/')
41 nh = ht[0].rstrip('/')
42 if nh:
42 if nh:
43 return nh, ht[1]
43 return nh, ht[1]
44 return ht[0] + '/', ht[1]
44 return ht[0] + '/', ht[1]
45
45
46 def openhardlinks():
46 def openhardlinks():
47 '''return true if it is safe to hold open file handles to hardlinks'''
47 '''return true if it is safe to hold open file handles to hardlinks'''
48 return True
48 return True
49
49
50 def nlinks(name):
50 def nlinks(name):
51 '''return number of hardlinks for the given file'''
51 '''return number of hardlinks for the given file'''
52 return os.lstat(name).st_nlink
52 return os.lstat(name).st_nlink
53
53
54 def parsepatchoutput(output_line):
54 def parsepatchoutput(output_line):
55 """parses the output produced by patch and returns the filename"""
55 """parses the output produced by patch and returns the filename"""
56 pf = output_line[14:]
56 pf = output_line[14:]
57 if os.sys.platform == 'OpenVMS':
57 if os.sys.platform == 'OpenVMS':
58 if pf[0] == '`':
58 if pf[0] == '`':
59 pf = pf[1:-1] # Remove the quotes
59 pf = pf[1:-1] # Remove the quotes
60 else:
60 else:
61 if pf.startswith("'") and pf.endswith("'") and " " in pf:
61 if pf.startswith("'") and pf.endswith("'") and " " in pf:
62 pf = pf[1:-1] # Remove the quotes
62 pf = pf[1:-1] # Remove the quotes
63 return pf
63 return pf
64
64
65 def sshargs(sshcmd, host, user, port):
65 def sshargs(sshcmd, host, user, port):
66 '''Build argument list for ssh'''
66 '''Build argument list for ssh'''
67 args = user and ("%s@%s" % (user, host)) or host
67 args = user and ("%s@%s" % (user, host)) or host
68 return port and ("%s -p %s" % (args, port)) or args
68 return port and ("%s -p %s" % (args, port)) or args
69
69
70 def isexec(f):
70 def isexec(f):
71 """check whether a file is executable"""
71 """check whether a file is executable"""
72 return (os.lstat(f).st_mode & 0100 != 0)
72 return (os.lstat(f).st_mode & 0100 != 0)
73
73
74 def setflags(f, l, x):
74 def setflags(f, l, x):
75 s = os.lstat(f).st_mode
75 s = os.lstat(f).st_mode
76 if l:
76 if l:
77 if not stat.S_ISLNK(s):
77 if not stat.S_ISLNK(s):
78 # switch file to link
78 # switch file to link
79 fp = open(f)
79 fp = open(f)
80 data = fp.read()
80 data = fp.read()
81 fp.close()
81 fp.close()
82 os.unlink(f)
82 os.unlink(f)
83 try:
83 try:
84 os.symlink(data, f)
84 os.symlink(data, f)
85 except OSError:
85 except OSError:
86 # failed to make a link, rewrite file
86 # failed to make a link, rewrite file
87 fp = open(f, "w")
87 fp = open(f, "w")
88 fp.write(data)
88 fp.write(data)
89 fp.close()
89 fp.close()
90 # no chmod needed at this point
90 # no chmod needed at this point
91 return
91 return
92 if stat.S_ISLNK(s):
92 if stat.S_ISLNK(s):
93 # switch link to file
93 # switch link to file
94 data = os.readlink(f)
94 data = os.readlink(f)
95 os.unlink(f)
95 os.unlink(f)
96 fp = open(f, "w")
96 fp = open(f, "w")
97 fp.write(data)
97 fp.write(data)
98 fp.close()
98 fp.close()
99 s = 0666 & ~umask # avoid restatting for chmod
99 s = 0666 & ~umask # avoid restatting for chmod
100
100
101 sx = s & 0100
101 sx = s & 0100
102 if x and not sx:
102 if x and not sx:
103 # Turn on +x for every +r bit when making a file executable
103 # Turn on +x for every +r bit when making a file executable
104 # and obey umask.
104 # and obey umask.
105 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
105 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
106 elif not x and sx:
106 elif not x and sx:
107 # Turn off all +x bits
107 # Turn off all +x bits
108 os.chmod(f, s & 0666)
108 os.chmod(f, s & 0666)
109
109
110 def copymode(src, dst, mode=None):
110 def copymode(src, dst, mode=None):
111 '''Copy the file mode from the file at path src to dst.
111 '''Copy the file mode from the file at path src to dst.
112 If src doesn't exist, we're using mode instead. If mode is None, we're
112 If src doesn't exist, we're using mode instead. If mode is None, we're
113 using umask.'''
113 using umask.'''
114 try:
114 try:
115 st_mode = os.lstat(src).st_mode & 0777
115 st_mode = os.lstat(src).st_mode & 0777
116 except OSError, inst:
116 except OSError, inst:
117 if inst.errno != errno.ENOENT:
117 if inst.errno != errno.ENOENT:
118 raise
118 raise
119 st_mode = mode
119 st_mode = mode
120 if st_mode is None:
120 if st_mode is None:
121 st_mode = ~umask
121 st_mode = ~umask
122 st_mode &= 0666
122 st_mode &= 0666
123 os.chmod(dst, st_mode)
123 os.chmod(dst, st_mode)
124
124
125 def checkexec(path):
125 def checkexec(path):
126 """
126 """
127 Check whether the given path is on a filesystem with UNIX-like exec flags
127 Check whether the given path is on a filesystem with UNIX-like exec flags
128
128
129 Requires a directory (like /foo/.hg)
129 Requires a directory (like /foo/.hg)
130 """
130 """
131
131
132 # VFAT on some Linux versions can flip mode but it doesn't persist
132 # VFAT on some Linux versions can flip mode but it doesn't persist
133 # a FS remount. Frequently we can detect it if files are created
133 # a FS remount. Frequently we can detect it if files are created
134 # with exec bit on.
134 # with exec bit on.
135
135
136 try:
136 try:
137 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
137 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
138 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
138 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
139 try:
139 try:
140 os.close(fh)
140 os.close(fh)
141 m = os.stat(fn).st_mode & 0777
141 m = os.stat(fn).st_mode & 0777
142 new_file_has_exec = m & EXECFLAGS
142 new_file_has_exec = m & EXECFLAGS
143 os.chmod(fn, m ^ EXECFLAGS)
143 os.chmod(fn, m ^ EXECFLAGS)
144 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
144 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
145 finally:
145 finally:
146 os.unlink(fn)
146 os.unlink(fn)
147 except (IOError, OSError):
147 except (IOError, OSError):
148 # we don't care, the user probably won't be able to commit anyway
148 # we don't care, the user probably won't be able to commit anyway
149 return False
149 return False
150 return not (new_file_has_exec or exec_flags_cannot_flip)
150 return not (new_file_has_exec or exec_flags_cannot_flip)
151
151
152 def checklink(path):
152 def checklink(path):
153 """check whether the given path is on a symlink-capable filesystem"""
153 """check whether the given path is on a symlink-capable filesystem"""
154 # mktemp is not racy because symlink creation will fail if the
154 # mktemp is not racy because symlink creation will fail if the
155 # file already exists
155 # file already exists
156 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
156 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
157 try:
157 try:
158 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
158 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
159 try:
159 try:
160 os.symlink(os.path.basename(fd.name), name)
160 os.symlink(os.path.basename(fd.name), name)
161 os.unlink(name)
161 os.unlink(name)
162 return True
162 return True
163 finally:
163 finally:
164 fd.close()
164 fd.close()
165 except AttributeError:
165 except AttributeError:
166 return False
166 return False
167 except OSError, inst:
167 except OSError, inst:
168 # sshfs might report failure while successfully creating the link
168 # sshfs might report failure while successfully creating the link
169 if inst[0] == errno.EIO and os.path.exists(name):
169 if inst[0] == errno.EIO and os.path.exists(name):
170 os.unlink(name)
170 os.unlink(name)
171 return False
171 return False
172
172
173 def checkosfilename(path):
173 def checkosfilename(path):
174 '''Check that the base-relative path is a valid filename on this platform.
174 '''Check that the base-relative path is a valid filename on this platform.
175 Returns None if the path is ok, or a UI string describing the problem.'''
175 Returns None if the path is ok, or a UI string describing the problem.'''
176 pass # on posix platforms, every path is ok
176 pass # on posix platforms, every path is ok
177
177
178 def setbinary(fd):
178 def setbinary(fd):
179 pass
179 pass
180
180
181 def pconvert(path):
181 def pconvert(path):
182 return path
182 return path
183
183
184 def localpath(path):
184 def localpath(path):
185 return path
185 return path
186
186
187 def samefile(fpath1, fpath2):
187 def samefile(fpath1, fpath2):
188 """Returns whether path1 and path2 refer to the same file. This is only
188 """Returns whether path1 and path2 refer to the same file. This is only
189 guaranteed to work for files, not directories."""
189 guaranteed to work for files, not directories."""
190 return os.path.samefile(fpath1, fpath2)
190 return os.path.samefile(fpath1, fpath2)
191
191
192 def samedevice(fpath1, fpath2):
192 def samedevice(fpath1, fpath2):
193 """Returns whether fpath1 and fpath2 are on the same device. This is only
193 """Returns whether fpath1 and fpath2 are on the same device. This is only
194 guaranteed to work for files, not directories."""
194 guaranteed to work for files, not directories."""
195 st1 = os.lstat(fpath1)
195 st1 = os.lstat(fpath1)
196 st2 = os.lstat(fpath2)
196 st2 = os.lstat(fpath2)
197 return st1.st_dev == st2.st_dev
197 return st1.st_dev == st2.st_dev
198
198
199 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
199 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
200 def normcase(path):
200 def normcase(path):
201 return path.lower()
201 return path.lower()
202
202
203 if sys.platform == 'darwin':
203 if sys.platform == 'darwin':
204
204
205 def normcase(path):
205 def normcase(path):
206 '''
206 '''
207 Normalize a filename for OS X-compatible comparison:
207 Normalize a filename for OS X-compatible comparison:
208 - escape-encode invalid characters
208 - escape-encode invalid characters
209 - decompose to NFD
209 - decompose to NFD
210 - lowercase
210 - lowercase
211 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
211
212
212 >>> normcase('UPPER')
213 >>> normcase('UPPER')
213 'upper'
214 'upper'
214 >>> normcase('Caf\xc3\xa9')
215 >>> normcase('Caf\xc3\xa9')
215 'cafe\\xcc\\x81'
216 'cafe\\xcc\\x81'
216 >>> normcase('\xc3\x89')
217 >>> normcase('\xc3\x89')
217 'e\\xcc\\x81'
218 'e\\xcc\\x81'
218 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
219 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
219 '%b8%ca%c3\\xca\\xbe%c8.jpg'
220 '%b8%ca%c3\\xca\\xbe%c8.jpg'
220 '''
221 '''
221
222
222 try:
223 try:
223 return encoding.asciilower(path) # exception for non-ASCII
224 return encoding.asciilower(path) # exception for non-ASCII
224 except UnicodeDecodeError:
225 except UnicodeDecodeError:
225 pass
226 pass
226 try:
227 try:
227 u = path.decode('utf-8')
228 u = path.decode('utf-8')
228 except UnicodeDecodeError:
229 except UnicodeDecodeError:
229 # OS X percent-encodes any bytes that aren't valid utf-8
230 # OS X percent-encodes any bytes that aren't valid utf-8
230 s = ''
231 s = ''
231 g = ''
232 g = ''
232 l = 0
233 l = 0
233 for c in path:
234 for c in path:
234 o = ord(c)
235 o = ord(c)
235 if l and o < 128 or o >= 192:
236 if l and o < 128 or o >= 192:
236 # we want a continuation byte, but didn't get one
237 # we want a continuation byte, but didn't get one
237 s += ''.join(["%%%02X" % ord(x) for x in g])
238 s += ''.join(["%%%02X" % ord(x) for x in g])
238 g = ''
239 g = ''
239 l = 0
240 l = 0
240 if l == 0 and o < 128:
241 if l == 0 and o < 128:
241 # ascii
242 # ascii
242 s += c
243 s += c
243 elif l == 0 and 194 <= o < 245:
244 elif l == 0 and 194 <= o < 245:
244 # valid leading bytes
245 # valid leading bytes
245 if o < 224:
246 if o < 224:
246 l = 1
247 l = 1
247 elif o < 240:
248 elif o < 240:
248 l = 2
249 l = 2
249 else:
250 else:
250 l = 3
251 l = 3
251 g = c
252 g = c
252 elif l > 0 and 128 <= o < 192:
253 elif l > 0 and 128 <= o < 192:
253 # valid continuations
254 # valid continuations
254 g += c
255 g += c
255 l -= 1
256 l -= 1
256 if not l:
257 if not l:
257 s += g
258 s += g
258 g = ''
259 g = ''
259 else:
260 else:
260 # invalid
261 # invalid
261 s += "%%%02X" % o
262 s += "%%%02X" % o
262
263
263 # any remaining partial characters
264 # any remaining partial characters
264 s += ''.join(["%%%02X" % ord(x) for x in g])
265 s += ''.join(["%%%02X" % ord(x) for x in g])
265 u = s.decode('utf-8')
266 u = s.decode('utf-8')
266
267
267 # Decompose then lowercase (HFS+ technote specifies lower)
268 # Decompose then lowercase (HFS+ technote specifies lower)
268 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
269 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
270 # drop HFS+ ignored characters
271 return encoding.hfsignoreclean(enc)
269
272
270 if sys.platform == 'cygwin':
273 if sys.platform == 'cygwin':
271 # workaround for cygwin, in which mount point part of path is
274 # workaround for cygwin, in which mount point part of path is
272 # treated as case sensitive, even though underlying NTFS is case
275 # treated as case sensitive, even though underlying NTFS is case
273 # insensitive.
276 # insensitive.
274
277
275 # default mount points
278 # default mount points
276 cygwinmountpoints = sorted([
279 cygwinmountpoints = sorted([
277 "/usr/bin",
280 "/usr/bin",
278 "/usr/lib",
281 "/usr/lib",
279 "/cygdrive",
282 "/cygdrive",
280 ], reverse=True)
283 ], reverse=True)
281
284
282 # use upper-ing as normcase as same as NTFS workaround
285 # use upper-ing as normcase as same as NTFS workaround
283 def normcase(path):
286 def normcase(path):
284 pathlen = len(path)
287 pathlen = len(path)
285 if (pathlen == 0) or (path[0] != os.sep):
288 if (pathlen == 0) or (path[0] != os.sep):
286 # treat as relative
289 # treat as relative
287 return encoding.upper(path)
290 return encoding.upper(path)
288
291
289 # to preserve case of mountpoint part
292 # to preserve case of mountpoint part
290 for mp in cygwinmountpoints:
293 for mp in cygwinmountpoints:
291 if not path.startswith(mp):
294 if not path.startswith(mp):
292 continue
295 continue
293
296
294 mplen = len(mp)
297 mplen = len(mp)
295 if mplen == pathlen: # mount point itself
298 if mplen == pathlen: # mount point itself
296 return mp
299 return mp
297 if path[mplen] == os.sep:
300 if path[mplen] == os.sep:
298 return mp + encoding.upper(path[mplen:])
301 return mp + encoding.upper(path[mplen:])
299
302
300 return encoding.upper(path)
303 return encoding.upper(path)
301
304
302 # Cygwin translates native ACLs to POSIX permissions,
305 # Cygwin translates native ACLs to POSIX permissions,
303 # but these translations are not supported by native
306 # but these translations are not supported by native
304 # tools, so the exec bit tends to be set erroneously.
307 # tools, so the exec bit tends to be set erroneously.
305 # Therefore, disable executable bit access on Cygwin.
308 # Therefore, disable executable bit access on Cygwin.
306 def checkexec(path):
309 def checkexec(path):
307 return False
310 return False
308
311
309 # Similarly, Cygwin's symlink emulation is likely to create
312 # Similarly, Cygwin's symlink emulation is likely to create
310 # problems when Mercurial is used from both Cygwin and native
313 # problems when Mercurial is used from both Cygwin and native
311 # Windows, with other native tools, or on shared volumes
314 # Windows, with other native tools, or on shared volumes
312 def checklink(path):
315 def checklink(path):
313 return False
316 return False
314
317
315 def shellquote(s):
318 def shellquote(s):
316 if os.sys.platform == 'OpenVMS':
319 if os.sys.platform == 'OpenVMS':
317 return '"%s"' % s
320 return '"%s"' % s
318 else:
321 else:
319 return "'%s'" % s.replace("'", "'\\''")
322 return "'%s'" % s.replace("'", "'\\''")
320
323
321 def quotecommand(cmd):
324 def quotecommand(cmd):
322 return cmd
325 return cmd
323
326
324 def popen(command, mode='r'):
327 def popen(command, mode='r'):
325 return os.popen(command, mode)
328 return os.popen(command, mode)
326
329
327 def testpid(pid):
330 def testpid(pid):
328 '''return False if pid dead, True if running or not sure'''
331 '''return False if pid dead, True if running or not sure'''
329 if os.sys.platform == 'OpenVMS':
332 if os.sys.platform == 'OpenVMS':
330 return True
333 return True
331 try:
334 try:
332 os.kill(pid, 0)
335 os.kill(pid, 0)
333 return True
336 return True
334 except OSError, inst:
337 except OSError, inst:
335 return inst.errno != errno.ESRCH
338 return inst.errno != errno.ESRCH
336
339
337 def explainexit(code):
340 def explainexit(code):
338 """return a 2-tuple (desc, code) describing a subprocess status
341 """return a 2-tuple (desc, code) describing a subprocess status
339 (codes from kill are negative - not os.system/wait encoding)"""
342 (codes from kill are negative - not os.system/wait encoding)"""
340 if code >= 0:
343 if code >= 0:
341 return _("exited with status %d") % code, code
344 return _("exited with status %d") % code, code
342 return _("killed by signal %d") % -code, -code
345 return _("killed by signal %d") % -code, -code
343
346
344 def isowner(st):
347 def isowner(st):
345 """Return True if the stat object st is from the current user."""
348 """Return True if the stat object st is from the current user."""
346 return st.st_uid == os.getuid()
349 return st.st_uid == os.getuid()
347
350
348 def findexe(command):
351 def findexe(command):
349 '''Find executable for command searching like which does.
352 '''Find executable for command searching like which does.
350 If command is a basename then PATH is searched for command.
353 If command is a basename then PATH is searched for command.
351 PATH isn't searched if command is an absolute or relative path.
354 PATH isn't searched if command is an absolute or relative path.
352 If command isn't found None is returned.'''
355 If command isn't found None is returned.'''
353 if sys.platform == 'OpenVMS':
356 if sys.platform == 'OpenVMS':
354 return command
357 return command
355
358
356 def findexisting(executable):
359 def findexisting(executable):
357 'Will return executable if existing file'
360 'Will return executable if existing file'
358 if os.path.isfile(executable) and os.access(executable, os.X_OK):
361 if os.path.isfile(executable) and os.access(executable, os.X_OK):
359 return executable
362 return executable
360 return None
363 return None
361
364
362 if os.sep in command:
365 if os.sep in command:
363 return findexisting(command)
366 return findexisting(command)
364
367
365 if sys.platform == 'plan9':
368 if sys.platform == 'plan9':
366 return findexisting(os.path.join('/bin', command))
369 return findexisting(os.path.join('/bin', command))
367
370
368 for path in os.environ.get('PATH', '').split(os.pathsep):
371 for path in os.environ.get('PATH', '').split(os.pathsep):
369 executable = findexisting(os.path.join(path, command))
372 executable = findexisting(os.path.join(path, command))
370 if executable is not None:
373 if executable is not None:
371 return executable
374 return executable
372 return None
375 return None
373
376
374 def setsignalhandler():
377 def setsignalhandler():
375 pass
378 pass
376
379
377 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
380 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
378
381
379 def statfiles(files):
382 def statfiles(files):
380 '''Stat each file in files. Yield each stat, or None if a file does not
383 '''Stat each file in files. Yield each stat, or None if a file does not
381 exist or has a type we don't care about.'''
384 exist or has a type we don't care about.'''
382 lstat = os.lstat
385 lstat = os.lstat
383 getkind = stat.S_IFMT
386 getkind = stat.S_IFMT
384 for nf in files:
387 for nf in files:
385 try:
388 try:
386 st = lstat(nf)
389 st = lstat(nf)
387 if getkind(st.st_mode) not in _wantedkinds:
390 if getkind(st.st_mode) not in _wantedkinds:
388 st = None
391 st = None
389 except OSError, err:
392 except OSError, err:
390 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
393 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
391 raise
394 raise
392 st = None
395 st = None
393 yield st
396 yield st
394
397
395 def getuser():
398 def getuser():
396 '''return name of current user'''
399 '''return name of current user'''
397 return getpass.getuser()
400 return getpass.getuser()
398
401
399 def username(uid=None):
402 def username(uid=None):
400 """Return the name of the user with the given uid.
403 """Return the name of the user with the given uid.
401
404
402 If uid is None, return the name of the current user."""
405 If uid is None, return the name of the current user."""
403
406
404 if uid is None:
407 if uid is None:
405 uid = os.getuid()
408 uid = os.getuid()
406 try:
409 try:
407 return pwd.getpwuid(uid)[0]
410 return pwd.getpwuid(uid)[0]
408 except KeyError:
411 except KeyError:
409 return str(uid)
412 return str(uid)
410
413
411 def groupname(gid=None):
414 def groupname(gid=None):
412 """Return the name of the group with the given gid.
415 """Return the name of the group with the given gid.
413
416
414 If gid is None, return the name of the current group."""
417 If gid is None, return the name of the current group."""
415
418
416 if gid is None:
419 if gid is None:
417 gid = os.getgid()
420 gid = os.getgid()
418 try:
421 try:
419 return grp.getgrgid(gid)[0]
422 return grp.getgrgid(gid)[0]
420 except KeyError:
423 except KeyError:
421 return str(gid)
424 return str(gid)
422
425
423 def groupmembers(name):
426 def groupmembers(name):
424 """Return the list of members of the group with the given
427 """Return the list of members of the group with the given
425 name, KeyError if the group does not exist.
428 name, KeyError if the group does not exist.
426 """
429 """
427 return list(grp.getgrnam(name).gr_mem)
430 return list(grp.getgrnam(name).gr_mem)
428
431
429 def spawndetached(args):
432 def spawndetached(args):
430 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
433 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
431 args[0], args)
434 args[0], args)
432
435
433 def gethgcmd():
436 def gethgcmd():
434 return sys.argv[:1]
437 return sys.argv[:1]
435
438
436 def termwidth():
439 def termwidth():
437 try:
440 try:
438 import termios, array
441 import termios, array
439 for dev in (sys.stderr, sys.stdout, sys.stdin):
442 for dev in (sys.stderr, sys.stdout, sys.stdin):
440 try:
443 try:
441 try:
444 try:
442 fd = dev.fileno()
445 fd = dev.fileno()
443 except AttributeError:
446 except AttributeError:
444 continue
447 continue
445 if not os.isatty(fd):
448 if not os.isatty(fd):
446 continue
449 continue
447 try:
450 try:
448 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
451 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
449 width = array.array('h', arri)[1]
452 width = array.array('h', arri)[1]
450 if width > 0:
453 if width > 0:
451 return width
454 return width
452 except AttributeError:
455 except AttributeError:
453 pass
456 pass
454 except ValueError:
457 except ValueError:
455 pass
458 pass
456 except IOError, e:
459 except IOError, e:
457 if e[0] == errno.EINVAL:
460 if e[0] == errno.EINVAL:
458 pass
461 pass
459 else:
462 else:
460 raise
463 raise
461 except ImportError:
464 except ImportError:
462 pass
465 pass
463 return 80
466 return 80
464
467
465 def makedir(path, notindexed):
468 def makedir(path, notindexed):
466 os.mkdir(path)
469 os.mkdir(path)
467
470
468 def unlinkpath(f, ignoremissing=False):
471 def unlinkpath(f, ignoremissing=False):
469 """unlink and remove the directory if it is empty"""
472 """unlink and remove the directory if it is empty"""
470 try:
473 try:
471 os.unlink(f)
474 os.unlink(f)
472 except OSError, e:
475 except OSError, e:
473 if not (ignoremissing and e.errno == errno.ENOENT):
476 if not (ignoremissing and e.errno == errno.ENOENT):
474 raise
477 raise
475 # try removing directories that might now be empty
478 # try removing directories that might now be empty
476 try:
479 try:
477 os.removedirs(os.path.dirname(f))
480 os.removedirs(os.path.dirname(f))
478 except OSError:
481 except OSError:
479 pass
482 pass
480
483
481 def lookupreg(key, name=None, scope=None):
484 def lookupreg(key, name=None, scope=None):
482 return None
485 return None
483
486
484 def hidewindow():
487 def hidewindow():
485 """Hide current shell window.
488 """Hide current shell window.
486
489
487 Used to hide the window opened when starting asynchronous
490 Used to hide the window opened when starting asynchronous
488 child process under Windows, unneeded on other systems.
491 child process under Windows, unneeded on other systems.
489 """
492 """
490 pass
493 pass
491
494
492 class cachestat(object):
495 class cachestat(object):
493 def __init__(self, path):
496 def __init__(self, path):
494 self.stat = os.stat(path)
497 self.stat = os.stat(path)
495
498
496 def cacheable(self):
499 def cacheable(self):
497 return bool(self.stat.st_ino)
500 return bool(self.stat.st_ino)
498
501
499 __hash__ = object.__hash__
502 __hash__ = object.__hash__
500
503
501 def __eq__(self, other):
504 def __eq__(self, other):
502 try:
505 try:
503 # Only dev, ino, size, mtime and atime are likely to change. Out
506 # Only dev, ino, size, mtime and atime are likely to change. Out
504 # of these, we shouldn't compare atime but should compare the
507 # of these, we shouldn't compare atime but should compare the
505 # rest. However, one of the other fields changing indicates
508 # rest. However, one of the other fields changing indicates
506 # something fishy going on, so return False if anything but atime
509 # something fishy going on, so return False if anything but atime
507 # changes.
510 # changes.
508 return (self.stat.st_mode == other.stat.st_mode and
511 return (self.stat.st_mode == other.stat.st_mode and
509 self.stat.st_ino == other.stat.st_ino and
512 self.stat.st_ino == other.stat.st_ino and
510 self.stat.st_dev == other.stat.st_dev and
513 self.stat.st_dev == other.stat.st_dev and
511 self.stat.st_nlink == other.stat.st_nlink and
514 self.stat.st_nlink == other.stat.st_nlink and
512 self.stat.st_uid == other.stat.st_uid and
515 self.stat.st_uid == other.stat.st_uid and
513 self.stat.st_gid == other.stat.st_gid and
516 self.stat.st_gid == other.stat.st_gid and
514 self.stat.st_size == other.stat.st_size and
517 self.stat.st_size == other.stat.st_size and
515 self.stat.st_mtime == other.stat.st_mtime and
518 self.stat.st_mtime == other.stat.st_mtime and
516 self.stat.st_ctime == other.stat.st_ctime)
519 self.stat.st_ctime == other.stat.st_ctime)
517 except AttributeError:
520 except AttributeError:
518 return False
521 return False
519
522
520 def __ne__(self, other):
523 def __ne__(self, other):
521 return not self == other
524 return not self == other
522
525
523 def executablepath():
526 def executablepath():
524 return None # available on Windows only
527 return None # available on Windows only
525
528
526 class unixdomainserver(socket.socket):
529 class unixdomainserver(socket.socket):
527 def __init__(self, join, subsystem):
530 def __init__(self, join, subsystem):
528 '''Create a unix domain socket with the given prefix.'''
531 '''Create a unix domain socket with the given prefix.'''
529 super(unixdomainserver, self).__init__(socket.AF_UNIX)
532 super(unixdomainserver, self).__init__(socket.AF_UNIX)
530 sockname = subsystem + '.sock'
533 sockname = subsystem + '.sock'
531 self.realpath = self.path = join(sockname)
534 self.realpath = self.path = join(sockname)
532 if os.path.islink(self.path):
535 if os.path.islink(self.path):
533 if os.path.exists(self.path):
536 if os.path.exists(self.path):
534 self.realpath = os.readlink(self.path)
537 self.realpath = os.readlink(self.path)
535 else:
538 else:
536 os.unlink(self.path)
539 os.unlink(self.path)
537 try:
540 try:
538 self.bind(self.realpath)
541 self.bind(self.realpath)
539 except socket.error, err:
542 except socket.error, err:
540 if err.args[0] == 'AF_UNIX path too long':
543 if err.args[0] == 'AF_UNIX path too long':
541 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
544 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
542 self.realpath = os.path.join(tmpdir, sockname)
545 self.realpath = os.path.join(tmpdir, sockname)
543 try:
546 try:
544 self.bind(self.realpath)
547 self.bind(self.realpath)
545 os.symlink(self.realpath, self.path)
548 os.symlink(self.realpath, self.path)
546 except (OSError, socket.error):
549 except (OSError, socket.error):
547 self.cleanup()
550 self.cleanup()
548 raise
551 raise
549 else:
552 else:
550 raise
553 raise
551 self.listen(5)
554 self.listen(5)
552
555
553 def cleanup(self):
556 def cleanup(self):
554 def okayifmissing(f, path):
557 def okayifmissing(f, path):
555 try:
558 try:
556 f(path)
559 f(path)
557 except OSError, err:
560 except OSError, err:
558 if err.errno != errno.ENOENT:
561 if err.errno != errno.ENOENT:
559 raise
562 raise
560
563
561 okayifmissing(os.unlink, self.path)
564 okayifmissing(os.unlink, self.path)
562 if self.realpath != self.path:
565 if self.realpath != self.path:
563 okayifmissing(os.unlink, self.realpath)
566 okayifmissing(os.unlink, self.realpath)
564 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
567 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
565
568
566 def statislink(st):
569 def statislink(st):
567 '''check whether a stat result is a symlink'''
570 '''check whether a stat result is a symlink'''
568 return st and stat.S_ISLNK(st.st_mode)
571 return st and stat.S_ISLNK(st.st_mode)
569
572
570 def statisexec(st):
573 def statisexec(st):
571 '''check whether a stat result is an executable file'''
574 '''check whether a stat result is an executable file'''
572 return st and (st.st_mode & 0100 != 0)
575 return st and (st.st_mode & 0100 != 0)
573
576
574 def readpipe(pipe):
577 def readpipe(pipe):
575 """Read all available data from a pipe."""
578 """Read all available data from a pipe."""
576 # We can't fstat() a pipe because Linux will always report 0.
579 # We can't fstat() a pipe because Linux will always report 0.
577 # So, we set the pipe to non-blocking mode and read everything
580 # So, we set the pipe to non-blocking mode and read everything
578 # that's available.
581 # that's available.
579 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
582 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
580 flags |= os.O_NONBLOCK
583 flags |= os.O_NONBLOCK
581 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
584 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
582
585
583 try:
586 try:
584 chunks = []
587 chunks = []
585 while True:
588 while True:
586 try:
589 try:
587 s = pipe.read()
590 s = pipe.read()
588 if not s:
591 if not s:
589 break
592 break
590 chunks.append(s)
593 chunks.append(s)
591 except IOError:
594 except IOError:
592 break
595 break
593
596
594 return ''.join(chunks)
597 return ''.join(chunks)
595 finally:
598 finally:
596 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
599 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
@@ -1,211 +1,210
1 #require icasefs
1 #require icasefs
2
2
3 $ hg debugfs | grep 'case-sensitive:'
3 $ hg debugfs | grep 'case-sensitive:'
4 case-sensitive: no
4 case-sensitive: no
5
5
6 test file addition with bad case
6 test file addition with bad case
7
7
8 $ hg init repo1
8 $ hg init repo1
9 $ cd repo1
9 $ cd repo1
10 $ echo a > a
10 $ echo a > a
11 $ hg add A
11 $ hg add A
12 adding a
12 adding a
13 $ hg st
13 $ hg st
14 A a
14 A a
15 $ hg ci -m adda
15 $ hg ci -m adda
16 $ hg manifest
16 $ hg manifest
17 a
17 a
18 $ cd ..
18 $ cd ..
19
19
20 test case collision on rename (issue750)
20 test case collision on rename (issue750)
21
21
22 $ hg init repo2
22 $ hg init repo2
23 $ cd repo2
23 $ cd repo2
24 $ echo a > a
24 $ echo a > a
25 $ hg --debug ci -Am adda
25 $ hg --debug ci -Am adda
26 adding a
26 adding a
27 a
27 a
28 committed changeset 0:07f4944404050f47db2e5c5071e0e84e7a27bba9
28 committed changeset 0:07f4944404050f47db2e5c5071e0e84e7a27bba9
29
29
30 Case-changing renames should work:
30 Case-changing renames should work:
31
31
32 $ hg mv a A
32 $ hg mv a A
33 $ hg mv A a
33 $ hg mv A a
34 $ hg st
34 $ hg st
35
35
36 test changing case of path components
36 test changing case of path components
37
37
38 $ mkdir D
38 $ mkdir D
39 $ echo b > D/b
39 $ echo b > D/b
40 $ hg ci -Am addb D/b
40 $ hg ci -Am addb D/b
41 $ hg mv D/b d/b
41 $ hg mv D/b d/b
42 D/b: not overwriting - file exists
42 D/b: not overwriting - file exists
43 $ hg mv D/b d/c
43 $ hg mv D/b d/c
44 $ hg st
44 $ hg st
45 A D/c
45 A D/c
46 R D/b
46 R D/b
47 $ mv D temp
47 $ mv D temp
48 $ mv temp d
48 $ mv temp d
49 $ hg st
49 $ hg st
50 A D/c
50 A D/c
51 R D/b
51 R D/b
52 $ hg revert -aq
52 $ hg revert -aq
53 $ rm d/c
53 $ rm d/c
54 $ echo c > D/c
54 $ echo c > D/c
55 $ hg add D/c
55 $ hg add D/c
56 $ hg st
56 $ hg st
57 A D/c
57 A D/c
58 $ hg ci -m addc D/c
58 $ hg ci -m addc D/c
59 $ hg mv d/b d/e
59 $ hg mv d/b d/e
60 moving D/b to D/e (glob)
60 moving D/b to D/e (glob)
61 $ hg st
61 $ hg st
62 A D/e
62 A D/e
63 R D/b
63 R D/b
64 $ hg revert -aq
64 $ hg revert -aq
65 $ rm d/e
65 $ rm d/e
66 $ hg mv d/b D/B
66 $ hg mv d/b D/B
67 moving D/b to D/B (glob)
67 moving D/b to D/B (glob)
68 $ hg st
68 $ hg st
69 A D/B
69 A D/B
70 R D/b
70 R D/b
71 $ cd ..
71 $ cd ..
72
72
73 test case collision between revisions (issue912)
73 test case collision between revisions (issue912)
74
74
75 $ hg init repo3
75 $ hg init repo3
76 $ cd repo3
76 $ cd repo3
77 $ echo a > a
77 $ echo a > a
78 $ hg ci -Am adda
78 $ hg ci -Am adda
79 adding a
79 adding a
80 $ hg rm a
80 $ hg rm a
81 $ hg ci -Am removea
81 $ hg ci -Am removea
82 $ echo A > A
82 $ echo A > A
83
83
84 on linux hfs keeps the old case stored, force it
84 on linux hfs keeps the old case stored, force it
85
85
86 $ mv a aa
86 $ mv a aa
87 $ mv aa A
87 $ mv aa A
88 $ hg ci -Am addA
88 $ hg ci -Am addA
89 adding A
89 adding A
90
90
91 used to fail under case insensitive fs
91 used to fail under case insensitive fs
92
92
93 $ hg up -C 0
93 $ hg up -C 0
94 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
94 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
95 $ hg up -C
95 $ hg up -C
96 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
96 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
97
97
98 no clobbering of untracked files with wrong casing
98 no clobbering of untracked files with wrong casing
99
99
100 $ hg up -r null
100 $ hg up -r null
101 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
101 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
102 $ echo gold > a
102 $ echo gold > a
103 $ hg up
103 $ hg up
104 A: untracked file differs
104 A: untracked file differs
105 abort: untracked files in working directory differ from files in requested revision
105 abort: untracked files in working directory differ from files in requested revision
106 [255]
106 [255]
107 $ cat a
107 $ cat a
108 gold
108 gold
109 $ rm a
109 $ rm a
110
110
111 test that normal file in different case on target context is not
111 test that normal file in different case on target context is not
112 unlinked by largefiles extension.
112 unlinked by largefiles extension.
113
113
114 $ cat >> .hg/hgrc <<EOF
114 $ cat >> .hg/hgrc <<EOF
115 > [extensions]
115 > [extensions]
116 > largefiles=
116 > largefiles=
117 > EOF
117 > EOF
118 $ hg update -q -C 1
118 $ hg update -q -C 1
119 $ hg status -A
119 $ hg status -A
120 $ echo 'A as largefiles' > A
120 $ echo 'A as largefiles' > A
121 $ hg add --large A
121 $ hg add --large A
122 $ hg commit -m '#3'
122 $ hg commit -m '#3'
123 created new head
123 created new head
124 $ hg manifest -r 3
124 $ hg manifest -r 3
125 .hglf/A
125 .hglf/A
126 $ hg manifest -r 0
126 $ hg manifest -r 0
127 a
127 a
128 $ hg update -q -C 0
128 $ hg update -q -C 0
129 $ hg status -A
129 $ hg status -A
130 C a
130 C a
131 $ hg update -q -C 3
131 $ hg update -q -C 3
132 $ hg update -q 0
132 $ hg update -q 0
133
133
134 $ cd ..
134 $ cd ..
135
135
136 issue 3342: file in nested directory causes unexpected abort
136 issue 3342: file in nested directory causes unexpected abort
137
137
138 $ hg init issue3342
138 $ hg init issue3342
139 $ cd issue3342
139 $ cd issue3342
140
140
141 $ mkdir -p a/B/c/D
141 $ mkdir -p a/B/c/D
142 $ echo e > a/B/c/D/e
142 $ echo e > a/B/c/D/e
143 $ hg add a/B/c/D/e
143 $ hg add a/B/c/D/e
144
144
145 $ cd ..
145 $ cd ..
146
146
147 issue 3340: mq does not handle case changes correctly
147 issue 3340: mq does not handle case changes correctly
148
148
149 in addition to reported case, 'hg qrefresh' is also tested against
149 in addition to reported case, 'hg qrefresh' is also tested against
150 case changes.
150 case changes.
151
151
152 $ echo "[extensions]" >> $HGRCPATH
152 $ echo "[extensions]" >> $HGRCPATH
153 $ echo "mq=" >> $HGRCPATH
153 $ echo "mq=" >> $HGRCPATH
154
154
155 $ hg init issue3340
155 $ hg init issue3340
156 $ cd issue3340
156 $ cd issue3340
157
157
158 $ echo a > mIxEdCaSe
158 $ echo a > mIxEdCaSe
159 $ hg add mIxEdCaSe
159 $ hg add mIxEdCaSe
160 $ hg commit -m '#0'
160 $ hg commit -m '#0'
161 $ hg rename mIxEdCaSe tmp
161 $ hg rename mIxEdCaSe tmp
162 $ hg rename tmp MiXeDcAsE
162 $ hg rename tmp MiXeDcAsE
163 $ hg status -A
163 $ hg status -A
164 A MiXeDcAsE
164 A MiXeDcAsE
165 mIxEdCaSe
165 mIxEdCaSe
166 R mIxEdCaSe
166 R mIxEdCaSe
167 $ hg qnew changecase
167 $ hg qnew changecase
168 $ hg status -A
168 $ hg status -A
169 C MiXeDcAsE
169 C MiXeDcAsE
170
170
171 $ hg qpop -a
171 $ hg qpop -a
172 popping changecase
172 popping changecase
173 patch queue now empty
173 patch queue now empty
174 $ hg qnew refresh-casechange
174 $ hg qnew refresh-casechange
175 $ hg status -A
175 $ hg status -A
176 C mIxEdCaSe
176 C mIxEdCaSe
177 $ hg rename mIxEdCaSe tmp
177 $ hg rename mIxEdCaSe tmp
178 $ hg rename tmp MiXeDcAsE
178 $ hg rename tmp MiXeDcAsE
179 $ hg status -A
179 $ hg status -A
180 A MiXeDcAsE
180 A MiXeDcAsE
181 mIxEdCaSe
181 mIxEdCaSe
182 R mIxEdCaSe
182 R mIxEdCaSe
183 $ hg qrefresh
183 $ hg qrefresh
184 $ hg status -A
184 $ hg status -A
185 C MiXeDcAsE
185 C MiXeDcAsE
186
186
187 $ hg qpop -a
187 $ hg qpop -a
188 popping refresh-casechange
188 popping refresh-casechange
189 patch queue now empty
189 patch queue now empty
190 $ hg qnew refresh-pattern
190 $ hg qnew refresh-pattern
191 $ hg status
191 $ hg status
192 $ echo A > A
192 $ echo A > A
193 $ hg add
193 $ hg add
194 adding A
194 adding A
195 $ hg qrefresh a # issue 3271, qrefresh with file handled case wrong
195 $ hg qrefresh a # issue 3271, qrefresh with file handled case wrong
196 $ hg status # empty status means the qrefresh worked
196 $ hg status # empty status means the qrefresh worked
197
197
198 #if osx
198 #if osx
199
199
200 We assume anyone running the tests on a case-insensitive volume on OS
200 We assume anyone running the tests on a case-insensitive volume on OS
201 X will be using HFS+. If that's not true, this test will fail.
201 X will be using HFS+. If that's not true, this test will fail.
202
202
203 Bug: some codepoints are to be ignored on HFS+:
204
205 $ rm A
203 $ rm A
206 >>> open(u'a\u200c'.encode('utf-8'), 'w').write('unicode is fun')
204 >>> open(u'a\u200c'.encode('utf-8'), 'w').write('unicode is fun')
207 $ hg status
205 $ hg status
208 M A
206 M A
209 ? a\xe2\x80\x8c (esc)
207
210 #endif
208 #endif
209
211 $ cd ..
210 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now