Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6c40451fe | ||
|
|
5fa2709a84 | ||
|
|
386d8aa2f2 | ||
|
|
70b06d2b3a | ||
|
|
00c668b595 | ||
|
|
12a38b7e6a | ||
|
|
37fd2192a5 |
No files matched your search
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(*)"
|
||||||
|
],
|
||||||
"ask": [
|
"ask": [
|
||||||
"Skill(update-config)",
|
"Skill(update-config)",
|
||||||
"Skill(update-config:*)"
|
"Skill(update-config:*)"
|
||||||
@@ -7,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"failIfUnavailable": true,
|
||||||
"autoAllowBashIfSandboxed": true
|
"autoAllowBashIfSandboxed": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,34 @@
|
|||||||
|
## [1.13.4] - 2026-05-27
|
||||||
|
|
||||||
|
* Changed
|
||||||
|
|
||||||
|
* pkgmgr release now derives the distro-package name from existing
|
||||||
|
packaging metadata instead of the repository folder name. The lookup
|
||||||
|
order is packaging/debian/control Package field, then
|
||||||
|
packaging/arch/PKGBUILD pkgname value, then RPM spec Name field, then
|
||||||
|
folder basename as legacy fallback. Renaming a repository folder (for
|
||||||
|
example infinito-nexus to infinito-nexus-core) no longer silently
|
||||||
|
flips the debian/changelog top entry and the RPM changelog stanza to
|
||||||
|
a new identifier. Those keep matching the authoritative Package,
|
||||||
|
pkgname, or Name value in the packaging files, which is what apt,
|
||||||
|
pacman, and dnf index against.
|
||||||
|
|
||||||
|
Added
|
||||||
|
|
||||||
|
* RepoPaths gains a debian_control slot that is discovered alongside
|
||||||
|
debian_changelog under both packaging/debian (new layout) and debian
|
||||||
|
(legacy layout).
|
||||||
|
* pkgmgr.actions.release.package_name.resolve_package_name centralises
|
||||||
|
the priority chain and is unit-tested under
|
||||||
|
tests/unit/pkgmgr/actions/release/test_package_name.py.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
|
||||||
|
* dpkg-source --before-build no longer fails with the message about
|
||||||
|
source package having two conflicting values after a repo-folder
|
||||||
|
rename, because the changelog and control file stay in agreement.
|
||||||
|
|
||||||
|
|
||||||
## [1.13.3] - 2026-03-26
|
## [1.13.3] - 2026-03-26
|
||||||
|
|
||||||
* CI pipelines now include automated security scanning (CodeQL, Docker lint), increasing detection of vulnerabilities and misconfigurations
|
* CI pipelines now include automated security scanning (CodeQL, Docker lint), increasing detection of vulnerabilities and misconfigurations
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
rec {
|
rec {
|
||||||
pkgmgr = pyPkgs.buildPythonApplication {
|
pkgmgr = pyPkgs.buildPythonApplication {
|
||||||
pname = "package-manager";
|
pname = "package-manager";
|
||||||
version = "1.13.3";
|
version = "1.13.4";
|
||||||
|
|
||||||
# Use the git repo as source
|
# Use the git repo as source
|
||||||
src = ./.;
|
src = ./.;
|
||||||
@@ -40,6 +40,10 @@
|
|||||||
# Build using pyproject.toml
|
# Build using pyproject.toml
|
||||||
format = "pyproject";
|
format = "pyproject";
|
||||||
|
|
||||||
|
# Clear any stale wheels carried in from the source tree so
|
||||||
|
# pypaInstallPhase doesn't collide on bin/pkgmgr.
|
||||||
|
preBuild = "rm -rf dist";
|
||||||
|
|
||||||
# Build backend requirements from [build-system]
|
# Build backend requirements from [build-system]
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
pyPkgs.setuptools
|
pyPkgs.setuptools
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Kevin Veen-Birkenbach <info@veen.world>
|
# Maintainer: Kevin Veen-Birkenbach <info@veen.world>
|
||||||
|
|
||||||
pkgname=package-manager
|
pkgname=package-manager
|
||||||
pkgver=1.13.3
|
pkgver=1.13.4
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)."
|
pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)."
|
||||||
arch=('any')
|
arch=('any')
|
||||||
|
|||||||
@@ -1,3 +1,35 @@
|
|||||||
|
package-manager (1.13.4-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Changed
|
||||||
|
|
||||||
|
* pkgmgr release now derives the distro-package name from existing
|
||||||
|
packaging metadata instead of the repository folder name. The lookup
|
||||||
|
order is packaging/debian/control Package field, then
|
||||||
|
packaging/arch/PKGBUILD pkgname value, then RPM spec Name field, then
|
||||||
|
folder basename as legacy fallback. Renaming a repository folder (for
|
||||||
|
example infinito-nexus to infinito-nexus-core) no longer silently
|
||||||
|
flips the debian/changelog top entry and the RPM changelog stanza to
|
||||||
|
a new identifier. Those keep matching the authoritative Package,
|
||||||
|
pkgname, or Name value in the packaging files, which is what apt,
|
||||||
|
pacman, and dnf index against.
|
||||||
|
|
||||||
|
Added
|
||||||
|
|
||||||
|
* RepoPaths gains a debian_control slot that is discovered alongside
|
||||||
|
debian_changelog under both packaging/debian (new layout) and debian
|
||||||
|
(legacy layout).
|
||||||
|
* pkgmgr.actions.release.package_name.resolve_package_name centralises
|
||||||
|
the priority chain and is unit-tested under
|
||||||
|
tests/unit/pkgmgr/actions/release/test_package_name.py.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
|
||||||
|
* dpkg-source --before-build no longer fails with the message about
|
||||||
|
source package having two conflicting values after a repo-folder
|
||||||
|
rename, because the changelog and control file stay in agreement.
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Wed, 27 May 2026 20:32:39 +0200
|
||||||
|
|
||||||
package-manager (1.13.3-1) unstable; urgency=medium
|
package-manager (1.13.3-1) unstable; urgency=medium
|
||||||
|
|
||||||
* CI pipelines now include automated security scanning (CodeQL, Docker lint), increasing detection of vulnerabilities and misconfigurations
|
* CI pipelines now include automated security scanning (CodeQL, Docker lint), increasing detection of vulnerabilities and misconfigurations
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: package-manager
|
Name: package-manager
|
||||||
Version: 1.13.3
|
Version: 1.13.4
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: Wrapper that runs Kevin's package-manager via Nix flake
|
Summary: Wrapper that runs Kevin's package-manager via Nix flake
|
||||||
|
|
||||||
@@ -74,6 +74,35 @@ echo ">>> package-manager removed. Nix itself was not removed."
|
|||||||
/usr/lib/package-manager/
|
/usr/lib/package-manager/
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Wed May 27 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.13.4-1
|
||||||
|
- Changed
|
||||||
|
|
||||||
|
* pkgmgr release now derives the distro-package name from existing
|
||||||
|
packaging metadata instead of the repository folder name. The lookup
|
||||||
|
order is packaging/debian/control Package field, then
|
||||||
|
packaging/arch/PKGBUILD pkgname value, then RPM spec Name field, then
|
||||||
|
folder basename as legacy fallback. Renaming a repository folder (for
|
||||||
|
example infinito-nexus to infinito-nexus-core) no longer silently
|
||||||
|
flips the debian/changelog top entry and the RPM changelog stanza to
|
||||||
|
a new identifier. Those keep matching the authoritative Package,
|
||||||
|
pkgname, or Name value in the packaging files, which is what apt,
|
||||||
|
pacman, and dnf index against.
|
||||||
|
|
||||||
|
Added
|
||||||
|
|
||||||
|
* RepoPaths gains a debian_control slot that is discovered alongside
|
||||||
|
debian_changelog under both packaging/debian (new layout) and debian
|
||||||
|
(legacy layout).
|
||||||
|
* pkgmgr.actions.release.package_name.resolve_package_name centralises
|
||||||
|
the priority chain and is unit-tested under
|
||||||
|
tests/unit/pkgmgr/actions/release/test_package_name.py.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
|
||||||
|
* dpkg-source --before-build no longer fails with the message about
|
||||||
|
source package having two conflicting values after a repo-folder
|
||||||
|
rename, because the changelog and control file stay in agreement.
|
||||||
|
|
||||||
* Thu Mar 26 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.13.3-1
|
* Thu Mar 26 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.13.3-1
|
||||||
- CI pipelines now include automated security scanning (CodeQL, Docker lint), increasing detection of vulnerabilities and misconfigurations
|
- CI pipelines now include automated security scanning (CodeQL, Docker lint), increasing detection of vulnerabilities and misconfigurations
|
||||||
* Workflow permissions were tightened and fixed, ensuring secure and reliable execution of reusable workflows
|
* Workflow permissions were tightened and fixed, ensuring secure and reliable execution of reusable workflows
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "kpmx"
|
name = "kpmx"
|
||||||
version = "1.13.3"
|
version = "1.13.4"
|
||||||
description = "Kevin's package-manager tool (pkgmgr)"
|
description = "Kevin's package-manager tool (pkgmgr)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ echo "[aur-builder-setup] Configuring sudoers for aur_builder..."
|
|||||||
${ROOT_CMD} bash -c "echo '%aur_builder ALL=(ALL) NOPASSWD: /usr/bin/pacman' > /etc/sudoers.d/aur_builder"
|
${ROOT_CMD} bash -c "echo '%aur_builder ALL=(ALL) NOPASSWD: /usr/bin/pacman' > /etc/sudoers.d/aur_builder"
|
||||||
${ROOT_CMD} chmod 0440 /etc/sudoers.d/aur_builder
|
${ROOT_CMD} chmod 0440 /etc/sudoers.d/aur_builder
|
||||||
|
|
||||||
RUN_AS_AUR=(su - aur_builder -s /bin/bash -c)
|
RUN_AS_AUR=(runuser -u aur_builder -- bash -c)
|
||||||
|
|
||||||
echo "[aur-builder-setup] Ensuring yay is installed for aur_builder..."
|
echo "[aur-builder-setup] Ensuring yay is installed for aur_builder..."
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ echo "[arch/package] Using 'aur_builder' user for makepkg..."
|
|||||||
chown -R aur_builder:aur_builder "${BUILD_ROOT}"
|
chown -R aur_builder:aur_builder "${BUILD_ROOT}"
|
||||||
|
|
||||||
echo "[arch/package] Running makepkg in: ${PKG_BUILD_DIR}"
|
echo "[arch/package] Running makepkg in: ${PKG_BUILD_DIR}"
|
||||||
su aur_builder -c "cd '${PKG_BUILD_DIR}' && rm -f package-manager-*.pkg.tar.* && makepkg --noconfirm --clean --nodeps"
|
runuser -u aur_builder -- bash -c "cd '${PKG_BUILD_DIR}' && rm -f package-manager-*.pkg.tar.* && makepkg --noconfirm --clean --nodeps"
|
||||||
|
|
||||||
echo "[arch/package] Installing generated Arch package..."
|
echo "[arch/package] Installing generated Arch package..."
|
||||||
pkg_path="$(find "${PKG_BUILD_DIR}" -maxdepth 1 -type f -name 'package-manager-*.pkg.tar.*' | head -n1)"
|
pkg_path="$(find "${PKG_BUILD_DIR}" -maxdepth 1 -type f -name 'package-manager-*.pkg.tar.*' | head -n1)"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||||
echo "[installation/install] Warning: Installation is just possible via root."
|
echo "[installation/install] ERROR: Installation requires root. Re-run with sudo." >&2
|
||||||
exit 0
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[installation] Running as root (EUID=0)."
|
echo "[installation] Running as root (EUID=0)."
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""Resolve the distro-package name for a release.
|
||||||
|
|
||||||
|
The release flow writes the package identifier into `debian/changelog`,
|
||||||
|
the RPM `%changelog` stanza, etc. Historically pkgmgr derived this
|
||||||
|
identifier from the repository folder name (`os.path.basename(repo_root)`),
|
||||||
|
which silently breaks when the repo is renamed but the existing packaging
|
||||||
|
files still ship the legacy name. Renaming the folder must not change the
|
||||||
|
distro-package identity — `apt`, `pacman`, `dnf`, and every downstream
|
||||||
|
manifest pin the old name.
|
||||||
|
|
||||||
|
The resolver therefore walks the existing packaging files in priority
|
||||||
|
order and only falls back to the folder name when none of them ship an
|
||||||
|
explicit name.
|
||||||
|
|
||||||
|
Priority:
|
||||||
|
1. `debian/control` `Package:` field (most authoritative — dpkg-source
|
||||||
|
refuses to build if changelog and control disagree)
|
||||||
|
2. `packaging/arch/PKGBUILD` `pkgname=` value
|
||||||
|
3. RPM spec `Name:` field
|
||||||
|
4. Repository folder basename (legacy fallback)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pkgmgr.core.repository.paths import RepoPaths
|
||||||
|
|
||||||
|
|
||||||
|
_DEBIAN_PACKAGE_RE = re.compile(r"^Package:\s*(\S+)\s*$", re.MULTILINE)
|
||||||
|
_PKGBUILD_NAME_RE = re.compile(r"^pkgname=([^\s#]+)\s*$", re.MULTILINE)
|
||||||
|
_RPM_NAME_RE = re.compile(r"^Name:\s*(\S+)\s*$", re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
def _read(path: Optional[str]) -> str:
|
||||||
|
if not path or not os.path.isfile(path):
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _extract(pattern: re.Pattern[str], text: str) -> Optional[str]:
|
||||||
|
if not text:
|
||||||
|
return None
|
||||||
|
match = pattern.search(text)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
value = match.group(1).strip().strip('"').strip("'")
|
||||||
|
return value or None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_package_name(paths: RepoPaths) -> str:
|
||||||
|
"""Return the distro-package name for the repo, with a folder fallback.
|
||||||
|
|
||||||
|
The fallback uses `os.path.basename(paths.repo_dir)` so behaviour is
|
||||||
|
backwards-compatible for repos that ship no packaging metadata yet.
|
||||||
|
"""
|
||||||
|
debian_name = _extract(_DEBIAN_PACKAGE_RE, _read(paths.debian_control))
|
||||||
|
if debian_name:
|
||||||
|
return debian_name
|
||||||
|
|
||||||
|
pkgbuild_name = _extract(_PKGBUILD_NAME_RE, _read(paths.arch_pkgbuild))
|
||||||
|
if pkgbuild_name:
|
||||||
|
return pkgbuild_name
|
||||||
|
|
||||||
|
rpm_name = _extract(_RPM_NAME_RE, _read(paths.rpm_spec))
|
||||||
|
if rpm_name:
|
||||||
|
return rpm_name
|
||||||
|
|
||||||
|
return os.path.basename(paths.repo_dir) or "package"
|
||||||
@@ -24,6 +24,7 @@ from .git_ops import (
|
|||||||
is_highest_version_tag,
|
is_highest_version_tag,
|
||||||
update_latest_tag,
|
update_latest_tag,
|
||||||
)
|
)
|
||||||
|
from .package_name import resolve_package_name
|
||||||
from .prompts import confirm_proceed_release, should_delete_branch
|
from .prompts import confirm_proceed_release, should_delete_branch
|
||||||
from .versioning import bump_semver, determine_current_version
|
from .versioning import bump_semver, determine_current_version
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ def _release_impl(
|
|||||||
if changelog_message.strip():
|
if changelog_message.strip():
|
||||||
effective_message = changelog_message.strip()
|
effective_message = changelog_message.strip()
|
||||||
|
|
||||||
package_name = os.path.basename(repo_root) or "package-manager"
|
package_name = resolve_package_name(paths)
|
||||||
|
|
||||||
if paths.debian_changelog:
|
if paths.debian_changelog:
|
||||||
update_debian_changelog(
|
update_debian_changelog(
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from typing import Any, Callable, Dict, List, Tuple
|
||||||
|
|
||||||
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
|
||||||
|
Repository = Dict[str, Any]
|
||||||
|
RepoRef = Tuple[str, str]
|
||||||
|
OpResult = Tuple[bool, str]
|
||||||
|
RepoOp = Callable[[str], OpResult]
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_repos(
|
||||||
|
selected_repos: List[Repository],
|
||||||
|
repositories_base_dir: str,
|
||||||
|
all_repos: List[Repository],
|
||||||
|
) -> List[RepoRef]:
|
||||||
|
"""
|
||||||
|
Resolve ``(identifier, repo_dir)`` pairs for ``selected_repos``.
|
||||||
|
|
||||||
|
Repositories whose directory does not exist on disk are reported and
|
||||||
|
skipped, matching the prior behavior of pull/push handlers.
|
||||||
|
"""
|
||||||
|
resolved: List[RepoRef] = []
|
||||||
|
for repo in selected_repos:
|
||||||
|
ident = get_repo_identifier(repo, all_repos)
|
||||||
|
rd = get_repo_dir(repositories_base_dir, repo)
|
||||||
|
if not os.path.exists(rd):
|
||||||
|
print(f"Repository directory '{rd}' not found for {ident}.")
|
||||||
|
continue
|
||||||
|
resolved.append((ident, rd))
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
|
def run_on_repos(
|
||||||
|
repos: List[RepoRef],
|
||||||
|
op: RepoOp,
|
||||||
|
*,
|
||||||
|
jobs: int,
|
||||||
|
op_name: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Run ``op(repo_dir) -> (ok, msg)`` for each repo, optionally in parallel.
|
||||||
|
|
||||||
|
- ``jobs == 1``: serial, quiet on success, prints ``msg`` on failure.
|
||||||
|
- ``jobs > 1``: parallel via ThreadPoolExecutor, prints a banner plus
|
||||||
|
``[OK]``/``[FAIL]`` per repo and a final summary.
|
||||||
|
- Exits with status 1 if any operation failed.
|
||||||
|
"""
|
||||||
|
if not repos:
|
||||||
|
return
|
||||||
|
|
||||||
|
effective_jobs = max(1, min(jobs, len(repos)))
|
||||||
|
failed: List[Tuple[str, str]] = []
|
||||||
|
|
||||||
|
if effective_jobs == 1:
|
||||||
|
for ident, rd in repos:
|
||||||
|
ok, msg = op(rd)
|
||||||
|
if not ok:
|
||||||
|
print(msg)
|
||||||
|
failed.append((ident, msg))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"[{op_name.upper()}] Running {len(repos)} {op_name}(s) with up to "
|
||||||
|
f"{effective_jobs} parallel jobs..."
|
||||||
|
)
|
||||||
|
with ThreadPoolExecutor(max_workers=effective_jobs) as executor:
|
||||||
|
futures = {executor.submit(op, rd): ident for ident, rd in repos}
|
||||||
|
for future in as_completed(futures):
|
||||||
|
ident = futures[future]
|
||||||
|
ok, msg = future.result()
|
||||||
|
if ok:
|
||||||
|
print(f"[OK] {ident}")
|
||||||
|
else:
|
||||||
|
print(f"[FAIL] {ident}")
|
||||||
|
for line in msg.splitlines():
|
||||||
|
print(f" {line}")
|
||||||
|
failed.append((ident, msg))
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
if effective_jobs > 1:
|
||||||
|
print(f"\n[SUMMARY] {len(failed)} of {len(repos)} {op_name}(s) failed:")
|
||||||
|
for ident, _msg in failed:
|
||||||
|
print(f" - {ident}")
|
||||||
|
sys.exit(1)
|
||||||
@@ -1,17 +1,67 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from typing import List, Dict, Any
|
from typing import Any, Dict, List, Tuple
|
||||||
|
|
||||||
|
from pkgmgr.actions.repository._parallel import RepoRef, run_on_repos
|
||||||
from pkgmgr.core.git.commands import pull_args, GitPullArgsError
|
from pkgmgr.core.git.commands import pull_args, GitPullArgsError
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
from pkgmgr.core.repository.verify import verify_repository
|
from pkgmgr.core.repository.verify import verify_repository
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
def _pull_one(repo_dir: str, extra_args: List[str], preview: bool) -> Tuple[bool, str]:
|
||||||
|
try:
|
||||||
|
pull_args(extra_args, cwd=repo_dir, preview=preview)
|
||||||
|
return (True, "")
|
||||||
|
except GitPullArgsError as exc:
|
||||||
|
return (False, str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_one(
|
||||||
|
repo: Repository,
|
||||||
|
repo_dir: str,
|
||||||
|
no_verification: bool,
|
||||||
|
) -> Tuple[bool, bool, List[str]]:
|
||||||
|
"""Returns (has_verified_info, verified_ok, errors)."""
|
||||||
|
verified_ok, errors, _commit, _key = verify_repository(
|
||||||
|
repo,
|
||||||
|
repo_dir,
|
||||||
|
mode="pull",
|
||||||
|
no_verification=no_verification,
|
||||||
|
)
|
||||||
|
return (bool(repo.get("verified")), verified_ok, errors)
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_all(
|
||||||
|
candidates: List[Tuple[Repository, str, str]],
|
||||||
|
no_verification: bool,
|
||||||
|
jobs: int,
|
||||||
|
) -> List[Tuple[str, str, bool, bool, List[str]]]:
|
||||||
|
"""
|
||||||
|
Verify all candidates (parallel if ``jobs > 1``), preserving input order.
|
||||||
|
|
||||||
|
Returns one tuple per candidate: ``(ident, repo_dir, has_verified_info,
|
||||||
|
verified_ok, errors)``.
|
||||||
|
"""
|
||||||
|
verify_jobs = max(1, min(jobs, len(candidates)))
|
||||||
|
if verify_jobs == 1:
|
||||||
|
return [
|
||||||
|
(ident, rd, *_verify_one(repo, rd, no_verification))
|
||||||
|
for repo, ident, rd in candidates
|
||||||
|
]
|
||||||
|
with ThreadPoolExecutor(max_workers=verify_jobs) as executor:
|
||||||
|
futures = [
|
||||||
|
executor.submit(_verify_one, repo, rd, no_verification)
|
||||||
|
for repo, _ident, rd in candidates
|
||||||
|
]
|
||||||
|
results = [f.result() for f in futures]
|
||||||
|
return [(ident, rd, *res) for (_repo, ident, rd), res in zip(candidates, results)]
|
||||||
|
|
||||||
|
|
||||||
def pull_with_verification(
|
def pull_with_verification(
|
||||||
selected_repos: List[Repository],
|
selected_repos: List[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
@@ -19,41 +69,50 @@ def pull_with_verification(
|
|||||||
extra_args: List[str],
|
extra_args: List[str],
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
preview: bool,
|
preview: bool,
|
||||||
|
jobs: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Execute `git pull` for each repository with verification.
|
Execute `git pull` for each repository with verification.
|
||||||
|
|
||||||
- If verification fails and verification is enabled, prompt user to continue.
|
- Verification (I/O-bound) runs in parallel when ``jobs > 1``.
|
||||||
- Uses core.git.commands.pull_args() (no raw subprocess usage).
|
- Interactive prompts for failed verifications are handled serially on the
|
||||||
|
main thread after parallel verification completes.
|
||||||
|
- Approved repos are then pulled in parallel when ``jobs > 1``.
|
||||||
|
- On any pull failure, prints a summary and exits with status 1.
|
||||||
"""
|
"""
|
||||||
|
candidates: List[Tuple[Repository, str, str]] = []
|
||||||
for repo in selected_repos:
|
for repo in selected_repos:
|
||||||
repo_identifier = get_repo_identifier(repo, all_repos)
|
ident = get_repo_identifier(repo, all_repos)
|
||||||
repo_dir = get_repo_dir(repositories_base_dir, repo)
|
rd = get_repo_dir(repositories_base_dir, repo)
|
||||||
|
if not os.path.exists(rd):
|
||||||
if not os.path.exists(repo_dir):
|
print(f"Repository directory '{rd}' not found for {ident}.")
|
||||||
print(f"Repository directory '{repo_dir}' not found for {repo_identifier}.")
|
|
||||||
continue
|
continue
|
||||||
|
candidates.append((repo, ident, rd))
|
||||||
|
|
||||||
verified_info = repo.get("verified")
|
if not candidates:
|
||||||
verified_ok, errors, _commit_hash, _signing_key = verify_repository(
|
return
|
||||||
repo,
|
|
||||||
repo_dir,
|
|
||||||
mode="pull",
|
|
||||||
no_verification=no_verification,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not preview and not no_verification and verified_info and not verified_ok:
|
verify_results = _verify_all(candidates, no_verification, jobs)
|
||||||
print(f"Warning: Verification failed for {repo_identifier}:")
|
|
||||||
|
approved: List[RepoRef] = []
|
||||||
|
for ident, rd, has_verified_info, verified_ok, errors in verify_results:
|
||||||
|
if (
|
||||||
|
not preview
|
||||||
|
and not no_verification
|
||||||
|
and has_verified_info
|
||||||
|
and not verified_ok
|
||||||
|
):
|
||||||
|
print(f"Warning: Verification failed for {ident}:")
|
||||||
for err in errors:
|
for err in errors:
|
||||||
print(f" - {err}")
|
print(f" - {err}")
|
||||||
choice = input("Proceed with 'git pull'? (y/N): ").strip().lower()
|
choice = input("Proceed with 'git pull'? (y/N): ").strip().lower()
|
||||||
if choice != "y":
|
if choice != "y":
|
||||||
continue
|
continue
|
||||||
|
approved.append((ident, rd))
|
||||||
|
|
||||||
try:
|
run_on_repos(
|
||||||
pull_args(extra_args, cwd=repo_dir, preview=preview)
|
approved,
|
||||||
except GitPullArgsError as exc:
|
lambda rd: _pull_one(rd, extra_args, preview),
|
||||||
# Keep behavior consistent with previous implementation:
|
jobs=jobs,
|
||||||
# stop on first failure and propagate return code as generic failure.
|
op_name="pull",
|
||||||
print(str(exc))
|
)
|
||||||
sys.exit(1)
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Tuple
|
||||||
|
|
||||||
|
from pkgmgr.actions.repository._parallel import (
|
||||||
|
resolve_repos,
|
||||||
|
run_on_repos,
|
||||||
|
)
|
||||||
|
from pkgmgr.core.git.commands import push_args, GitPushArgsError
|
||||||
|
|
||||||
|
Repository = Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
def _push_one(repo_dir: str, extra_args: List[str], preview: bool) -> Tuple[bool, str]:
|
||||||
|
try:
|
||||||
|
push_args(extra_args, cwd=repo_dir, preview=preview)
|
||||||
|
return (True, "")
|
||||||
|
except GitPushArgsError as exc:
|
||||||
|
return (False, str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
def push_in_parallel(
|
||||||
|
selected_repos: List[Repository],
|
||||||
|
repositories_base_dir: str,
|
||||||
|
all_repos: List[Repository],
|
||||||
|
extra_args: List[str],
|
||||||
|
preview: bool,
|
||||||
|
jobs: int = 1,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Execute `git push` for each repository, optionally in parallel.
|
||||||
|
"""
|
||||||
|
repos = resolve_repos(selected_repos, repositories_base_dir, all_repos)
|
||||||
|
run_on_repos(
|
||||||
|
repos,
|
||||||
|
lambda rd: _push_one(rd, extra_args, preview),
|
||||||
|
jobs=jobs,
|
||||||
|
op_name="push",
|
||||||
|
)
|
||||||
@@ -12,6 +12,7 @@ from pkgmgr.cli.context import CLIContext
|
|||||||
from pkgmgr.actions.repository.clone import clone_repos
|
from pkgmgr.actions.repository.clone import clone_repos
|
||||||
from pkgmgr.actions.proxy import exec_proxy_command
|
from pkgmgr.actions.proxy import exec_proxy_command
|
||||||
from pkgmgr.actions.repository.pull import pull_with_verification
|
from pkgmgr.actions.repository.pull import pull_with_verification
|
||||||
|
from pkgmgr.actions.repository.push import push_in_parallel
|
||||||
from pkgmgr.core.repository.selected import get_selected_repos
|
from pkgmgr.core.repository.selected import get_selected_repos
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
|
|
||||||
@@ -177,6 +178,17 @@ def register_proxy_commands(
|
|||||||
default=False,
|
default=False,
|
||||||
help="Disable verification via commit/gpg",
|
help="Disable verification via commit/gpg",
|
||||||
)
|
)
|
||||||
|
if subcommand in ("pull", "push"):
|
||||||
|
parser.add_argument(
|
||||||
|
"-j",
|
||||||
|
"--jobs",
|
||||||
|
type=int,
|
||||||
|
default=min(os.cpu_count() or 4, 8),
|
||||||
|
help=(
|
||||||
|
f"Number of parallel {subcommand}s "
|
||||||
|
"(default: min(cpu_count, 8)). Use 1 for sequential."
|
||||||
|
),
|
||||||
|
)
|
||||||
if subcommand == "clone":
|
if subcommand == "clone":
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--clone-mode",
|
"--clone-mode",
|
||||||
@@ -234,6 +246,16 @@ def maybe_handle_proxy(args: argparse.Namespace, ctx: CLIContext) -> bool:
|
|||||||
args.extra_args,
|
args.extra_args,
|
||||||
args.no_verification,
|
args.no_verification,
|
||||||
args.preview,
|
args.preview,
|
||||||
|
jobs=args.jobs,
|
||||||
|
)
|
||||||
|
elif args.command == "push":
|
||||||
|
push_in_parallel(
|
||||||
|
selected,
|
||||||
|
ctx.repositories_base_dir,
|
||||||
|
ctx.all_repositories,
|
||||||
|
args.extra_args,
|
||||||
|
args.preview,
|
||||||
|
jobs=args.jobs,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
exec_proxy_command(
|
exec_proxy_command(
|
||||||
|
|||||||
@@ -5,16 +5,6 @@ directories:
|
|||||||
workspaces: ~/Workspaces/
|
workspaces: ~/Workspaces/
|
||||||
binaries: ~/.local/bin/
|
binaries: ~/.local/bin/
|
||||||
repositories:
|
repositories:
|
||||||
- account: kevinveenbirkenbach
|
|
||||||
alias: arc
|
|
||||||
provider: github.com
|
|
||||||
repository: analysis-ready-code
|
|
||||||
description: Analysis-Ready Code (ARC) is a Python utility that recursively scans directories and transforms source code into a streamlined, analysis-ready format by removing comments, filtering files, and compressing content—perfect for AI and automated code analysis.
|
|
||||||
homepage: https://github.com/kevinveenbirkenbach/analysis-ready-code
|
|
||||||
verified:
|
|
||||||
gpg_keys:
|
|
||||||
- 44D8F11FD62F878E
|
|
||||||
- B5690EEEBB952194
|
|
||||||
- account: kevinveenbirkenbach
|
- account: kevinveenbirkenbach
|
||||||
description: A configurable Python package manager that automates repository tasks—including cloning, installation, updates, and status reporting—based on a YAML configuration file for streamlined software management which gives you access to the Kevin Veen-Birkenbach Code Universe.
|
description: A configurable Python package manager that automates repository tasks—including cloning, installation, updates, and status reporting—based on a YAML configuration file for streamlined software management which gives you access to the Kevin Veen-Birkenbach Code Universe.
|
||||||
homepage: https://github.com/kevinveenbirkenbach/package-manager
|
homepage: https://github.com/kevinveenbirkenbach/package-manager
|
||||||
@@ -274,12 +264,11 @@ repositories:
|
|||||||
gpg_keys:
|
gpg_keys:
|
||||||
- 44D8F11FD62F878E
|
- 44D8F11FD62F878E
|
||||||
- B5690EEEBB952194
|
- B5690EEEBB952194
|
||||||
- account: kevinveenbirkenbach
|
- account: infinito-nexus
|
||||||
alias: infinito
|
|
||||||
provider: github.com
|
provider: github.com
|
||||||
description: Infinito.nexus streamlines Linux-based system setups and Docker image administration, perfect for servers and PCs. It offers extensive solutions for system initialization, admin tools, backups, monitoring, updates, driver management, security, and VPNs.
|
description: Infinito.nexus streamlines Linux-based system setups and Docker image administration, perfect for servers and PCs. It offers extensive solutions for system initialization, admin tools, backups, monitoring, updates, driver management, security, and VPNs.
|
||||||
homepage: https://infinito.nexus
|
homepage: https://infinito.nexus
|
||||||
repository: infinito-nexus
|
repository: core
|
||||||
verified:
|
verified:
|
||||||
gpg_keys:
|
gpg_keys:
|
||||||
- 44D8F11FD62F878E
|
- 44D8F11FD62F878E
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from .pull import GitPullError, pull
|
|||||||
from .pull_args import GitPullArgsError, pull_args
|
from .pull_args import GitPullArgsError, pull_args
|
||||||
from .pull_ff_only import GitPullFfOnlyError, pull_ff_only
|
from .pull_ff_only import GitPullFfOnlyError, pull_ff_only
|
||||||
from .push import GitPushError, push
|
from .push import GitPushError, push
|
||||||
|
from .push_args import GitPushArgsError, push_args
|
||||||
from .push_upstream import GitPushUpstreamError, push_upstream
|
from .push_upstream import GitPushUpstreamError, push_upstream
|
||||||
from .set_remote_url import GitSetRemoteUrlError, set_remote_url
|
from .set_remote_url import GitSetRemoteUrlError, set_remote_url
|
||||||
from .tag_annotated import GitTagAnnotatedError, tag_annotated
|
from .tag_annotated import GitTagAnnotatedError, tag_annotated
|
||||||
@@ -34,6 +35,7 @@ __all__ = [
|
|||||||
"pull_ff_only",
|
"pull_ff_only",
|
||||||
"merge_no_ff",
|
"merge_no_ff",
|
||||||
"push",
|
"push",
|
||||||
|
"push_args",
|
||||||
"commit",
|
"commit",
|
||||||
"delete_local_branch",
|
"delete_local_branch",
|
||||||
"delete_remote_branch",
|
"delete_remote_branch",
|
||||||
@@ -56,6 +58,7 @@ __all__ = [
|
|||||||
"GitPullFfOnlyError",
|
"GitPullFfOnlyError",
|
||||||
"GitMergeError",
|
"GitMergeError",
|
||||||
"GitPushError",
|
"GitPushError",
|
||||||
|
"GitPushArgsError",
|
||||||
"GitCommitError",
|
"GitCommitError",
|
||||||
"GitDeleteLocalBranchError",
|
"GitDeleteLocalBranchError",
|
||||||
"GitDeleteRemoteBranchError",
|
"GitDeleteRemoteBranchError",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from ..errors import GitRunError, GitCommandError
|
||||||
|
from ..run import run
|
||||||
|
|
||||||
|
|
||||||
|
class GitPushArgsError(GitCommandError):
|
||||||
|
"""Raised when `git push` with arbitrary args fails."""
|
||||||
|
|
||||||
|
|
||||||
|
def push_args(
|
||||||
|
args: List[str] | None = None,
|
||||||
|
*,
|
||||||
|
cwd: str = ".",
|
||||||
|
preview: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Execute `git push` with caller-provided arguments.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
[] -> git push
|
||||||
|
["--force"] -> git push --force
|
||||||
|
["origin", "main"] -> git push origin main
|
||||||
|
["-u", "origin", "feature"] -> git push -u origin feature
|
||||||
|
"""
|
||||||
|
extra = args or []
|
||||||
|
try:
|
||||||
|
run(["push", *extra], cwd=cwd, preview=preview)
|
||||||
|
except GitRunError as exc:
|
||||||
|
details = getattr(exc, "output", None) or getattr(exc, "stderr", None) or ""
|
||||||
|
raise GitPushArgsError(
|
||||||
|
(
|
||||||
|
f"Failed to run `git push` with args={extra!r} "
|
||||||
|
f"in cwd={cwd!r}.\n{details}"
|
||||||
|
).rstrip(),
|
||||||
|
cwd=cwd,
|
||||||
|
) from exc
|
||||||
@@ -36,6 +36,7 @@ class RepoPaths:
|
|||||||
# Packaging-related files
|
# Packaging-related files
|
||||||
arch_pkgbuild: Optional[str]
|
arch_pkgbuild: Optional[str]
|
||||||
debian_changelog: Optional[str]
|
debian_changelog: Optional[str]
|
||||||
|
debian_control: Optional[str]
|
||||||
rpm_spec: Optional[str]
|
rpm_spec: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
@@ -102,6 +103,13 @@ def resolve_repo_paths(repo_dir: str) -> RepoPaths:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
debian_control = _first_existing(
|
||||||
|
[
|
||||||
|
os.path.join(repo_dir, "packaging", "debian", "control"),
|
||||||
|
os.path.join(repo_dir, "debian", "control"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# RPM spec: prefer the canonical file, else first spec in packaging/fedora, else first spec in repo root.
|
# RPM spec: prefer the canonical file, else first spec in packaging/fedora, else first spec in repo root.
|
||||||
rpm_spec = _first_existing(
|
rpm_spec = _first_existing(
|
||||||
[
|
[
|
||||||
@@ -122,5 +130,6 @@ def resolve_repo_paths(repo_dir: str) -> RepoPaths:
|
|||||||
changelog_md=changelog_md,
|
changelog_md=changelog_md,
|
||||||
arch_pkgbuild=arch_pkgbuild,
|
arch_pkgbuild=arch_pkgbuild,
|
||||||
debian_changelog=debian_changelog,
|
debian_changelog=debian_changelog,
|
||||||
|
debian_control=debian_control,
|
||||||
rpm_spec=rpm_spec,
|
rpm_spec=rpm_spec,
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"""Unit tests for `pkgmgr.actions.release.package_name.resolve_package_name`.
|
||||||
|
|
||||||
|
The resolver must prefer the explicit name from existing packaging files
|
||||||
|
over the repository folder name so that renaming the folder does not
|
||||||
|
silently rename the distro package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pkgmgr.actions.release.package_name import resolve_package_name
|
||||||
|
from pkgmgr.core.repository.paths import RepoPaths
|
||||||
|
|
||||||
|
|
||||||
|
def _paths(
|
||||||
|
repo_dir: str,
|
||||||
|
*,
|
||||||
|
debian_control: Optional[str] = None,
|
||||||
|
arch_pkgbuild: Optional[str] = None,
|
||||||
|
rpm_spec: Optional[str] = None,
|
||||||
|
) -> RepoPaths:
|
||||||
|
return RepoPaths(
|
||||||
|
repo_dir=repo_dir,
|
||||||
|
pyproject_toml=os.path.join(repo_dir, "pyproject.toml"),
|
||||||
|
flake_nix=os.path.join(repo_dir, "flake.nix"),
|
||||||
|
changelog_md=None,
|
||||||
|
arch_pkgbuild=arch_pkgbuild,
|
||||||
|
debian_changelog=None,
|
||||||
|
debian_control=debian_control,
|
||||||
|
rpm_spec=rpm_spec,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestResolvePackageName(unittest.TestCase):
|
||||||
|
def test_debian_control_wins_over_folder(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="infinito-nexus-core_") as repo:
|
||||||
|
control = Path(repo) / "control"
|
||||||
|
control.write_text(
|
||||||
|
"Source: infinito-nexus\nPackage: infinito-nexus\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
resolve_package_name(_paths(repo, debian_control=str(control))),
|
||||||
|
"infinito-nexus",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_pkgbuild_used_when_no_control(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="infinito-nexus-core_") as repo:
|
||||||
|
pkgbuild = Path(repo) / "PKGBUILD"
|
||||||
|
pkgbuild.write_text(
|
||||||
|
"pkgname=infinito-nexus\npkgver=1.0\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
resolve_package_name(_paths(repo, arch_pkgbuild=str(pkgbuild))),
|
||||||
|
"infinito-nexus",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rpm_spec_used_when_no_control_no_pkgbuild(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="infinito-nexus-core_") as repo:
|
||||||
|
spec = Path(repo) / "pkg.spec"
|
||||||
|
spec.write_text("Name: infinito-nexus\n", encoding="utf-8")
|
||||||
|
self.assertEqual(
|
||||||
|
resolve_package_name(_paths(repo, rpm_spec=str(spec))),
|
||||||
|
"infinito-nexus",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_folder_fallback_when_no_packaging_metadata(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="solo-tool_") as repo:
|
||||||
|
self.assertEqual(
|
||||||
|
resolve_package_name(_paths(repo)),
|
||||||
|
os.path.basename(repo),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_priority_debian_over_pkgbuild_over_spec(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as repo:
|
||||||
|
control = Path(repo) / "control"
|
||||||
|
control.write_text("Package: deb-name\n", encoding="utf-8")
|
||||||
|
pkgbuild = Path(repo) / "PKGBUILD"
|
||||||
|
pkgbuild.write_text("pkgname=arch-name\n", encoding="utf-8")
|
||||||
|
spec = Path(repo) / "x.spec"
|
||||||
|
spec.write_text("Name: rpm-name\n", encoding="utf-8")
|
||||||
|
self.assertEqual(
|
||||||
|
resolve_package_name(
|
||||||
|
_paths(
|
||||||
|
repo,
|
||||||
|
debian_control=str(control),
|
||||||
|
arch_pkgbuild=str(pkgbuild),
|
||||||
|
rpm_spec=str(spec),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"deb-name",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_strips_quotes_in_pkgbuild(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as repo:
|
||||||
|
pkgbuild = Path(repo) / "PKGBUILD"
|
||||||
|
pkgbuild.write_text("pkgname='quoted-name'\n", encoding="utf-8")
|
||||||
|
self.assertEqual(
|
||||||
|
resolve_package_name(_paths(repo, arch_pkgbuild=str(pkgbuild))),
|
||||||
|
"quoted-name",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in new issue
Block a user