Skip to content

Commit 5b5536d

Browse files
committed
gh-156210: Fix shutil.copytree() detection of dangling relative symlinks
1 parent 999a046 commit 5b5536d

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

Lib/shutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
593593
copystat(srcobj, dstname, follow_symlinks=not symlinks)
594594
else:
595595
# ignore dangling symlink if the flag is on
596-
if not os.path.exists(linkto) and ignore_dangling_symlinks:
596+
if not os.path.exists(srcname) and ignore_dangling_symlinks:
597597
continue
598598
# otherwise let the copy occur. copy2 will raise an error
599599
if srcentry.is_dir():

Lib/test/test_shutil.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,35 @@ def test_copytree_dangling_symlinks(self):
10601060
shutil.copytree(src_dir, dst_dir, symlinks=True)
10611061
self.assertIn('test.txt', os.listdir(dst_dir))
10621062

1063+
@os_helper.skip_unless_symlink
1064+
def test_copytree_valid_relative_symlink(self):
1065+
root_dir = self.mkdtemp()
1066+
src_dir = os.path.join(root_dir, 'source')
1067+
os.mkdir(src_dir)
1068+
create_file(os.path.join(src_dir, 'target'), 'abc')
1069+
os.symlink('target', os.path.join(src_dir, 'link'))
1070+
1071+
dst_dir = os.path.join(root_dir, 'destination')
1072+
with os_helper.change_cwd(root_dir):
1073+
shutil.copytree(src_dir, dst_dir, ignore_dangling_symlinks=True)
1074+
self.assertEqual(['link', 'target'], sorted(os.listdir(dst_dir)))
1075+
self.assertFalse(os.path.islink(os.path.join(dst_dir, 'link')))
1076+
self.assertEqual('abc', read_file(os.path.join(dst_dir, 'link')))
1077+
1078+
@os_helper.skip_unless_symlink
1079+
def test_copytree_dangling_relative_symlink(self):
1080+
root_dir = self.mkdtemp()
1081+
src_dir = os.path.join(root_dir, 'source')
1082+
os.mkdir(src_dir)
1083+
os.symlink('target', os.path.join(src_dir, 'link'))
1084+
create_file(os.path.join(root_dir, 'target'), 'abc')
1085+
1086+
dst_dir = os.path.join(root_dir, 'destination')
1087+
with os_helper.change_cwd(root_dir):
1088+
shutil.copytree(src_dir, dst_dir,
1089+
ignore_dangling_symlinks=True)
1090+
self.assertEqual([], os.listdir(dst_dir))
1091+
10631092
@os_helper.skip_unless_symlink
10641093
def test_copytree_symlink_dir(self):
10651094
src_dir = self.mkdtemp()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`shutil.copytree` to determine whether relative symbolic links are
2+
dangling based on their parent directory rather than the current working
3+
directory when ``ignore_dangling_symlinks`` is true.

0 commit comments

Comments
 (0)