]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-113188: Fix shutil.copymode() on Windows (GH-113189)
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 23 Dec 2023 11:07:54 +0000 (13:07 +0200)
committerGitHub <noreply@github.com>
Sat, 23 Dec 2023 11:07:54 +0000 (11:07 +0000)
Previously it worked differently if dst is a symbolic link:
it modified the permission bits of dst itself rather than the file
it points to if follow_symlinks is true or src is not a symbolic link,
and did nothing if follow_symlinks is false and src is a symbolic link.

Also document similar changes in shutil.copystat().

Lib/shutil.py
Lib/test/test_shutil.py
Misc/NEWS.d/next/Library/2023-12-15-20-29-49.gh-issue-113188.AvoraB.rst [new file with mode: 0644]

index dc3aac3e07f91046dde515a9fc805c0a19b3fa2f..c40f6ddae39a17a9596e3b121f53f787f5f007d8 100644 (file)
@@ -306,7 +306,12 @@ def copymode(src, dst, *, follow_symlinks=True):
         else:
             return
     else:
-        stat_func, chmod_func = _stat, os.chmod
+        stat_func = _stat
+        if os.name == 'nt' and os.path.islink(dst):
+            def chmod_func(*args):
+                os.chmod(*args, follow_symlinks=True)
+        else:
+            chmod_func = os.chmod
 
     st = stat_func(src)
     chmod_func(dst, stat.S_IMODE(st.st_mode))
index 5ce8e5d77fbbf315cc8d8641592feb00886a61af..cc5459aa08fe33ae52baf4669e8747d6902b592f 100644 (file)
@@ -1101,19 +1101,18 @@ class TestCopy(BaseTest, unittest.TestCase):
         shutil.copymode(src, dst)
         self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
         # On Windows, os.chmod does not follow symlinks (issue #15411)
-        if os.name != 'nt':
-            # follow src link
-            os.chmod(dst, stat.S_IRWXO)
-            shutil.copymode(src_link, dst)
-            self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
-            # follow dst link
-            os.chmod(dst, stat.S_IRWXO)
-            shutil.copymode(src, dst_link)
-            self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
-            # follow both links
-            os.chmod(dst, stat.S_IRWXO)
-            shutil.copymode(src_link, dst_link)
-            self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
+        # follow src link
+        os.chmod(dst, stat.S_IRWXO)
+        shutil.copymode(src_link, dst)
+        self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
+        # follow dst link
+        os.chmod(dst, stat.S_IRWXO)
+        shutil.copymode(src, dst_link)
+        self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
+        # follow both links
+        os.chmod(dst, stat.S_IRWXO)
+        shutil.copymode(src_link, dst_link)
+        self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
 
     @unittest.skipUnless(hasattr(os, 'lchmod'), 'requires os.lchmod')
     @os_helper.skip_unless_symlink
diff --git a/Misc/NEWS.d/next/Library/2023-12-15-20-29-49.gh-issue-113188.AvoraB.rst b/Misc/NEWS.d/next/Library/2023-12-15-20-29-49.gh-issue-113188.AvoraB.rst
new file mode 100644 (file)
index 0000000..17c6957
--- /dev/null
@@ -0,0 +1,6 @@
+Fix :func:`shutil.copymode` and :func:`shutil.copystat` on Windows.
+Previously they worked differenly if *dst* is a symbolic link:
+they modified the permission bits of *dst* itself
+rather than the file it points to if *follow_symlinks* is true or *src* is
+not a symbolic link, and did not modify the permission bits if
+*follow_symlinks* is false and *src* is a symbolic link.