@@ -403,55 +403,66 @@ def realpath(filename, *, strict=False):
403403"""Return the canonical path of the specified filename, eliminating any
404404symbolic links encountered in the path."""
405405filename = 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 ):
414407sep = b'/'
415408curdir = b'.'
416409pardir = b'..'
410+ getcwd = os .getcwdb
417411else :
418412sep = '/'
419413curdir = '.'
420414pardir = '..'
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 ()
421426
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
425437
426438while 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
428444if not name or name == curdir :
429445# current dir
430446continue
431447if name == pardir :
432448# 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
444457continue
445- newpath = join (path ,name )
446458try :
447459st = os .lstat (newpath )
460+ if not stat .S_ISLNK (st .st_mode ):
461+ path = newpath
462+ continue
448463except OSError :
449464if strict :
450465raise
451- is_link = False
452- else :
453- is_link = stat .S_ISLNK (st .st_mode )
454- if not is_link :
455466path = newpath
456467continue
457468# Resolve the symbolic link
@@ -467,14 +478,23 @@ def _joinrealpath(path, rest, strict, seen):
467478os .stat (newpath )
468479else :
469480# Return already resolved part + rest of the path unchanged.
470- return join (newpath ,rest ),False
481+ path = newpath
482+ querying = False
483+ continue
471484seen [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 ])
476496
477- return path , True
497+ return path
478498
479499
480500supports_unicode_filenames = (sys .platform == 'darwin' )