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