diff --git a/package/share/ahriman/templates/build-status/table.jinja2 b/package/share/ahriman/templates/build-status/table.jinja2
index 337b9343..64fab9f7 100644
--- a/package/share/ahriman/templates/build-status/table.jinja2
+++ b/package/share/ahriman/templates/build-status/table.jinja2
@@ -112,7 +112,7 @@
const payload = response.map(description => {
const package_base = description.package.base;
- const web_url = description.package.remote?.web_url;
+ const web_url = description.package.remote.web_url;
return {
id: package_base,
base: web_url ? `${safe(package_base)}` : safe(package_base),
diff --git a/src/ahriman/application/application/application_packages.py b/src/ahriman/application/application/application_packages.py
index 6301031f..8c19aee7 100644
--- a/src/ahriman/application/application/application_packages.py
+++ b/src/ahriman/application/application/application_packages.py
@@ -95,7 +95,7 @@ class ApplicationPackages(ApplicationProperties):
if (source_dir := Path(source)).is_dir():
package = Package.from_build(source_dir, self.architecture, username)
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
elif (source_dir := self.repository.paths.cache_for(source)).is_dir():
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)
"""
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(name, username)
diff --git a/src/ahriman/core/build_tools/sources.py b/src/ahriman/core/build_tools/sources.py
index 88b71597..1587fb16 100644
--- a/src/ahriman/core/build_tools/sources.py
+++ b/src/ahriman/core/build_tools/sources.py
@@ -36,9 +36,11 @@ class Sources(LazyLogging):
Attributes:
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
+ DEFAULT_COMMIT_AUTHOR(tuple[str, str]): (class attribute) default commit author to be used if none set
"""
DEFAULT_BRANCH = "master" # default fallback branch
+ DEFAULT_COMMIT_AUTHOR = ("ahriman", "ahriman@localhost")
_check_output = check_output
@@ -61,13 +63,13 @@ class Sources(LazyLogging):
return [PkgbuildPatch("arch", list(architectures))]
@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``
Args:
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()
# 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)
return
- branch = remote.branch if remote is not None else instance.DEFAULT_BRANCH
+ branch = remote.branch or instance.DEFAULT_BRANCH
if is_initialized_git:
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)
- 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)
Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
remote.git_url, str(sources_dir), cwd=sources_dir.parent, logger=instance.logger)
@@ -95,7 +97,7 @@ class Sources(LazyLogging):
# move content if required
# 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)
@staticmethod
@@ -122,14 +124,16 @@ class Sources(LazyLogging):
sources_dir(Path): local path to sources
"""
instance = Sources()
- Sources._check_output("git", "init", "--initial-branch", instance.DEFAULT_BRANCH,
- cwd=sources_dir, logger=instance.logger)
+ if not (sources_dir / ".git").is_dir():
+ # 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...
files = ["PKGBUILD", ".SRCINFO"] + [str(path) for path in Package.local_files(sources_dir)]
instance.add(sources_dir, *files)
# ...and commit them
- instance.commit(sources_dir, commit_author=("ahriman", "ahriman@localhost"))
+ instance.commit(sources_dir)
@staticmethod
def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> None:
@@ -179,13 +183,15 @@ class Sources(LazyLogging):
sources_dir(Path): local path to git repository
remote(RemoteSource): remote target, branch and url
*pattern(str): glob patterns
- commit_author(tuple[str, str] | None, optional): commit author in form of git config (i.e. ``user ``)
- (Default value = None)
+ commit_author(tuple[str, str] | None, optional): commit author if any (Default value = None)
"""
instance = Sources()
instance.add(sources_dir, *pattern)
- instance.commit(sources_dir, commit_author=commit_author)
- Sources._check_output("git", "push", remote.git_url, remote.branch, cwd=sources_dir, logger=instance.logger)
+ if not instance.commit(sources_dir, commit_author=commit_author):
+ 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:
"""
@@ -210,7 +216,7 @@ class Sources(LazyLogging):
cwd=sources_dir, logger=self.logger)
def commit(self, sources_dir: Path, message: str | None = None,
- commit_author: tuple[str, str] | None = None) -> None:
+ commit_author: tuple[str, str] | None = None) -> bool:
"""
commit changes
@@ -219,19 +225,28 @@ class Sources(LazyLogging):
message(str | None, optional): optional commit message if any. If none set, message will be generated
according to the current timestamp (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:
message = f"Autogenerated commit at {utcnow()}"
- args = ["--allow-empty", "--message", message]
-
+ args = ["--message", message]
environment: dict[str, str] = {}
- if commit_author is not None:
- user, email = commit_author
- environment["GIT_AUTHOR_NAME"] = environment["GIT_COMMITTER_NAME"] = user
- environment["GIT_AUTHOR_EMAIL"] = environment["GIT_COMMITTER_EMAIL"] = email
+
+ 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:
"""
generate diff from the current version and write it to the output file
@@ -244,6 +259,20 @@ class Sources(LazyLogging):
"""
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:
"""
move content from pkgbuild_dir to sources_dir
diff --git a/src/ahriman/core/configuration/configuration.py b/src/ahriman/core/configuration/configuration.py
index 30a269ba..ca316cbc 100644
--- a/src/ahriman/core/configuration/configuration.py
+++ b/src/ahriman/core/configuration/configuration.py
@@ -48,7 +48,7 @@ class Configuration(configparser.RawConfigParser):
>>> 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")
>>> makepkg_flags = configuration.getlist("build", "makepkg_flags")
diff --git a/src/ahriman/core/database/migrations/m001_package_source.py b/src/ahriman/core/database/migrations/m001_package_source.py
index 89caae0b..ece66537 100644
--- a/src/ahriman/core/database/migrations/m001_package_source.py
+++ b/src/ahriman/core/database/migrations/m001_package_source.py
@@ -70,6 +70,7 @@ def migrate_package_remotes(connection: Connection, paths: RepositoryPaths) -> N
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
+ from ahriman.core.alpm.remote import AUR
from ahriman.core.database.operations import PackageOperations
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)
if local_cache.exists() and not package.is_vcs:
continue # skip packages which are not VCS and with local cache
- remote_source = RemoteSource.from_source(PackageSource.AUR, package_base, "aur")
- if remote_source is None:
- continue # should never happen
+ remote_source = RemoteSource(
+ source=PackageSource.AUR,
+ 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)
diff --git a/src/ahriman/core/database/migrations/m005_make_opt_depends.py b/src/ahriman/core/database/migrations/m005_make_opt_depends.py
index e337d4b5..ed9f6084 100644
--- a/src/ahriman/core/database/migrations/m005_make_opt_depends.py
+++ b/src/ahriman/core/database/migrations/m005_make_opt_depends.py
@@ -66,7 +66,7 @@ def migrate_package_depends(connection: Connection, configuration: Configuration
package_list = []
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():
package_list.append({
"make_depends": description.make_depends,
diff --git a/src/ahriman/core/database/migrations/m007_check_depends.py b/src/ahriman/core/database/migrations/m007_check_depends.py
index 2bf57903..9769bd01 100644
--- a/src/ahriman/core/database/migrations/m007_check_depends.py
+++ b/src/ahriman/core/database/migrations/m007_check_depends.py
@@ -63,7 +63,7 @@ def migrate_package_check_depends(connection: Connection, configuration: Configu
package_list = []
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():
package_list.append({
"check_depends": description.check_depends,
diff --git a/src/ahriman/core/database/migrations/m008_packagers.py b/src/ahriman/core/database/migrations/m008_packagers.py
index b54196e4..df62c21f 100644
--- a/src/ahriman/core/database/migrations/m008_packagers.py
+++ b/src/ahriman/core/database/migrations/m008_packagers.py
@@ -69,7 +69,7 @@ def migrate_package_base_packager(connection: Connection, configuration: Configu
package_list = []
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_base": package.base,
"packager": package.packager,
diff --git a/src/ahriman/core/database/migrations/m009_local_source.py b/src/ahriman/core/database/migrations/m009_local_source.py
new file mode 100644
index 00000000..b12a646c
--- /dev/null
+++ b/src/ahriman/core/database/migrations/m009_local_source.py
@@ -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 .
+#
+__all__ = ["steps"]
+
+
+steps = [
+ """
+ update package_bases set source = 'local' where source is null
+ """,
+]
diff --git a/src/ahriman/core/database/operations/package_operations.py b/src/ahriman/core/database/operations/package_operations.py
index 8ddf5ea6..acf28703 100644
--- a/src/ahriman/core/database/operations/package_operations.py
+++ b/src/ahriman/core/database/operations/package_operations.py
@@ -86,11 +86,11 @@ class PackageOperations(Operations):
{
"package_base": package.base,
"version": package.version,
- "branch": package.remote.branch if package.remote is not None else None,
- "git_url": package.remote.git_url if package.remote is not None else None,
- "path": package.remote.path if package.remote is not None else None,
- "web_url": package.remote.web_url if package.remote is not None else None,
- "source": package.remote.source.value if package.remote is not None else None,
+ "branch": package.remote.branch,
+ "git_url": package.remote.git_url,
+ "path": package.remote.path,
+ "web_url": package.remote.web_url,
+ "source": package.remote.source.value,
"packager": package.packager,
}
)
@@ -270,5 +270,4 @@ class PackageOperations(Operations):
return {
package_base: package.remote
for package_base, package in packages.items()
- if package.remote is not None
}
diff --git a/src/ahriman/core/repository/repository.py b/src/ahriman/core/repository/repository.py
index e22dcfea..dbedc455 100644
--- a/src/ahriman/core/repository/repository.py
+++ b/src/ahriman/core/repository/repository.py
@@ -117,8 +117,9 @@ class Repository(Executor, UpdateHandler):
# we are iterating over bases, not single packages
for full_path in packages:
try:
- local = Package.from_archive(full_path, self.pacman, None)
- local.remote = sources.get(local.base)
+ local = Package.from_archive(full_path, self.pacman)
+ if (source := sources.get(local.base)) is not None:
+ local.remote = source
current = result.setdefault(local.base, local)
if current.version != local.version:
diff --git a/src/ahriman/core/repository/update_handler.py b/src/ahriman/core/repository/update_handler.py
index a3246c27..95a91703 100644
--- a/src/ahriman/core/repository/update_handler.py
+++ b/src/ahriman/core/repository/update_handler.py
@@ -24,6 +24,7 @@ from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.repository.cleaner import Cleaner
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
+from ahriman.models.remote_source import RemoteSource
class UpdateHandler(Cleaner):
@@ -55,12 +56,10 @@ class UpdateHandler(Cleaner):
list[Package]: list of packages which are out-of-dated
"""
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
for probe in [package.base] + sorted(package.packages.keys()):
try:
- if source == PackageSource.Repository:
+ if package.remote.source == PackageSource.Repository:
return Package.from_official(probe, self.pacman, None)
return Package.from_aur(probe, self.pacman, None)
except UnknownPackageError:
@@ -71,6 +70,8 @@ class UpdateHandler(Cleaner):
for local in self.packages():
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:
continue
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():
with self.in_package_context(cache_dir.name):
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)
local = packages.get(remote.base)
diff --git a/src/ahriman/core/spawn.py b/src/ahriman/core/spawn.py
index c29e6243..edf115a0 100644
--- a/src/ahriman/core/spawn.py
+++ b/src/ahriman/core/spawn.py
@@ -27,7 +27,6 @@ from multiprocessing import Process, Queue
from threading import Lock, Thread
from ahriman.core.log import LazyLogging
-from ahriman.models.package_source import PackageSource
class Spawn(Thread, LazyLogging):
@@ -133,8 +132,7 @@ class Spawn(Thread, LazyLogging):
username(str | None): optional override of username for build process
now(bool): build packages now
"""
- # avoid abusing by building non-aur packages
- kwargs = {"source": PackageSource.AUR.value, "username": username}
+ kwargs = {"username": username}
if now:
kwargs["now"] = ""
self._spawn_process("package-add", *packages, **kwargs)
diff --git a/src/ahriman/core/util.py b/src/ahriman/core/util.py
index 0fcce09a..50acb647 100644
--- a/src/ahriman/core/util.py
+++ b/src/ahriman/core/util.py
@@ -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]]:
"""
- 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:
source(list[T]): source list to be partitioned
diff --git a/src/ahriman/models/aur_package.py b/src/ahriman/models/aur_package.py
index fb7cc82d..8b9e620e 100644
--- a/src/ahriman/models/aur_package.py
+++ b/src/ahriman/models/aur_package.py
@@ -66,13 +66,12 @@ class AURPackage:
>>> 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
>>>
- >>>
>>> from ahriman.core.alpm.pacman import Pacman
>>> from ahriman.core.configuration import Configuration
>>>
>>> configuration = 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
"""
diff --git a/src/ahriman/models/package.py b/src/ahriman/models/package.py
index b911d48d..b89d766b 100644
--- a/src/ahriman/models/package.py
+++ b/src/ahriman/models/package.py
@@ -51,7 +51,7 @@ class Package(LazyLogging):
packager(str | None): package packager if available
packages(dict[str, PackageDescription): map of package names to their properties.
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
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::
- >>> 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).
@@ -76,7 +76,7 @@ class Package(LazyLogging):
base: str
version: str
- remote: RemoteSource | None
+ remote: RemoteSource
packages: dict[str, PackageDescription]
packager: str | None = None
@@ -192,22 +192,26 @@ class Package(LazyLogging):
return sorted(packages)
@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
Args:
path(Path): path to package archive
pacman(Pacman): alpm wrapper instance
- remote(RemoteSource): package remote source if applicable
Returns:
Self: package properties
"""
package = pacman.handle.load_pkg(str(path))
description = PackageDescription.from_package(package, path)
- return cls(base=package.base, version=package.version, remote=remote, packages={package.name: description},
- packager=package.packager)
+ return cls(
+ base=package.base,
+ version=package.version,
+ remote=RemoteSource(source=PackageSource.Archive),
+ packages={package.name: description},
+ packager=package.packager,
+ )
@classmethod
def from_aur(cls, name: str, pacman: Pacman, packager: str | None = None) -> Self:
@@ -223,7 +227,15 @@ class Package(LazyLogging):
Self: package properties
"""
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(
base=package.package_base,
version=package.version,
@@ -265,14 +277,20 @@ class Package(LazyLogging):
version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"])
remote = RemoteSource(
+ source=PackageSource.Local,
git_url=path.absolute().as_uri(),
- web_url="",
+ web_url=None,
path=".",
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
def from_json(cls, dump: dict[str, Any]) -> Self:
@@ -291,8 +309,13 @@ class Package(LazyLogging):
for key, value in packages_json.items()
}
remote = dump.get("remote") or {}
- return cls(base=dump["base"], version=dump["version"], remote=RemoteSource.from_json(remote), packages=packages,
- packager=dump.get("packager"))
+ return cls(
+ base=dump["base"],
+ version=dump["version"],
+ remote=RemoteSource.from_json(remote),
+ packages=packages,
+ packager=dump.get("packager"),
+ )
@classmethod
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
"""
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(
base=package.package_base,
version=package.version,
diff --git a/src/ahriman/models/package_description.py b/src/ahriman/models/package_description.py
index 6a41a260..7154e217 100644
--- a/src/ahriman/models/package_description.py
+++ b/src/ahriman/models/package_description.py
@@ -53,14 +53,13 @@ class PackageDescription:
>>> description = PackageDescription.from_json(dump)
>>>
- >>>
>>> from pathlib import Path
>>> from ahriman.core.alpm.pacman import Pacman
>>> from ahriman.core.configuration import Configuration
>>>
>>> configuration = 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(
>>> pyalpm_description, Path("/var/cache/pacman/pkg/pacman-6.0.1-4-x86_64.pkg.tar.zst"))
"""
diff --git a/src/ahriman/models/package_source.py b/src/ahriman/models/package_source.py
index ee9b6565..2dfeb61c 100644
--- a/src/ahriman/models/package_source.py
+++ b/src/ahriman/models/package_source.py
@@ -24,6 +24,7 @@ from pathlib import Path
from urllib.parse import urlparse
from ahriman.core.util import package_like
+from ahriman.models.repository_paths import RepositoryPaths
class PackageSource(str, Enum):
@@ -42,7 +43,7 @@ class PackageSource(str, Enum):
Examples:
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.
"""
@@ -55,12 +56,13 @@ class PackageSource(str, Enum):
Remote = "remote"
Repository = "repository"
- def resolve(self, source: str) -> PackageSource:
+ def resolve(self, source: str, paths: RepositoryPaths) -> PackageSource:
"""
resolve auto into the correct type
Args:
source(str): source of the package
+ paths(RepositoryPaths): repository paths instance
Returns:
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):
return PackageSource.Remote
try:
- if (maybe_path / "PKGBUILD").is_file():
+ if (maybe_path / "PKGBUILD").is_file() or paths.cache_for(source).is_dir():
return PackageSource.Local
if maybe_path.is_dir():
return PackageSource.Directory
diff --git a/src/ahriman/models/remote_source.py b/src/ahriman/models/remote_source.py
index 39552a44..5d7cfaa1 100644
--- a/src/ahriman/models/remote_source.py
+++ b/src/ahriman/models/remote_source.py
@@ -21,6 +21,7 @@ from dataclasses import dataclass, fields
from pathlib import Path
from typing import Any, Self
+from ahriman.core.exceptions import InitializeError
from ahriman.core.util import dataclass_view, filter_json
from ahriman.models.package_source import PackageSource
@@ -31,18 +32,18 @@ class RemoteSource:
remote package source properties
Attributes:
- branch(str): branch of the git repository
- git_url(str): url of the git repository
- path(str): path to directory with PKGBUILD inside the git repository
+ branch(str | None): branch of the git repository
+ git_url(str | None): url of the git repository
+ path(str | None): path to directory with PKGBUILD inside the git repository
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
+ git_url: str | None = None
+ web_url: str | None = None
+ path: str | None = None
+ branch: str | None = None
def __post_init__(self) -> None:
"""
@@ -51,17 +52,27 @@ class RemoteSource:
object.__setattr__(self, "source", PackageSource(self.source))
@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)
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
- 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)
@@ -69,47 +80,25 @@ class RemoteSource:
dump(dict[str, Any]): json dump body
Returns:
- Self | None: remote source
+ Self: remote source
"""
# filter to only known fields
known_fields = [pair.name for pair in fields(cls)]
- dump = filter_json(dump, known_fields)
- if dump:
- return cls(**dump)
- return None
+ return cls(**filter_json(dump, known_fields))
- @classmethod
- def from_source(cls, source: PackageSource, package_base: str, repository: str) -> Self | None:
+ def git_source(self) -> tuple[str, str]:
"""
- generate remote source from the package base
-
- Args:
- source(PackageSource): source of the package
- package_base(str): package base
- repository(str): repository name
+ get git source if available
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:
- from ahriman.core.alpm.remote import AUR
- return cls(
- 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
+ if self.git_url is None or self.branch is None:
+ raise InitializeError("Remote source is empty")
+ return self.git_url, self.branch
def view(self) -> dict[str, Any]:
"""
diff --git a/src/ahriman/models/user.py b/src/ahriman/models/user.py
index 494a5685..35e6c0db 100644
--- a/src/ahriman/models/user.py
+++ b/src/ahriman/models/user.py
@@ -41,7 +41,7 @@ class User:
Simply create user from database data and perform required validation::
>>> 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::
@@ -63,8 +63,8 @@ class User:
username: str
password: str
access: UserAccess
- packager_id: str | None
- key: str | None
+ packager_id: str | None = None
+ key: str | None = None
_HASHER = sha512_crypt
diff --git a/src/ahriman/web/schemas/remote_schema.py b/src/ahriman/web/schemas/remote_schema.py
index c8dfa822..9e2d6b84 100644
--- a/src/ahriman/web/schemas/remote_schema.py
+++ b/src/ahriman/web/schemas/remote_schema.py
@@ -27,22 +27,22 @@ class RemoteSchema(Schema):
request and response package remote schema
"""
- branch = fields.String(required=True, metadata={
+ branch = fields.String(metadata={
"description": "Repository branch",
"example": "master",
})
- git_url = fields.String(required=True, metadata={
+ git_url = fields.String(metadata={
"description": "Package git url",
"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",
"example": ".",
})
source = fields.Enum(PackageSource, by_value=True, required=True, metadata={
"description": "Pacakge source",
})
- web_url = fields.String(required=True, metadata={
+ web_url = fields.String(metadata={
"description": "Package repository page",
"example": "https://aur.archlinux.org/packages/ahriman",
})
diff --git a/tests/ahriman/application/application/test_application_packages.py b/tests/ahriman/application/application/test_application_packages.py
index 1e96a7f4..56cfb121 100644
--- a/tests/ahriman/application/application/test_application_packages.py
+++ b/tests/ahriman/application/application/test_application_packages.py
@@ -86,7 +86,9 @@ def test_add_local(application_packages: ApplicationPackages, package_ahriman: P
application_packages._add_local(package_ahriman.base, "packager")
is_dir_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))
build_queue_mock.assert_called_once_with(package_ahriman)
diff --git a/tests/ahriman/conftest.py b/tests/ahriman/conftest.py
index 2a6c4d92..12b96d86 100644
--- a/tests/ahriman/conftest.py
+++ b/tests/ahriman/conftest.py
@@ -7,6 +7,7 @@ from typing import Any, TypeVar
from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman
+from ahriman.core.alpm.remote import AUR
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
@@ -314,7 +315,13 @@ def package_python_schedule(
return Package(
base="python-schedule",
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)
@@ -451,7 +458,13 @@ def remote_source() -> RemoteSource:
Returns:
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
diff --git a/tests/ahriman/core/alpm/test_pacman.py b/tests/ahriman/core/alpm/test_pacman.py
index a22ac2ea..63644055 100644
--- a/tests/ahriman/core/alpm/test_pacman.py
+++ b/tests/ahriman/core/alpm/test_pacman.py
@@ -8,6 +8,7 @@ from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.configuration import Configuration
+from ahriman.models.pacman_synchronization import PacmanSynchronization
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:
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
- pacman = Pacman("x86_64", configuration, refresh_database=1)
+ pacman = Pacman("x86_64", configuration, refresh_database=PacmanSynchronization.Enabled)
assert pacman.handle
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:
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
- pacman = Pacman("x86_64", configuration, refresh_database=2)
+ pacman = Pacman("x86_64", configuration, refresh_database=PacmanSynchronization.Force)
assert pacman.handle
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")
mocker.patch("pathlib.Path.is_dir", return_value=True)
# 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")
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")
mocker.patch("pathlib.Path.is_dir", return_value=True)
# 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")
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")
mocker.patch("pathlib.Path.is_dir", return_value=False)
# 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")
pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True)
diff --git a/tests/ahriman/core/build_tools/test_sources.py b/tests/ahriman/core/build_tools/test_sources.py
index 8eb0eb6e..137e3185 100644
--- a/tests/ahriman/core/build_tools/test_sources.py
+++ b/tests/ahriman/core/build_tools/test_sources.py
@@ -6,6 +6,7 @@ from unittest.mock import call as MockCall
from ahriman.core.build_tools.sources import Sources
from ahriman.models.package import Package
+from ahriman.models.package_source import PackageSource
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.remote_source import RemoteSource
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")
local = Path("local")
- Sources.fetch(local, None)
+ Sources.fetch(local, RemoteSource(source=PackageSource.Archive))
check_output_mock.assert_has_calls([
MockCall("git", "checkout", "--force", Sources.DEFAULT_BRANCH, cwd=local, logger=pytest.helpers.anyvar(int)),
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
"""
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")
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
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,
cwd=local, logger=pytest.helpers.anyvar(int))
add_mock.assert_called_once_with(local, "PKGBUILD", ".SRCINFO", "local")
- commit_mock.assert_called_once_with(local, commit_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:
@@ -216,12 +232,12 @@ def test_push(package_ahriman: Package, mocker: MockerFixture) -> None:
must correctly push files to remote repository
"""
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")
commit_author = ("commit author", "user@host")
local = Path("local")
- Sources.push(Path("local"), package_ahriman.remote, "glob", commit_author=commit_author)
+ Sources.push(local, package_ahriman.remote, "glob", commit_author=commit_author)
add_mock.assert_called_once_with(local, "glob")
commit_mock.assert_called_once_with(local, commit_author=commit_author)
check_output_mock.assert_called_once_with(
@@ -229,6 +245,18 @@ def test_push(package_ahriman: Package, mocker: MockerFixture) -> None:
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:
"""
must add files to git
@@ -274,34 +302,53 @@ def test_commit(sources: Sources, mocker: MockerFixture) -> None:
"""
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")
local = Path("local")
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(
- "git", "commit", "--allow-empty", "--message", message,
- cwd=local, logger=pytest.helpers.anyvar(int), environment={}
+ "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:
"""
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")
local = Path("local")
message = "Commit message"
- author = ("commit author", "user@host")
- sources.commit(Path("local"), message=message, commit_author=author)
+ user, email = author = ("commit author", "user@host")
+ assert sources.commit(Path("local"), message=message, commit_author=author)
check_output_mock.assert_called_once_with(
- "git", "commit", "--allow-empty", "--message", message,
+ "git", "commit", "--message", message,
cwd=local, logger=pytest.helpers.anyvar(int), environment={
- "GIT_AUTHOR_NAME": "commit author",
- "GIT_AUTHOR_EMAIL": "user@host",
- "GIT_COMMITTER_NAME": "commit author",
- "GIT_COMMITTER_EMAIL": "user@host",
+ "GIT_AUTHOR_NAME": user,
+ "GIT_AUTHOR_EMAIL": email,
+ "GIT_COMMITTER_NAME": user,
+ "GIT_COMMITTER_EMAIL": email,
}
)
@@ -310,13 +357,20 @@ def test_commit_autogenerated_message(sources: Sources, mocker: MockerFixture) -
"""
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")
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(
- "git", "commit", "--allow-empty", "--message", pytest.helpers.anyvar(str, strict=True),
- cwd=local, logger=pytest.helpers.anyvar(int), environment={}
+ "git", "commit", "--message", pytest.helpers.anyvar(str, strict=True),
+ cwd=local, logger=pytest.helpers.anyvar(int), environment={
+ "GIT_AUTHOR_NAME": user,
+ "GIT_AUTHOR_EMAIL": email,
+ "GIT_COMMITTER_NAME": user,
+ "GIT_COMMITTER_EMAIL": email,
+ }
)
@@ -331,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))
+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:
"""
must move content between directories
diff --git a/tests/ahriman/core/database/migrations/test_m001_package_source.py b/tests/ahriman/core/database/migrations/test_m001_package_source.py
index e5e67329..61cca58d 100644
--- a/tests/ahriman/core/database/migrations/test_m001_package_source.py
+++ b/tests/ahriman/core/database/migrations/test_m001_package_source.py
@@ -66,18 +66,3 @@ def test_migrate_package_remotes_vcs(package_ahriman: Package, connection: Conne
migrate_package_remotes(connection, repository_paths)
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()
diff --git a/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py b/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py
index c392a540..83335a17 100644
--- a/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py
+++ b/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py
@@ -35,7 +35,7 @@ def test_migrate_package_depends(connection: Connection, configuration: Configur
migrate_package_depends(connection, configuration)
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), [{
"make_depends": package_ahriman.packages[package_ahriman.base].make_depends,
"opt_depends": package_ahriman.packages[package_ahriman.base].opt_depends,
diff --git a/tests/ahriman/core/database/migrations/test_m007_check_depends.py b/tests/ahriman/core/database/migrations/test_m007_check_depends.py
index 53908bee..2f537af6 100644
--- a/tests/ahriman/core/database/migrations/test_m007_check_depends.py
+++ b/tests/ahriman/core/database/migrations/test_m007_check_depends.py
@@ -35,7 +35,7 @@ def test_migrate_package_depends(connection: Connection, configuration: Configur
migrate_package_check_depends(connection, configuration)
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), [{
"check_depends": package_ahriman.packages[package_ahriman.base].check_depends,
"package": package_ahriman.base,
diff --git a/tests/ahriman/core/database/migrations/test_m008_packagers.py b/tests/ahriman/core/database/migrations/test_m008_packagers.py
index 71e626ed..f3861fcc 100644
--- a/tests/ahriman/core/database/migrations/test_m008_packagers.py
+++ b/tests/ahriman/core/database/migrations/test_m008_packagers.py
@@ -35,7 +35,7 @@ def test_migrate_package_base_packager(connection: Connection, configuration: Co
migrate_package_base_packager(connection, configuration)
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), [{
"package_base": package_ahriman.base,
"packager": package_ahriman.packager,
diff --git a/tests/ahriman/core/database/migrations/test_m009_local_source.py b/tests/ahriman/core/database/migrations/test_m009_local_source.py
new file mode 100644
index 00000000..4c63ef3e
--- /dev/null
+++ b/tests/ahriman/core/database/migrations/test_m009_local_source.py
@@ -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
diff --git a/tests/ahriman/core/database/operations/test_package_operations.py b/tests/ahriman/core/database/operations/test_package_operations.py
index 5e7309c3..7ad2cee7 100644
--- a/tests/ahriman/core/database/operations/test_package_operations.py
+++ b/tests/ahriman/core/database/operations/test_package_operations.py
@@ -193,7 +193,7 @@ def test_remote_update_update(database: SQLite, package_ahriman: Package) -> Non
must perform package remote update for existing package
"""
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
database.remote_update(package_ahriman)
diff --git a/tests/ahriman/core/repository/test_repository.py b/tests/ahriman/core/repository/test_repository.py
index e45ff345..413d2aca 100644
--- a/tests/ahriman/core/repository/test_repository.py
+++ b/tests/ahriman/core/repository/test_repository.py
@@ -11,6 +11,8 @@ from ahriman.core.repository import Repository
from ahriman.core.sign.gpg import GPG
from ahriman.models.context_key import ContextKey
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:
@@ -51,6 +53,9 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag
for package, props in package_python_schedule.packages.items()
] + [package_ahriman]
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")])
assert len(packages) == 2
diff --git a/tests/ahriman/core/repository/test_update_handler.py b/tests/ahriman/core/repository/test_update_handler.py
index 506d7a47..1d355b98 100644
--- a/tests/ahriman/core/repository/test_update_handler.py
+++ b/tests/ahriman/core/repository/test_update_handler.py
@@ -42,7 +42,7 @@ def test_updates_aur_official(update_handler: UpdateHandler, package_ahriman: Pa
"""
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.models.package.Package.is_outdated", return_value=True)
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)
+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,
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)
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)
status_client_mock.assert_called_once_with(package_ahriman.base)
package_is_outdated_mock.assert_called_once_with(
diff --git a/tests/ahriman/core/test_spawn.py b/tests/ahriman/core/test_spawn.py
index 625bc10c..9e3f6ddc 100644
--- a/tests/ahriman/core/test_spawn.py
+++ b/tests/ahriman/core/test_spawn.py
@@ -75,7 +75,7 @@ def test_packages_add(spawner: Spawn, mocker: MockerFixture) -> None:
"""
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
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:
@@ -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")
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:
@@ -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")
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:
diff --git a/tests/ahriman/models/conftest.py b/tests/ahriman/models/conftest.py
index 0ea86ccf..4b62b4fc 100644
--- a/tests/ahriman/models/conftest.py
+++ b/tests/ahriman/models/conftest.py
@@ -4,6 +4,7 @@ import pytest
from unittest.mock import MagicMock, PropertyMock
from ahriman import __version__
+from ahriman.core.alpm.remote import AUR
from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.counters import Counters
@@ -70,7 +71,13 @@ def package_tpacpi_bat_git() -> Package:
return Package(
base="tpacpi-bat-git",
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()})
diff --git a/tests/ahriman/models/test_package.py b/tests/ahriman/models/test_package.py
index 452b25df..e6f0e0c8 100644
--- a/tests/ahriman/models/test_package.py
+++ b/tests/ahriman/models/test_package.py
@@ -158,7 +158,10 @@ def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker
"""
mocker.patch("ahriman.models.package_description.PackageDescription.from_package",
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,
diff --git a/tests/ahriman/models/test_package_source.py b/tests/ahriman/models/test_package_source.py
index 22c55651..27c18b53 100644
--- a/tests/ahriman/models/test_package_source.py
+++ b/tests/ahriman/models/test_package_source.py
@@ -4,6 +4,7 @@ from pathlib import Path
from ahriman.models.package_description import PackageDescription
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]:
@@ -21,71 +22,86 @@ def _is_file_mock(is_any_file: bool, is_pkgbuild: bool) -> Callable[[Path], bool
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
"""
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
"""
mocker.patch("pathlib.Path.is_dir", return_value=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
"""
mocker.patch("pathlib.Path.is_dir", 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
"""
mocker.patch("pathlib.Path.is_dir", return_value=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
"""
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
"""
- 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))
- 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
"""
- 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))
- 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
"""
mocker.patch("pathlib.Path.is_dir", return_value=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
diff --git a/tests/ahriman/models/test_remote_source.py b/tests/ahriman/models/test_remote_source.py
index 9d9969fd..b1126b6a 100644
--- a/tests/ahriman/models/test_remote_source.py
+++ b/tests/ahriman/models/test_remote_source.py
@@ -1,7 +1,8 @@
-from pathlib import Path
-from pytest_mock import MockerFixture
+import pytest
-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.remote_source import RemoteSource
@@ -20,6 +21,14 @@ def test_post_init(remote_source: RemoteSource) -> None:
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:
"""
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
-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")
- remote_web_url_mock = mocker.patch("ahriman.core.alpm.remote.AUR.remote_web_url")
-
- 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
+ with pytest.raises(InitializeError):
+ RemoteSource(source=PackageSource.Remote).git_source()