]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-26791: Update shutil.move() to provide the same symlink move behavior as the...
authorJeffrey Kintscher <49998481+websurfer5@users.noreply.github.com>
Wed, 27 Dec 2023 16:23:42 +0000 (08:23 -0800)
committerGitHub <noreply@github.com>
Wed, 27 Dec 2023 16:23:42 +0000 (16:23 +0000)
Lib/shutil.py
Lib/test/test_shutil.py
Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst [new file with mode: 0644]

index c40f6ddae39a17a9596e3b121f53f787f5f007d8..acc9419be4dfca65fa7fa8657a8a7177c065d192 100644 (file)
@@ -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)
index cc5459aa08fe33ae52baf4669e8747d6902b592f..8edd75e9907ec02986041b16f3a7c8cc9bd472c3 100644 (file)
@@ -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 (file)
index 0000000..c6f8dcb
--- /dev/null
@@ -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.