diff --git a/path/__init__.py b/path/__init__.py index 33dd979..8557720 100644 --- a/path/__init__.py +++ b/path/__init__.py @@ -1662,8 +1662,12 @@ def ignored(item: Self) -> bool: for source in itertools.filterfalse(ignored, sources): dest = dst_path / source.name if symlinks and source.islink(): - target = source.readlink() - target.symlink(dest) + # copy2 overwrites files; replace dest so a re-merge does not fail + if dest.islink() or dest.is_file(): + dest.remove() + elif dest.is_dir(): + dest.rmtree() + source.readlink().symlink(dest) elif source.is_dir(): source.merge_tree( dest, diff --git a/tests/test_path.py b/tests/test_path.py index b424bb6..3877dd6 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -919,6 +919,39 @@ def test_with_nonexisting_dst_args(self): assert set(self.subdir_b.iterdir()) == expected self.check_link() + def test_overwrite_existing_symlink(self): + """ + Re-merging a tree that contains symlinks should overwrite + destination links instead of raising FileExistsError. + """ + self.subdir_a.merge_tree(self.subdir_b, True) + self.subdir_a.merge_tree(self.subdir_b, True) + self.check_link() + + def test_overwrite_retargets_existing_symlink(self): + self.subdir_a.merge_tree(self.subdir_b, True) + self.test_link.remove() + other = self.subdir_a / 'other.txt' + other.write_text('y', encoding='utf-8') + other.symlink(self.test_link) + self.subdir_a.merge_tree(self.subdir_b, True) + dest_link = self.subdir_b / self.test_link.name + assert dest_link.islink() + assert dest_link.readlink().basename() == other.name + + def test_symlink_replaces_existing_file(self): + dest_link = self.subdir_b / self.test_link.name + dest_link.write_text('not a link', encoding='utf-8') + self.subdir_a.merge_tree(self.subdir_b, True) + assert dest_link.islink() + + def test_symlink_replaces_existing_directory(self): + dest_link = self.subdir_b / self.test_link.name + dest_link.mkdir() + (dest_link / 'child').touch() + self.subdir_a.merge_tree(self.subdir_b, True) + assert dest_link.islink() + def test_with_existing_dst(self): self.subdir_b.rmtree() self.subdir_a.copytree(self.subdir_b, True)