Skip to content

Commit 5a294d8

Browse files
committed
Issue #21697: shutil.copytree() now correctly handles symbolic links that point to directories.
Patch by Eduardo Seabra and Thomas Kluyver.
1 parent 7e732a7 commit 5a294d8

File tree

3 files changed

+28
-1
lines changed

3 files changed

+28
-1
lines changed

Lib/shutil.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,11 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
321321
if not os.path.exists(linkto) and ignore_dangling_symlinks:
322322
continue
323323
# otherwise let the copy occurs. copy2 will raise an error
324-
copy_function(srcname, dstname)
324+
if os.path.isdir(srcname):
325+
copytree(srcname, dstname, symlinks, ignore,
326+
copy_function)
327+
else:
328+
copy_function(srcname, dstname)
325329
elif os.path.isdir(srcname):
326330
copytree(srcname, dstname, symlinks, ignore, copy_function)
327331
else:

Lib/test/test_shutil.py

+20
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,26 @@ def test_copytree_dangling_symlinks(self):
895895
shutil.copytree(src_dir, dst_dir, symlinks=True)
896896
self.assertIn('test.txt', os.listdir(dst_dir))
897897

898+
@support.skip_unless_symlink
899+
def test_copytree_symlink_dir(self):
900+
src_dir = self.mkdtemp()
901+
dst_dir = os.path.join(self.mkdtemp(), 'destination')
902+
os.mkdir(os.path.join(src_dir, 'real_dir'))
903+
with open(os.path.join(src_dir, 'real_dir', 'test.txt'), 'w'):
904+
pass
905+
os.symlink(os.path.join(src_dir, 'real_dir'),
906+
os.path.join(src_dir, 'link_to_dir'),
907+
target_is_directory=True)
908+
909+
shutil.copytree(src_dir, dst_dir, symlinks=False)
910+
self.assertFalse(os.path.islink(os.path.join(dst_dir, 'link_to_dir')))
911+
self.assertIn('test.txt', os.listdir(os.path.join(dst_dir, 'link_to_dir')))
912+
913+
dst_dir = os.path.join(self.mkdtemp(), 'destination2')
914+
shutil.copytree(src_dir, dst_dir, symlinks=True)
915+
self.assertTrue(os.path.islink(os.path.join(dst_dir, 'link_to_dir')))
916+
self.assertIn('test.txt', os.listdir(os.path.join(dst_dir, 'link_to_dir')))
917+
898918
def _copy_file(self, method):
899919
fname = 'test.txt'
900920
tmpdir = self.mkdtemp()

Misc/NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ Core and Builtins
6666
Library
6767
-------
6868

69+
- Issue #21697: shutil.copytree() now correctly handles symbolic links that
70+
point to directories. Patch by Eduardo Seabra and Thomas Kluyver.
71+
6972
- Issue #24620: Random.setstate() now validates the value of state last element.
7073

7174
- Issue #22153: Improve unittest docs. Patch from Martin Panter and evilzero.

0 commit comments

Comments
 (0)