Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
class Error(OSError):
pass

class ErrorGroup(Error):
"""Raised when multiple exceptions have been caught"""

class SameFileError(Error):
"""Raised when source and destination are the same file."""

Expand Down Expand Up @@ -590,12 +593,9 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
copytree(srcobj, dstname, symlinks, ignore, copy_function,
ignore_dangling_symlinks, dirs_exist_ok)
else:
# Will raise a SpecialFileError for unsupported file types
copy_function(srcobj, dstname)
# catch the Error from the recursive copytree so that we can
# continue with other files
except Error as err:
errors.extend(err.args[0])
except ErrorGroup as err_group:
errors.extend(err_group.args[0])
except OSError as why:
errors.append((srcname, dstname, str(why)))
try:
Expand All @@ -605,7 +605,7 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
if getattr(why, 'winerror', None) is None:
errors.append((src, dst, str(why)))
if errors:
raise Error(errors)
raise ErrorGroup(errors)
return dst

def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
Expand Down
33 changes: 33 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,39 @@ def test_copytree_subdirectory(self):
rv = shutil.copytree(src_dir, dst_dir)
self.assertEqual(['pol'], os.listdir(rv))

def test_copytree_to_itself_gives_sensible_error_message(self):
base_dir = self.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir, ignore_errors=True)
src_dir = os.path.join(base_dir, "src")
os.makedirs(src_dir)
create_file((src_dir, "somefilename"), "somecontent")
self._assert_are_the_same_file_is_raised(src_dir, src_dir)

@os_helper.skip_unless_symlink
def test_copytree_to_backpointing_symlink_gives_sensible_error_message(self):
base_dir = self.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir, ignore_errors=True)
src_dir = os.path.join(base_dir, "src")
target_dir = os.path.join(base_dir, "target")
os.makedirs(src_dir)
os.makedirs(target_dir)
some_file = os.path.join(src_dir, "somefilename")
create_file(some_file, "somecontent")
os.symlink(some_file, os.path.join(target_dir, "somefilename"))
self._assert_are_the_same_file_is_raised(src_dir, target_dir)

def _assert_are_the_same_file_is_raised(self, src_dir, target_dir):
try:
shutil.copytree(src_dir, target_dir, dirs_exist_ok=True)
self.fail("shutil.Error should have been raised")
except Error as error:
self.assertEqual(len(error.args[0]), 1)
if sys.platform == "win32":
self.assertIn("it is being used by another process", error.args[0][0][2])
else:
self.assertIn("are the same file", error.args[0][0][2])


class TestCopy(BaseTest, unittest.TestCase):

### shutil.copymode
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make exception from :func:`shutil.copytree` readable when a
:exc:`shutil.SameFileError` is raised.
Loading