diff -r e63a9695a0d4 Lib/pathlib.py --- a/Lib/pathlib.py Fri Dec 06 17:46:22 2013 -0800 +++ b/Lib/pathlib.py Sat Dec 07 16:50:00 2013 +0200 @@ -254,42 +254,45 @@ def resolve(self, path): sep = self.sep - def split(p): - return [x for x in p.split(sep) if x] - def absparts(p): - # Our own abspath(), since the posixpath one makes - # the mistake of "normalizing" the path without resolving the - # symlinks first. - if not p.startswith(sep): - return split(os.getcwd()) + split(p) - else: - return split(p) - parts = absparts(str(path))[::-1] accessor = path._accessor - resolved = cur = "" - symlinks = {} - while parts: - part = parts.pop() - cur = resolved + sep + part - if cur in symlinks and symlinks[cur] <= len(parts): - # We've already seen the symlink and there's not less - # work to do than the last time. - raise RuntimeError("Symlink loop from %r" % cur) - try: - target = accessor.readlink(cur) - except OSError as e: - if e.errno != EINVAL: - raise - # Not a symlink - resolved = cur - else: - # Take note of remaining work from this symlink - symlinks[cur] = len(parts) - if target.startswith(sep): - # Symlink points to absolute path - resolved = "" - parts.extend(split(target)[::-1]) - return resolved or sep + seen = {} + def _resolve(path, rest): + if rest.startswith(sep): + path = '' + + for name in rest.split(sep): + if not name or name == '.': + # current dir + continue + if name == '..': + # parent dir + path, _, _ = path.rpartition(sep) + continue + newpath = path + sep + name + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + raise RuntimeError("Symlink loop from %r" % newpath) + # Resolve the symbolic link + try: + target = accessor.readlink(newpath) + except OSError as e: + if e.errno != EINVAL: + raise + # Not a symlink + path = newpath + else: + seen[newpath] = None # not resolved symlink + path = _resolve(path, target) + seen[newpath] = path # resolved symlink + + return path + base = '' if path.is_absolute() else os.getcwd() + return _resolve(base, str(path)) or sep def is_reserved(self, parts): return False diff -r e63a9695a0d4 Lib/test/test_pathlib.py --- a/Lib/test/test_pathlib.py Fri Dec 06 17:46:22 2013 -0800 +++ b/Lib/test/test_pathlib.py Sat Dec 07 16:50:00 2013 +0200 @@ -1620,6 +1620,211 @@ # 'bin' self.assertIs(p.parts[2], q.parts[3]) + @with_symlinks + def test_recursion(self): + old_path = os.getcwd() + self.dirlink(os.path.join('link0', 'link0'), join('link1')) + self.dirlink(os.path.join('link1', 'link1'), join('link2')) + self.dirlink(os.path.join('link2', 'link2'), join('link3')) + self.dirlink(os.path.join('link3', 'link3'), join('link4')) + if os.name != 'nt': + self.dirlink(os.path.join('link4', 'link4'), join('link5')) + self.dirlink(os.path.join('link5', 'link5'), join('link6')) + self.dirlink(os.path.join('link6', 'link6'), join('link7')) + self.dirlink(os.path.join('link7', 'link7'), join('link8')) + self.dirlink(os.path.join('link8', 'link8'), join('link9')) + self.dirlink(os.path.join('link9', 'link9'), join('link10')) + self.dirlink(os.path.join('link10', 'link10'), join('link11')) + self.dirlink(os.path.join('link11', 'link11'), join('link12')) + self.dirlink(os.path.join('link12', 'link12'), join('link13')) + self.dirlink(os.path.join('link13', 'link13'), join('link14')) + self.dirlink(os.path.join('link14', 'link14'), join('link15')) + self.dirlink(os.path.join('link15', 'link15'), join('link16')) + self.dirlink(os.path.join('link16', 'link16'), join('link17')) + self.dirlink(os.path.join('link17', 'link17'), join('link18')) + self.dirlink(os.path.join('link18', 'link18'), join('link19')) + self.dirlink(os.path.join('link19', 'link19'), join('link20')) + self.dirlink(os.path.join('link20', 'link20'), join('link21')) + self.dirlink(os.path.join('link21', 'link21'), join('link22')) + self.dirlink(os.path.join('link22', 'link22'), join('link23')) + self.dirlink(os.path.join('link23', 'link23'), join('link24')) + self.dirlink(os.path.join('link24', 'link24'), join('link25')) + self.dirlink(os.path.join('link25', 'link25'), join('link26')) + self.dirlink(os.path.join('link26', 'link26'), join('link27')) + self.dirlink(os.path.join('link27', 'link27'), join('link28')) + self.dirlink(os.path.join('link28', 'link28'), join('link29')) + self.dirlink(os.path.join('link29', 'link29'), join('link30')) + self.dirlink(os.path.join('link30', 'link30'), join('link31')) + self.dirlink(os.path.join('link31', 'link31'), join('link32')) + self.dirlink(os.path.join('link32', 'link32'), join('link33')) + self.dirlink(os.path.join('link33', 'link33'), join('link34')) + self.dirlink(os.path.join('link34', 'link34'), join('link35')) + self.dirlink(os.path.join('link35', 'link35'), join('link36')) + self.dirlink(os.path.join('link36', 'link36'), join('link37')) + self.dirlink(os.path.join('link37', 'link37'), join('link38')) + self.dirlink(os.path.join('link38', 'link38'), join('link39')) + self.dirlink(os.path.join('link39', 'link39'), join('link40')) + self.dirlink(os.path.join('link40', 'link40'), join('link41')) + self.dirlink(os.path.join('link41', 'link41'), join('link42')) + self.dirlink(os.path.join('link42', 'link42'), join('link43')) + self.dirlink(os.path.join('link43', 'link43'), join('link44')) + self.dirlink(os.path.join('link44', 'link44'), join('link45')) + self.dirlink(os.path.join('link45', 'link45'), join('link46')) + self.dirlink(os.path.join('link46', 'link46'), join('link47')) + self.dirlink(os.path.join('link47', 'link47'), join('link48')) + self.dirlink(os.path.join('link48', 'link48'), join('link49')) + self.dirlink(os.path.join('link49', 'link49'), join('link50')) + self.dirlink(os.path.join('link50', 'link50'), join('link51')) + self.dirlink(os.path.join('link51', 'link51'), join('link52')) + self.dirlink(os.path.join('link52', 'link52'), join('link53')) + self.dirlink(os.path.join('link53', 'link53'), join('link54')) + self.dirlink(os.path.join('link54', 'link54'), join('link55')) + self.dirlink(os.path.join('link55', 'link55'), join('link56')) + self.dirlink(os.path.join('link56', 'link56'), join('link57')) + self.dirlink(os.path.join('link57', 'link57'), join('link58')) + self.dirlink(os.path.join('link58', 'link58'), join('link59')) + self.dirlink(os.path.join('link59', 'link59'), join('link60')) + self.dirlink(os.path.join('link60', 'link60'), join('link61')) + self.dirlink(os.path.join('link61', 'link61'), join('link62')) + self.dirlink(os.path.join('link62', 'link62'), join('link63')) + self.dirlink(os.path.join('link63', 'link63'), join('link64')) + self.dirlink(os.path.join('link64', 'link64'), join('link65')) + self.dirlink(os.path.join('link65', 'link65'), join('link66')) + self.dirlink(os.path.join('link66', 'link66'), join('link67')) + self.dirlink(os.path.join('link67', 'link67'), join('link68')) + self.dirlink(os.path.join('link68', 'link68'), join('link69')) + self.dirlink(os.path.join('link69', 'link69'), join('link70')) + self.dirlink(os.path.join('link70', 'link70'), join('link71')) + self.dirlink(os.path.join('link71', 'link71'), join('link72')) + self.dirlink(os.path.join('link72', 'link72'), join('link73')) + self.dirlink(os.path.join('link73', 'link73'), join('link74')) + self.dirlink(os.path.join('link74', 'link74'), join('link75')) + self.dirlink(os.path.join('link75', 'link75'), join('link76')) + self.dirlink(os.path.join('link76', 'link76'), join('link77')) + self.dirlink(os.path.join('link77', 'link77'), join('link78')) + self.dirlink(os.path.join('link78', 'link78'), join('link79')) + self.dirlink(os.path.join('link79', 'link79'), join('link80')) + self.dirlink(os.path.join('link80', 'link80'), join('link81')) + self.dirlink(os.path.join('link81', 'link81'), join('link82')) + self.dirlink(os.path.join('link82', 'link82'), join('link83')) + self.dirlink(os.path.join('link83', 'link83'), join('link84')) + self.dirlink(os.path.join('link84', 'link84'), join('link85')) + self.dirlink(os.path.join('link85', 'link85'), join('link86')) + self.dirlink(os.path.join('link86', 'link86'), join('link87')) + self.dirlink(os.path.join('link87', 'link87'), join('link88')) + self.dirlink(os.path.join('link88', 'link88'), join('link89')) + self.dirlink(os.path.join('link89', 'link89'), join('link90')) + self.dirlink(os.path.join('link90', 'link90'), join('link91')) + self.dirlink(os.path.join('link91', 'link91'), join('link92')) + self.dirlink(os.path.join('link92', 'link92'), join('link93')) + self.dirlink(os.path.join('link93', 'link93'), join('link94')) + self.dirlink(os.path.join('link94', 'link94'), join('link95')) + self.dirlink(os.path.join('link95', 'link95'), join('link96')) + self.dirlink(os.path.join('link96', 'link96'), join('link97')) + self.dirlink(os.path.join('link97', 'link97'), join('link98')) + self.dirlink(os.path.join('link98', 'link98'), join('link99')) + self.dirlink(os.path.join('link99', 'link99'), join('link100')) + link0 = join('link0') + + self.dirlink(BASE, link0) + # absolute path + P = self.cls(BASE) / 'link0' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link1' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link2' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link3' + self.assertEqual(str(P.resolve()), BASE) + if os.name != 'nt': + P = self.cls(BASE) / 'link10' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link100' + self.assertEqual(str(P.resolve()), BASE) + os.chdir(BASE) + # relative path + P = self.cls('link0') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link1') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link2') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link3') + self.assertEqual(str(P.resolve()), BASE) + if os.name != 'nt': + P = self.cls('link10') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link100') + self.assertEqual(str(P.resolve()), BASE) + os.chdir(old_path) + os.unlink(link0) + + self.dirlink('.', link0) + # absolute path + P = self.cls(BASE) / 'link0' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link1' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link2' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link3' + self.assertEqual(str(P.resolve()), BASE) + if os.name != 'nt': + P = self.cls(BASE) / 'link10' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link100' + self.assertEqual(str(P.resolve()), BASE) + os.chdir(BASE) + # relative path + P = self.cls('link0') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link1') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link2') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link3') + self.assertEqual(str(P.resolve()), BASE) + if os.name != 'nt': + P = self.cls('link10') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link100') + self.assertEqual(str(P.resolve()), BASE) + os.chdir(old_path) + os.unlink(link0) + + self.dirlink(os.path.join('dirA', '..'), link0) + # absolute path + P = self.cls(BASE) / 'link0' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link1' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link2' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link3' + self.assertEqual(str(P.resolve()), BASE) + if os.name != 'nt': + P = self.cls(BASE) / 'link10' + self.assertEqual(str(P.resolve()), BASE) + P = self.cls(BASE) / 'link100' + self.assertEqual(str(P.resolve()), BASE) + os.chdir(BASE) + # relative path + P = self.cls('link0') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link1') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link2') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link3') + self.assertEqual(str(P.resolve()), BASE) + if os.name != 'nt': + P = self.cls('link10') + self.assertEqual(str(P.resolve()), BASE) + P = self.cls('link100') + self.assertEqual(str(P.resolve()), BASE) + os.chdir(old_path) + os.unlink(link0) + class PathTest(_BasePathTest, unittest.TestCase): cls = pathlib.Path