GH-73991: Support preserving metadata in pathlib.Path.copytree() (#121438)

Add *preserve_metadata* keyword-only argument to `pathlib.Path.copytree()`,
defaulting to false. When set to true, we copy timestamps, permissions,
extended attributes and flags where available, like `shutil.copystat()`.
This commit is contained in:
Barney Gale 2024-07-20 23:32:52 +01:00 committed by GitHub
parent 094375b9b7
commit c4c7097e64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 45 additions and 3 deletions

View file

@ -721,6 +721,36 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertIsInstance(errors[0], PermissionError)
self.assertFalse(target.exists())
def test_copytree_preserve_metadata(self):
base = self.cls(self.base)
source = base / 'dirC'
if hasattr(os, 'chmod'):
os.chmod(source / 'dirD', stat.S_IRWXU | stat.S_IRWXO)
if hasattr(os, 'chflags') and hasattr(stat, 'UF_NODUMP'):
os.chflags(source / 'fileC', stat.UF_NODUMP)
target = base / 'copyA'
source.copytree(target, preserve_metadata=True)
for subpath in ['.', 'fileC', 'dirD', 'dirD/fileD']:
source_st = source.joinpath(subpath).stat()
target_st = target.joinpath(subpath).stat()
self.assertLessEqual(source_st.st_atime, target_st.st_atime)
self.assertLessEqual(source_st.st_mtime, target_st.st_mtime)
self.assertEqual(source_st.st_mode, target_st.st_mode)
if hasattr(source_st, 'st_flags'):
self.assertEqual(source_st.st_flags, target_st.st_flags)
@os_helper.skip_unless_xattr
def test_copytree_preserve_metadata_xattrs(self):
base = self.cls(self.base)
source = base / 'dirC'
source_file = source.joinpath('dirD', 'fileD')
os.setxattr(source_file, b'user.foo', b'42')
target = base / 'copyA'
source.copytree(target, preserve_metadata=True)
target_file = target.joinpath('dirD', 'fileD')
self.assertEqual(os.getxattr(target_file, b'user.foo'), b'42')
def test_resolve_nonexist_relative_issue38671(self):
p = self.cls('non', 'exist')