From 2c10b06e3ce63762d7f111f7426610af8b18adb6 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Mon, 16 Mar 2026 10:06:15 +0200 Subject: [PATCH] initial impl --- .../application/application_repository.py | 4 + src/ahriman/application/handlers/add.py | 49 +++---- src/ahriman/application/handlers/rollback.py | 132 ++++++++++++++++++ src/ahriman/application/handlers/update.py | 42 +++--- 4 files changed, 187 insertions(+), 40 deletions(-) create mode 100644 src/ahriman/application/handlers/rollback.py diff --git a/src/ahriman/application/application/application_repository.py b/src/ahriman/application/application/application_repository.py index 400ace68..4f1931f5 100644 --- a/src/ahriman/application/application/application_repository.py +++ b/src/ahriman/application/application/application_repository.py @@ -162,6 +162,10 @@ class ApplicationRepository(ApplicationProperties): self.on_result(build_result) result.merge(build_result) + # filter packages which were prebuilt + succeeded = {package.base for package in build_result.success} + updates = filter(lambda package: package.base not in succeeded, updates) + builder = Updater.load(self.repository_id, self.configuration, self.repository) # ok so for now we split all packages into chunks and process each chunk accordingly diff --git a/src/ahriman/application/handlers/add.py b/src/ahriman/application/handlers/add.py index 6da7b9c5..172cec88 100644 --- a/src/ahriman/application/handlers/add.py +++ b/src/ahriman/application/handlers/add.py @@ -21,10 +21,10 @@ import argparse from ahriman.application.application import Application from ahriman.application.handlers.handler import Handler, SubParserAction +from ahriman.application.handlers.update import Update from ahriman.core.configuration import Configuration from ahriman.core.utils import enum_values, extract_user from ahriman.models.package_source import PackageSource -from ahriman.models.packagers import Packagers from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.repository_id import RepositoryId @@ -48,26 +48,7 @@ class Add(Handler): """ application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh) application.on_start() - - application.add(args.package, args.source, args.username) - patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else [] - for package in args.package: # for each requested package insert patch - for patch in patches: - application.reporter.package_patches_update(package, patch) - - if not args.now: - return - - packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False, check_files=False) - if args.changes: # generate changes if requested - application.changes(packages) - - packages = application.with_dependencies(packages, process_dependencies=args.dependencies) - packagers = Packagers(args.username, {package.base: package.packager for package in packages}) - - application.print_updates(packages, log_fn=application.logger.info) - result = application.update(packages, packagers, bump_pkgrel=args.increment) - Add.check_status(args.exit_code, not result.is_empty) + Add.perform_action(application, args) @staticmethod def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser: @@ -103,14 +84,34 @@ class Add(Handler): parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate", action=argparse.BooleanOptionalAction, default=True) parser.add_argument("-n", "--now", help="run update function after", action="store_true") - parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " - "-yy to force refresh even if up to date", - action="count", default=False) parser.add_argument("-s", "--source", help="explicitly specify the package source for this command", type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto) parser.add_argument("-u", "--username", help="build as user", default=extract_user()) parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build", action="append") + parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " + "-yy to force refresh even if up to date", + action="count", default=False) + parser.set_defaults(aur=False, check_files=False, dry_run=False, local=False, manual=True, vcs=False) return parser + @staticmethod + def perform_action(application: Application, args: argparse.Namespace) -> None: + """ + perform add action + + Args: + application(Application): application instance + args(argparse.Namespace): command line args + """ + application.add(args.package, args.source, args.username) + patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else [] + for package in args.package: # for each requested package insert patch + for patch in patches: + application.reporter.package_patches_update(package, patch) + + if not args.now: + return + Update.perform_action(application, args) + arguments = [_set_package_add_parser] diff --git a/src/ahriman/application/handlers/rollback.py b/src/ahriman/application/handlers/rollback.py new file mode 100644 index 00000000..b3a71220 --- /dev/null +++ b/src/ahriman/application/handlers/rollback.py @@ -0,0 +1,132 @@ +# +# Copyright (c) 2021-2026 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 . +# +import argparse + +from pathlib import Path + +from ahriman.application.application import Application +from ahriman.application.handlers.add import Add +from ahriman.application.handlers.handler import Handler, SubParserAction +from ahriman.core.configuration import Configuration +from ahriman.core.exceptions import UnknownPackageError +from ahriman.core.utils import extract_user +from ahriman.models.package import Package +from ahriman.models.package_source import PackageSource +from ahriman.models.repository_id import RepositoryId + + +class Rollback(Handler): + """ + package rollback handler + """ + + @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() + + package = Rollback.package_load(application, args.package, args.version) + artifacts = Rollback.package_artifacts(application, package) + + args.package = [str(artifact) for artifact in artifacts] + Add.perform_action(application, args) + + if args.hold: + application.reporter.package_hold_update(package.base, enabled=True) + + @staticmethod + def _set_package_archives_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for package archives subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("package-rollback", help="rollback package", + description="rollback package to specified version from archives") + parser.add_argument("package", help="package base") + parser.add_argument("version", help="package version") + parser.add_argument("--hold", help="hold package afterwards", + action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("-u", "--username", help="build as user", default=extract_user()) + parser.set_defaults(aur=False, changes=False, check_files=False, dependencies=False, dry_run=False, + exit_code=False, increment=False, now=True, local=False, manual=False, refresh=False, + source=PackageSource.Archive, variable=None, vcs=False) + return parser + + @staticmethod + def package_artifacts(application: Application, package: Package) -> list[Path]: + """ + look for package artifacts and returns paths to them if any + + Args: + application(Application): application instance + package(Package): package descriptor + + Returns: + list[Path]: paths to found artifacts + + Raises: + UnknownPackageError: if artifacts do not exist + """ + # lookup for built artifacts + artifacts = application.repository.package_archives_lookup(package) + if not artifacts: + raise UnknownPackageError(package.base) from None + return artifacts + + @staticmethod + def package_load(application: Application, package_base: str, version: str) -> Package: + """ + load package from given arguments + + Args: + application(Application): application instance + package_base(str): package base + version(str): package version + + Returns: + Package: loaded package + + Raises: + UnknownPackageError: if package does not exist + """ + try: + package, _ = next(iter(application.reporter.package_get(package_base))) + package.version = version + + return package + except StopIteration: + raise UnknownPackageError(package_base) from None + + arguments = [_set_package_archives_parser] diff --git a/src/ahriman/application/handlers/update.py b/src/ahriman/application/handlers/update.py index 239ef12a..5fa10f25 100644 --- a/src/ahriman/application/handlers/update.py +++ b/src/ahriman/application/handlers/update.py @@ -48,22 +48,7 @@ class Update(Handler): """ application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh) application.on_start() - - packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs, - check_files=args.check_files) - if args.changes: # generate changes if requested - application.changes(packages) - - if args.dry_run: # exit from application if no build requested - Update.check_status(args.exit_code, packages) # status code check - return - - packages = application.with_dependencies(packages, process_dependencies=args.dependencies) - packagers = Packagers(args.username, {package.base: package.packager for package in packages}) - - application.print_updates(packages, log_fn=application.logger.info) - result = application.update(packages, packagers, bump_pkgrel=args.increment) - Update.check_status(args.exit_code, not result.is_empty) + Update.perform_action(application, args) @staticmethod def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser: @@ -153,6 +138,31 @@ class Update(Handler): return print(line) if dry_run else application.logger.info(line) # pylint: disable=bad-builtin return inner + @staticmethod + def perform_action(application: Application, args: argparse.Namespace) -> None: + """ + perform update action + + Args: + application(Application): application instance + args(argparse.Namespace): command line args + """ + packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs, + check_files=args.check_files) + if args.changes: # generate changes if requested + application.changes(packages) + + if args.dry_run: # exit from application if no build requested + Update.check_status(args.exit_code, packages) # status code check + return + + packages = application.with_dependencies(packages, process_dependencies=args.dependencies) + packagers = Packagers(args.username, {package.base: package.packager for package in packages}) + + application.print_updates(packages, log_fn=application.logger.info) + result = application.update(packages, packagers, bump_pkgrel=args.increment) + Update.check_status(args.exit_code, not result.is_empty) + arguments = [ _set_repo_check_parser, _set_repo_update_parser,