import os from pathlib import Path import pytest from plumbum import local from copier import run_copy, run_update from copier.errors import DirtyLocalWarning from .helpers import build_file_tree, git def test_copy_symlink(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: true """, repo / "target.txt": "Symlink target", repo / "symlink.txt": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, vcs_ref="HEAD", ) assert (dst / "target.txt").exists() assert (dst / "symlink.txt").exists() assert (dst / "symlink.txt").is_symlink() assert (dst / "symlink.txt").readlink() == Path("target.txt") def test_copy_symlink_templated_name(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: true symlink_name: symlink """, repo / "target.txt": "Symlink target", repo / "{{ symlink_name }}.txt": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, vcs_ref="HEAD", ) assert (dst / "target.txt").exists() assert (dst / "symlink.txt").exists() assert (dst / "symlink.txt").is_symlink() assert (dst / "symlink.txt").readlink() == Path("target.txt") def test_copy_symlink_templated_target( tmp_path_factory: pytest.TempPathFactory, ) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: true target_name: target """, repo / "{{ target_name }}.txt": "Symlink target", repo / "symlink1.txt.jinja": Path("{{ target_name }}.txt"), repo / "symlink2.txt": Path("{{ target_name }}.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, vcs_ref="HEAD", ) assert (dst / "target.txt").exists() assert (dst / "symlink1.txt").exists() assert (dst / "symlink1.txt").is_symlink() assert (dst / "symlink1.txt").readlink() == Path("target.txt") assert not (dst / "symlink2.txt").exists() assert (dst / "symlink2.txt").is_symlink() assert (dst / "symlink2.txt").readlink() == Path("{{ target_name }}.txt") def test_copy_symlink_missing_target(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: true """, repo / "symlink.txt": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, vcs_ref="HEAD", ) assert (dst / "symlink.txt").is_symlink() assert (dst / "symlink.txt").readlink() == Path("target.txt") assert not ( dst / "symlink.txt" ).exists() # exists follows symlinks, It returns False as the target doesn't exist def test_option_preserve_symlinks_false( tmp_path_factory: pytest.TempPathFactory, ) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: false """, repo / "target.txt": "Symlink target", repo / "symlink.txt": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, vcs_ref="HEAD", ) assert (dst / "target.txt").exists() assert (dst / "symlink.txt").exists() assert not (dst / "symlink.txt").is_symlink() def test_option_preserve_symlinks_default( tmp_path_factory: pytest.TempPathFactory, ) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ """, repo / "target.txt": "Symlink target", repo / "symlink.txt": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, vcs_ref="HEAD", ) assert (dst / "target.txt").exists() assert (dst / "symlink.txt").exists() assert not (dst / "symlink.txt").is_symlink() def test_update_symlink(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) build_file_tree( { src / ".copier-answers.yml.jinja": """\ # Changes here will be overwritten by Copier {{ _copier_answers|to_nice_yaml }} """, src / "copier.yml": """\ _preserve_symlinks: true """, src / "aaaa.txt": """ Lorem ipsum """, src / "bbbb.txt": """ dolor sit amet """, src / "symlink.txt": Path("./aaaa.txt"), } ) with local.cwd(src): git("init") git("add", "-A") git("commit", "-m", "first commit on src") run_copy(str(src), dst, defaults=True, overwrite=True) with local.cwd(src): # test updating a symlink Path("symlink.txt").unlink() os.symlink("bbbb.txt", "symlink.txt") # dst must be vcs-tracked to use run_update with local.cwd(dst): git("init") git("add", "-A") git("commit", "-m", "first commit on dst") # make sure changes have not yet propagated p1 = src / "symlink.txt" p2 = dst / "symlink.txt" assert p1.read_text() != p2.read_text() with pytest.warns(DirtyLocalWarning): run_update(dst, defaults=True, overwrite=True) # make sure changes propagate after update p1 = src / "symlink.txt" p2 = dst / "symlink.txt" assert p1.read_text() == p2.read_text() assert (dst / "symlink.txt").readlink() == Path("bbbb.txt") def test_update_file_to_symlink(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) build_file_tree( { src / ".copier-answers.yml.jinja": """\ # Changes here will be overwritten by Copier {{ _copier_answers|to_nice_yaml }} """, src / "copier.yml": """\ _preserve_symlinks: true """, src / "aaaa.txt": """ Lorem ipsum """, src / "bbbb.txt": """ dolor sit amet """, src / "cccc.txt": Path("./aaaa.txt"), } ) with local.cwd(src): git("init") git("add", "-A") git("commit", "-m", "first commit on src") run_copy(str(src), dst, defaults=True, overwrite=True) with local.cwd(src): # test updating a symlink Path("bbbb.txt").unlink() os.symlink("aaaa.txt", "bbbb.txt") Path("cccc.txt").unlink() with Path("cccc.txt").open("w+") as f: f.write("Lorem ipsum") # dst must be vcs-tracked to use run_update with local.cwd(dst): git("init") git("add", "-A") git("commit", "-m", "first commit on dst") # make sure changes have not yet propagated assert not (dst / "bbbb.txt").is_symlink() assert (dst / "cccc.txt").is_symlink() with pytest.warns(DirtyLocalWarning): run_update(dst, defaults=True, overwrite=True) # make sure changes propagate after update assert (dst / "bbbb.txt").is_symlink() assert (dst / "bbbb.txt").readlink() == Path("aaaa.txt") assert not (dst / "cccc.txt").is_symlink() assert (dst / "cccc.txt").read_text() == "Lorem ipsum" def test_exclude_symlink(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: true """, repo / "target.txt": "Symlink target", repo / "symlink.txt": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, exclude=["symlink.txt"], vcs_ref="HEAD", ) assert not (dst / "symlink.txt").exists() assert not (dst / "symlink.txt").is_symlink() def test_pretend_symlink(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: true """, repo / "target.txt": "Symlink target", repo / "symlink.txt": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, pretend=True, vcs_ref="HEAD", ) assert not (dst / "symlink.txt").exists() assert not (dst / "symlink.txt").is_symlink() def test_copy_symlink_none_path(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) # Prepare repo bundle repo = src / "repo" repo.mkdir() build_file_tree( { repo / "copier.yaml": """\ _preserve_symlinks: true render: false """, repo / "target.txt": "Symlink target", repo / "{% if render %}symlink.txt{% endif %}": Path("target.txt"), } ) with local.cwd(src): git("init") git("add", ".") git("commit", "-m", "hello world") run_copy( str(repo), dst, defaults=True, overwrite=True, vcs_ref="HEAD", ) assert (dst / "target.txt").exists() assert not (dst / "symlink.txt").exists() assert not (dst / "symlink.txt").is_symlink() def test_recursive_symlink(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) build_file_tree( { src / "copier.yaml": "_preserve_symlinks: true", src / "one" / "two" / "three" / "root": Path("../../../"), } ) run_copy(str(src), dst, defaults=True, overwrite=True) assert (dst / "one" / "two" / "three" / "root").is_symlink() assert (dst / "one" / "two" / "three" / "root").readlink() == Path("../../../") def test_symlinked_dir_expanded(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) build_file_tree( { src / "a_dir" / "a_file.txt": "some content", src / "a_symlinked_dir": Path("a_dir"), src / "a_nested" / "symlink": Path("../a_dir"), } ) run_copy(str(src), dst, defaults=True, overwrite=True) assert (dst / "a_dir" / "a_file.txt").read_text() == "some content" assert (dst / "a_symlinked_dir" / "a_file.txt").read_text() == "some content" assert (dst / "a_nested" / "symlink" / "a_file.txt").read_text() == "some content"