summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/shutil.rst11
-rw-r--r--Lib/shutil.py22
-rw-r--r--Lib/test/test_shutil.py22
-rw-r--r--Misc/NEWS2
4 files changed, 52 insertions, 5 deletions
diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst
index 8b9ee8e..53e34d7 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 f3a31a4..e3a79ef 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 dfa6f9f..f6b047e 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 25ef541..eaf2452 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.