##// END OF EJS Templates
posix: fix split() for the case where the path is at the root of the filesystem...
Remy Blank -
r18288:0d5a22f7 default
parent child Browse files
Show More
@@ -1,529 +1,541 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 or any later version.
7 7
8 8 from i18n import _
9 9 import encoding
10 10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
11 11
12 12 posixfile = open
13 13 normpath = os.path.normpath
14 14 samestat = os.path.samestat
15 15 oslink = os.link
16 16 unlink = os.unlink
17 17 rename = os.rename
18 18 expandglobs = False
19 19
20 20 umask = os.umask(0)
21 21 os.umask(umask)
22 22
23 23 def split(p):
24 '''Same as os.path.split, but faster'''
24 '''Same as posixpath.split, but faster
25
26 >>> import posixpath
27 >>> for f in ['/absolute/path/to/file',
28 ... 'relative/path/to/file',
29 ... 'file_alone',
30 ... 'path/to/directory/',
31 ... '/multiple/path//separators',
32 ... '/file_at_root',
33 ... '///multiple_leading_separators_at_root',
34 ... '']:
35 ... assert split(f) == posixpath.split(f), f
36 '''
25 37 ht = p.rsplit('/', 1)
26 38 if len(ht) == 1:
27 39 return '', p
28 40 nh = ht[0].rstrip('/')
29 41 if nh:
30 42 return nh, ht[1]
31 return ht
43 return ht[0] + '/', ht[1]
32 44
33 45 def openhardlinks():
34 46 '''return true if it is safe to hold open file handles to hardlinks'''
35 47 return True
36 48
37 49 def nlinks(name):
38 50 '''return number of hardlinks for the given file'''
39 51 return os.lstat(name).st_nlink
40 52
41 53 def parsepatchoutput(output_line):
42 54 """parses the output produced by patch and returns the filename"""
43 55 pf = output_line[14:]
44 56 if os.sys.platform == 'OpenVMS':
45 57 if pf[0] == '`':
46 58 pf = pf[1:-1] # Remove the quotes
47 59 else:
48 60 if pf.startswith("'") and pf.endswith("'") and " " in pf:
49 61 pf = pf[1:-1] # Remove the quotes
50 62 return pf
51 63
52 64 def sshargs(sshcmd, host, user, port):
53 65 '''Build argument list for ssh'''
54 66 args = user and ("%s@%s" % (user, host)) or host
55 67 return port and ("%s -p %s" % (args, port)) or args
56 68
57 69 def isexec(f):
58 70 """check whether a file is executable"""
59 71 return (os.lstat(f).st_mode & 0100 != 0)
60 72
61 73 def setflags(f, l, x):
62 74 s = os.lstat(f).st_mode
63 75 if l:
64 76 if not stat.S_ISLNK(s):
65 77 # switch file to link
66 78 fp = open(f)
67 79 data = fp.read()
68 80 fp.close()
69 81 os.unlink(f)
70 82 try:
71 83 os.symlink(data, f)
72 84 except OSError:
73 85 # failed to make a link, rewrite file
74 86 fp = open(f, "w")
75 87 fp.write(data)
76 88 fp.close()
77 89 # no chmod needed at this point
78 90 return
79 91 if stat.S_ISLNK(s):
80 92 # switch link to file
81 93 data = os.readlink(f)
82 94 os.unlink(f)
83 95 fp = open(f, "w")
84 96 fp.write(data)
85 97 fp.close()
86 98 s = 0666 & ~umask # avoid restatting for chmod
87 99
88 100 sx = s & 0100
89 101 if x and not sx:
90 102 # Turn on +x for every +r bit when making a file executable
91 103 # and obey umask.
92 104 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
93 105 elif not x and sx:
94 106 # Turn off all +x bits
95 107 os.chmod(f, s & 0666)
96 108
97 109 def copymode(src, dst, mode=None):
98 110 '''Copy the file mode from the file at path src to dst.
99 111 If src doesn't exist, we're using mode instead. If mode is None, we're
100 112 using umask.'''
101 113 try:
102 114 st_mode = os.lstat(src).st_mode & 0777
103 115 except OSError, inst:
104 116 if inst.errno != errno.ENOENT:
105 117 raise
106 118 st_mode = mode
107 119 if st_mode is None:
108 120 st_mode = ~umask
109 121 st_mode &= 0666
110 122 os.chmod(dst, st_mode)
111 123
112 124 def checkexec(path):
113 125 """
114 126 Check whether the given path is on a filesystem with UNIX-like exec flags
115 127
116 128 Requires a directory (like /foo/.hg)
117 129 """
118 130
119 131 # VFAT on some Linux versions can flip mode but it doesn't persist
120 132 # a FS remount. Frequently we can detect it if files are created
121 133 # with exec bit on.
122 134
123 135 try:
124 136 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
125 137 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
126 138 try:
127 139 os.close(fh)
128 140 m = os.stat(fn).st_mode & 0777
129 141 new_file_has_exec = m & EXECFLAGS
130 142 os.chmod(fn, m ^ EXECFLAGS)
131 143 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
132 144 finally:
133 145 os.unlink(fn)
134 146 except (IOError, OSError):
135 147 # we don't care, the user probably won't be able to commit anyway
136 148 return False
137 149 return not (new_file_has_exec or exec_flags_cannot_flip)
138 150
139 151 def checklink(path):
140 152 """check whether the given path is on a symlink-capable filesystem"""
141 153 # mktemp is not racy because symlink creation will fail if the
142 154 # file already exists
143 155 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
144 156 try:
145 157 os.symlink(".", name)
146 158 os.unlink(name)
147 159 return True
148 160 except (OSError, AttributeError):
149 161 return False
150 162
151 163 def checkosfilename(path):
152 164 '''Check that the base-relative path is a valid filename on this platform.
153 165 Returns None if the path is ok, or a UI string describing the problem.'''
154 166 pass # on posix platforms, every path is ok
155 167
156 168 def setbinary(fd):
157 169 pass
158 170
159 171 def pconvert(path):
160 172 return path
161 173
162 174 def localpath(path):
163 175 return path
164 176
165 177 def samefile(fpath1, fpath2):
166 178 """Returns whether path1 and path2 refer to the same file. This is only
167 179 guaranteed to work for files, not directories."""
168 180 return os.path.samefile(fpath1, fpath2)
169 181
170 182 def samedevice(fpath1, fpath2):
171 183 """Returns whether fpath1 and fpath2 are on the same device. This is only
172 184 guaranteed to work for files, not directories."""
173 185 st1 = os.lstat(fpath1)
174 186 st2 = os.lstat(fpath2)
175 187 return st1.st_dev == st2.st_dev
176 188
177 189 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
178 190 def normcase(path):
179 191 return path.lower()
180 192
181 193 if sys.platform == 'darwin':
182 194 import fcntl # only needed on darwin, missing on jython
183 195
184 196 def normcase(path):
185 197 try:
186 198 u = path.decode('utf-8')
187 199 except UnicodeDecodeError:
188 200 # percent-encode any characters that don't round-trip
189 201 p2 = path.decode('utf-8', 'ignore').encode('utf-8')
190 202 s = ""
191 203 pos = 0
192 204 for c in path:
193 205 if p2[pos:pos + 1] == c:
194 206 s += c
195 207 pos += 1
196 208 else:
197 209 s += "%%%02X" % ord(c)
198 210 u = s.decode('utf-8')
199 211
200 212 # Decompose then lowercase (HFS+ technote specifies lower)
201 213 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
202 214
203 215 def realpath(path):
204 216 '''
205 217 Returns the true, canonical file system path equivalent to the given
206 218 path.
207 219
208 220 Equivalent means, in this case, resulting in the same, unique
209 221 file system link to the path. Every file system entry, whether a file,
210 222 directory, hard link or symbolic link or special, will have a single
211 223 path preferred by the system, but may allow multiple, differing path
212 224 lookups to point to it.
213 225
214 226 Most regular UNIX file systems only allow a file system entry to be
215 227 looked up by its distinct path. Obviously, this does not apply to case
216 228 insensitive file systems, whether case preserving or not. The most
217 229 complex issue to deal with is file systems transparently reencoding the
218 230 path, such as the non-standard Unicode normalisation required for HFS+
219 231 and HFSX.
220 232 '''
221 233 # Constants copied from /usr/include/sys/fcntl.h
222 234 F_GETPATH = 50
223 235 O_SYMLINK = 0x200000
224 236
225 237 try:
226 238 fd = os.open(path, O_SYMLINK)
227 239 except OSError, err:
228 240 if err.errno == errno.ENOENT:
229 241 return path
230 242 raise
231 243
232 244 try:
233 245 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
234 246 finally:
235 247 os.close(fd)
236 248 elif sys.version_info < (2, 4, 2, 'final'):
237 249 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
238 250 # didn't resolve symlinks that were the first component of the path.)
239 251 def realpath(path):
240 252 if os.path.isabs(path):
241 253 return os.path.realpath(path)
242 254 else:
243 255 return os.path.realpath('./' + path)
244 256 else:
245 257 # Fallback to the likely inadequate Python builtin function.
246 258 realpath = os.path.realpath
247 259
248 260 if sys.platform == 'cygwin':
249 261 # workaround for cygwin, in which mount point part of path is
250 262 # treated as case sensitive, even though underlying NTFS is case
251 263 # insensitive.
252 264
253 265 # default mount points
254 266 cygwinmountpoints = sorted([
255 267 "/usr/bin",
256 268 "/usr/lib",
257 269 "/cygdrive",
258 270 ], reverse=True)
259 271
260 272 # use upper-ing as normcase as same as NTFS workaround
261 273 def normcase(path):
262 274 pathlen = len(path)
263 275 if (pathlen == 0) or (path[0] != os.sep):
264 276 # treat as relative
265 277 return encoding.upper(path)
266 278
267 279 # to preserve case of mountpoint part
268 280 for mp in cygwinmountpoints:
269 281 if not path.startswith(mp):
270 282 continue
271 283
272 284 mplen = len(mp)
273 285 if mplen == pathlen: # mount point itself
274 286 return mp
275 287 if path[mplen] == os.sep:
276 288 return mp + encoding.upper(path[mplen:])
277 289
278 290 return encoding.upper(path)
279 291
280 292 # Cygwin translates native ACLs to POSIX permissions,
281 293 # but these translations are not supported by native
282 294 # tools, so the exec bit tends to be set erroneously.
283 295 # Therefore, disable executable bit access on Cygwin.
284 296 def checkexec(path):
285 297 return False
286 298
287 299 # Similarly, Cygwin's symlink emulation is likely to create
288 300 # problems when Mercurial is used from both Cygwin and native
289 301 # Windows, with other native tools, or on shared volumes
290 302 def checklink(path):
291 303 return False
292 304
293 305 def shellquote(s):
294 306 if os.sys.platform == 'OpenVMS':
295 307 return '"%s"' % s
296 308 else:
297 309 return "'%s'" % s.replace("'", "'\\''")
298 310
299 311 def quotecommand(cmd):
300 312 return cmd
301 313
302 314 def popen(command, mode='r'):
303 315 return os.popen(command, mode)
304 316
305 317 def testpid(pid):
306 318 '''return False if pid dead, True if running or not sure'''
307 319 if os.sys.platform == 'OpenVMS':
308 320 return True
309 321 try:
310 322 os.kill(pid, 0)
311 323 return True
312 324 except OSError, inst:
313 325 return inst.errno != errno.ESRCH
314 326
315 327 def explainexit(code):
316 328 """return a 2-tuple (desc, code) describing a subprocess status
317 329 (codes from kill are negative - not os.system/wait encoding)"""
318 330 if code >= 0:
319 331 return _("exited with status %d") % code, code
320 332 return _("killed by signal %d") % -code, -code
321 333
322 334 def isowner(st):
323 335 """Return True if the stat object st is from the current user."""
324 336 return st.st_uid == os.getuid()
325 337
326 338 def findexe(command):
327 339 '''Find executable for command searching like which does.
328 340 If command is a basename then PATH is searched for command.
329 341 PATH isn't searched if command is an absolute or relative path.
330 342 If command isn't found None is returned.'''
331 343 if sys.platform == 'OpenVMS':
332 344 return command
333 345
334 346 def findexisting(executable):
335 347 'Will return executable if existing file'
336 348 if os.path.isfile(executable) and os.access(executable, os.X_OK):
337 349 return executable
338 350 return None
339 351
340 352 if os.sep in command:
341 353 return findexisting(command)
342 354
343 355 if sys.platform == 'plan9':
344 356 return findexisting(os.path.join('/bin', command))
345 357
346 358 for path in os.environ.get('PATH', '').split(os.pathsep):
347 359 executable = findexisting(os.path.join(path, command))
348 360 if executable is not None:
349 361 return executable
350 362 return None
351 363
352 364 def setsignalhandler():
353 365 pass
354 366
355 367 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
356 368
357 369 def statfiles(files):
358 370 '''Stat each file in files. Yield each stat, or None if a file does not
359 371 exist or has a type we don't care about.'''
360 372 lstat = os.lstat
361 373 getkind = stat.S_IFMT
362 374 for nf in files:
363 375 try:
364 376 st = lstat(nf)
365 377 if getkind(st.st_mode) not in _wantedkinds:
366 378 st = None
367 379 except OSError, err:
368 380 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
369 381 raise
370 382 st = None
371 383 yield st
372 384
373 385 def getuser():
374 386 '''return name of current user'''
375 387 return getpass.getuser()
376 388
377 389 def username(uid=None):
378 390 """Return the name of the user with the given uid.
379 391
380 392 If uid is None, return the name of the current user."""
381 393
382 394 if uid is None:
383 395 uid = os.getuid()
384 396 try:
385 397 return pwd.getpwuid(uid)[0]
386 398 except KeyError:
387 399 return str(uid)
388 400
389 401 def groupname(gid=None):
390 402 """Return the name of the group with the given gid.
391 403
392 404 If gid is None, return the name of the current group."""
393 405
394 406 if gid is None:
395 407 gid = os.getgid()
396 408 try:
397 409 return grp.getgrgid(gid)[0]
398 410 except KeyError:
399 411 return str(gid)
400 412
401 413 def groupmembers(name):
402 414 """Return the list of members of the group with the given
403 415 name, KeyError if the group does not exist.
404 416 """
405 417 return list(grp.getgrnam(name).gr_mem)
406 418
407 419 def spawndetached(args):
408 420 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
409 421 args[0], args)
410 422
411 423 def gethgcmd():
412 424 return sys.argv[:1]
413 425
414 426 def termwidth():
415 427 try:
416 428 import termios, array, fcntl
417 429 for dev in (sys.stderr, sys.stdout, sys.stdin):
418 430 try:
419 431 try:
420 432 fd = dev.fileno()
421 433 except AttributeError:
422 434 continue
423 435 if not os.isatty(fd):
424 436 continue
425 437 try:
426 438 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
427 439 width = array.array('h', arri)[1]
428 440 if width > 0:
429 441 return width
430 442 except AttributeError:
431 443 pass
432 444 except ValueError:
433 445 pass
434 446 except IOError, e:
435 447 if e[0] == errno.EINVAL:
436 448 pass
437 449 else:
438 450 raise
439 451 except ImportError:
440 452 pass
441 453 return 80
442 454
443 455 def makedir(path, notindexed):
444 456 os.mkdir(path)
445 457
446 458 def unlinkpath(f, ignoremissing=False):
447 459 """unlink and remove the directory if it is empty"""
448 460 try:
449 461 os.unlink(f)
450 462 except OSError, e:
451 463 if not (ignoremissing and e.errno == errno.ENOENT):
452 464 raise
453 465 # try removing directories that might now be empty
454 466 try:
455 467 os.removedirs(os.path.dirname(f))
456 468 except OSError:
457 469 pass
458 470
459 471 def lookupreg(key, name=None, scope=None):
460 472 return None
461 473
462 474 def hidewindow():
463 475 """Hide current shell window.
464 476
465 477 Used to hide the window opened when starting asynchronous
466 478 child process under Windows, unneeded on other systems.
467 479 """
468 480 pass
469 481
470 482 class cachestat(object):
471 483 def __init__(self, path):
472 484 self.stat = os.stat(path)
473 485
474 486 def cacheable(self):
475 487 return bool(self.stat.st_ino)
476 488
477 489 __hash__ = object.__hash__
478 490
479 491 def __eq__(self, other):
480 492 try:
481 493 return self.stat == other.stat
482 494 except AttributeError:
483 495 return False
484 496
485 497 def __ne__(self, other):
486 498 return not self == other
487 499
488 500 def executablepath():
489 501 return None # available on Windows only
490 502
491 503 class unixdomainserver(socket.socket):
492 504 def __init__(self, join, subsystem):
493 505 '''Create a unix domain socket with the given prefix.'''
494 506 super(unixdomainserver, self).__init__(socket.AF_UNIX)
495 507 sockname = subsystem + '.sock'
496 508 self.realpath = self.path = join(sockname)
497 509 if os.path.islink(self.path):
498 510 if os.path.exists(self.path):
499 511 self.realpath = os.readlink(self.path)
500 512 else:
501 513 os.unlink(self.path)
502 514 try:
503 515 self.bind(self.realpath)
504 516 except socket.error, err:
505 517 if err.args[0] == 'AF_UNIX path too long':
506 518 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
507 519 self.realpath = os.path.join(tmpdir, sockname)
508 520 try:
509 521 self.bind(self.realpath)
510 522 os.symlink(self.realpath, self.path)
511 523 except (OSError, socket.error):
512 524 self.cleanup()
513 525 raise
514 526 else:
515 527 raise
516 528 self.listen(5)
517 529
518 530 def cleanup(self):
519 531 def okayifmissing(f, path):
520 532 try:
521 533 f(path)
522 534 except OSError, err:
523 535 if err.errno != errno.ENOENT:
524 536 raise
525 537
526 538 okayifmissing(os.unlink, self.path)
527 539 if self.realpath != self.path:
528 540 okayifmissing(os.unlink, self.realpath)
529 541 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
@@ -1,44 +1,46 b''
1 1 # this is hack to make sure no escape characters are inserted into the output
2 2 import os
3 3 if 'TERM' in os.environ:
4 4 del os.environ['TERM']
5 5 import doctest
6 6
7 7 import mercurial.util
8 8 doctest.testmod(mercurial.util)
9 # Only run doctests for the current platform
10 doctest.testmod(mercurial.util.platform)
9 11
10 12 import mercurial.changelog
11 13 doctest.testmod(mercurial.changelog)
12 14
13 15 import mercurial.dagparser
14 16 doctest.testmod(mercurial.dagparser, optionflags=doctest.NORMALIZE_WHITESPACE)
15 17
16 18 import mercurial.match
17 19 doctest.testmod(mercurial.match)
18 20
19 21 import mercurial.store
20 22 doctest.testmod(mercurial.store)
21 23
22 24 import mercurial.ui
23 25 doctest.testmod(mercurial.ui)
24 26
25 27 import mercurial.url
26 28 doctest.testmod(mercurial.url)
27 29
28 30 import mercurial.encoding
29 31 doctest.testmod(mercurial.encoding)
30 32
31 33 import mercurial.hgweb.hgwebdir_mod
32 34 doctest.testmod(mercurial.hgweb.hgwebdir_mod)
33 35
34 36 import hgext.convert.cvsps
35 37 doctest.testmod(hgext.convert.cvsps)
36 38
37 39 import mercurial.revset
38 40 doctest.testmod(mercurial.revset)
39 41
40 42 import mercurial.minirst
41 43 doctest.testmod(mercurial.minirst)
42 44
43 45 import mercurial.templatefilters
44 46 doctest.testmod(mercurial.templatefilters)
General Comments 0
You need to be logged in to leave comments. Login now