GH-73991: Add follow_symlinks argument to pathlib.Path.copy() (#120519)

Add support for not following symlinks in `pathlib.Path.copy()`.

On Windows we add the `COPY_FILE_COPY_SYMLINK` flag is following symlinks is disabled. If the source is symlink to a directory, this call will fail with `ERROR_ACCESS_DENIED`. In this case we add `COPY_FILE_DIRECTORY` to the flags and retry. This can fail on old Windowses, which we note in the docs.

No news as `copy()` was only just added.
This commit is contained in:
Barney Gale 2024-06-19 01:59:54 +01:00 committed by GitHub
parent 9f741e55c1
commit 20d5b84f57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 86 additions and 11 deletions

View file

@ -1743,7 +1743,7 @@ class DummyPathTest(DummyPurePathTest):
source.copy(target)
@needs_symlinks
def test_copy_symlink(self):
def test_copy_symlink_follow_symlinks_true(self):
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
@ -1752,6 +1752,26 @@ class DummyPathTest(DummyPurePathTest):
self.assertFalse(target.is_symlink())
self.assertEqual(source.read_text(), target.read_text())
@needs_symlinks
def test_copy_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
source.copy(target, follow_symlinks=False)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())
@needs_symlinks
def test_copy_directory_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'linkB'
target = base / 'copyA'
source.copy(target, follow_symlinks=False)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())
def test_copy_to_existing_file(self):
base = self.cls(self.base)
source = base / 'fileA'
@ -1780,6 +1800,19 @@ class DummyPathTest(DummyPurePathTest):
self.assertFalse(real_target.is_symlink())
self.assertEqual(source.read_text(), real_target.read_text())
@needs_symlinks
def test_copy_to_existing_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'dirB' / 'fileB'
target = base / 'linkA'
real_target = base / 'fileA'
source.copy(target, follow_symlinks=False)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertTrue(real_target.exists())
self.assertFalse(real_target.is_symlink())
self.assertEqual(source.read_text(), real_target.read_text())
def test_copy_empty(self):
base = self.cls(self.base)
source = base / 'empty'