diff --git a/src/ahriman/application/handlers/tree_migrate.py b/src/ahriman/application/handlers/tree_migrate.py index 1f033207..7d9a0f38 100644 --- a/src/ahriman/application/handlers/tree_migrate.py +++ b/src/ahriman/application/handlers/tree_migrate.py @@ -21,7 +21,7 @@ import argparse from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.core.configuration import Configuration -from ahriman.core.utils import walk +from ahriman.core.utils import symlink_relative, walk from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_paths import RepositoryPaths @@ -82,7 +82,7 @@ class TreeMigrate(Handler): continue if (source_archive := archives.get(symlink.name)) is not None: symlink.unlink() - symlink.symlink_to(source_archive.relative_to(symlink.parent, walk_up=True)) + symlink_relative(symlink, source_archive) @staticmethod def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None: diff --git a/src/ahriman/core/archive/archive_tree.py b/src/ahriman/core/archive/archive_tree.py index c145f0af..6c420fd3 100644 --- a/src/ahriman/core/archive/archive_tree.py +++ b/src/ahriman/core/archive/archive_tree.py @@ -23,7 +23,7 @@ from pathlib import Path from ahriman.core.alpm.repo import Repo from ahriman.core.log import LazyLogging -from ahriman.core.utils import utcnow, walk +from ahriman.core.utils import symlink_relative, utcnow, walk from ahriman.models.package import Package from ahriman.models.repository_paths import RepositoryPaths @@ -92,7 +92,7 @@ class ArchiveTree(LazyLogging): for file in archive.glob(f"{single.filename}*"): symlink = root / file.name try: - symlink.symlink_to(file.relative_to(symlink.parent, walk_up=True)) + symlink_relative(symlink, file) has_file = True except FileExistsError: continue # symlink is already created, skip processing diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py index f4aa296c..634b4aa1 100644 --- a/src/ahriman/core/repository/executor.py +++ b/src/ahriman/core/repository/executor.py @@ -27,7 +27,7 @@ from ahriman.core.build_tools.package_archive import PackageArchive from ahriman.core.build_tools.task import Task from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.package_info import PackageInfo -from ahriman.core.utils import atomic_move, filelock, package_like, safe_filename +from ahriman.core.utils import atomic_move, filelock, package_like, safe_filename, symlink_relative from ahriman.models.changes import Changes from ahriman.models.event import EventType from ahriman.models.package import Package @@ -172,7 +172,7 @@ class Executor(PackageInfo, Cleaner): dst = self.paths.archive_for(package_base) / src.name atomic_move(src, dst) # move package to archive directory if not (symlink := self.paths.repository / dst.name).exists(): - symlink.symlink_to(dst.relative_to(symlink.parent, walk_up=True)) # create link to archive + symlink_relative(symlink, dst) # create link to archive self.repo.add(self.paths.repository / filename) diff --git a/src/ahriman/core/utils.py b/src/ahriman/core/utils.py index 504f8050..dc9d757c 100644 --- a/src/ahriman/core/utils.py +++ b/src/ahriman/core/utils.py @@ -517,6 +517,17 @@ def srcinfo_property_list(key: str, srcinfo: Mapping[str, Any], package_srcinfo: return values +def symlink_relative(symlink: Path, source: Path) -> None: + """ + create symlink with relative path to the target directory + + Args: + symlink(Path): path to symlink to create + source(Path): source file to be symlinked + """ + symlink.symlink_to(source.relative_to(symlink.parent, walk_up=True)) + + def trim_package(package_name: str) -> str: """ remove version bound and description from package name. Pacman allows to specify version bound (=, <=, >= etc.) for diff --git a/tests/ahriman/core/test_utils.py b/tests/ahriman/core/test_utils.py index e2b25375..0ae5de1a 100644 --- a/tests/ahriman/core/test_utils.py +++ b/tests/ahriman/core/test_utils.py @@ -30,6 +30,7 @@ from ahriman.core.utils import ( safe_filename, srcinfo_property, srcinfo_property_list, + symlink_relative, trim_package, utcnow, walk, @@ -532,6 +533,23 @@ def test_srcinfo_property_list() -> None: assert srcinfo_property_list("key", {"key_x86_64": ["overrides"]}, {}, architecture="x86_64") == ["overrides"] +def test_symlink_relative(mocker: MockerFixture) -> None: + """ + must create symlinks with relative paths + """ + symlink_mock = mocker.patch("pathlib.Path.symlink_to") + + symlink_relative(Path("a"), Path("b")) + symlink_relative(Path("root/a"), Path("root/c")) + symlink_relative(Path("root/sub/a"), Path("root/c")) + + symlink_mock.assert_has_calls([ + MockCall(Path("b")), + MockCall(Path("c")), + MockCall(Path("../c")), + ]) + + def test_trim_package() -> None: """ must trim package version