1
0
Fork 0
mirror of synced 2025-01-23 12:10:28 -05:00

Avoid deleting the source file when linking

Fixes #360
This commit is contained in:
Kurt McKee 2025-01-14 10:09:48 -06:00
parent 9cd5d073f2
commit 733bb82292
No known key found for this signature in database
GPG key ID: 64713C0B5BA8E1C2
2 changed files with 51 additions and 0 deletions

View file

@ -197,6 +197,12 @@ class Link(Plugin):
success = True
source = os.path.join(self._context.base_directory(canonical_path=canonical_path), source)
fullpath = os.path.abspath(os.path.expanduser(path))
if self._exists(path) and not self._is_link(path) and os.path.realpath(fullpath) == source:
# Special case: The path is not a symlink but resolves to the source anyway.
# Deleting the path would actually delete the source.
# This may happen if a parent directory is a symlink.
self._log.warning(f"{path} appears to be the same file as {source}.")
return False
if relative:
source = self._relative_path(source, fullpath)
if (self._is_link(path) and self._link_destination(path) != source) or (

View file

@ -1,4 +1,5 @@
import os
import pathlib
import sys
from typing import Callable, Optional
@ -962,6 +963,50 @@ def test_link_relink_relative_leaves_file(home: str, dotfiles: Dotfiles, run_dot
assert mtime == new_mtime
def test_source_is_not_overwritten_by_symlink_trickery(
capsys: pytest.CaptureFixture[str], home: str, dotfiles: Dotfiles, run_dotbot: Callable[..., None]
) -> None:
dotfiles_path = pathlib.Path(dotfiles.directory)
home_path = pathlib.Path(home)
# Setup:
# * A symlink exists from `~/.ssh` to `ssh` in the dotfiles directory.
# * Dotbot is configured to force-recreate a symlink between two files
# when, in reality, it's actually the same file when resolved.
ssh_config = (dotfiles_path / "ssh/config").absolute()
os.mkdir(str(ssh_config.parent))
ssh_config.write_text("preserve me!")
os.symlink(str(ssh_config.parent), str(home_path / ".ssh"))
dotfiles.write_config(
[
{
"defaults": {
"link": {
"relink": True,
"create": True,
"force": True,
},
}
},
{
"link": {
# When symlinks are resolved, these are actually the same file.
"~/.ssh/config": "ssh/config",
},
},
]
)
# Execute dotbot.
with pytest.raises(SystemExit):
run_dotbot()
stdout, _ = capsys.readouterr()
assert "appears to be the same file" in stdout
# Verify that the file was not overwritten.
assert ssh_config.read_text() == "preserve me!"
def test_link_defaults_1(home: str, dotfiles: Dotfiles, run_dotbot: Callable[..., None]) -> None:
"""Verify that link doesn't overwrite non-dotfiles links by default."""