@@ -403,55 +403,66 @@ def realpath(filename, *, strict=False):
403
403
"""Return the canonical path of the specified filename, eliminating any
404
404
symbolic links encountered in the path."""
405
405
filename = os .fspath (filename )
406
- path , ok = _joinrealpath (filename [:0 ], filename , strict , {})
407
- return abspath (path )
408
-
409
- # Join two paths, normalizing and eliminating any symbolic links
410
- # encountered in the second path.
411
- # Two leading slashes are replaced by a single slash.
412
- def _joinrealpath (path , rest , strict , seen ):
413
- if isinstance (path , bytes ):
406
+ if isinstance (filename , bytes ):
414
407
sep = b'/'
415
408
curdir = b'.'
416
409
pardir = b'..'
410
+ getcwd = os .getcwdb
417
411
else :
418
412
sep = '/'
419
413
curdir = '.'
420
414
pardir = '..'
415
+ getcwd = os .getcwd
416
+
417
+ # The stack of unresolved path parts. When popped, a special value of None
418
+ # indicates that a symlink target has been resolved, and that the original
419
+ # symlink path can be retrieved by popping again. The [::-1] slice is a
420
+ # very fast way of spelling list(reversed(...)).
421
+ rest = filename .split (sep )[::- 1 ]
422
+
423
+ # The resolved path, which is absolute throughout this function.
424
+ # Note: getcwd() returns a normalized and symlink-free path.
425
+ path = sep if filename .startswith (sep ) else getcwd ()
421
426
422
- if rest .startswith (sep ):
423
- rest = rest [1 :]
424
- path = sep
427
+ # Mapping from symlink paths to *fully resolved* symlink targets. If a
428
+ # symlink is encountered but not yet resolved, the value is None. This is
429
+ # used both to detect symlink loops and to speed up repeated traversals of
430
+ # the same links.
431
+ seen = {}
432
+
433
+ # Whether we're calling lstat() and readlink() to resolve symlinks. If we
434
+ # encounter an OSError for a symlink loop in non-strict mode, this is
435
+ # switched off.
436
+ querying = True
425
437
426
438
while rest :
427
- name , _ , rest = rest .partition (sep )
439
+ name = rest .pop ()
440
+ if name is None :
441
+ # resolved symlink target
442
+ seen [rest .pop ()] = path
443
+ continue
428
444
if not name or name == curdir :
429
445
# current dir
430
446
continue
431
447
if name == pardir :
432
448
# parent dir
433
- if path :
434
- parent , name = split (path )
435
- if name == pardir :
436
- # ../..
437
- path = join (path , pardir )
438
- else :
439
- # foo/bar/.. -> foo
440
- path = parent
441
- else :
442
- # ..
443
- path = pardir
449
+ path = path [:path .rindex (sep )] or sep
450
+ continue
451
+ if path == sep :
452
+ newpath = path + name
453
+ else :
454
+ newpath = path + sep + name
455
+ if not querying :
456
+ path = newpath
444
457
continue
445
- newpath = join (path , name )
446
458
try :
447
459
st = os .lstat (newpath )
460
+ if not stat .S_ISLNK (st .st_mode ):
461
+ path = newpath
462
+ continue
448
463
except OSError :
449
464
if strict :
450
465
raise
451
- is_link = False
452
- else :
453
- is_link = stat .S_ISLNK (st .st_mode )
454
- if not is_link :
455
466
path = newpath
456
467
continue
457
468
# Resolve the symbolic link
@@ -467,14 +478,23 @@ def _joinrealpath(path, rest, strict, seen):
467
478
os .stat (newpath )
468
479
else :
469
480
# Return already resolved part + rest of the path unchanged.
470
- return join (newpath , rest ), False
481
+ path = newpath
482
+ querying = False
483
+ continue
471
484
seen [newpath ] = None # not resolved symlink
472
- path , ok = _joinrealpath (path , os .readlink (newpath ), strict , seen )
473
- if not ok :
474
- return join (path , rest ), False
475
- seen [newpath ] = path # resolved symlink
485
+ target = os .readlink (newpath )
486
+ if target .startswith (sep ):
487
+ # Symlink target is absolute; reset resolved path.
488
+ path = sep
489
+ # Push the symlink path onto the stack, and signal its specialness by
490
+ # also pushing None. When these entries are popped, we'll record the
491
+ # fully-resolved symlink target in the 'seen' mapping.
492
+ rest .append (newpath )
493
+ rest .append (None )
494
+ # Push the unresolved symlink target parts onto the stack.
495
+ rest .extend (target .split (sep )[::- 1 ])
476
496
477
- return path , True
497
+ return path
478
498
479
499
480
500
supports_unicode_filenames = (sys .platform == 'darwin' )
0 commit comments