From f01d24f1bd31f681ef6b5ab3ce799ed6e21d8df4 Mon Sep 17 00:00:00 2001 From: Maks Snegov Date: Sat, 23 Oct 2021 21:07:14 +0300 Subject: [PATCH] Fix copying symlinks in Linux --- spqr/curateipsum/fs.py | 9 ++++++--- tests/test_fs.py | 25 ++++++++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/spqr/curateipsum/fs.py b/spqr/curateipsum/fs.py index 3c6d99c..0ef0dc0 100644 --- a/spqr/curateipsum/fs.py +++ b/spqr/curateipsum/fs.py @@ -112,9 +112,12 @@ def copy_direntry(entry: os.DirEntry, dst_path): copy_file(entry.path, dst_path) src_stat = entry.stat(follow_symlinks=False) - os.chown(dst_path, src_stat.st_uid, src_stat.st_gid, follow_symlinks=False) - os.chmod(dst_path, src_stat.st_mode, follow_symlinks=False) - os.utime(dst_path, (src_stat.st_atime, src_stat.st_mtime), follow_symlinks=False) + if not entry.is_symlink() or os.chown in os.supports_follow_symlinks: + os.chown(dst_path, src_stat.st_uid, src_stat.st_gid, follow_symlinks=False) + if not entry.is_symlink() or os.chmod in os.supports_follow_symlinks: + os.chmod(dst_path, src_stat.st_mode, follow_symlinks=False) + if not entry.is_symlink() or os.utime in os.supports_follow_symlinks: + os.utime(dst_path, (src_stat.st_atime, src_stat.st_mtime), follow_symlinks=False) def update_direntry(src_entry: os.DirEntry, dst_entry: os.DirEntry): diff --git a/tests/test_fs.py b/tests/test_fs.py index fea6f2b..354ab9b 100644 --- a/tests/test_fs.py +++ b/tests/test_fs.py @@ -20,17 +20,26 @@ class CommonFSTestCase(unittest.TestCase): self.tmp_dir_dst.cleanup() @staticmethod - def create_file(parent_dir, prefix=None): + def create_file(parent_dir: str, prefix: str = None) -> str: + """ + Create file with random name in parent_dir. + Returns absolute path to created file. + """ fd, path = tempfile.mkstemp(prefix=prefix, dir=parent_dir) with open(fd, "w") as f: f.write(string.printable) return path @staticmethod - def create_dir(parent_dir, prefix=None): + def create_dir(parent_dir: str, prefix: str = None) -> str: + """ + Create directory with random name in parent_dir. + Returns absolute path to created directory. + """ return tempfile.mkdtemp(prefix=prefix, dir=parent_dir) - def relpath(self, full_path): + def relpath(self, full_path: str) -> str: + """ Get relative path for entity in src/dst dirs. """ if full_path.startswith(self.src_dir): p_dir = self.src_dir elif full_path.startswith(self.dst_dir): @@ -118,7 +127,8 @@ class TestHardlinkDir(CommonFSTestCase): # TODO not finished class TestRsync(CommonFSTestCase): @staticmethod - def check_identical_file(file1, file2): + def check_identical_file(file1: str, file2: str): + """ Check that files are identical. Fails test, if not. """ st1 = os.lstat(file1) st2 = os.lstat(file2) @@ -135,7 +145,7 @@ class TestRsync(CommonFSTestCase): assert not os.path.lexists(dst_fpath) def test_dst_has_excess_symlink(self): - dst_lpath = os.path.join(self.dst_dir, 'broken_symlink') + dst_lpath = os.path.join(self.dst_dir, 'nonexisting_file') os.symlink('broken_symlink', dst_lpath) fs.rsync(self.src_dir, self.dst_dir) @@ -239,3 +249,8 @@ class TestRsync(CommonFSTestCase): fs.rsync(self.src_dir, self.dst_dir) assert os.path.lexists(dst_fpath) self.check_identical_file(src_fpath, dst_fpath) + + # TODO add tests for changing ownership + # TODO add tests for changing permissions + # TODO add tests for changing times (?) + # TODO add tests for symlink behaviour