Skip to content

Commit

Permalink
feat: add package copy subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
arcan1s committed Sep 27, 2024
1 parent 7bc4810 commit 1e7d4da
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 12 deletions.
8 changes: 8 additions & 0 deletions docs/ahriman.application.handlers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ ahriman.application.handlers.clean module
:no-undoc-members:
:show-inheritance:

ahriman.application.handlers.copy module
----------------------------------------

.. automodule:: ahriman.application.handlers.copy
:members:
:no-undoc-members:
:show-inheritance:

ahriman.application.handlers.daemon module
------------------------------------------

Expand Down
6 changes: 2 additions & 4 deletions docs/faq/general.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,11 @@ Before using this command you will need to create local directory and put ``PKGB
How to copy package from another repository
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

As simple as add package from archive. Considering case when you would like to copy package ``package`` with version ``ver-rel`` from repository ``source-repository`` to ``target-respository`` (same architecture), the command will be following:
It is possible to copy package and its metadata between local repositories, optionally removing the source archive, e.g.:

.. code-block:: shell
sudo -u ahriman ahriman -r target-repository package-add /var/lib/ahriman/repository/source-repository/x86_64/package-ver-rel-x86_64.pkg.tar.zst
In addition, you can remove source package as usual later.
sudo -u ahriman ahriman -r target-repository package-copy source-repository ahriman
This feature in particular useful if for managing multiple repositories like ``[testing]`` and ``[extra]``.

Expand Down
22 changes: 22 additions & 0 deletions src/ahriman/application/ahriman.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def _parser() -> argparse.ArgumentParser:
_set_package_add_parser(subparsers)
_set_package_changes_parser(subparsers)
_set_package_changes_remove_parser(subparsers)
_set_package_copy_parser(subparsers)
_set_package_remove_parser(subparsers)
_set_package_status_parser(subparsers)
_set_package_status_remove_parser(subparsers)
Expand Down Expand Up @@ -334,6 +335,27 @@ def _set_package_changes_remove_parser(root: SubParserAction) -> argparse.Argume
return parser


