From 5a294d822b7f5732135662907ec1a1d4a7b0fc9a Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 25 Jul 2015 14:53:48 +0300 Subject: [PATCH] Issue #21697: shutil.copytree() now correctly handles symbolic links that point to directories. Patch by Eduardo Seabra and Thomas Kluyver. --- Lib/shutil.py | 6 +++++- Lib/test/test_shutil.py | 20 ++++++++++++++++++++ Misc/NEWS | 3 +++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index ac06ae5e6cd..e87d18e9c80 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -321,7 +321,11 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, if not os.path.exists(linkto) and ignore_dangling_symlinks: continue # otherwise let the copy occurs. copy2 will raise an error - copy_function(srcname, dstname) + if os.path.isdir(srcname): + copytree(srcname, dstname, symlinks, ignore, + copy_function) + else: + copy_function(srcname, dstname) elif os.path.isdir(srcname): copytree(srcname, dstname, symlinks, ignore, copy_function) else: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 9325bc7dc15..ca2bfc4bfeb 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -895,6 +895,26 @@ class TestShutil(unittest.TestCase): shutil.copytree(src_dir, dst_dir, symlinks=True) self.assertIn('test.txt', os.listdir(dst_dir)) + @support.skip_unless_symlink + def test_copytree_symlink_dir(self): + src_dir = self.mkdtemp() + dst_dir = os.path.join(self.mkdtemp(), 'destination') + os.mkdir(os.path.join(src_dir, 'real_dir')) + with open(os.path.join(src_dir, 'real_dir', 'test.txt'), 'w'): + pass + os.symlink(os.path.join(src_dir, 'real_dir'), + os.path.join(src_dir, 'link_to_dir'), + target_is_directory=True) + + shutil.copytree(src_dir, dst_dir, symlinks=False) + self.assertFalse(os.path.islink(os.path.join(dst_dir, 'link_to_dir'))) + self.assertIn('test.txt', os.listdir(os.path.join(dst_dir, 'link_to_dir'))) + + dst_dir = os.path.join(self.mkdtemp(), 'destination2') + shutil.copytree(src_dir, dst_dir, symlinks=True) + self.assertTrue(os.path.islink(os.path.join(dst_dir, 'link_to_dir'))) + self.assertIn('test.txt', os.listdir(os.path.join(dst_dir, 'link_to_dir'))) + def _copy_file(self, method): fname = 'test.txt' tmpdir = self.mkdtemp() diff --git a/Misc/NEWS b/Misc/NEWS index 1a67632e918..a347b940bb2 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -66,6 +66,9 @@ Core and Builtins Library ------- +- Issue #21697: shutil.copytree() now correctly handles symbolic links that + point to directories. Patch by Eduardo Seabra and Thomas Kluyver. + - Issue #24620: Random.setstate() now validates the value of state last element. - Issue #22153: Improve unittest docs. Patch from Martin Panter and evilzero.