From fb437513d67c526c8579c96d2986f1e46f9140ed Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tarek=20Ziad=C3=A9?= Date: Tue, 20 Apr 2010 08:57:33 +0000 Subject: [PATCH] Fixed #6547: Added the ignore_dangling_symlinks option to shutil.copytree --- Doc/library/shutil.rst | 11 +++++++++++ Lib/shutil.py | 22 ++++++++++++++++++---- Lib/test/test_shutil.py | 22 +++++++++++++++++++++- Misc/NEWS | 2 ++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 8b9ee8ed1a..53e34d75d7 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -99,6 +99,12 @@ Directory and files operations symbolic links in the new tree; if false or omitted, the contents of the linked files are copied to the new tree. + When *symlinks* is false, if the file pointed by the symlink doesn't + exist, a exception will be added in the list of errors raised in + a :exc:`Error` exception at the end of the copy process. + You can set the optional *ignore_dangling_symlinks* flag to true if you + want to silent this exception. + If *ignore* is given, it must be a callable that will receive as its arguments the directory being visited by :func:`copytree`, and a list of its contents, as returned by :func:`os.listdir`. Since :func:`copytree` is @@ -120,6 +126,11 @@ Directory and files operations Added the *copy_function* argument to be able to provide a custom copy function. + .. versionchanged:: 3.2 + Added the *ignore_dangling_symlinks* argument to silent dangling symlinks + errors when *symlinks* is false. + + .. function:: rmtree(path, ignore_errors=False, onerror=None) .. index:: single: directory; deleting diff --git a/Lib/shutil.py b/Lib/shutil.py index f3a31a4ff4..e3a79efde0 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -147,7 +147,8 @@ def ignore_patterns(*patterns): return set(ignored_names) return _ignore_patterns -def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2): +def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, + ignore_dangling_symlinks=False): """Recursively copy a directory tree. The destination directory must not already exist. @@ -156,7 +157,13 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2): If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic - links are copied. + links are copied. If the file pointed by the symlink doesn't + exist, an exception will be added in the list of errors raised in + an Error exception at the end of the copy process. + + You can set the optional ignore_dangling_symlinks flag to true if you + want to silent this exception. + The optional ignore argument is a callable. If given, it is called with the `src` parameter, which is the directory @@ -190,9 +197,16 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2): srcname = os.path.join(src, name) dstname = os.path.join(dst, name) try: - if symlinks and os.path.islink(srcname): + if os.path.islink(srcname): linkto = os.readlink(srcname) - os.symlink(linkto, dstname) + if symlinks: + os.symlink(linkto, dstname) + else: + # ignore dangling symlink if the flag is on + 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) 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 dfa6f9ff30..f6b047e44b 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -13,7 +13,7 @@ from os.path import splitdrive from distutils.spawn import find_executable, spawn from shutil import (_make_tarball, _make_zipfile, make_archive, register_archive_format, unregister_archive_format, - get_archive_formats) + get_archive_formats, Error) import tarfile import warnings @@ -351,6 +351,26 @@ class TestShutil(unittest.TestCase): shutil.copytree(src_dir, dst_dir, copy_function=_copy) self.assertEquals(len(copied), 2) + def test_copytree_dangling_symlinks(self): + + # a dangling symlink raises an error at the end + src_dir = self.mkdtemp() + dst_dir = os.path.join(self.mkdtemp(), 'destination') + os.symlink('IDONTEXIST', os.path.join(src_dir, 'test.txt')) + os.mkdir(os.path.join(src_dir, 'test_dir')) + self._write_data(os.path.join(src_dir, 'test_dir', 'test.txt'), '456') + self.assertRaises(Error, shutil.copytree, src_dir, dst_dir) + + # a dangling symlink is ignored with the proper flag + dst_dir = os.path.join(self.mkdtemp(), 'destination2') + shutil.copytree(src_dir, dst_dir, ignore_dangling_symlinks=True) + self.assertNotIn('test.txt', os.listdir(dst_dir)) + + # a dangling symlink is copied if symlinks=True + dst_dir = os.path.join(self.mkdtemp(), 'destination3') + shutil.copytree(src_dir, dst_dir, symlinks=True) + self.assertIn('test.txt', os.listdir(dst_dir)) + @unittest.skipUnless(zlib, "requires zlib") def test_make_tarball(self): # creating something to tar diff --git a/Misc/NEWS b/Misc/NEWS index 25ef541b23..eaf2452008 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -318,6 +318,8 @@ C-API Library ------- +- Issue #6547: Added the ignore_dangling_symlinks option to shutil.copytree. + - Issue #1540112: Now allowing the choice of a copy function in shutil.copytree. -- 2.40.0