def _set_package_copy_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package copy subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-copy", aliases=["copy"], help="copy package from another repository",
description="copy package and its metadata from another repository",
formatter_class=_HelpFormatter)
parser.add_argument("source", help="source repository name")
parser.add_argument("package", help="package base", nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--remove", help="remove package from the source repository after", action="store_true")
parser.set_defaults(handler=handlers.Copy)
return parser


def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package removal subcommand
Expand Down
16 changes: 8 additions & 8 deletions src/ahriman/application/application/application_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,19 @@ def _add_repository(self, source: str, username: str | None) -> None:
self.database.build_queue_insert(package)
self.reporter.set_unknown(package)

def add(self, names: Iterable[str], source: PackageSource, username: str | None = None) -> None:
def add(self, packages: Iterable[str], source: PackageSource, username: str | None = None) -> None:
"""
add packages for the next build
Args:
names(Iterable[str]): list of package bases to add
packages(Iterable[str]): list of package bases to add
source(PackageSource): package source to add
username(str | None, optional): optional override of username for build process (Default value = None)
"""
for name in names:
resolved_source = source.resolve(name, self.repository.paths)
for package in packages:
resolved_source = source.resolve(package, self.repository.paths)
fn = getattr(self, f"_add_{resolved_source.value}")
fn(name, username)
fn(package, username)

def on_result(self, result: Result) -> None:
"""
Expand All @@ -167,16 +167,16 @@ def on_result(self, result: Result) -> None:
"""
raise NotImplementedError

def remove(self, names: Iterable[str]) -> Result:
def remove(self, packages: Iterable[str]) -> Result:
"""
remove packages from repository
Args:
names(Iterable[str]): list of packages (either base or name) to remove
packages(Iterable[str]): list of packages (either base or name) to remove
Returns:
Result: removal result
"""
result = self.repository.process_remove(names)
result = self.repository.process_remove(packages)
self.on_result(result)
return result
6 changes: 6 additions & 0 deletions src/ahriman/application/application/application_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ def updates(self, filter_packages: Iterable[str], *,
"""
updates = {}

# always add already built packages, because they will be always added
updates.update({
package.base: package
for package in self.repository.load_archives(self.repository.packages_built())
})

if aur:
updates.update({package.base: package for package in self.repository.updates_aur(filter_packages, vcs=vcs)})
if local:
Expand Down
1 change: 1 addition & 0 deletions src/ahriman/application/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ahriman.application.handlers.backup import Backup
from ahriman.application.handlers.change import Change
from ahriman.application.handlers.clean import Clean
from ahriman.application.handlers.copy import Copy
from ahriman.application.handlers.daemon import Daemon
from ahriman.application.handlers.dump import Dump
from ahriman.application.handlers.handler import Handler
Expand Down
95 changes: 95 additions & 0 deletions src/ahriman/application/handlers/copy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#
# Copyright (c) 2021-2024 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse

from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.repository_id import RepositoryId


class Copy(Handler):
"""
copy packages handler
"""

ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting action

@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=report)
application.on_start()

configuration_path, _ = configuration.check_loaded()
source_repository_id = RepositoryId(repository_id.architecture, args.source)
source_configuration = Configuration.from_path(configuration_path, source_repository_id)
source_application = Application(source_repository_id, source_configuration, report=report)

packages = source_application.repository.packages(args.package)
Copy.check_status(args.exit_code, packages)

for package in packages:
Copy.copy_package(package, application, source_application)

# run update
application.update([])

if args.remove:
source_application.remove(args.package)

@staticmethod
def copy_package(package: Package, application: Application, source_application: Application) -> None:
"""
copy package ``package`` from source repository to target repository
Args:
package(Package): package to copy
application(Application): application instance of the target repository
source_application(Application): application instance of the source repository
"""
# copy files
source_paths = [
str(source_application.repository.paths.repository / source.filename)
for source in package.packages.values()
if source.filename is not None
]
application.add(source_paths, PackageSource.Archive)

# copy metadata
application.reporter.package_changes_update(
package.base, source_application.reporter.package_changes_get(package.base)
)
application.reporter.package_dependencies_update(
package.base, source_application.reporter.package_dependencies_get(package.base)
)
application.reporter.package_update(package, BuildStatusEnum.Pending)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import call as MockCall

Expand Down Expand Up @@ -213,13 +214,17 @@ def test_updates_all(application_repository: ApplicationRepository, package_ahri
"""
must get updates for all
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur",
return_value=[package_ahriman])
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates([], aur=True, local=True, manual=True, vcs=True, check_files=True)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_called_once_with([], vcs=True)
updates_local_mock.assert_called_once_with(vcs=True)
updates_manual_mock.assert_called_once_with()
Expand All @@ -230,12 +235,16 @@ def test_updates_disabled(application_repository: ApplicationRepository, mocker:
"""
must get updates without anything
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates([], aur=False, local=False, manual=False, vcs=True, check_files=False)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_not_called()
updates_local_mock.assert_not_called()
updates_manual_mock.assert_not_called()
Expand All @@ -246,12 +255,16 @@ def test_updates_no_aur(application_repository: ApplicationRepository, mocker: M
"""
must get updates without aur
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates([], aur=False, local=True, manual=True, vcs=True, check_files=True)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_not_called()
updates_local_mock.assert_called_once_with(vcs=True)
updates_manual_mock.assert_called_once_with()
Expand All @@ -262,12 +275,16 @@ def test_updates_no_local(application_repository: ApplicationRepository, mocker:
"""
must get updates without local packages
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates([], aur=True, local=False, manual=True, vcs=True, check_files=True)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_called_once_with([], vcs=True)
updates_local_mock.assert_not_called()
updates_manual_mock.assert_called_once_with()
Expand All @@ -278,12 +295,16 @@ def test_updates_no_manual(application_repository: ApplicationRepository, mocker
"""
must get updates without manual
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates([], aur=True, local=True, manual=False, vcs=True, check_files=True)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_called_once_with([], vcs=True)
updates_local_mock.assert_called_once_with(vcs=True)
updates_manual_mock.assert_not_called()
Expand All @@ -294,12 +315,16 @@ def test_updates_no_vcs(application_repository: ApplicationRepository, mocker: M
"""
must get updates without VCS
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates([], aur=True, local=True, manual=True, vcs=False, check_files=True)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_called_once_with([], vcs=False)
updates_local_mock.assert_called_once_with(vcs=False)
updates_manual_mock.assert_called_once_with()
Expand All @@ -310,12 +335,16 @@ def test_updates_no_check_files(application_repository: ApplicationRepository, m
"""
must get updates without checking broken links
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates([], aur=True, local=True, manual=True, vcs=True, check_files=False)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_called_once_with([], vcs=True)
updates_local_mock.assert_called_once_with(vcs=True)
updates_manual_mock.assert_called_once_with()
Expand All @@ -326,12 +355,16 @@ def test_updates_with_filter(application_repository: ApplicationRepository, mock
"""
must get updates with filter
"""
path = Path("local")
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[path])
updates_built_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
updates_deps_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_dependencies")

application_repository.updates(["filter"], aur=True, local=True, manual=True, vcs=True, check_files=True)
updates_built_mock.assert_called_once_with([path])
updates_aur_mock.assert_called_once_with(["filter"], vcs=True)
updates_local_mock.assert_called_once_with(vcs=True)
updates_manual_mock.assert_called_once_with()
Expand Down
Loading

0 comments on commit 1e7d4da

Please sign in to comment.