##// END OF EJS Templates
cygwin: define normcase spec and fallback...
Siddharth Agarwal -
r24596:75ea27f1 default
parent child Browse files
Show More
@@ -1,615 +1,618
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, re
11 import fcntl, re
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 # what normcase does to ASCII strings
203 # what normcase does to ASCII strings
204 normcasespec = encoding.normcasespecs.lower
204 normcasespec = encoding.normcasespecs.lower
205 # fallback normcase function for non-ASCII strings
205 # fallback normcase function for non-ASCII strings
206 normcasefallback = normcase
206 normcasefallback = normcase
207
207
208 if sys.platform == 'darwin':
208 if sys.platform == 'darwin':
209
209
210 def normcase(path):
210 def normcase(path):
211 '''
211 '''
212 Normalize a filename for OS X-compatible comparison:
212 Normalize a filename for OS X-compatible comparison:
213 - escape-encode invalid characters
213 - escape-encode invalid characters
214 - decompose to NFD
214 - decompose to NFD
215 - lowercase
215 - lowercase
216 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
216 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
217
217
218 >>> normcase('UPPER')
218 >>> normcase('UPPER')
219 'upper'
219 'upper'
220 >>> normcase('Caf\xc3\xa9')
220 >>> normcase('Caf\xc3\xa9')
221 'cafe\\xcc\\x81'
221 'cafe\\xcc\\x81'
222 >>> normcase('\xc3\x89')
222 >>> normcase('\xc3\x89')
223 'e\\xcc\\x81'
223 'e\\xcc\\x81'
224 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
224 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
225 '%b8%ca%c3\\xca\\xbe%c8.jpg'
225 '%b8%ca%c3\\xca\\xbe%c8.jpg'
226 '''
226 '''
227
227
228 try:
228 try:
229 return encoding.asciilower(path) # exception for non-ASCII
229 return encoding.asciilower(path) # exception for non-ASCII
230 except UnicodeDecodeError:
230 except UnicodeDecodeError:
231 return normcasefallback(path)
231 return normcasefallback(path)
232
232
233 normcasespec = encoding.normcasespecs.lower
233 normcasespec = encoding.normcasespecs.lower
234
234
235 def normcasefallback(path):
235 def normcasefallback(path):
236 try:
236 try:
237 u = path.decode('utf-8')
237 u = path.decode('utf-8')
238 except UnicodeDecodeError:
238 except UnicodeDecodeError:
239 # OS X percent-encodes any bytes that aren't valid utf-8
239 # OS X percent-encodes any bytes that aren't valid utf-8
240 s = ''
240 s = ''
241 g = ''
241 g = ''
242 l = 0
242 l = 0
243 for c in path:
243 for c in path:
244 o = ord(c)
244 o = ord(c)
245 if l and o < 128 or o >= 192:
245 if l and o < 128 or o >= 192:
246 # we want a continuation byte, but didn't get one
246 # we want a continuation byte, but didn't get one
247 s += ''.join(["%%%02X" % ord(x) for x in g])
247 s += ''.join(["%%%02X" % ord(x) for x in g])
248 g = ''
248 g = ''
249 l = 0
249 l = 0
250 if l == 0 and o < 128:
250 if l == 0 and o < 128:
251 # ascii
251 # ascii
252 s += c
252 s += c
253 elif l == 0 and 194 <= o < 245:
253 elif l == 0 and 194 <= o < 245:
254 # valid leading bytes
254 # valid leading bytes
255 if o < 224:
255 if o < 224:
256 l = 1
256 l = 1
257 elif o < 240:
257 elif o < 240:
258 l = 2
258 l = 2
259 else:
259 else:
260 l = 3
260 l = 3
261 g = c
261 g = c
262 elif l > 0 and 128 <= o < 192:
262 elif l > 0 and 128 <= o < 192:
263 # valid continuations
263 # valid continuations
264 g += c
264 g += c
265 l -= 1
265 l -= 1
266 if not l:
266 if not l:
267 s += g
267 s += g
268 g = ''
268 g = ''
269 else:
269 else:
270 # invalid
270 # invalid
271 s += "%%%02X" % o
271 s += "%%%02X" % o
272
272
273 # any remaining partial characters
273 # any remaining partial characters
274 s += ''.join(["%%%02X" % ord(x) for x in g])
274 s += ''.join(["%%%02X" % ord(x) for x in g])
275 u = s.decode('utf-8')
275 u = s.decode('utf-8')
276
276
277 # Decompose then lowercase (HFS+ technote specifies lower)
277 # Decompose then lowercase (HFS+ technote specifies lower)
278 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
278 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
279 # drop HFS+ ignored characters
279 # drop HFS+ ignored characters
280 return encoding.hfsignoreclean(enc)
280 return encoding.hfsignoreclean(enc)
281
281
282 if sys.platform == 'cygwin':
282 if sys.platform == 'cygwin':
283 # workaround for cygwin, in which mount point part of path is
283 # workaround for cygwin, in which mount point part of path is
284 # treated as case sensitive, even though underlying NTFS is case
284 # treated as case sensitive, even though underlying NTFS is case
285 # insensitive.
285 # insensitive.
286
286
287 # default mount points
287 # default mount points
288 cygwinmountpoints = sorted([
288 cygwinmountpoints = sorted([
289 "/usr/bin",
289 "/usr/bin",
290 "/usr/lib",
290 "/usr/lib",
291 "/cygdrive",
291 "/cygdrive",
292 ], reverse=True)
292 ], reverse=True)
293
293
294 # use upper-ing as normcase as same as NTFS workaround
294 # use upper-ing as normcase as same as NTFS workaround
295 def normcase(path):
295 def normcase(path):
296 pathlen = len(path)
296 pathlen = len(path)
297 if (pathlen == 0) or (path[0] != os.sep):
297 if (pathlen == 0) or (path[0] != os.sep):
298 # treat as relative
298 # treat as relative
299 return encoding.upper(path)
299 return encoding.upper(path)
300
300
301 # to preserve case of mountpoint part
301 # to preserve case of mountpoint part
302 for mp in cygwinmountpoints:
302 for mp in cygwinmountpoints:
303 if not path.startswith(mp):
303 if not path.startswith(mp):
304 continue
304 continue
305
305
306 mplen = len(mp)
306 mplen = len(mp)
307 if mplen == pathlen: # mount point itself
307 if mplen == pathlen: # mount point itself
308 return mp
308 return mp
309 if path[mplen] == os.sep:
309 if path[mplen] == os.sep:
310 return mp + encoding.upper(path[mplen:])
310 return mp + encoding.upper(path[mplen:])
311
311
312 return encoding.upper(path)
312 return encoding.upper(path)
313
313
314 normcasespec = encoding.normcasespecs.other
315 normcasefallback = normcase
316
314 # Cygwin translates native ACLs to POSIX permissions,
317 # Cygwin translates native ACLs to POSIX permissions,
315 # but these translations are not supported by native
318 # but these translations are not supported by native
316 # tools, so the exec bit tends to be set erroneously.
319 # tools, so the exec bit tends to be set erroneously.
317 # Therefore, disable executable bit access on Cygwin.
320 # Therefore, disable executable bit access on Cygwin.
318 def checkexec(path):
321 def checkexec(path):
319 return False
322 return False
320
323
321 # Similarly, Cygwin's symlink emulation is likely to create
324 # Similarly, Cygwin's symlink emulation is likely to create
322 # problems when Mercurial is used from both Cygwin and native
325 # problems when Mercurial is used from both Cygwin and native
323 # Windows, with other native tools, or on shared volumes
326 # Windows, with other native tools, or on shared volumes
324 def checklink(path):
327 def checklink(path):
325 return False
328 return False
326
329
327 _needsshellquote = None
330 _needsshellquote = None
328 def shellquote(s):
331 def shellquote(s):
329 if os.sys.platform == 'OpenVMS':
332 if os.sys.platform == 'OpenVMS':
330 return '"%s"' % s
333 return '"%s"' % s
331 global _needsshellquote
334 global _needsshellquote
332 if _needsshellquote is None:
335 if _needsshellquote is None:
333 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/-]').search
336 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/-]').search
334 if s and not _needsshellquote(s):
337 if s and not _needsshellquote(s):
335 # "s" shouldn't have to be quoted
338 # "s" shouldn't have to be quoted
336 return s
339 return s
337 else:
340 else:
338 return "'%s'" % s.replace("'", "'\\''")
341 return "'%s'" % s.replace("'", "'\\''")
339
342
340 def quotecommand(cmd):
343 def quotecommand(cmd):
341 return cmd
344 return cmd
342
345
343 def popen(command, mode='r'):
346 def popen(command, mode='r'):
344 return os.popen(command, mode)
347 return os.popen(command, mode)
345
348
346 def testpid(pid):
349 def testpid(pid):
347 '''return False if pid dead, True if running or not sure'''
350 '''return False if pid dead, True if running or not sure'''
348 if os.sys.platform == 'OpenVMS':
351 if os.sys.platform == 'OpenVMS':
349 return True
352 return True
350 try:
353 try:
351 os.kill(pid, 0)
354 os.kill(pid, 0)
352 return True
355 return True
353 except OSError, inst:
356 except OSError, inst:
354 return inst.errno != errno.ESRCH
357 return inst.errno != errno.ESRCH
355
358
356 def explainexit(code):
359 def explainexit(code):
357 """return a 2-tuple (desc, code) describing a subprocess status
360 """return a 2-tuple (desc, code) describing a subprocess status
358 (codes from kill are negative - not os.system/wait encoding)"""
361 (codes from kill are negative - not os.system/wait encoding)"""
359 if code >= 0:
362 if code >= 0:
360 return _("exited with status %d") % code, code
363 return _("exited with status %d") % code, code
361 return _("killed by signal %d") % -code, -code
364 return _("killed by signal %d") % -code, -code
362
365
363 def isowner(st):
366 def isowner(st):
364 """Return True if the stat object st is from the current user."""
367 """Return True if the stat object st is from the current user."""
365 return st.st_uid == os.getuid()
368 return st.st_uid == os.getuid()
366
369
367 def findexe(command):
370 def findexe(command):
368 '''Find executable for command searching like which does.
371 '''Find executable for command searching like which does.
369 If command is a basename then PATH is searched for command.
372 If command is a basename then PATH is searched for command.
370 PATH isn't searched if command is an absolute or relative path.
373 PATH isn't searched if command is an absolute or relative path.
371 If command isn't found None is returned.'''
374 If command isn't found None is returned.'''
372 if sys.platform == 'OpenVMS':
375 if sys.platform == 'OpenVMS':
373 return command
376 return command
374
377
375 def findexisting(executable):
378 def findexisting(executable):
376 'Will return executable if existing file'
379 'Will return executable if existing file'
377 if os.path.isfile(executable) and os.access(executable, os.X_OK):
380 if os.path.isfile(executable) and os.access(executable, os.X_OK):
378 return executable
381 return executable
379 return None
382 return None
380
383
381 if os.sep in command:
384 if os.sep in command:
382 return findexisting(command)
385 return findexisting(command)
383
386
384 if sys.platform == 'plan9':
387 if sys.platform == 'plan9':
385 return findexisting(os.path.join('/bin', command))
388 return findexisting(os.path.join('/bin', command))
386
389
387 for path in os.environ.get('PATH', '').split(os.pathsep):
390 for path in os.environ.get('PATH', '').split(os.pathsep):
388 executable = findexisting(os.path.join(path, command))
391 executable = findexisting(os.path.join(path, command))
389 if executable is not None:
392 if executable is not None:
390 return executable
393 return executable
391 return None
394 return None
392
395
393 def setsignalhandler():
396 def setsignalhandler():
394 pass
397 pass
395
398
396 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
399 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
397
400
398 def statfiles(files):
401 def statfiles(files):
399 '''Stat each file in files. Yield each stat, or None if a file does not
402 '''Stat each file in files. Yield each stat, or None if a file does not
400 exist or has a type we don't care about.'''
403 exist or has a type we don't care about.'''
401 lstat = os.lstat
404 lstat = os.lstat
402 getkind = stat.S_IFMT
405 getkind = stat.S_IFMT
403 for nf in files:
406 for nf in files:
404 try:
407 try:
405 st = lstat(nf)
408 st = lstat(nf)
406 if getkind(st.st_mode) not in _wantedkinds:
409 if getkind(st.st_mode) not in _wantedkinds:
407 st = None
410 st = None
408 except OSError, err:
411 except OSError, err:
409 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
412 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
410 raise
413 raise
411 st = None
414 st = None
412 yield st
415 yield st
413
416
414 def getuser():
417 def getuser():
415 '''return name of current user'''
418 '''return name of current user'''
416 return getpass.getuser()
419 return getpass.getuser()
417
420
418 def username(uid=None):
421 def username(uid=None):
419 """Return the name of the user with the given uid.
422 """Return the name of the user with the given uid.
420
423
421 If uid is None, return the name of the current user."""
424 If uid is None, return the name of the current user."""
422
425
423 if uid is None:
426 if uid is None:
424 uid = os.getuid()
427 uid = os.getuid()
425 try:
428 try:
426 return pwd.getpwuid(uid)[0]
429 return pwd.getpwuid(uid)[0]
427 except KeyError:
430 except KeyError:
428 return str(uid)
431 return str(uid)
429
432
430 def groupname(gid=None):
433 def groupname(gid=None):
431 """Return the name of the group with the given gid.
434 """Return the name of the group with the given gid.
432
435
433 If gid is None, return the name of the current group."""
436 If gid is None, return the name of the current group."""
434
437
435 if gid is None:
438 if gid is None:
436 gid = os.getgid()
439 gid = os.getgid()
437 try:
440 try:
438 return grp.getgrgid(gid)[0]
441 return grp.getgrgid(gid)[0]
439 except KeyError:
442 except KeyError:
440 return str(gid)
443 return str(gid)
441
444
442 def groupmembers(name):
445 def groupmembers(name):
443 """Return the list of members of the group with the given
446 """Return the list of members of the group with the given
444 name, KeyError if the group does not exist.
447 name, KeyError if the group does not exist.
445 """
448 """
446 return list(grp.getgrnam(name).gr_mem)
449 return list(grp.getgrnam(name).gr_mem)
447
450
448 def spawndetached(args):
451 def spawndetached(args):
449 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
452 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
450 args[0], args)
453 args[0], args)
451
454
452 def gethgcmd():
455 def gethgcmd():
453 return sys.argv[:1]
456 return sys.argv[:1]
454
457
455 def termwidth():
458 def termwidth():
456 try:
459 try:
457 import termios, array
460 import termios, array
458 for dev in (sys.stderr, sys.stdout, sys.stdin):
461 for dev in (sys.stderr, sys.stdout, sys.stdin):
459 try:
462 try:
460 try:
463 try:
461 fd = dev.fileno()
464 fd = dev.fileno()
462 except AttributeError:
465 except AttributeError:
463 continue
466 continue
464 if not os.isatty(fd):
467 if not os.isatty(fd):
465 continue
468 continue
466 try:
469 try:
467 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
470 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
468 width = array.array('h', arri)[1]
471 width = array.array('h', arri)[1]
469 if width > 0:
472 if width > 0:
470 return width
473 return width
471 except AttributeError:
474 except AttributeError:
472 pass
475 pass
473 except ValueError:
476 except ValueError:
474 pass
477 pass
475 except IOError, e:
478 except IOError, e:
476 if e[0] == errno.EINVAL:
479 if e[0] == errno.EINVAL:
477 pass
480 pass
478 else:
481 else:
479 raise
482 raise
480 except ImportError:
483 except ImportError:
481 pass
484 pass
482 return 80
485 return 80
483
486
484 def makedir(path, notindexed):
487 def makedir(path, notindexed):
485 os.mkdir(path)
488 os.mkdir(path)
486
489
487 def unlinkpath(f, ignoremissing=False):
490 def unlinkpath(f, ignoremissing=False):
488 """unlink and remove the directory if it is empty"""
491 """unlink and remove the directory if it is empty"""
489 try:
492 try:
490 os.unlink(f)
493 os.unlink(f)
491 except OSError, e:
494 except OSError, e:
492 if not (ignoremissing and e.errno == errno.ENOENT):
495 if not (ignoremissing and e.errno == errno.ENOENT):
493 raise
496 raise
494 # try removing directories that might now be empty
497 # try removing directories that might now be empty
495 try:
498 try:
496 os.removedirs(os.path.dirname(f))
499 os.removedirs(os.path.dirname(f))
497 except OSError:
500 except OSError:
498 pass
501 pass
499
502
500 def lookupreg(key, name=None, scope=None):
503 def lookupreg(key, name=None, scope=None):
501 return None
504 return None
502
505
503 def hidewindow():
506 def hidewindow():
504 """Hide current shell window.
507 """Hide current shell window.
505
508
506 Used to hide the window opened when starting asynchronous
509 Used to hide the window opened when starting asynchronous
507 child process under Windows, unneeded on other systems.
510 child process under Windows, unneeded on other systems.
508 """
511 """
509 pass
512 pass
510
513
511 class cachestat(object):
514 class cachestat(object):
512 def __init__(self, path):
515 def __init__(self, path):
513 self.stat = os.stat(path)
516 self.stat = os.stat(path)
514
517
515 def cacheable(self):
518 def cacheable(self):
516 return bool(self.stat.st_ino)
519 return bool(self.stat.st_ino)
517
520
518 __hash__ = object.__hash__
521 __hash__ = object.__hash__
519
522
520 def __eq__(self, other):
523 def __eq__(self, other):
521 try:
524 try:
522 # Only dev, ino, size, mtime and atime are likely to change. Out
525 # Only dev, ino, size, mtime and atime are likely to change. Out
523 # of these, we shouldn't compare atime but should compare the
526 # of these, we shouldn't compare atime but should compare the
524 # rest. However, one of the other fields changing indicates
527 # rest. However, one of the other fields changing indicates
525 # something fishy going on, so return False if anything but atime
528 # something fishy going on, so return False if anything but atime
526 # changes.
529 # changes.
527 return (self.stat.st_mode == other.stat.st_mode and
530 return (self.stat.st_mode == other.stat.st_mode and
528 self.stat.st_ino == other.stat.st_ino and
531 self.stat.st_ino == other.stat.st_ino and
529 self.stat.st_dev == other.stat.st_dev and
532 self.stat.st_dev == other.stat.st_dev and
530 self.stat.st_nlink == other.stat.st_nlink and
533 self.stat.st_nlink == other.stat.st_nlink and
531 self.stat.st_uid == other.stat.st_uid and
534 self.stat.st_uid == other.stat.st_uid and
532 self.stat.st_gid == other.stat.st_gid and
535 self.stat.st_gid == other.stat.st_gid and
533 self.stat.st_size == other.stat.st_size and
536 self.stat.st_size == other.stat.st_size and
534 self.stat.st_mtime == other.stat.st_mtime and
537 self.stat.st_mtime == other.stat.st_mtime and
535 self.stat.st_ctime == other.stat.st_ctime)
538 self.stat.st_ctime == other.stat.st_ctime)
536 except AttributeError:
539 except AttributeError:
537 return False
540 return False
538
541
539 def __ne__(self, other):
542 def __ne__(self, other):
540 return not self == other
543 return not self == other
541
544
542 def executablepath():
545 def executablepath():
543 return None # available on Windows only
546 return None # available on Windows only
544
547
545 class unixdomainserver(socket.socket):
548 class unixdomainserver(socket.socket):
546 def __init__(self, join, subsystem):
549 def __init__(self, join, subsystem):
547 '''Create a unix domain socket with the given prefix.'''
550 '''Create a unix domain socket with the given prefix.'''
548 super(unixdomainserver, self).__init__(socket.AF_UNIX)
551 super(unixdomainserver, self).__init__(socket.AF_UNIX)
549 sockname = subsystem + '.sock'
552 sockname = subsystem + '.sock'
550 self.realpath = self.path = join(sockname)
553 self.realpath = self.path = join(sockname)
551 if os.path.islink(self.path):
554 if os.path.islink(self.path):
552 if os.path.exists(self.path):
555 if os.path.exists(self.path):
553 self.realpath = os.readlink(self.path)
556 self.realpath = os.readlink(self.path)
554 else:
557 else:
555 os.unlink(self.path)
558 os.unlink(self.path)
556 try:
559 try:
557 self.bind(self.realpath)
560 self.bind(self.realpath)
558 except socket.error, err:
561 except socket.error, err:
559 if err.args[0] == 'AF_UNIX path too long':
562 if err.args[0] == 'AF_UNIX path too long':
560 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
563 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
561 self.realpath = os.path.join(tmpdir, sockname)
564 self.realpath = os.path.join(tmpdir, sockname)
562 try:
565 try:
563 self.bind(self.realpath)
566 self.bind(self.realpath)
564 os.symlink(self.realpath, self.path)
567 os.symlink(self.realpath, self.path)
565 except (OSError, socket.error):
568 except (OSError, socket.error):
566 self.cleanup()
569 self.cleanup()
567 raise
570 raise
568 else:
571 else:
569 raise
572 raise
570 self.listen(5)
573 self.listen(5)
571
574
572 def cleanup(self):
575 def cleanup(self):
573 def okayifmissing(f, path):
576 def okayifmissing(f, path):
574 try:
577 try:
575 f(path)
578 f(path)
576 except OSError, err:
579 except OSError, err:
577 if err.errno != errno.ENOENT:
580 if err.errno != errno.ENOENT:
578 raise
581 raise
579
582
580 okayifmissing(os.unlink, self.path)
583 okayifmissing(os.unlink, self.path)
581 if self.realpath != self.path:
584 if self.realpath != self.path:
582 okayifmissing(os.unlink, self.realpath)
585 okayifmissing(os.unlink, self.realpath)
583 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
586 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
584
587
585 def statislink(st):
588 def statislink(st):
586 '''check whether a stat result is a symlink'''
589 '''check whether a stat result is a symlink'''
587 return st and stat.S_ISLNK(st.st_mode)
590 return st and stat.S_ISLNK(st.st_mode)
588
591
589 def statisexec(st):
592 def statisexec(st):
590 '''check whether a stat result is an executable file'''
593 '''check whether a stat result is an executable file'''
591 return st and (st.st_mode & 0100 != 0)
594 return st and (st.st_mode & 0100 != 0)
592
595
593 def readpipe(pipe):
596 def readpipe(pipe):
594 """Read all available data from a pipe."""
597 """Read all available data from a pipe."""
595 # We can't fstat() a pipe because Linux will always report 0.
598 # We can't fstat() a pipe because Linux will always report 0.
596 # So, we set the pipe to non-blocking mode and read everything
599 # So, we set the pipe to non-blocking mode and read everything
597 # that's available.
600 # that's available.
598 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
601 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
599 flags |= os.O_NONBLOCK
602 flags |= os.O_NONBLOCK
600 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
603 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
601
604
602 try:
605 try:
603 chunks = []
606 chunks = []
604 while True:
607 while True:
605 try:
608 try:
606 s = pipe.read()
609 s = pipe.read()
607 if not s:
610 if not s:
608 break
611 break
609 chunks.append(s)
612 chunks.append(s)
610 except IOError:
613 except IOError:
611 break
614 break
612
615
613 return ''.join(chunks)
616 return ''.join(chunks)
614 finally:
617 finally:
615 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
618 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
General Comments 0
You need to be logged in to leave comments. Login now