fix issues with remote pull triggers (see #103)

* The issue appears when repository contains PKGBUILD in root. In this
  case it will copy tree with loosing package information, because
  the repository will be cloned to temporary path with random generated
  name
* The issue appears when branch which is different from master is used
  for any reposittory with git files (e.g. single-pkgbuild repo or repo
  with submodules)
This commit is contained in:
Evgenii Alekseev 2023-08-08 02:30:44 +03:00
parent bd0f850d25
commit 237fec3f85
4 changed files with 66 additions and 26 deletions

View File

@ -27,6 +27,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import GitRemoteError from ahriman.core.exceptions import GitRemoteError
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.util import walk from ahriman.core.util import walk
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource from ahriman.models.remote_source import RemoteSource
@ -36,16 +37,18 @@ class RemotePull(LazyLogging):
fetch PKGBUILDs from remote repository and use them for following actions fetch PKGBUILDs from remote repository and use them for following actions
Attributes: Attributes:
architecture(str): repository architecture
remote_source(RemoteSource): repository remote source (remote pull url and branch) remote_source(RemoteSource): repository remote source (remote pull url and branch)
repository_paths(RepositoryPaths): repository paths instance repository_paths(RepositoryPaths): repository paths instance
""" """
def __init__(self, configuration: Configuration, section: str) -> None: def __init__(self, configuration: Configuration, architecture: str, section: str) -> None:
""" """
default constructor default constructor
Args: Args:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
architecture(str): repository architecture
section(str): settings section name section(str): settings section name
""" """
self.remote_source = RemoteSource( self.remote_source = RemoteSource(
@ -55,8 +58,30 @@ class RemotePull(LazyLogging):
branch=configuration.get(section, "pull_branch", fallback="master"), branch=configuration.get(section, "pull_branch", fallback="master"),
source=PackageSource.Local, source=PackageSource.Local,
) )
self.architecture = architecture
self.repository_paths = configuration.repository_paths self.repository_paths = configuration.repository_paths
def package_copy(self, pkgbuild_path: Path) -> None:
"""
copy single PKGBUILD content to the repository tree
Args:
pkgbuild_path(Path): path to PKGBUILD to copy
"""
cloned_pkgbuild_dir = pkgbuild_path.parent
# load package from the PKGBUILD, because it might be possible that name doesn't match
# e.g. if we have just cloned repo with just one PKGBUILD
package = Package.from_build(cloned_pkgbuild_dir, self.architecture, None)
package_base = package.base
local_pkgbuild_dir = self.repository_paths.cache_for(package_base)
# copy source ignoring the git files
shutil.copytree(cloned_pkgbuild_dir, local_pkgbuild_dir,
ignore=shutil.ignore_patterns(".git*"), dirs_exist_ok=True)
# initialized git repository is required for local sources
Sources.init(local_pkgbuild_dir)
def repo_clone(self) -> None: def repo_clone(self) -> None:
""" """
clone repository from remote source clone repository from remote source
@ -74,11 +99,7 @@ class RemotePull(LazyLogging):
clone_dir(Path): path to temporary cloned directory clone_dir(Path): path to temporary cloned directory
""" """
for pkgbuild_path in filter(lambda path: path.name == "PKGBUILD", walk(clone_dir)): for pkgbuild_path in filter(lambda path: path.name == "PKGBUILD", walk(clone_dir)):
cloned_pkgbuild_dir = pkgbuild_path.parent self.package_copy(pkgbuild_path)
package_base = cloned_pkgbuild_dir.name
local_pkgbuild_dir = self.repository_paths.cache_for(package_base)
shutil.copytree(cloned_pkgbuild_dir, local_pkgbuild_dir, dirs_exist_ok=True)
Sources.init(local_pkgbuild_dir) # initialized git repository is required for local sources
def run(self) -> None: def run(self) -> None:
""" """

View File

@ -87,5 +87,5 @@ class RemotePullTrigger(Trigger):
for target in self.targets: for target in self.targets:
section, _ = self.configuration.gettype( section, _ = self.configuration.gettype(
target, self.architecture, fallback=self.CONFIGURATION_SCHEMA_FALLBACK) target, self.architecture, fallback=self.CONFIGURATION_SCHEMA_FALLBACK)
runner = RemotePull(self.configuration, section) runner = RemotePull(self.configuration, self.architecture, section)
runner.run() runner.run()

View File

@ -79,6 +79,7 @@ class RemotePush(LazyLogging):
package_target_dir = target_dir / package.base package_target_dir = target_dir / package.base
shutil.rmtree(package_target_dir, ignore_errors=True) shutil.rmtree(package_target_dir, ignore_errors=True)
# ...secondly, we clone whole tree... # ...secondly, we clone whole tree...
# fetch is used intentionally here in order to avoid copying downloaded blobs
Sources.fetch(package_target_dir, package.remote) Sources.fetch(package_target_dir, package.remote)
# ...and last, but not least, we remove the dot-git directory... # ...and last, but not least, we remove the dot-git directory...
for git_file in package_target_dir.glob(".git*"): for git_file in package_target_dir.glob(".git*"):

View File

@ -7,6 +7,7 @@ from unittest.mock import call as MockCall
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import GitRemoteError from ahriman.core.exceptions import GitRemoteError
from ahriman.core.gitremote.remote_pull import RemotePull from ahriman.core.gitremote.remote_pull import RemotePull
from ahriman.models.package import Package
def test_repo_clone(configuration: Configuration, mocker: MockerFixture) -> None: def test_repo_clone(configuration: Configuration, mocker: MockerFixture) -> None:
@ -15,36 +16,53 @@ def test_repo_clone(configuration: Configuration, mocker: MockerFixture) -> None
""" """
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
copy_mock = mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_copy") copy_mock = mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_copy")
runner = RemotePull(configuration, "gitremote") runner = RemotePull(configuration, "x86_64", "gitremote")
runner.repo_clone() runner.repo_clone()
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source) fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source)
copy_mock.assert_called_once_with(pytest.helpers.anyvar(int)) copy_mock.assert_called_once_with(pytest.helpers.anyvar(int))
def test_package_copy(configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must copy single package
"""
package_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
patterns = object()
ignore_patterns_mock = mocker.patch("shutil.ignore_patterns", return_value=patterns)
copytree_mock = mocker.patch("shutil.copytree")
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
runner = RemotePull(configuration, "x86_64", "gitremote")
local = Path("local")
runner.package_copy(local / "PKGBUILD")
package_mock.assert_called_once_with(local, "x86_64", None)
ignore_patterns_mock.assert_called_once_with(".git*")
copytree_mock.assert_called_once_with(
local, configuration.repository_paths.cache_for(package_ahriman.base),
ignore=patterns, dirs_exist_ok=True)
init_mock.assert_called_once_with(configuration.repository_paths.cache_for(package_ahriman.base))
def test_repo_copy(configuration: Configuration, mocker: MockerFixture) -> None: def test_repo_copy(configuration: Configuration, mocker: MockerFixture) -> None:
""" """
must copy repository tree from temporary directory to the local cache must copy repository tree from temporary directory to the local cache
""" """
local = Path("local")
mocker.patch("ahriman.core.gitremote.remote_pull.walk", return_value=[ mocker.patch("ahriman.core.gitremote.remote_pull.walk", return_value=[
Path("local") / "package1" / "PKGBUILD", local / "package1" / "PKGBUILD",
Path("local") / "package1" / ".SRCINFO", local / "package1" / ".SRCINFO",
Path("local") / "package2" / ".SRCINFO", local / "package2" / ".SRCINFO",
Path("local") / "package3" / "PKGBUILD", local / "package3" / "PKGBUILD",
Path("local") / "package3" / ".SRCINFO", local / "package3" / ".SRCINFO",
]) ])
copytree_mock = mocker.patch("shutil.copytree") copy_mock = mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.package_copy")
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init") runner = RemotePull(configuration, "x86_64", "gitremote")
runner = RemotePull(configuration, "gitremote")
runner.repo_copy(Path("local")) runner.repo_copy(local)
copytree_mock.assert_has_calls([ copy_mock.assert_has_calls([
MockCall(Path("local") / "package1", configuration.repository_paths.cache_for("package1"), dirs_exist_ok=True), MockCall(local / "package1" / "PKGBUILD"),
MockCall(Path("local") / "package3", configuration.repository_paths.cache_for("package3"), dirs_exist_ok=True), MockCall(local / "package3" / "PKGBUILD"),
])
init_mock.assert_has_calls([
MockCall(configuration.repository_paths.cache_for("package1")),
MockCall(configuration.repository_paths.cache_for("package3")),
]) ])
@ -53,7 +71,7 @@ def test_run(configuration: Configuration, mocker: MockerFixture) -> None:
must clone repo on run must clone repo on run
""" """
clone_mock = mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone") clone_mock = mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone")
runner = RemotePull(configuration, "gitremote") runner = RemotePull(configuration, "x86_64", "gitremote")
runner.run() runner.run()
clone_mock.assert_called_once_with() clone_mock.assert_called_once_with()
@ -64,7 +82,7 @@ def test_run_failed(configuration: Configuration, mocker: MockerFixture) -> None
must reraise exception on error occurred must reraise exception on error occurred
""" """
mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone", side_effect=Exception()) mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone", side_effect=Exception())
runner = RemotePull(configuration, "gitremote") runner = RemotePull(configuration, "x86_64", "gitremote")
with pytest.raises(GitRemoteError): with pytest.raises(GitRemoteError):
runner.run() runner.run()