summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeffrey Kintscher <49998481+websurfer5@users.noreply.github.com>2023-12-27 16:23:42 (GMT)
committerGitHub <noreply@github.com>2023-12-27 16:23:42 (GMT)
commitc66b577d9f7a11ffab57985fd6fb22e9dfd4f245 (patch)
tree9f1f83c16e016d6d4b6d4025d596c109318f27a5
parent1b19d7376818d14ab865fa22cb66baeacdb88277 (diff)
downloadcpython-c66b577d9f7a11ffab57985fd6fb22e9dfd4f245.zip
cpython-c66b577d9f7a11ffab57985fd6fb22e9dfd4f245.tar.gz
cpython-c66b577d9f7a11ffab57985fd6fb22e9dfd4f245.tar.bz2
bpo-26791: Update shutil.move() to provide the same symlink move behavior as the mv shell when moving a symlink into a directory that is the target of the symlink (GH-21759)
-rw-r--r--Lib/shutil.py2
-rw-r--r--Lib/test/test_shutil.py29
-rw-r--r--Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst4
3 files changed, 34 insertions, 1 deletions
diff --git a/Lib/shutil.py b/Lib/shutil.py
index c40f6dd..acc9419 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -885,7 +885,7 @@ def move(src, dst, copy_function=copy2):
sys.audit("shutil.move", src, dst)
real_dst = dst
if os.path.isdir(dst):
- if _samefile(src, dst):
+ if _samefile(src, dst) and not os.path.islink(src):
# We might be on a case insensitive filesystem,
# perform the rename anyway.
os.rename(src, dst)
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index cc5459a..8edd75e 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -2688,6 +2688,35 @@ class TestMove(BaseTest, unittest.TestCase):
finally:
os.rmdir(dst_dir)
+ # bpo-26791: Check that a symlink to a directory can
+ # be moved into that directory.
+ @mock_rename
+ def _test_move_symlink_to_dir_into_dir(self, dst):
+ src = os.path.join(self.src_dir, 'linktodir')
+ dst_link = os.path.join(self.dst_dir, 'linktodir')
+ os.symlink(self.dst_dir, src, target_is_directory=True)
+ shutil.move(src, dst)
+ self.assertTrue(os.path.islink(dst_link))
+ self.assertTrue(os.path.samefile(self.dst_dir, dst_link))
+ self.assertFalse(os.path.exists(src))
+
+ # Repeat the move operation with the destination
+ # symlink already in place (should raise shutil.Error).
+ os.symlink(self.dst_dir, src, target_is_directory=True)
+ with self.assertRaises(shutil.Error):
+ shutil.move(src, dst)
+ self.assertTrue(os.path.samefile(self.dst_dir, dst_link))
+ self.assertTrue(os.path.exists(src))
+
+ @os_helper.skip_unless_symlink
+ def test_move_symlink_to_dir_into_dir(self):
+ self._test_move_symlink_to_dir_into_dir(self.dst_dir)
+
+ @os_helper.skip_unless_symlink
+ def test_move_symlink_to_dir_into_symlink_to_dir(self):
+ dst = os.path.join(self.src_dir, 'otherlinktodir')
+ os.symlink(self.dst_dir, dst, target_is_directory=True)
+ self._test_move_symlink_to_dir_into_dir(dst)
@os_helper.skip_unless_dac_override
@unittest.skipUnless(hasattr(os, 'lchflags')
diff --git a/Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst b/Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst
new file mode 100644
index 0000000..c6f8dcb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst
@@ -0,0 +1,4 @@
+:func:`shutil.move` now moves a symlink into a directory when that
+directory is the target of the symlink. This provides the same behavior as
+the mv shell command. The previous behavior raised an exception. Patch by
+Jeffrey Kintscher.