##// END OF EJS Templates
util: add realpath() for getting the 'true' path....
Dan Villiom Podlaski Christiansen -
r9238:40196d03 default
parent child Browse files
Show More
@@ -1,214 +1,252 b''
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import osutil
10 import os, sys, errno, stat, getpass, pwd, grp
10 import os, sys, errno, stat, getpass, pwd, grp, fcntl
11 11
12 12 posixfile = open
13 13 nulldev = '/dev/null'
14 14 normpath = os.path.normpath
15 15 samestat = os.path.samestat
16 16 expandglobs = False
17 17
18 18 umask = os.umask(0)
19 19 os.umask(umask)
20 20
21 21 def openhardlinks():
22 22 '''return true if it is safe to hold open file handles to hardlinks'''
23 23 return True
24 24
25 25 def rcfiles(path):
26 26 rcs = [os.path.join(path, 'hgrc')]
27 27 rcdir = os.path.join(path, 'hgrc.d')
28 28 try:
29 29 rcs.extend([os.path.join(rcdir, f)
30 30 for f, kind in osutil.listdir(rcdir)
31 31 if f.endswith(".rc")])
32 32 except OSError:
33 33 pass
34 34 return rcs
35 35
36 36 def system_rcpath():
37 37 path = []
38 38 # old mod_python does not set sys.argv
39 39 if len(getattr(sys, 'argv', [])) > 0:
40 40 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
41 41 '/../etc/mercurial'))
42 42 path.extend(rcfiles('/etc/mercurial'))
43 43 return path
44 44
45 45 def user_rcpath():
46 46 return [os.path.expanduser('~/.hgrc')]
47 47
48 48 def parse_patch_output(output_line):
49 49 """parses the output produced by patch and returns the filename"""
50 50 pf = output_line[14:]
51 51 if os.sys.platform == 'OpenVMS':
52 52 if pf[0] == '`':
53 53 pf = pf[1:-1] # Remove the quotes
54 54 else:
55 55 if pf.startswith("'") and pf.endswith("'") and " " in pf:
56 56 pf = pf[1:-1] # Remove the quotes
57 57 return pf
58 58
59 59 def sshargs(sshcmd, host, user, port):
60 60 '''Build argument list for ssh'''
61 61 args = user and ("%s@%s" % (user, host)) or host
62 62 return port and ("%s -p %s" % (args, port)) or args
63 63
64 64 def is_exec(f):
65 65 """check whether a file is executable"""
66 66 return (os.lstat(f).st_mode & 0100 != 0)
67 67
68 68 def set_flags(f, l, x):
69 69 s = os.lstat(f).st_mode
70 70 if l:
71 71 if not stat.S_ISLNK(s):
72 72 # switch file to link
73 73 data = open(f).read()
74 74 os.unlink(f)
75 75 try:
76 76 os.symlink(data, f)
77 77 except:
78 78 # failed to make a link, rewrite file
79 79 open(f, "w").write(data)
80 80 # no chmod needed at this point
81 81 return
82 82 if stat.S_ISLNK(s):
83 83 # switch link to file
84 84 data = os.readlink(f)
85 85 os.unlink(f)
86 86 open(f, "w").write(data)
87 87 s = 0666 & ~umask # avoid restatting for chmod
88 88
89 89 sx = s & 0100
90 90 if x and not sx:
91 91 # Turn on +x for every +r bit when making a file executable
92 92 # and obey umask.
93 93 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
94 94 elif not x and sx:
95 95 # Turn off all +x bits
96 96 os.chmod(f, s & 0666)
97 97
98 98 def set_binary(fd):
99 99 pass
100 100
101 101 def pconvert(path):
102 102 return path
103 103
104 104 def localpath(path):
105 105 return path
106 106
107 if sys.platform == 'darwin':
108 def realpath(path):
109 '''
110 Returns the true, canonical file system path equivalent to the given
111 path.
112
113 Equivalent means, in this case, resulting in the same, unique
114 file system link to the path. Every file system entry, whether a file,
115 directory, hard link or symbolic link or special, will have a single
116 path preferred by the system, but may allow multiple, differing path
117 lookups to point to it.
118
119 Most regular UNIX file systems only allow a file system entry to be
120 looked up by its distinct path. Obviously, this does not apply to case
121 insensitive file systems, whether case preserving or not. The most
122 complex issue to deal with is file systems transparently reencoding the
123 path, such as the non-standard Unicode normalisation required for HFS+
124 and HFSX.
125 '''
126 # Constants copied from /usr/include/sys/fcntl.h
127 F_GETPATH = 50
128 O_SYMLINK = 0x200000
129
130 try:
131 fd = os.open(path, O_SYMLINK)
132 except OSError, err:
133 if err.errno is errno.ENOENT:
134 return path
135 raise
136
137 try:
138 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
139 finally:
140 os.close(fd)
141 else:
142 # Fallback to the likely inadequate Python builtin function.
143 realpath = os.path.realpath
144
107 145 def shellquote(s):
108 146 if os.sys.platform == 'OpenVMS':
109 147 return '"%s"' % s
110 148 else:
111 149 return "'%s'" % s.replace("'", "'\\''")
112 150
113 151 def quotecommand(cmd):
114 152 return cmd
115 153
116 154 def popen(command, mode='r'):
117 155 return os.popen(command, mode)
118 156
119 157 def testpid(pid):
120 158 '''return False if pid dead, True if running or not sure'''
121 159 if os.sys.platform == 'OpenVMS':
122 160 return True
123 161 try:
124 162 os.kill(pid, 0)
125 163 return True
126 164 except OSError, inst:
127 165 return inst.errno != errno.ESRCH
128 166
129 167 def explain_exit(code):
130 168 """return a 2-tuple (desc, code) describing a process's status"""
131 169 if os.WIFEXITED(code):
132 170 val = os.WEXITSTATUS(code)
133 171 return _("exited with status %d") % val, val
134 172 elif os.WIFSIGNALED(code):
135 173 val = os.WTERMSIG(code)
136 174 return _("killed by signal %d") % val, val
137 175 elif os.WIFSTOPPED(code):
138 176 val = os.WSTOPSIG(code)
139 177 return _("stopped by signal %d") % val, val
140 178 raise ValueError(_("invalid exit code"))
141 179
142 180 def isowner(st):
143 181 """Return True if the stat object st is from the current user."""
144 182 return st.st_uid == os.getuid()
145 183
146 184 def find_exe(command):
147 185 '''Find executable for command searching like which does.
148 186 If command is a basename then PATH is searched for command.
149 187 PATH isn't searched if command is an absolute or relative path.
150 188 If command isn't found None is returned.'''
151 189 if sys.platform == 'OpenVMS':
152 190 return command
153 191
154 192 def findexisting(executable):
155 193 'Will return executable if existing file'
156 194 if os.path.exists(executable):
157 195 return executable
158 196 return None
159 197
160 198 if os.sep in command:
161 199 return findexisting(command)
162 200
163 201 for path in os.environ.get('PATH', '').split(os.pathsep):
164 202 executable = findexisting(os.path.join(path, command))
165 203 if executable is not None:
166 204 return executable
167 205 return None
168 206
169 207 def set_signal_handler():
170 208 pass
171 209
172 210 def statfiles(files):
173 211 'Stat each file in files and yield stat or None if file does not exist.'
174 212 lstat = os.lstat
175 213 for nf in files:
176 214 try:
177 215 st = lstat(nf)
178 216 except OSError, err:
179 217 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
180 218 raise
181 219 st = None
182 220 yield st
183 221
184 222 def getuser():
185 223 '''return name of current user'''
186 224 return getpass.getuser()
187 225
188 226 def expand_glob(pats):
189 227 '''On Windows, expand the implicit globs in a list of patterns'''
190 228 return list(pats)
191 229
192 230 def username(uid=None):
193 231 """Return the name of the user with the given uid.
194 232
195 233 If uid is None, return the name of the current user."""
196 234
197 235 if uid is None:
198 236 uid = os.getuid()
199 237 try:
200 238 return pwd.getpwuid(uid)[0]
201 239 except KeyError:
202 240 return str(uid)
203 241
204 242 def groupname(gid=None):
205 243 """Return the name of the group with the given gid.
206 244
207 245 If gid is None, return the name of the current group."""
208 246
209 247 if gid is None:
210 248 gid = os.getgid()
211 249 try:
212 250 return grp.getgrgid(gid)[0]
213 251 except KeyError:
214 252 return str(gid)
@@ -1,283 +1,292 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import osutil, error
10 10 import errno, msvcrt, os, re, sys
11 11
12 12 nulldev = 'NUL:'
13 13 umask = 002
14 14
15 15 # wrap osutil.posixfile to provide friendlier exceptions
16 16 def posixfile(name, mode='r', buffering=-1):
17 17 try:
18 18 return osutil.posixfile(name, mode, buffering)
19 19 except WindowsError, err:
20 20 raise IOError(err.errno, err.strerror)
21 21 posixfile.__doc__ = osutil.posixfile.__doc__
22 22
23 23 class winstdout(object):
24 24 '''stdout on windows misbehaves if sent through a pipe'''
25 25
26 26 def __init__(self, fp):
27 27 self.fp = fp
28 28
29 29 def __getattr__(self, key):
30 30 return getattr(self.fp, key)
31 31
32 32 def close(self):
33 33 try:
34 34 self.fp.close()
35 35 except: pass
36 36
37 37 def write(self, s):
38 38 try:
39 39 # This is workaround for "Not enough space" error on
40 40 # writing large size of data to console.
41 41 limit = 16000
42 42 l = len(s)
43 43 start = 0
44 44 self.softspace = 0;
45 45 while start < l:
46 46 end = start + limit
47 47 self.fp.write(s[start:end])
48 48 start = end
49 49 except IOError, inst:
50 50 if inst.errno != 0: raise
51 51 self.close()
52 52 raise IOError(errno.EPIPE, 'Broken pipe')
53 53
54 54 def flush(self):
55 55 try:
56 56 return self.fp.flush()
57 57 except IOError, inst:
58 58 if inst.errno != errno.EINVAL: raise
59 59 self.close()
60 60 raise IOError(errno.EPIPE, 'Broken pipe')
61 61
62 62 sys.stdout = winstdout(sys.stdout)
63 63
64 64 def _is_win_9x():
65 65 '''return true if run on windows 95, 98 or me.'''
66 66 try:
67 67 return sys.getwindowsversion()[3] == 1
68 68 except AttributeError:
69 69 return 'command' in os.environ.get('comspec', '')
70 70
71 71 def openhardlinks():
72 72 return not _is_win_9x() and "win32api" in globals()
73 73
74 74 def system_rcpath():
75 75 try:
76 76 return system_rcpath_win32()
77 77 except:
78 78 return [r'c:\mercurial\mercurial.ini']
79 79
80 80 def user_rcpath():
81 81 '''return os-specific hgrc search path to the user dir'''
82 82 try:
83 83 path = user_rcpath_win32()
84 84 except:
85 85 home = os.path.expanduser('~')
86 86 path = [os.path.join(home, 'mercurial.ini'),
87 87 os.path.join(home, '.hgrc')]
88 88 userprofile = os.environ.get('USERPROFILE')
89 89 if userprofile:
90 90 path.append(os.path.join(userprofile, 'mercurial.ini'))
91 91 path.append(os.path.join(userprofile, '.hgrc'))
92 92 return path
93 93
94 94 def parse_patch_output(output_line):
95 95 """parses the output produced by patch and returns the filename"""
96 96 pf = output_line[14:]
97 97 if pf[0] == '`':
98 98 pf = pf[1:-1] # Remove the quotes
99 99 return pf
100 100
101 101 def sshargs(sshcmd, host, user, port):
102 102 '''Build argument list for ssh or Plink'''
103 103 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
104 104 args = user and ("%s@%s" % (user, host)) or host
105 105 return port and ("%s %s %s" % (args, pflag, port)) or args
106 106
107 107 def testpid(pid):
108 108 '''return False if pid dead, True if running or not known'''
109 109 return True
110 110
111 111 def set_flags(f, l, x):
112 112 pass
113 113
114 114 def set_binary(fd):
115 115 # When run without console, pipes may expose invalid
116 116 # fileno(), usually set to -1.
117 117 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
118 118 msvcrt.setmode(fd.fileno(), os.O_BINARY)
119 119
120 120 def pconvert(path):
121 121 return '/'.join(path.split(os.sep))
122 122
123 123 def localpath(path):
124 124 return path.replace('/', '\\')
125 125
126 126 def normpath(path):
127 127 return pconvert(os.path.normpath(path))
128 128
129 def realpath(path):
130 '''
131 Returns the true, canonical file system path equivalent to the given
132 path.
133 '''
134 # TODO: There may be a more clever way to do this that also handles other,
135 # less common file systems.
136 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
137
129 138 def samestat(s1, s2):
130 139 return False
131 140
132 141 # A sequence of backslashes is special iff it precedes a double quote:
133 142 # - if there's an even number of backslashes, the double quote is not
134 143 # quoted (i.e. it ends the quoted region)
135 144 # - if there's an odd number of backslashes, the double quote is quoted
136 145 # - in both cases, every pair of backslashes is unquoted into a single
137 146 # backslash
138 147 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
139 148 # So, to quote a string, we must surround it in double quotes, double
140 149 # the number of backslashes that preceed double quotes and add another
141 150 # backslash before every double quote (being careful with the double
142 151 # quote we've appended to the end)
143 152 _quotere = None
144 153 def shellquote(s):
145 154 global _quotere
146 155 if _quotere is None:
147 156 _quotere = re.compile(r'(\\*)("|\\$)')
148 157 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
149 158
150 159 def quotecommand(cmd):
151 160 """Build a command string suitable for os.popen* calls."""
152 161 # The extra quotes are needed because popen* runs the command
153 162 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
154 163 return '"' + cmd + '"'
155 164
156 165 def popen(command, mode='r'):
157 166 # Work around "popen spawned process may not write to stdout
158 167 # under windows"
159 168 # http://bugs.python.org/issue1366
160 169 command += " 2> %s" % nulldev
161 170 return os.popen(quotecommand(command), mode)
162 171
163 172 def explain_exit(code):
164 173 return _("exited with status %d") % code, code
165 174
166 175 # if you change this stub into a real check, please try to implement the
167 176 # username and groupname functions above, too.
168 177 def isowner(st):
169 178 return True
170 179
171 180 def find_exe(command):
172 181 '''Find executable for command searching like cmd.exe does.
173 182 If command is a basename then PATH is searched for command.
174 183 PATH isn't searched if command is an absolute or relative path.
175 184 An extension from PATHEXT is found and added if not present.
176 185 If command isn't found None is returned.'''
177 186 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
178 187 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
179 188 if os.path.splitext(command)[1].lower() in pathexts:
180 189 pathexts = ['']
181 190
182 191 def findexisting(pathcommand):
183 192 'Will append extension (if needed) and return existing file'
184 193 for ext in pathexts:
185 194 executable = pathcommand + ext
186 195 if os.path.exists(executable):
187 196 return executable
188 197 return None
189 198
190 199 if os.sep in command:
191 200 return findexisting(command)
192 201
193 202 for path in os.environ.get('PATH', '').split(os.pathsep):
194 203 executable = findexisting(os.path.join(path, command))
195 204 if executable is not None:
196 205 return executable
197 206 return None
198 207
199 208 def set_signal_handler():
200 209 try:
201 210 set_signal_handler_win32()
202 211 except NameError:
203 212 pass
204 213
205 214 def statfiles(files):
206 215 '''Stat each file in files and yield stat or None if file does not exist.
207 216 Cluster and cache stat per directory to minimize number of OS stat calls.'''
208 217 ncase = os.path.normcase
209 218 sep = os.sep
210 219 dircache = {} # dirname -> filename -> status | None if file does not exist
211 220 for nf in files:
212 221 nf = ncase(nf)
213 222 dir, base = os.path.split(nf)
214 223 if not dir:
215 224 dir = '.'
216 225 cache = dircache.get(dir, None)
217 226 if cache is None:
218 227 try:
219 228 dmap = dict([(ncase(n), s)
220 229 for n, k, s in osutil.listdir(dir, True)])
221 230 except OSError, err:
222 231 # handle directory not found in Python version prior to 2.5
223 232 # Python <= 2.4 returns native Windows code 3 in errno
224 233 # Python >= 2.5 returns ENOENT and adds winerror field
225 234 # EINVAL is raised if dir is not a directory.
226 235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
227 236 errno.ENOTDIR):
228 237 raise
229 238 dmap = {}
230 239 cache = dircache.setdefault(dir, dmap)
231 240 yield cache.get(base, None)
232 241
233 242 def getuser():
234 243 '''return name of current user'''
235 244 raise error.Abort(_('user name not available - set USERNAME '
236 245 'environment variable'))
237 246
238 247 def username(uid=None):
239 248 """Return the name of the user with the given uid.
240 249
241 250 If uid is None, return the name of the current user."""
242 251 return None
243 252
244 253 def groupname(gid=None):
245 254 """Return the name of the group with the given gid.
246 255
247 256 If gid is None, return the name of the current group."""
248 257 return None
249 258
250 259 def _removedirs(name):
251 260 """special version of os.removedirs that does not remove symlinked
252 261 directories or junction points if they actually contain files"""
253 262 if osutil.listdir(name):
254 263 return
255 264 os.rmdir(name)
256 265 head, tail = os.path.split(name)
257 266 if not tail:
258 267 head, tail = os.path.split(head)
259 268 while head and tail:
260 269 try:
261 270 if osutil.listdir(name):
262 271 return
263 272 os.rmdir(head)
264 273 except:
265 274 break
266 275 head, tail = os.path.split(head)
267 276
268 277 def unlink(f):
269 278 """unlink and remove the directory if it is empty"""
270 279 os.unlink(f)
271 280 # try removing directories that might now be empty
272 281 try:
273 282 _removedirs(os.path.dirname(f))
274 283 except OSError:
275 284 pass
276 285
277 286 try:
278 287 # override functions with win32 versions if possible
279 288 from win32 import *
280 289 except ImportError:
281 290 pass
282 291
283 292 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now