Local packages support improvements (#104)

* handle git author correctly
* make remote source required argument
This commit is contained in:
Evgenii Alekseev 2023-08-11 18:17:23 +03:00 committed by Evgeniy Alekseev
parent 1f2d56e605
commit 95e29d16bb
43 changed files with 451 additions and 232 deletions

View File

@ -76,6 +76,14 @@ ahriman.core.database.migrations.m008\_packagers module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.migrations.m009\_local\_source module
-----------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m009_local_source
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -179,7 +179,8 @@ Available options are:
Remote push trigger Remote push trigger
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
* ``commit_author`` - git commit author, string, optional. In case if not set, the git will generate author for you. Note, however, that in this case it will disclosure your hostname. * ``commit_email`` - git commit email, string, optional, default is ``ahriman@localhost``.
* ``commit_user`` - git commit user, string, optional, default is ``ahriman``.
* ``push_url`` - url of the remote repository to which PKGBUILDs should be pushed after build process, string, required. * ``push_url`` - url of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
* ``push_branch`` - branch of the remote repository to which PKGBUILDs should be pushed after build process, string, optional, default is ``master``. * ``push_branch`` - branch of the remote repository to which PKGBUILDs should be pushed after build process, string, optional, default is ``master``.

View File

@ -112,7 +112,7 @@
const payload = response.map(description => { const payload = response.map(description => {
const package_base = description.package.base; const package_base = description.package.base;
const web_url = description.package.remote?.web_url; const web_url = description.package.remote.web_url;
return { return {
id: package_base, id: package_base,
base: web_url ? `<a href="${safe(web_url)}" title="${safe(package_base)}">${safe(package_base)}</a>` : safe(package_base), base: web_url ? `<a href="${safe(web_url)}" title="${safe(package_base)}">${safe(package_base)}</a>` : safe(package_base),

View File

@ -95,7 +95,7 @@ class ApplicationPackages(ApplicationProperties):
if (source_dir := Path(source)).is_dir(): if (source_dir := Path(source)).is_dir():
package = Package.from_build(source_dir, self.architecture, username) package = Package.from_build(source_dir, self.architecture, username)
cache_dir = self.repository.paths.cache_for(package.base) cache_dir = self.repository.paths.cache_for(package.base)
shutil.copytree(source_dir, cache_dir) # copy package to store in caches shutil.copytree(source_dir, cache_dir, dirs_exist_ok=True) # copy package to store in caches
Sources.init(cache_dir) # we need to run init command in directory where we do have permissions Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
elif (source_dir := self.repository.paths.cache_for(source)).is_dir(): elif (source_dir := self.repository.paths.cache_for(source)).is_dir():
package = Package.from_build(source_dir, self.architecture, username) package = Package.from_build(source_dir, self.architecture, username)
@ -145,7 +145,7 @@ class ApplicationPackages(ApplicationProperties):
username(str | None, optional): optional override of username for build process (Default value = None) username(str | None, optional): optional override of username for build process (Default value = None)
""" """
for name in names: for name in names:
resolved_source = source.resolve(name) resolved_source = source.resolve(name, self.repository.paths)
fn = getattr(self, f"_add_{resolved_source.value}") fn = getattr(self, f"_add_{resolved_source.value}")
fn(name, username) fn(name, username)

View File

@ -36,9 +36,11 @@ class Sources(LazyLogging):
Attributes: Attributes:
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories. DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages
DEFAULT_COMMIT_AUTHOR(tuple[str, str]): (class attribute) default commit author to be used if none set
""" """
DEFAULT_BRANCH = "master" # default fallback branch DEFAULT_BRANCH = "master" # default fallback branch
DEFAULT_COMMIT_AUTHOR = ("ahriman", "ahriman@localhost")
_check_output = check_output _check_output = check_output
@ -61,13 +63,13 @@ class Sources(LazyLogging):
return [PkgbuildPatch("arch", list(architectures))] return [PkgbuildPatch("arch", list(architectures))]
@staticmethod @staticmethod
def fetch(sources_dir: Path, remote: RemoteSource | None) -> None: def fetch(sources_dir: Path, remote: RemoteSource) -> None:
""" """
either clone repository or update it to origin/``remote.branch`` either clone repository or update it to origin/``remote.branch``
Args: Args:
sources_dir(Path): local path to fetch sources_dir(Path): local path to fetch
remote(RemoteSource | None): remote target (from where to fetch) remote(RemoteSource): remote target (from where to fetch)
""" """
instance = Sources() instance = Sources()
# local directory exists and there is .git directory # local directory exists and there is .git directory
@ -77,11 +79,11 @@ class Sources(LazyLogging):
instance.logger.info("skip update at %s because there are no branches configured", sources_dir) instance.logger.info("skip update at %s because there are no branches configured", sources_dir)
return return
branch = remote.branch if remote is not None else instance.DEFAULT_BRANCH branch = remote.branch or instance.DEFAULT_BRANCH
if is_initialized_git: if is_initialized_git:
instance.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch) instance.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
Sources._check_output("git", "fetch", "origin", branch, cwd=sources_dir, logger=instance.logger) Sources._check_output("git", "fetch", "origin", branch, cwd=sources_dir, logger=instance.logger)
elif remote is not None: elif remote.git_url is not None:
instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch) instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
Sources._check_output("git", "clone", "--branch", branch, "--single-branch", Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
remote.git_url, str(sources_dir), cwd=sources_dir.parent, logger=instance.logger) remote.git_url, str(sources_dir), cwd=sources_dir.parent, logger=instance.logger)
@ -95,7 +97,7 @@ class Sources(LazyLogging):
# move content if required # move content if required
# we are using full path to source directory in order to make append possible # we are using full path to source directory in order to make append possible
pkgbuild_dir = remote.pkgbuild_dir if remote is not None else sources_dir.resolve() pkgbuild_dir = remote.pkgbuild_dir or sources_dir.resolve()
instance.move((sources_dir / pkgbuild_dir).resolve(), sources_dir) instance.move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
@staticmethod @staticmethod
@ -122,14 +124,16 @@ class Sources(LazyLogging):
sources_dir(Path): local path to sources sources_dir(Path): local path to sources
""" """
instance = Sources() instance = Sources()
Sources._check_output("git", "init", "--initial-branch", instance.DEFAULT_BRANCH, if not (sources_dir / ".git").is_dir():
cwd=sources_dir, logger=instance.logger) # skip initializing in case if it was already
Sources._check_output("git", "init", "--initial-branch", instance.DEFAULT_BRANCH,
cwd=sources_dir, logger=instance.logger)
# extract local files... # extract local files...
files = ["PKGBUILD", ".SRCINFO"] + [str(path) for path in Package.local_files(sources_dir)] files = ["PKGBUILD", ".SRCINFO"] + [str(path) for path in Package.local_files(sources_dir)]
instance.add(sources_dir, *files) instance.add(sources_dir, *files)
# ...and commit them # ...and commit them
instance.commit(sources_dir, author="ahriman <ahriman@localhost>") instance.commit(sources_dir)
@staticmethod @staticmethod
def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> None: def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> None:
@ -170,7 +174,8 @@ class Sources(LazyLogging):
return f"{diff}\n" # otherwise, patch will be broken return f"{diff}\n" # otherwise, patch will be broken
@staticmethod @staticmethod
def push(sources_dir: Path, remote: RemoteSource, *pattern: str, commit_author: str | None = None) -> None: def push(sources_dir: Path, remote: RemoteSource, *pattern: str,
commit_author: tuple[str, str] | None = None) -> None:
""" """
commit selected changes and push files to the remote repository commit selected changes and push files to the remote repository
@ -178,13 +183,15 @@ class Sources(LazyLogging):
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
remote(RemoteSource): remote target, branch and url remote(RemoteSource): remote target, branch and url
*pattern(str): glob patterns *pattern(str): glob patterns
commit_author(str | None, optional): commit author in form of git config (i.e. ``user <user@host>``) commit_author(tuple[str, str] | None, optional): commit author if any (Default value = None)
(Default value = None)
""" """
instance = Sources() instance = Sources()
instance.add(sources_dir, *pattern) instance.add(sources_dir, *pattern)
instance.commit(sources_dir, author=commit_author) if not instance.commit(sources_dir, commit_author=commit_author):
Sources._check_output("git", "push", remote.git_url, remote.branch, cwd=sources_dir, logger=instance.logger) return # no changes to push, just skip action
git_url, branch = remote.git_source()
Sources._check_output("git", "push", git_url, branch, cwd=sources_dir, logger=instance.logger)
def add(self, sources_dir: Path, *pattern: str, intent_to_add: bool = False) -> None: def add(self, sources_dir: Path, *pattern: str, intent_to_add: bool = False) -> None:
""" """
@ -208,7 +215,8 @@ class Sources(LazyLogging):
Sources._check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files], Sources._check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
cwd=sources_dir, logger=self.logger) cwd=sources_dir, logger=self.logger)
def commit(self, sources_dir: Path, message: str | None = None, author: str | None = None) -> None: def commit(self, sources_dir: Path, message: str | None = None,
commit_author: tuple[str, str] | None = None) -> bool:
""" """
commit changes commit changes
@ -216,14 +224,28 @@ class Sources(LazyLogging):
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
message(str | None, optional): optional commit message if any. If none set, message will be generated message(str | None, optional): optional commit message if any. If none set, message will be generated
according to the current timestamp (Default value = None) according to the current timestamp (Default value = None)
author(str | None, optional): optional commit author if any (Default value = None) commit_author(tuple[str, str] | None, optional): optional commit author if any (Default value = None)
Returns:
bool: True in case if changes have been committed and False otherwise
""" """
if not self.has_changes(sources_dir):
return False # nothing to commit
if message is None: if message is None:
message = f"Autogenerated commit at {utcnow()}" message = f"Autogenerated commit at {utcnow()}"
args = ["--allow-empty", "--message", message] args = ["--message", message]
if author is not None: environment: dict[str, str] = {}
args.extend(["--author", author])
Sources._check_output("git", "commit", *args, cwd=sources_dir, logger=self.logger) if commit_author is None:
commit_author = self.DEFAULT_COMMIT_AUTHOR
user, email = commit_author
environment["GIT_AUTHOR_NAME"] = environment["GIT_COMMITTER_NAME"] = user
environment["GIT_AUTHOR_EMAIL"] = environment["GIT_COMMITTER_EMAIL"] = email
Sources._check_output("git", "commit", *args, cwd=sources_dir, logger=self.logger, environment=environment)
return True
def diff(self, sources_dir: Path) -> str: def diff(self, sources_dir: Path) -> str:
""" """
@ -237,6 +259,20 @@ class Sources(LazyLogging):
""" """
return Sources._check_output("git", "diff", cwd=sources_dir, logger=self.logger) return Sources._check_output("git", "diff", cwd=sources_dir, logger=self.logger)
def has_changes(self, sources_dir: Path) -> bool:
"""
check if there are changes in current git tree
Args:
sources_dir(Path): local path to git repository
Returns:
bool: True if there are uncommitted changes and False otherwise
"""
# there is --exit-code argument to diff, however, there might be other process errors
changes = Sources._check_output("git", "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger)
return bool(changes)
def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None: def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None:
""" """
move content from pkgbuild_dir to sources_dir move content from pkgbuild_dir to sources_dir

View File

@ -48,7 +48,7 @@ class Configuration(configparser.RawConfigParser):
>>> from pathlib import Path >>> from pathlib import Path
>>> >>>
>>> configuration = Configuration.from_path(Path("/etc/ahriman.ini"), "x86_64", quiet=False) >>> configuration = Configuration.from_path(Path("/etc/ahriman.ini"), "x86_64")
>>> repository_name = configuration.get("repository", "name") >>> repository_name = configuration.get("repository", "name")
>>> makepkg_flags = configuration.getlist("build", "makepkg_flags") >>> makepkg_flags = configuration.getlist("build", "makepkg_flags")

View File

@ -70,6 +70,7 @@ def migrate_package_remotes(connection: Connection, paths: RepositoryPaths) -> N
connection(Connection): database connection connection(Connection): database connection
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
""" """
from ahriman.core.alpm.remote import AUR
from ahriman.core.database.operations import PackageOperations from ahriman.core.database.operations import PackageOperations
def insert_remote(base: str, remote: RemoteSource) -> None: def insert_remote(base: str, remote: RemoteSource) -> None:
@ -92,7 +93,11 @@ def migrate_package_remotes(connection: Connection, paths: RepositoryPaths) -> N
local_cache = paths.cache_for(package_base) local_cache = paths.cache_for(package_base)
if local_cache.exists() and not package.is_vcs: if local_cache.exists() and not package.is_vcs:
continue # skip packages which are not VCS and with local cache continue # skip packages which are not VCS and with local cache
remote_source = RemoteSource.from_source(PackageSource.AUR, package_base, "aur") remote_source = RemoteSource(
if remote_source is None: source=PackageSource.AUR,
continue # should never happen git_url=AUR.remote_git_url(package_base, "aur"),
web_url=AUR.remote_web_url(package_base),
path=".",
branch="master",
)
insert_remote(package_base, remote_source) insert_remote(package_base, remote_source)

View File

@ -66,7 +66,7 @@ def migrate_package_depends(connection: Connection, configuration: Configuration
package_list = [] package_list = []
for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()): for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()):
base = Package.from_archive(full_path, pacman, remote=None) base = Package.from_archive(full_path, pacman)
for package, description in base.packages.items(): for package, description in base.packages.items():
package_list.append({ package_list.append({
"make_depends": description.make_depends, "make_depends": description.make_depends,

View File

@ -63,7 +63,7 @@ def migrate_package_check_depends(connection: Connection, configuration: Configu
package_list = [] package_list = []
for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()): for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()):
base = Package.from_archive(full_path, pacman, remote=None) base = Package.from_archive(full_path, pacman)
for package, description in base.packages.items(): for package, description in base.packages.items():
package_list.append({ package_list.append({
"check_depends": description.check_depends, "check_depends": description.check_depends,

View File

@ -69,7 +69,7 @@ def migrate_package_base_packager(connection: Connection, configuration: Configu
package_list = [] package_list = []
for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()): for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()):
package = Package.from_archive(full_path, pacman, remote=None) package = Package.from_archive(full_path, pacman)
package_list.append({ package_list.append({
"package_base": package.base, "package_base": package.base,
"packager": package.packager, "packager": package.packager,

View File

@ -0,0 +1,27 @@
#
# Copyright (c) 2021-2023 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/>.
#
__all__ = ["steps"]
steps = [
"""
update package_bases set source = 'local' where source is null
""",
]

View File

@ -86,11 +86,11 @@ class PackageOperations(Operations):
{ {
"package_base": package.base, "package_base": package.base,
"version": package.version, "version": package.version,
"branch": package.remote.branch if package.remote is not None else None, "branch": package.remote.branch,
"git_url": package.remote.git_url if package.remote is not None else None, "git_url": package.remote.git_url,
"path": package.remote.path if package.remote is not None else None, "path": package.remote.path,
"web_url": package.remote.web_url if package.remote is not None else None, "web_url": package.remote.web_url,
"source": package.remote.source.value if package.remote is not None else None, "source": package.remote.source.value,
"packager": package.packager, "packager": package.packager,
} }
) )
@ -270,5 +270,4 @@ class PackageOperations(Operations):
return { return {
package_base: package.remote package_base: package.remote
for package_base, package in packages.items() for package_base, package in packages.items()
if package.remote is not None
} }

View File

@ -39,7 +39,7 @@ class RemotePush(LazyLogging):
sync PKGBUILDs to remote repository after actions sync PKGBUILDs to remote repository after actions
Attributes: Attributes:
commit_author(str | None): optional commit author in form of git config (i.e. ``user <user@host>``) commit_author(tuple[str, str] | None): optional commit author in form of git config
database(SQLite): database instance database(SQLite): database instance
remote_source(RemoteSource): repository remote source (remote pull url and branch) remote_source(RemoteSource): repository remote source (remote pull url and branch)
""" """
@ -54,7 +54,11 @@ class RemotePush(LazyLogging):
section(str): settings section name section(str): settings section name
""" """
self.database = database self.database = database
self.commit_author = configuration.get(section, "commit_author", fallback=None)
commit_email = configuration.get(section, "commit_email", fallback="ahriman@localhost")
commit_user = configuration.get(section, "commit_user", fallback="ahriman")
self.commit_author = (commit_user, commit_email)
self.remote_source = RemoteSource( self.remote_source = RemoteSource(
git_url=configuration.get(section, "push_url"), git_url=configuration.get(section, "push_url"),
web_url="", web_url="",

View File

@ -49,7 +49,10 @@ class RemotePushTrigger(Trigger):
"gitremote": { "gitremote": {
"type": "dict", "type": "dict",
"schema": { "schema": {
"commit_author": { "commit_email": {
"type": "string",
},
"commit_user": {
"type": "string", "type": "string",
}, },
"push_url": { "push_url": {

View File

@ -117,8 +117,9 @@ class Repository(Executor, UpdateHandler):
# we are iterating over bases, not single packages # we are iterating over bases, not single packages
for full_path in packages: for full_path in packages:
try: try:
local = Package.from_archive(full_path, self.pacman, None) local = Package.from_archive(full_path, self.pacman)
local.remote = sources.get(local.base) if (source := sources.get(local.base)) is not None:
local.remote = source
current = result.setdefault(local.base, local) current = result.setdefault(local.base, local)
if current.version != local.version: if current.version != local.version:

View File

@ -24,6 +24,7 @@ from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.cleaner import Cleaner
from ahriman.models.package import Package 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
class UpdateHandler(Cleaner): class UpdateHandler(Cleaner):
@ -55,12 +56,10 @@ class UpdateHandler(Cleaner):
list[Package]: list of packages which are out-of-dated list[Package]: list of packages which are out-of-dated
""" """
def load_remote(package: Package) -> Package: def load_remote(package: Package) -> Package:
source = package.remote.source if package.remote is not None else None
# try to load package from base and if none found try to load by separated packages # try to load package from base and if none found try to load by separated packages
for probe in [package.base] + sorted(package.packages.keys()): for probe in [package.base] + sorted(package.packages.keys()):
try: try:
if source == PackageSource.Repository: if package.remote.source == PackageSource.Repository:
return Package.from_official(probe, self.pacman, None) return Package.from_official(probe, self.pacman, None)
return Package.from_aur(probe, self.pacman, None) return Package.from_aur(probe, self.pacman, None)
except UnknownPackageError: except UnknownPackageError:
@ -71,6 +70,8 @@ class UpdateHandler(Cleaner):
for local in self.packages(): for local in self.packages():
with self.in_package_context(local.base): with self.in_package_context(local.base):
if not local.remote.is_remote:
continue # avoid checking local packages
if local.base in self.ignore_list: if local.base in self.ignore_list:
continue continue
if filter_packages and local.base not in filter_packages: if filter_packages and local.base not in filter_packages:
@ -107,7 +108,15 @@ class UpdateHandler(Cleaner):
for cache_dir in self.paths.cache.iterdir(): for cache_dir in self.paths.cache.iterdir():
with self.in_package_context(cache_dir.name): with self.in_package_context(cache_dir.name):
try: try:
Sources.fetch(cache_dir, remote=None) source = RemoteSource(
source=PackageSource.Local,
git_url=cache_dir.absolute().as_uri(),
web_url="",
path=".",
branch="master",
)
Sources.fetch(cache_dir, source)
remote = Package.from_build(cache_dir, self.architecture, None) remote = Package.from_build(cache_dir, self.architecture, None)
local = packages.get(remote.base) local = packages.get(remote.base)

View File

@ -27,7 +27,6 @@ from multiprocessing import Process, Queue
from threading import Lock, Thread from threading import Lock, Thread
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.models.package_source import PackageSource
class Spawn(Thread, LazyLogging): class Spawn(Thread, LazyLogging):
@ -133,8 +132,7 @@ class Spawn(Thread, LazyLogging):
username(str | None): optional override of username for build process username(str | None): optional override of username for build process
now(bool): build packages now now(bool): build packages now
""" """
# avoid abusing by building non-aur packages kwargs = {"username": username}
kwargs = {"source": PackageSource.AUR.value, "username": username}
if now: if now:
kwargs["now"] = "" kwargs["now"] = ""
self._spawn_process("package-add", *packages, **kwargs) self._spawn_process("package-add", *packages, **kwargs)

View File

@ -304,7 +304,7 @@ def parse_version(version: str) -> tuple[str | None, str, str]:
def partition(source: list[T], predicate: Callable[[T], bool]) -> tuple[list[T], list[T]]: def partition(source: list[T], predicate: Callable[[T], bool]) -> tuple[list[T], list[T]]:
""" """
partition list into two based on predicate, based on # https://docs.python.org/dev/library/itertools.html#itertools-recipes partition list into two based on predicate, based on https://docs.python.org/dev/library/itertools.html#itertools-recipes
Args: Args:
source(list[T]): source list to be partitioned source(list[T]): source list to be partitioned

View File

@ -66,13 +66,12 @@ class AURPackage:
>>> package = AURPackage.from_repo(metadata) # load package from official repository RPC >>> package = AURPackage.from_repo(metadata) # load package from official repository RPC
>>> # properties of the class are built based on ones from AUR RPC, thus additional method is required >>> # properties of the class are built based on ones from AUR RPC, thus additional method is required
>>> >>>
>>>
>>> from ahriman.core.alpm.pacman import Pacman >>> from ahriman.core.alpm.pacman import Pacman
>>> from ahriman.core.configuration import Configuration >>> from ahriman.core.configuration import Configuration
>>> >>>
>>> configuration = Configuration() >>> configuration = Configuration()
>>> pacman = Pacman("x86_64", configuration) >>> pacman = Pacman("x86_64", configuration)
>>> metadata = pacman.get("pacman") >>> metadata = pacman.package_get("pacman")
>>> package = AURPackage.from_pacman(next(metadata)) # load package from pyalpm wrapper >>> package = AURPackage.from_pacman(next(metadata)) # load package from pyalpm wrapper
""" """

View File

@ -51,7 +51,7 @@ class Package(LazyLogging):
packager(str | None): package packager if available packager(str | None): package packager if available
packages(dict[str, PackageDescription): map of package names to their properties. packages(dict[str, PackageDescription): map of package names to their properties.
Filled only on load from archive Filled only on load from archive
remote(RemoteSource | None): package remote source if applicable remote(RemoteSource): package remote source if applicable
version(str): package full version version(str): package full version
Examples: Examples:
@ -61,7 +61,7 @@ class Package(LazyLogging):
it will contain every data available in the json body. Otherwise, if generate package from local archive:: it will contain every data available in the json body. Otherwise, if generate package from local archive::
>>> package = Package.from_archive(local_path, pacman, remote=None) >>> package = Package.from_archive(local_path, pacman)
it will probably miss file descriptions (in case if there are multiple packages which belong to the base). it will probably miss file descriptions (in case if there are multiple packages which belong to the base).
@ -76,7 +76,7 @@ class Package(LazyLogging):
base: str base: str
version: str version: str
remote: RemoteSource | None remote: RemoteSource
packages: dict[str, PackageDescription] packages: dict[str, PackageDescription]
packager: str | None = None packager: str | None = None
@ -192,22 +192,26 @@ class Package(LazyLogging):
return sorted(packages) return sorted(packages)
@classmethod @classmethod
def from_archive(cls, path: Path, pacman: Pacman, remote: RemoteSource | None) -> Self: def from_archive(cls, path: Path, pacman: Pacman) -> Self:
""" """
construct package properties from package archive construct package properties from package archive
Args: Args:
path(Path): path to package archive path(Path): path to package archive
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
remote(RemoteSource): package remote source if applicable
Returns: Returns:
Self: package properties Self: package properties
""" """
package = pacman.handle.load_pkg(str(path)) package = pacman.handle.load_pkg(str(path))
description = PackageDescription.from_package(package, path) description = PackageDescription.from_package(package, path)
return cls(base=package.base, version=package.version, remote=remote, packages={package.name: description}, return cls(
packager=package.packager) base=package.base,
version=package.version,
remote=RemoteSource(source=PackageSource.Archive),
packages={package.name: description},
packager=package.packager,
)
@classmethod @classmethod
def from_aur(cls, name: str, pacman: Pacman, packager: str | None = None) -> Self: def from_aur(cls, name: str, pacman: Pacman, packager: str | None = None) -> Self:
@ -223,7 +227,15 @@ class Package(LazyLogging):
Self: package properties Self: package properties
""" """
package = AUR.info(name, pacman=pacman) package = AUR.info(name, pacman=pacman)
remote = RemoteSource.from_source(PackageSource.AUR, package.package_base, package.repository)
remote = RemoteSource(
source=PackageSource.AUR,
git_url=AUR.remote_git_url(package.package_base, package.repository),
web_url=AUR.remote_web_url(package.package_base),
path=".",
branch="master",
)
return cls( return cls(
base=package.package_base, base=package.package_base,
version=package.version, version=package.version,
@ -265,14 +277,20 @@ class Package(LazyLogging):
version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"]) version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"])
remote = RemoteSource( remote = RemoteSource(
source=PackageSource.Local,
git_url=path.absolute().as_uri(), git_url=path.absolute().as_uri(),
web_url="", web_url=None,
path=".", path=".",
branch="master", branch="master",
source=PackageSource.Local,
) )
return cls(base=srcinfo["pkgbase"], version=version, remote=remote, packages=packages, packager=packager) return cls(
base=srcinfo["pkgbase"],
version=version,
remote=remote,
packages=packages,
packager=packager,
)
@classmethod @classmethod
def from_json(cls, dump: dict[str, Any]) -> Self: def from_json(cls, dump: dict[str, Any]) -> Self:
@ -291,8 +309,13 @@ class Package(LazyLogging):
for key, value in packages_json.items() for key, value in packages_json.items()
} }
remote = dump.get("remote") or {} remote = dump.get("remote") or {}
return cls(base=dump["base"], version=dump["version"], remote=RemoteSource.from_json(remote), packages=packages, return cls(
packager=dump.get("packager")) base=dump["base"],
version=dump["version"],
remote=RemoteSource.from_json(remote),
packages=packages,
packager=dump.get("packager"),
)
@classmethod @classmethod
def from_official(cls, name: str, pacman: Pacman, packager: str | None = None, *, use_syncdb: bool = True) -> Self: def from_official(cls, name: str, pacman: Pacman, packager: str | None = None, *, use_syncdb: bool = True) -> Self:
@ -309,7 +332,15 @@ class Package(LazyLogging):
Self: package properties Self: package properties
""" """
package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman) package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman)
remote = RemoteSource.from_source(PackageSource.Repository, package.package_base, package.repository)
remote = RemoteSource(
source=PackageSource.Repository,
git_url=Official.remote_git_url(package.package_base, package.repository),
web_url=Official.remote_web_url(package.package_base),
path=".",
branch="main",
)
return cls( return cls(
base=package.package_base, base=package.package_base,
version=package.version, version=package.version,

View File

@ -53,14 +53,13 @@ class PackageDescription:
>>> description = PackageDescription.from_json(dump) >>> description = PackageDescription.from_json(dump)
>>> >>>
>>>
>>> from pathlib import Path >>> from pathlib import Path
>>> from ahriman.core.alpm.pacman import Pacman >>> from ahriman.core.alpm.pacman import Pacman
>>> from ahriman.core.configuration import Configuration >>> from ahriman.core.configuration import Configuration
>>> >>>
>>> configuration = Configuration() >>> configuration = Configuration()
>>> pacman = Pacman("x86_64", configuration) >>> pacman = Pacman("x86_64", configuration)
>>> pyalpm_description = next(package for package in pacman.get("pacman")) >>> pyalpm_description = next(package for package in pacman.package_get("pacman"))
>>> description = PackageDescription.from_package( >>> description = PackageDescription.from_package(
>>> pyalpm_description, Path("/var/cache/pacman/pkg/pacman-6.0.1-4-x86_64.pkg.tar.zst")) >>> pyalpm_description, Path("/var/cache/pacman/pkg/pacman-6.0.1-4-x86_64.pkg.tar.zst"))
""" """

View File

@ -24,6 +24,7 @@ from pathlib import Path
from urllib.parse import urlparse from urllib.parse import urlparse
from ahriman.core.util import package_like from ahriman.core.util import package_like
from ahriman.models.repository_paths import RepositoryPaths
class PackageSource(str, Enum): class PackageSource(str, Enum):
@ -42,7 +43,7 @@ class PackageSource(str, Enum):
Examples: Examples:
In case if source is unknown the ``resolve()`` and the source descriptor is available method must be used:: In case if source is unknown the ``resolve()`` and the source descriptor is available method must be used::
>>> real_source = PackageSource.Auto.resolve("ahriman") >>> real_source = PackageSource.Auto.resolve("ahriman", configuration.repository_paths)
the code above will ensure that the presudo-source ``PackageSource.Auto`` will not be processed later. the code above will ensure that the presudo-source ``PackageSource.Auto`` will not be processed later.
""" """
@ -55,12 +56,13 @@ class PackageSource(str, Enum):
Remote = "remote" Remote = "remote"
Repository = "repository" Repository = "repository"
def resolve(self, source: str) -> PackageSource: def resolve(self, source: str, paths: RepositoryPaths) -> PackageSource:
""" """
resolve auto into the correct type resolve auto into the correct type
Args: Args:
source(str): source of the package source(str): source of the package
paths(RepositoryPaths): repository paths instance
Returns: Returns:
PackageSource: non-auto type of the package source PackageSource: non-auto type of the package source
@ -74,7 +76,7 @@ class PackageSource(str, Enum):
if maybe_url.scheme and maybe_url.scheme not in ("data", "file") and package_like(maybe_path): if maybe_url.scheme and maybe_url.scheme not in ("data", "file") and package_like(maybe_path):
return PackageSource.Remote return PackageSource.Remote
try: try:
if (maybe_path / "PKGBUILD").is_file(): if (maybe_path / "PKGBUILD").is_file() or paths.cache_for(source).is_dir():
return PackageSource.Local return PackageSource.Local
if maybe_path.is_dir(): if maybe_path.is_dir():
return PackageSource.Directory return PackageSource.Directory

View File

@ -21,6 +21,7 @@ from dataclasses import dataclass, fields
from pathlib import Path from pathlib import Path
from typing import Any, Self from typing import Any, Self
from ahriman.core.exceptions import InitializeError
from ahriman.core.util import dataclass_view, filter_json from ahriman.core.util import dataclass_view, filter_json
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
@ -31,18 +32,18 @@ class RemoteSource:
remote package source properties remote package source properties
Attributes: Attributes:
branch(str): branch of the git repository branch(str | None): branch of the git repository
git_url(str): url of the git repository git_url(str | None): url of the git repository
path(str): path to directory with PKGBUILD inside the git repository path(str | None): path to directory with PKGBUILD inside the git repository
source(PackageSource): package source pointer used by some parsers source(PackageSource): package source pointer used by some parsers
web_url(str): url of the package in the web interface web_url(str | None): url of the package in the web interface
""" """
git_url: str
web_url: str
path: str
branch: str
source: PackageSource source: PackageSource
git_url: str | None = None
web_url: str | None = None
path: str | None = None
branch: str | None = None
def __post_init__(self) -> None: def __post_init__(self) -> None:
""" """
@ -51,17 +52,27 @@ class RemoteSource:
object.__setattr__(self, "source", PackageSource(self.source)) object.__setattr__(self, "source", PackageSource(self.source))
@property @property
def pkgbuild_dir(self) -> Path: def is_remote(self) -> bool:
"""
check if source is remote
Returns:
bool: True in case if package is well-known remote source (e.g. AUR) and False otherwise
"""
return self.source in (PackageSource.AUR, PackageSource.Repository)
@property
def pkgbuild_dir(self) -> Path | None:
""" """
get path to directory with package sources (PKGBUILD etc) get path to directory with package sources (PKGBUILD etc)
Returns: Returns:
Path: path to directory with package sources based on settings Path | None: path to directory with package sources based on settings if available
""" """
return Path(self.path) return Path(self.path) if self.path is not None else None
@classmethod @classmethod
def from_json(cls, dump: dict[str, Any]) -> Self | None: def from_json(cls, dump: dict[str, Any]) -> Self:
""" """
construct remote source from the json dump (or database row) construct remote source from the json dump (or database row)
@ -69,47 +80,25 @@ class RemoteSource:
dump(dict[str, Any]): json dump body dump(dict[str, Any]): json dump body
Returns: Returns:
Self | None: remote source Self: remote source
""" """
# filter to only known fields # filter to only known fields
known_fields = [pair.name for pair in fields(cls)] known_fields = [pair.name for pair in fields(cls)]
dump = filter_json(dump, known_fields) return cls(**filter_json(dump, known_fields))
if dump:
return cls(**dump)
return None
@classmethod def git_source(self) -> tuple[str, str]:
def from_source(cls, source: PackageSource, package_base: str, repository: str) -> Self | None:
""" """
generate remote source from the package base get git source if available
Args:
source(PackageSource): source of the package
package_base(str): package base
repository(str): repository name
Returns: Returns:
Self | None: generated remote source if any, None otherwise tuple[str, str]: git url and branch
Raises:
InitializeError: in case if git url and/or branch are not set
""" """
if source == PackageSource.AUR: if self.git_url is None or self.branch is None:
from ahriman.core.alpm.remote import AUR raise InitializeError("Remote source is empty")
return cls( return self.git_url, self.branch
git_url=AUR.remote_git_url(package_base, repository),
web_url=AUR.remote_web_url(package_base),
path=".",
branch="master",
source=source,
)
if source == PackageSource.Repository:
from ahriman.core.alpm.remote import Official
return cls(
git_url=Official.remote_git_url(package_base, repository),
web_url=Official.remote_web_url(package_base),
path=".",
branch="main",
source=source,
)
return None
def view(self) -> dict[str, Any]: def view(self) -> dict[str, Any]:
""" """

View File

@ -41,7 +41,7 @@ class User:
Simply create user from database data and perform required validation:: Simply create user from database data and perform required validation::
>>> password = User.generate_password(24) >>> password = User.generate_password(24)
>>> user = User(username="ahriman", password=password, access=UserAccess.Full, packager_id=None, key=None) >>> user = User(username="ahriman", password=password, access=UserAccess.Full)
Since the password supplied may be plain text, the ``hash_password`` method can be used to hash the password:: Since the password supplied may be plain text, the ``hash_password`` method can be used to hash the password::
@ -63,8 +63,8 @@ class User:
username: str username: str
password: str password: str
access: UserAccess access: UserAccess
packager_id: str | None packager_id: str | None = None
key: str | None key: str | None = None
_HASHER = sha512_crypt _HASHER = sha512_crypt

View File

@ -27,22 +27,22 @@ class RemoteSchema(Schema):
request and response package remote schema request and response package remote schema
""" """
branch = fields.String(required=True, metadata={ branch = fields.String(metadata={
"description": "Repository branch", "description": "Repository branch",
"example": "master", "example": "master",
}) })
git_url = fields.String(required=True, metadata={ git_url = fields.String(metadata={
"description": "Package git url", "description": "Package git url",
"example": "https://aur.archlinux.org/ahriman.git", "example": "https://aur.archlinux.org/ahriman.git",
}) })
path = fields.String(required=True, metadata={ path = fields.String(metadata={
"description": "Path to package sources in git repository", "description": "Path to package sources in git repository",
"example": ".", "example": ".",
}) })
source = fields.Enum(PackageSource, by_value=True, required=True, metadata={ source = fields.Enum(PackageSource, by_value=True, required=True, metadata={
"description": "Pacakge source", "description": "Pacakge source",
}) })
web_url = fields.String(required=True, metadata={ web_url = fields.String(metadata={
"description": "Package repository page", "description": "Package repository page",
"example": "https://aur.archlinux.org/packages/ahriman", "example": "https://aur.archlinux.org/packages/ahriman",
}) })

View File

@ -86,7 +86,9 @@ def test_add_local(application_packages: ApplicationPackages, package_ahriman: P
application_packages._add_local(package_ahriman.base, "packager") application_packages._add_local(package_ahriman.base, "packager")
is_dir_mock.assert_called_once_with() is_dir_mock.assert_called_once_with()
copytree_mock.assert_called_once_with( copytree_mock.assert_called_once_with(
Path(package_ahriman.base), application_packages.repository.paths.cache_for(package_ahriman.base)) Path(package_ahriman.base),
application_packages.repository.paths.cache_for(package_ahriman.base),
dirs_exist_ok=True)
init_mock.assert_called_once_with(application_packages.repository.paths.cache_for(package_ahriman.base)) init_mock.assert_called_once_with(application_packages.repository.paths.cache_for(package_ahriman.base))
build_queue_mock.assert_called_once_with(package_ahriman) build_queue_mock.assert_called_once_with(package_ahriman)

View File

@ -7,6 +7,7 @@ from typing import Any, TypeVar
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import AUR
from ahriman.core.auth import Auth from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
@ -314,7 +315,13 @@ def package_python_schedule(
return Package( return Package(
base="python-schedule", base="python-schedule",
version="1.0.0-2", version="1.0.0-2",
remote=RemoteSource.from_source(PackageSource.AUR, "python-schedule", "aur"), remote=RemoteSource(
source=PackageSource.AUR,
git_url=AUR.remote_git_url("python-schedule", "aur"),
web_url=AUR.remote_web_url("python-schedule"),
path=".",
branch="master",
),
packages=packages) packages=packages)
@ -451,7 +458,13 @@ def remote_source() -> RemoteSource:
Returns: Returns:
RemoteSource: remote source test instance RemoteSource: remote source test instance
""" """
return RemoteSource.from_source(PackageSource.AUR, "ahriman", "aur") return RemoteSource(
source=PackageSource.AUR,
git_url=AUR.remote_git_url("ahriman", "aur"),
web_url=AUR.remote_web_url("ahriman"),
path=".",
branch="master",
)
@pytest.fixture @pytest.fixture

View File

@ -8,6 +8,7 @@ from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.models.pacman_synchronization import PacmanSynchronization
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -23,7 +24,7 @@ def test_init_with_local_cache(configuration: Configuration, mocker: MockerFixtu
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root: with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root)) mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
# during the creation pyalpm.Handle will create also version file which we would like to remove later # during the creation pyalpm.Handle will create also version file which we would like to remove later
pacman = Pacman("x86_64", configuration, refresh_database=1) pacman = Pacman("x86_64", configuration, refresh_database=PacmanSynchronization.Enabled)
assert pacman.handle assert pacman.handle
sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=False) sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=False)
@ -40,7 +41,7 @@ def test_init_with_local_cache_forced(configuration: Configuration, mocker: Mock
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root: with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root)) mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
# during the creation pyalpm.Handle will create also version file which we would like to remove later # during the creation pyalpm.Handle will create also version file which we would like to remove later
pacman = Pacman("x86_64", configuration, refresh_database=2) pacman = Pacman("x86_64", configuration, refresh_database=PacmanSynchronization.Force)
assert pacman.handle assert pacman.handle
sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=True) sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=True)
@ -54,7 +55,7 @@ def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker
dst_path = Path("/var/lib/pacman/sync/core.db") dst_path = Path("/var/lib/pacman/sync/core.db")
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=True)
# root database exists, local database does not # root database exists, local database does not
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: p.is_relative_to(path))
copy_mock = mocker.patch("shutil.copy") copy_mock = mocker.patch("shutil.copy")
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown") chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
@ -71,7 +72,7 @@ def test_database_copy_skip(pacman: Pacman, repository_paths: RepositoryPaths, m
path = Path("randomname") path = Path("randomname")
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=True)
# root database exists, local database does not # root database exists, local database does not
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: p.is_relative_to(path))
copy_mock = mocker.patch("shutil.copy") copy_mock = mocker.patch("shutil.copy")
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=False) pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=False)
@ -86,7 +87,7 @@ def test_database_copy_no_directory(pacman: Pacman, repository_paths: Repository
path = Path("randomname") path = Path("randomname")
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
# root database exists, local database does not # root database exists, local database does not
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: p.is_relative_to(path))
copy_mock = mocker.patch("shutil.copy") copy_mock = mocker.patch("shutil.copy")
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True) pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True)

View File

@ -6,6 +6,7 @@ from unittest.mock import call as MockCall
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.remote_source import RemoteSource from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -92,7 +93,7 @@ def test_fetch_new_without_remote(mocker: MockerFixture) -> None:
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move") move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local") local = Path("local")
Sources.fetch(local, None) Sources.fetch(local, RemoteSource(source=PackageSource.Archive))
check_output_mock.assert_has_calls([ check_output_mock.assert_has_calls([
MockCall("git", "checkout", "--force", Sources.DEFAULT_BRANCH, cwd=local, logger=pytest.helpers.anyvar(int)), MockCall("git", "checkout", "--force", Sources.DEFAULT_BRANCH, cwd=local, logger=pytest.helpers.anyvar(int)),
MockCall("git", "reset", "--hard", f"origin/{Sources.DEFAULT_BRANCH}", MockCall("git", "reset", "--hard", f"origin/{Sources.DEFAULT_BRANCH}",
@ -136,6 +137,7 @@ def test_init(mocker: MockerFixture) -> None:
must create empty repository at the specified path must create empty repository at the specified path
""" """
mocker.patch("ahriman.models.package.Package.local_files", return_value=[Path("local")]) mocker.patch("ahriman.models.package.Package.local_files", return_value=[Path("local")])
mocker.patch("pathlib.Path.is_dir", return_value=False)
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add") add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add")
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit") commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit")
@ -145,7 +147,21 @@ def test_init(mocker: MockerFixture) -> None:
check_output_mock.assert_called_once_with("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH, check_output_mock.assert_called_once_with("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH,
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=pytest.helpers.anyvar(int))
add_mock.assert_called_once_with(local, "PKGBUILD", ".SRCINFO", "local") add_mock.assert_called_once_with(local, "PKGBUILD", ".SRCINFO", "local")
commit_mock.assert_called_once_with(local, author="ahriman <ahriman@localhost>") commit_mock.assert_called_once_with(local)
def test_init_skip(mocker: MockerFixture) -> None:
"""
must skip git init if it was already
"""
mocker.patch("ahriman.models.package.Package.local_files", return_value=[Path("local")])
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.add")
mocker.patch("ahriman.core.build_tools.sources.Sources.commit")
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
Sources.init(Path("local"))
check_output_mock.assert_not_called()
def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
@ -216,19 +232,31 @@ def test_push(package_ahriman: Package, mocker: MockerFixture) -> None:
must correctly push files to remote repository must correctly push files to remote repository
""" """
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add") add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add")
commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit") commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
author = "commit author <user@host>" commit_author = ("commit author", "user@host")
local = Path("local") local = Path("local")
Sources.push(Path("local"), package_ahriman.remote, "glob", commit_author=author) Sources.push(local, package_ahriman.remote, "glob", commit_author=commit_author)
add_mock.assert_called_once_with(local, "glob") add_mock.assert_called_once_with(local, "glob")
commit_mock.assert_called_once_with(local, author=author) commit_mock.assert_called_once_with(local, commit_author=commit_author)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "push", package_ahriman.remote.git_url, package_ahriman.remote.branch, "git", "push", package_ahriman.remote.git_url, package_ahriman.remote.branch,
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=pytest.helpers.anyvar(int))
def test_push_skipped(package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must skip push if no changes were committed
"""
mocker.patch("ahriman.core.build_tools.sources.Sources.add")
mocker.patch("ahriman.core.build_tools.sources.Sources.commit", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
Sources.push(Path("local"), package_ahriman.remote)
check_output_mock.assert_not_called()
def test_add(sources: Sources, mocker: MockerFixture) -> None: def test_add(sources: Sources, mocker: MockerFixture) -> None:
""" """
must add files to git must add files to git
@ -274,29 +302,54 @@ def test_commit(sources: Sources, mocker: MockerFixture) -> None:
""" """
must commit changes must commit changes
""" """
mocker.patch("ahriman.core.build_tools.sources.Sources.has_changes", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local") local = Path("local")
message = "Commit message" message = "Commit message"
sources.commit(local, message=message) user, email = sources.DEFAULT_COMMIT_AUTHOR
assert sources.commit(local, message=message)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "commit", "--allow-empty", "--message", message, cwd=local, logger=pytest.helpers.anyvar(int) "git", "commit", "--message", message,
cwd=local, logger=pytest.helpers.anyvar(int), environment={
"GIT_AUTHOR_NAME": user,
"GIT_AUTHOR_EMAIL": email,
"GIT_COMMITTER_NAME": user,
"GIT_COMMITTER_EMAIL": email,
}
) )
def test_commit_no_changes(sources: Sources, mocker: MockerFixture) -> None:
"""
must skip commit if there are no changes
"""
mocker.patch("ahriman.core.build_tools.sources.Sources.has_changes", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
assert not sources.commit(Path("local"))
check_output_mock.assert_not_called()
def test_commit_author(sources: Sources, mocker: MockerFixture) -> None: def test_commit_author(sources: Sources, mocker: MockerFixture) -> None:
""" """
must commit changes with commit author must commit changes with commit author
""" """
mocker.patch("ahriman.core.build_tools.sources.Sources.has_changes", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local") local = Path("local")
message = "Commit message" message = "Commit message"
author = "commit author <user@host>" user, email = author = ("commit author", "user@host")
sources.commit(Path("local"), message=message, author=author) assert sources.commit(Path("local"), message=message, commit_author=author)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "commit", "--allow-empty", "--message", message, "--author", author, "git", "commit", "--message", message,
cwd=local, logger=pytest.helpers.anyvar(int) cwd=local, logger=pytest.helpers.anyvar(int), environment={
"GIT_AUTHOR_NAME": user,
"GIT_AUTHOR_EMAIL": email,
"GIT_COMMITTER_NAME": user,
"GIT_COMMITTER_EMAIL": email,
}
) )
@ -304,13 +357,20 @@ def test_commit_autogenerated_message(sources: Sources, mocker: MockerFixture) -
""" """
must commit changes with autogenerated commit message must commit changes with autogenerated commit message
""" """
mocker.patch("ahriman.core.build_tools.sources.Sources.has_changes", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local") local = Path("local")
sources.commit(Path("local")) assert sources.commit(Path("local"))
user, email = sources.DEFAULT_COMMIT_AUTHOR
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "commit", "--allow-empty", "--message", pytest.helpers.anyvar(str, strict=True), "git", "commit", "--message", pytest.helpers.anyvar(str, strict=True),
cwd=local, logger=pytest.helpers.anyvar(int) cwd=local, logger=pytest.helpers.anyvar(int), environment={
"GIT_AUTHOR_NAME": user,
"GIT_AUTHOR_EMAIL": email,
"GIT_COMMITTER_NAME": user,
"GIT_COMMITTER_EMAIL": email,
}
) )
@ -325,6 +385,23 @@ def test_diff(sources: Sources, mocker: MockerFixture) -> None:
check_output_mock.assert_called_once_with("git", "diff", cwd=local, logger=pytest.helpers.anyvar(int)) check_output_mock.assert_called_once_with("git", "diff", cwd=local, logger=pytest.helpers.anyvar(int))
def test_has_changes(sources: Sources, mocker: MockerFixture) -> None:
"""
must correctly identify if there are changes
"""
local = Path("local")
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output", return_value="M a.txt")
assert sources.has_changes(local)
check_output_mock.assert_called_once_with("git", "diff", "--cached", "--name-only",
cwd=local, logger=pytest.helpers.anyvar(int))
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output", return_value="")
assert not sources.has_changes(local)
check_output_mock.assert_called_once_with("git", "diff", "--cached", "--name-only",
cwd=local, logger=pytest.helpers.anyvar(int))
def test_move(sources: Sources, mocker: MockerFixture) -> None: def test_move(sources: Sources, mocker: MockerFixture) -> None:
""" """
must move content between directories must move content between directories

View File

@ -66,18 +66,3 @@ def test_migrate_package_remotes_vcs(package_ahriman: Package, connection: Conne
migrate_package_remotes(connection, repository_paths) migrate_package_remotes(connection, repository_paths)
connection.execute.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int)) connection.execute.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
def test_migrate_package_remotes_no_remotes(package_ahriman: Package, connection: Connection,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must skip processing in case if no remotes generated (should never happen)
"""
mocker.patch(
"ahriman.core.database.operations.PackageOperations._packages_get_select_package_bases",
return_value={package_ahriman.base: package_ahriman})
mocker.patch("pathlib.Path.exists", return_value=False)
mocker.patch("ahriman.models.remote_source.RemoteSource.from_source", return_value=None)
migrate_package_remotes(connection, repository_paths)
connection.execute.assert_not_called()

View File

@ -35,7 +35,7 @@ def test_migrate_package_depends(connection: Connection, configuration: Configur
migrate_package_depends(connection, configuration) migrate_package_depends(connection, configuration)
package_mock.assert_called_once_with( package_mock.assert_called_once_with(
package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int), remote=None) package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int))
connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{ connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{
"make_depends": package_ahriman.packages[package_ahriman.base].make_depends, "make_depends": package_ahriman.packages[package_ahriman.base].make_depends,
"opt_depends": package_ahriman.packages[package_ahriman.base].opt_depends, "opt_depends": package_ahriman.packages[package_ahriman.base].opt_depends,

View File

@ -35,7 +35,7 @@ def test_migrate_package_depends(connection: Connection, configuration: Configur
migrate_package_check_depends(connection, configuration) migrate_package_check_depends(connection, configuration)
package_mock.assert_called_once_with( package_mock.assert_called_once_with(
package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int), remote=None) package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int))
connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{ connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{
"check_depends": package_ahriman.packages[package_ahriman.base].check_depends, "check_depends": package_ahriman.packages[package_ahriman.base].check_depends,
"package": package_ahriman.base, "package": package_ahriman.base,

View File

@ -35,7 +35,7 @@ def test_migrate_package_base_packager(connection: Connection, configuration: Co
migrate_package_base_packager(connection, configuration) migrate_package_base_packager(connection, configuration)
package_mock.assert_called_once_with( package_mock.assert_called_once_with(
package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int), remote=None) package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int))
connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{ connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{
"package_base": package_ahriman.base, "package_base": package_ahriman.base,
"packager": package_ahriman.packager, "packager": package_ahriman.packager,

View File

@ -0,0 +1,8 @@
from ahriman.core.database.migrations.m009_local_source import steps
def test_migration_packagers() -> None:
"""
migration must not be empty
"""
assert steps

View File

@ -193,7 +193,7 @@ def test_remote_update_update(database: SQLite, package_ahriman: Package) -> Non
must perform package remote update for existing package must perform package remote update for existing package
""" """
database.remote_update(package_ahriman) database.remote_update(package_ahriman)
remote_source = RemoteSource.from_source(PackageSource.Repository, package_ahriman.base, "community") remote_source = RemoteSource(source=PackageSource.Repository)
package_ahriman.remote = remote_source package_ahriman.remote = remote_source
database.remote_update(package_ahriman) database.remote_update(package_ahriman)

View File

@ -11,6 +11,8 @@ from ahriman.core.repository import Repository
from ahriman.core.sign.gpg import GPG from ahriman.core.sign.gpg import GPG
from ahriman.models.context_key import ContextKey from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
def test_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None: def test_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None:
@ -51,6 +53,9 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag
for package, props in package_python_schedule.packages.items() for package, props in package_python_schedule.packages.items()
] + [package_ahriman] ] + [package_ahriman]
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages) mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
mocker.patch("ahriman.core.database.SQLite.remotes_get", return_value={
package_ahriman.base: package_ahriman.base
})
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")]) packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
assert len(packages) == 2 assert len(packages) == 2

View File

@ -42,7 +42,7 @@ def test_updates_aur_official(update_handler: UpdateHandler, package_ahriman: Pa
""" """
must provide updates based on repository data must provide updates based on repository data
""" """
package_ahriman.remote = RemoteSource.from_source(PackageSource.Repository, package_ahriman.base, "community") package_ahriman.remote = RemoteSource(source=PackageSource.Repository)
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman)
@ -65,6 +65,19 @@ def test_updates_aur_failed(update_handler: UpdateHandler, package_ahriman: Pack
status_client_mock.assert_called_once_with(package_ahriman.base) status_client_mock.assert_called_once_with(package_ahriman.base)
def test_updates_aur_local(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must skip packages with local sources
"""
package_ahriman.remote = RemoteSource(source=PackageSource.Local)
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
package_load_mock = mocker.patch("ahriman.models.package.Package.from_aur")
assert not update_handler.updates_aur([], vcs=True)
package_load_mock.assert_not_called()
def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Package, package_python_schedule: Package, def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """
@ -150,7 +163,7 @@ def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package,
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
assert update_handler.updates_local(vcs=True) == [package_ahriman] assert update_handler.updates_local(vcs=True) == [package_ahriman]
fetch_mock.assert_called_once_with(Path(package_ahriman.base), remote=None) fetch_mock.assert_called_once_with(Path(package_ahriman.base), pytest.helpers.anyvar(int))
package_load_mock.assert_called_once_with(Path(package_ahriman.base), "x86_64", None) package_load_mock.assert_called_once_with(Path(package_ahriman.base), "x86_64", None)
status_client_mock.assert_called_once_with(package_ahriman.base) status_client_mock.assert_called_once_with(package_ahriman.base)
package_is_outdated_mock.assert_called_once_with( package_is_outdated_mock.assert_called_once_with(

View File

@ -75,7 +75,7 @@ def test_packages_add(spawner: Spawn, mocker: MockerFixture) -> None:
""" """
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process") spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
spawner.packages_add(["ahriman", "linux"], None, now=False) spawner.packages_add(["ahriman", "linux"], None, now=False)
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", username=None) spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", username=None)
def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None: def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
@ -84,7 +84,7 @@ def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
""" """
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process") spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
spawner.packages_add(["ahriman", "linux"], None, now=True) spawner.packages_add(["ahriman", "linux"], None, now=True)
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", username=None, now="") spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", username=None, now="")
def test_packages_add_with_username(spawner: Spawn, mocker: MockerFixture) -> None: def test_packages_add_with_username(spawner: Spawn, mocker: MockerFixture) -> None:
@ -93,7 +93,7 @@ def test_packages_add_with_username(spawner: Spawn, mocker: MockerFixture) -> No
""" """
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process") spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
spawner.packages_add(["ahriman", "linux"], "username", now=False) spawner.packages_add(["ahriman", "linux"], "username", now=False)
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", username="username") spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", username="username")
def test_packages_rebuild(spawner: Spawn, mocker: MockerFixture) -> None: def test_packages_rebuild(spawner: Spawn, mocker: MockerFixture) -> None:

View File

@ -4,6 +4,7 @@ import pytest
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
from ahriman import __version__ from ahriman import __version__
from ahriman.core.alpm.remote import AUR
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.counters import Counters from ahriman.models.counters import Counters
@ -70,7 +71,13 @@ def package_tpacpi_bat_git() -> Package:
return Package( return Package(
base="tpacpi-bat-git", base="tpacpi-bat-git",
version="3.1.r12.g4959b52-1", version="3.1.r12.g4959b52-1",
remote=RemoteSource.from_source(PackageSource.AUR, "tpacpi-bat-git", "aur"), remote=RemoteSource(
source=PackageSource.AUR,
git_url=AUR.remote_git_url("tpacpi-bat-git", "aur"),
web_url=AUR.remote_web_url("tpacpi-bat-git"),
path=".",
branch="master",
),
packages={"tpacpi-bat-git": PackageDescription()}) packages={"tpacpi-bat-git": PackageDescription()})

View File

@ -158,7 +158,10 @@ def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker
""" """
mocker.patch("ahriman.models.package_description.PackageDescription.from_package", mocker.patch("ahriman.models.package_description.PackageDescription.from_package",
return_value=package_ahriman.packages[package_ahriman.base]) return_value=package_ahriman.packages[package_ahriman.base])
assert Package.from_archive(Path("path"), pyalpm_handle, package_ahriman.remote) == package_ahriman generated = Package.from_archive(Path("path"), pyalpm_handle)
generated.remote = package_ahriman.remote
assert generated == package_ahriman
def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, pacman: Pacman, def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, pacman: Pacman,

View File

@ -4,6 +4,7 @@ from pathlib import Path
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.repository_paths import RepositoryPaths
def _is_file_mock(is_any_file: bool, is_pkgbuild: bool) -> Callable[[Path], bool]: def _is_file_mock(is_any_file: bool, is_pkgbuild: bool) -> Callable[[Path], bool]:
@ -21,71 +22,86 @@ def _is_file_mock(is_any_file: bool, is_pkgbuild: bool) -> Callable[[Path], bool
return side_effect return side_effect
def test_resolve_non_auto() -> None: def test_resolve_non_auto(repository_paths: RepositoryPaths) -> None:
""" """
must resolve non auto type to itself must resolve non auto type to itself
""" """
for source in filter(lambda src: src != PackageSource.Auto, PackageSource): for source in filter(lambda src: src != PackageSource.Auto, PackageSource):
assert source.resolve("") == source assert source.resolve("", repository_paths) == source
def test_resolve_archive(package_description_ahriman: PackageDescription, mocker: MockerFixture) -> None: def test_resolve_archive(package_description_ahriman: PackageDescription, repository_paths: RepositoryPaths,
mocker: MockerFixture) -> None:
""" """
must resolve auto type into the archive must resolve auto type into the archive
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, False)) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, False))
assert PackageSource.Auto.resolve(package_description_ahriman.filename) == PackageSource.Archive assert PackageSource.Auto.resolve(package_description_ahriman.filename, repository_paths) == PackageSource.Archive
def test_resolve_aur(mocker: MockerFixture) -> None: def test_resolve_aur(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
""" """
must resolve auto type into the AUR package must resolve auto type into the AUR package
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
mocker.patch("pathlib.Path.is_file", return_value=False) mocker.patch("pathlib.Path.is_file", return_value=False)
assert PackageSource.Auto.resolve("package") == PackageSource.AUR assert PackageSource.Auto.resolve("package", repository_paths) == PackageSource.AUR
def test_resolve_aur_not_package_like(mocker: MockerFixture) -> None: def test_resolve_aur_not_package_like(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
""" """
must resolve auto type into the AUR package if it is file, but does not look like a package archive must resolve auto type into the AUR package if it is file, but does not look like a package archive
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, False)) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, False))
assert PackageSource.Auto.resolve("package") == PackageSource.AUR assert PackageSource.Auto.resolve("package", repository_paths) == PackageSource.AUR
def test_resolve_aur_no_access(mocker: MockerFixture) -> None: def test_resolve_aur_no_access(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
""" """
must resolve auto type into the AUR package in case if we cannot read in suggested path must resolve auto type into the AUR package in case if we cannot read in suggested path
""" """
mocker.patch("pathlib.Path.is_dir", side_effect=PermissionError()) mocker.patch("pathlib.Path.is_dir", side_effect=PermissionError())
assert PackageSource.Auto.resolve("package") == PackageSource.AUR assert PackageSource.Auto.resolve("package", repository_paths) == PackageSource.AUR
def test_resolve_directory(mocker: MockerFixture) -> None: def test_resolve_directory(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
""" """
must resolve auto type into the directory must resolve auto type into the directory
""" """
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", autospec=True, side_effect=lambda p: p == Path("path"))
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(False, False)) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(False, False))
assert PackageSource.Auto.resolve("path") == PackageSource.Directory assert PackageSource.Auto.resolve("path", repository_paths) == PackageSource.Directory
def test_resolve_local(mocker: MockerFixture) -> None: def test_resolve_local(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
""" """
must resolve auto type into the local sources must resolve auto type into the local sources
""" """
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=False)
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, True)) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, True))
assert PackageSource.Auto.resolve("path") == PackageSource.Local assert PackageSource.Auto.resolve("path", repository_paths) == PackageSource.Local
def test_resolve_remote(package_description_ahriman: PackageDescription, mocker: MockerFixture) -> None: def test_resolve_local_cache(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must resolve auto type into the local sources
"""
cache_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.cache_for", return_value=Path("cache"))
mocker.patch("pathlib.Path.is_dir", autospec=True, side_effect=lambda p: p == Path("cache"))
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(False, False))
assert PackageSource.Auto.resolve("path", repository_paths) == PackageSource.Local
cache_mock.assert_called_once_with("path")
def test_resolve_remote(package_description_ahriman: PackageDescription, repository_paths: RepositoryPaths,
mocker: MockerFixture) -> None:
""" """
must resolve auto type into the remote sources must resolve auto type into the remote sources
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(False, False)) mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(False, False))
assert PackageSource.Auto.resolve(f"https://host/{package_description_ahriman.filename}") == PackageSource.Remote url = f"https://host/{package_description_ahriman.filename}"
assert PackageSource.Auto.resolve(url, repository_paths) == PackageSource.Remote

View File

@ -1,7 +1,8 @@
from pathlib import Path import pytest
from pytest_mock import MockerFixture
from ahriman.models.package import Package from pathlib import Path
from ahriman.core.exceptions import InitializeError
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
@ -20,6 +21,14 @@ def test_post_init(remote_source: RemoteSource) -> None:
assert remote == remote_source assert remote == remote_source
def test_is_remote() -> None:
"""
must correctly define if source is remote or not
"""
for source in PackageSource:
assert RemoteSource(source=source).is_remote or source not in (PackageSource.AUR, PackageSource.Repository)
def test_pkgbuild_dir(remote_source: RemoteSource) -> None: def test_pkgbuild_dir(remote_source: RemoteSource) -> None:
""" """
must return path as is in `path` property must return path as is in `path` property
@ -35,48 +44,16 @@ def test_from_json(remote_source: RemoteSource) -> None:
assert RemoteSource.from_json(remote_source.view()) == remote_source assert RemoteSource.from_json(remote_source.view()) == remote_source
def test_from_json_empty() -> None: def test_git_source(remote_source: RemoteSource) -> None:
""" """
must return None in case of empty dictionary, which is required by the database wrapper must correctly return git source
""" """
assert RemoteSource.from_json({}) is None assert remote_source.git_source() == (remote_source.git_url, remote_source.branch)
def test_from_source_aur(package_ahriman: Package, mocker: MockerFixture) -> None: def test_git_source_empty() -> None:
""" """
must construct remote from AUR source must raise exception if path is none
""" """
remote_git_url_mock = mocker.patch("ahriman.core.alpm.remote.AUR.remote_git_url") with pytest.raises(InitializeError):
remote_web_url_mock = mocker.patch("ahriman.core.alpm.remote.AUR.remote_web_url") RemoteSource(source=PackageSource.Remote).git_source()
remote = RemoteSource.from_source(PackageSource.AUR, package_ahriman.base, "aur")
remote_git_url_mock.assert_called_once_with(package_ahriman.base, "aur")
remote_web_url_mock.assert_called_once_with(package_ahriman.base)
assert remote.pkgbuild_dir == Path(".")
assert remote.branch == "master"
assert remote.source == PackageSource.AUR
def test_from_source_official(package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must construct remote from official repository source
"""
remote_git_url_mock = mocker.patch("ahriman.core.alpm.remote.Official.remote_git_url")
remote_web_url_mock = mocker.patch("ahriman.core.alpm.remote.Official.remote_web_url")
remote = RemoteSource.from_source(PackageSource.Repository, package_ahriman.base, "community")
remote_git_url_mock.assert_called_once_with(package_ahriman.base, "community")
remote_web_url_mock.assert_called_once_with(package_ahriman.base)
assert remote.pkgbuild_dir == Path(".")
assert remote.branch == "main"
assert remote.source == PackageSource.Repository
def test_from_source_other() -> None:
"""
must return None in case if source is not one of AUR or Repository
"""
assert RemoteSource.from_source(PackageSource.Archive, "package", "repository") is None
assert RemoteSource.from_source(PackageSource.Directory, "package", "repository") is None
assert RemoteSource.from_source(PackageSource.Local, "package", "repository") is None
assert RemoteSource.from_source(PackageSource.Remote, "package", "repository") is None

View File

@ -48,7 +48,8 @@ target = gitremote
target = gitremote target = gitremote
[gitremote] [gitremote]
commit_author = "user <user@host>" commit_user = user
commit_email = user@host
push_url = https://github.com/arcan1s/repository.git push_url = https://github.com/arcan1s/repository.git
pull_url = https://github.com/arcan1s/repository.git pull_url = https://github.com/arcan1s/repository.git