Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3875338fb7 | ||
|
|
196f55c58e | ||
|
|
9a149715f6 | ||
|
|
bf40533469 | ||
|
|
7bc7259988 | ||
|
|
66b96ac3a5 | ||
|
|
f974e0b14a | ||
|
|
de8c3f768d |
2
.github/workflows/test-container.yml
vendored
2
.github/workflows/test-container.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Test Distribution Containers
|
name: Test OS Containers
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
2
.github/workflows/test-e2e.yml
vendored
2
.github/workflows/test-e2e.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Test package-manager (e2e)
|
name: Test End-To-End
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
8
.github/workflows/test-integration.yml
vendored
8
.github/workflows/test-integration.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Test package-manager (integration)
|
name: Test Code Integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -21,9 +21,5 @@ jobs:
|
|||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|
||||||
# Build Arch test image (same as used in test-unit and test-e2e)
|
|
||||||
- name: Build test images
|
|
||||||
run: make build
|
|
||||||
|
|
||||||
- name: Run integration tests via make (Arch container)
|
- name: Run integration tests via make (Arch container)
|
||||||
run: make test-integration
|
run: make test-integration DISTROS="arch"
|
||||||
|
|||||||
4
.github/workflows/test-unit.yml
vendored
4
.github/workflows/test-unit.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Test package-manager (unit)
|
name: Test Units
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -22,4 +22,4 @@ jobs:
|
|||||||
run: docker version
|
run: docker version
|
||||||
|
|
||||||
- name: Run unit tests via make (Arch container)
|
- name: Run unit tests via make (Arch container)
|
||||||
run: make test-unit
|
run: make test-unit DISTROS="arch"
|
||||||
|
|||||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,3 +1,23 @@
|
|||||||
|
## [0.7.6] - 2025-12-09
|
||||||
|
|
||||||
|
* Fixed pull --preview bug in e2e test
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.5] - 2025-12-09
|
||||||
|
|
||||||
|
* Fixed wrong directory permissions for nix
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.4] - 2025-12-09
|
||||||
|
|
||||||
|
* Fixed missing build in test workflow -> Tests pass now
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.3] - 2025-12-09
|
||||||
|
|
||||||
|
* Fixed bug: Ignored packages are now ignored
|
||||||
|
|
||||||
|
|
||||||
## [0.7.2] - 2025-12-09
|
## [0.7.2] - 2025-12-09
|
||||||
|
|
||||||
* Implemented Changelog Support for Fedora and Debian
|
* Implemented Changelog Support for Fedora and Debian
|
||||||
|
|||||||
10
Makefile
10
Makefile
@@ -46,16 +46,16 @@ build:
|
|||||||
# Test targets (delegated to scripts/test)
|
# Test targets (delegated to scripts/test)
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
test-unit:
|
test-unit: build-missing
|
||||||
@bash scripts/test/test-unit.sh
|
@bash scripts/test/test-unit.sh
|
||||||
|
|
||||||
test-integration:
|
test-integration: build-missing
|
||||||
@bash scripts/test/test-integration.sh
|
@bash scripts/test/test-integration.sh
|
||||||
|
|
||||||
test-e2e:
|
test-e2e: build-missing
|
||||||
@bash scripts/test/test-e2e.sh
|
@bash scripts/test/test-e2e.sh
|
||||||
|
|
||||||
test-container:
|
test-container: build-missing
|
||||||
@bash scripts/test/test-container.sh
|
@bash scripts/test/test-container.sh
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
@@ -65,7 +65,7 @@ build-missing:
|
|||||||
@bash scripts/build/build-image-missing.sh
|
@bash scripts/build/build-image-missing.sh
|
||||||
|
|
||||||
# Combined test target for local + CI (unit + e2e + integration)
|
# Combined test target for local + CI (unit + e2e + integration)
|
||||||
test: build-missing test-container test-unit test-e2e test-integration
|
test: test-container test-unit test-e2e test-integration
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# System install (native packages, calls scripts/installation/run-package.sh)
|
# System install (native packages, calls scripts/installation/run-package.sh)
|
||||||
|
|||||||
2
PKGBUILD
2
PKGBUILD
@@ -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=0.7.2
|
pkgver=0.7.6
|
||||||
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')
|
||||||
|
|||||||
24
debian/changelog
vendored
24
debian/changelog
vendored
@@ -1,3 +1,27 @@
|
|||||||
|
package-manager (0.7.6-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fixed pull --preview bug in e2e test
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 09 Dec 2025 17:14:19 +0100
|
||||||
|
|
||||||
|
package-manager (0.7.5-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fixed wrong directory permissions for nix
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 09 Dec 2025 16:45:42 +0100
|
||||||
|
|
||||||
|
package-manager (0.7.4-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fixed missing build in test workflow -> Tests pass now
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 09 Dec 2025 16:22:00 +0100
|
||||||
|
|
||||||
|
package-manager (0.7.3-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fixed bug: Ignored packages are now ignored
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 09 Dec 2025 16:08:31 +0100
|
||||||
|
|
||||||
package-manager (0.7.2-1) unstable; urgency=medium
|
package-manager (0.7.2-1) unstable; urgency=medium
|
||||||
|
|
||||||
* Implemented Changelog Support for Fedora and Debian
|
* Implemented Changelog Support for Fedora and Debian
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
rec {
|
rec {
|
||||||
pkgmgr = pyPkgs.buildPythonApplication {
|
pkgmgr = pyPkgs.buildPythonApplication {
|
||||||
pname = "package-manager";
|
pname = "package-manager";
|
||||||
version = "0.7.2";
|
version = "0.7.6";
|
||||||
|
|
||||||
# Use the git repo as source
|
# Use the git repo as source
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: package-manager
|
Name: package-manager
|
||||||
Version: 0.7.2
|
Version: 0.7.6
|
||||||
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
|
||||||
|
|
||||||
@@ -77,6 +77,18 @@ echo ">>> package-manager removed. Nix itself was not removed."
|
|||||||
/usr/lib/package-manager/
|
/usr/lib/package-manager/
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.6-1
|
||||||
|
- Fixed pull --preview bug in e2e test
|
||||||
|
|
||||||
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.5-1
|
||||||
|
- Fixed wrong directory permissions for nix
|
||||||
|
|
||||||
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.4-1
|
||||||
|
- Fixed missing build in test workflow -> Tests pass now
|
||||||
|
|
||||||
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.3-1
|
||||||
|
- Fixed bug: Ignored packages are now ignored
|
||||||
|
|
||||||
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.2-1
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.2-1
|
||||||
- Implemented Changelog Support for Fedora and Debian
|
- Implemented Changelog Support for Fedora and Debian
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,57 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
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.dir import get_repo_dir
|
||||||
from pkgmgr.core.repository.verify import verify_repository
|
from pkgmgr.core.repository.verify import verify_repository
|
||||||
|
|
||||||
|
|
||||||
def pull_with_verification(
|
def pull_with_verification(
|
||||||
selected_repos,
|
selected_repos,
|
||||||
repositories_base_dir,
|
repositories_base_dir,
|
||||||
all_repos,
|
all_repos,
|
||||||
extra_args,
|
extra_args,
|
||||||
no_verification,
|
no_verification,
|
||||||
preview:bool):
|
preview: bool,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Executes "git pull" for each repository with verification.
|
Execute `git pull` for each repository with verification.
|
||||||
|
|
||||||
Uses the verify_repository function in "pull" mode.
|
- Uses verify_repository() in "pull" mode.
|
||||||
If verification fails (and verification info is set) and --no-verification is not enabled,
|
- If verification fails (and verification info is set) and
|
||||||
the user is prompted to confirm the pull.
|
--no-verification is not enabled, the user is prompted to confirm
|
||||||
|
the pull.
|
||||||
|
- In preview mode, no interactive prompts are performed and no
|
||||||
|
Git commands are executed; only the would-be command is printed.
|
||||||
"""
|
"""
|
||||||
for repo in selected_repos:
|
for repo in selected_repos:
|
||||||
repo_identifier = get_repo_identifier(repo, all_repos)
|
repo_identifier = get_repo_identifier(repo, all_repos)
|
||||||
repo_dir = get_repo_dir(repositories_base_dir, repo)
|
repo_dir = get_repo_dir(repositories_base_dir, repo)
|
||||||
|
|
||||||
if not os.path.exists(repo_dir):
|
if not os.path.exists(repo_dir):
|
||||||
print(f"Repository directory '{repo_dir}' not found for {repo_identifier}.")
|
print(f"Repository directory '{repo_dir}' not found for {repo_identifier}.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
verified_info = repo.get("verified")
|
verified_info = repo.get("verified")
|
||||||
verified_ok, errors, commit_hash, signing_key = verify_repository(repo, repo_dir, mode="pull", no_verification=no_verification)
|
verified_ok, errors, commit_hash, signing_key = verify_repository(
|
||||||
|
repo,
|
||||||
|
repo_dir,
|
||||||
|
mode="pull",
|
||||||
|
no_verification=no_verification,
|
||||||
|
)
|
||||||
|
|
||||||
if not no_verification and verified_info and not verified_ok:
|
# Only prompt the user if:
|
||||||
|
# - we are NOT in preview mode
|
||||||
|
# - verification is enabled
|
||||||
|
# - the repo has verification info configured
|
||||||
|
# - verification failed
|
||||||
|
if (
|
||||||
|
not preview
|
||||||
|
and not no_verification
|
||||||
|
and verified_info
|
||||||
|
and not verified_ok
|
||||||
|
):
|
||||||
print(f"Warning: Verification failed for {repo_identifier}:")
|
print(f"Warning: Verification failed for {repo_identifier}:")
|
||||||
for err in errors:
|
for err in errors:
|
||||||
print(f" - {err}")
|
print(f" - {err}")
|
||||||
@@ -37,12 +59,19 @@ def pull_with_verification(
|
|||||||
if choice != "y":
|
if choice != "y":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
full_cmd = f"git pull {' '.join(extra_args)}"
|
# Build the git pull command (include extra args if present)
|
||||||
|
args_part = " ".join(extra_args) if extra_args else ""
|
||||||
|
full_cmd = f"git pull{(' ' + args_part) if args_part else ''}"
|
||||||
|
|
||||||
if preview:
|
if preview:
|
||||||
|
# Preview mode: only show the command, do not execute or prompt.
|
||||||
print(f"[Preview] In '{repo_dir}': {full_cmd}")
|
print(f"[Preview] In '{repo_dir}': {full_cmd}")
|
||||||
else:
|
else:
|
||||||
print(f"Running in '{repo_dir}': {full_cmd}")
|
print(f"Running in '{repo_dir}': {full_cmd}")
|
||||||
result = subprocess.run(full_cmd, cwd=repo_dir, shell=True)
|
result = subprocess.run(full_cmd, cwd=repo_dir, shell=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print(f"'git pull' for {repo_identifier} failed with exit code {result.returncode}.")
|
print(
|
||||||
|
f"'git pull' for {repo_identifier} failed "
|
||||||
|
f"with exit code {result.returncode}."
|
||||||
|
)
|
||||||
sys.exit(result.returncode)
|
sys.exit(result.returncode)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import re
|
|||||||
from typing import Any, Dict, List, Sequence
|
from typing import Any, Dict, List, Sequence
|
||||||
|
|
||||||
from pkgmgr.core.repository.resolve import resolve_repos
|
from pkgmgr.core.repository.resolve import resolve_repos
|
||||||
|
from pkgmgr.core.repository.ignored import filter_ignored
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = Dict[str, Any]
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ def _apply_filters(
|
|||||||
if not _match_pattern(ident_str, string_pattern):
|
if not _match_pattern(ident_str, string_pattern):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Category filter: nur echte Kategorien, KEINE Tags
|
# Category filter: only real categories, NOT tags
|
||||||
if category_patterns:
|
if category_patterns:
|
||||||
cats: List[str] = []
|
cats: List[str] = []
|
||||||
cats.extend(map(str, repo.get("category_files", [])))
|
cats.extend(map(str, repo.get("category_files", [])))
|
||||||
@@ -106,7 +107,7 @@ def _apply_filters(
|
|||||||
if not ok:
|
if not ok:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Tag filter: ausschließlich YAML-Tags
|
# Tag filter: YAML tags only
|
||||||
if tag_patterns:
|
if tag_patterns:
|
||||||
tags: List[str] = list(map(str, repo.get("tags", [])))
|
tags: List[str] = list(map(str, repo.get("tags", [])))
|
||||||
if not tags:
|
if not tags:
|
||||||
@@ -124,16 +125,38 @@ def _apply_filters(
|
|||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def _maybe_filter_ignored(args, repos: List[Repository]) -> List[Repository]:
|
||||||
|
"""
|
||||||
|
Apply ignore filtering unless the caller explicitly opted to include ignored
|
||||||
|
repositories (via args.include_ignored).
|
||||||
|
|
||||||
|
Note: this helper is used only for *implicit* selections (all / filters /
|
||||||
|
by-directory). For *explicit* identifiers we do NOT filter ignored repos,
|
||||||
|
so the user can still target them directly if desired.
|
||||||
|
"""
|
||||||
|
include_ignored: bool = bool(getattr(args, "include_ignored", False))
|
||||||
|
if include_ignored:
|
||||||
|
return repos
|
||||||
|
return filter_ignored(repos)
|
||||||
|
|
||||||
|
|
||||||
def get_selected_repos(args, all_repositories: List[Repository]) -> List[Repository]:
|
def get_selected_repos(args, all_repositories: List[Repository]) -> List[Repository]:
|
||||||
"""
|
"""
|
||||||
Compute the list of repositories selected by CLI arguments.
|
Compute the list of repositories selected by CLI arguments.
|
||||||
|
|
||||||
Modes:
|
Modes:
|
||||||
- If identifiers are given: select via resolve_repos() from all_repositories.
|
- If identifiers are given: select via resolve_repos() from all_repositories.
|
||||||
- Else if any of --category/--string/--tag is used: start from all_repositories
|
Ignored repositories are *not* filtered here, so explicit identifiers
|
||||||
and apply filters.
|
always win.
|
||||||
- Else if --all is set: select all_repositories.
|
- Else if any of --category/--string/--tag is used: start from
|
||||||
- Else: try to select the repository of the current working directory.
|
all_repositories, apply filters and then drop ignored repos.
|
||||||
|
- Else if --all is set: select all_repositories and then drop ignored repos.
|
||||||
|
- Else: try to select the repository of the current working directory
|
||||||
|
and then drop it if it is ignored.
|
||||||
|
|
||||||
|
The ignore filter can be bypassed by setting args.include_ignored = True
|
||||||
|
(e.g. via a CLI flag --include-ignored).
|
||||||
"""
|
"""
|
||||||
identifiers: List[str] = getattr(args, "identifiers", []) or []
|
identifiers: List[str] = getattr(args, "identifiers", []) or []
|
||||||
use_all: bool = bool(getattr(args, "all", False))
|
use_all: bool = bool(getattr(args, "all", False))
|
||||||
@@ -143,18 +166,25 @@ def get_selected_repos(args, all_repositories: List[Repository]) -> List[Reposit
|
|||||||
|
|
||||||
has_filters = bool(category_patterns or string_pattern or tag_patterns)
|
has_filters = bool(category_patterns or string_pattern or tag_patterns)
|
||||||
|
|
||||||
# 1) Explicit identifiers win
|
# 1) Explicit identifiers win and bypass ignore filtering
|
||||||
if identifiers:
|
if identifiers:
|
||||||
base = resolve_repos(identifiers, all_repositories)
|
base = resolve_repos(identifiers, all_repositories)
|
||||||
return _apply_filters(base, string_pattern, category_patterns, tag_patterns)
|
return _apply_filters(base, string_pattern, category_patterns, tag_patterns)
|
||||||
|
|
||||||
# 2) Filter-only mode: start from all repositories
|
# 2) Filter-only mode: start from all repositories
|
||||||
if has_filters:
|
if has_filters:
|
||||||
return _apply_filters(list(all_repositories), string_pattern, category_patterns, tag_patterns)
|
base = _apply_filters(
|
||||||
|
list(all_repositories),
|
||||||
|
string_pattern,
|
||||||
|
category_patterns,
|
||||||
|
tag_patterns,
|
||||||
|
)
|
||||||
|
return _maybe_filter_ignored(args, base)
|
||||||
|
|
||||||
# 3) --all (no filters): all repos
|
# 3) --all (no filters): all repos
|
||||||
if use_all:
|
if use_all:
|
||||||
return list(all_repositories)
|
base = list(all_repositories)
|
||||||
|
return _maybe_filter_ignored(args, base)
|
||||||
|
|
||||||
# 4) Fallback: try to select repository of current working directory
|
# 4) Fallback: try to select repository of current working directory
|
||||||
cwd = os.path.abspath(os.getcwd())
|
cwd = os.path.abspath(os.getcwd())
|
||||||
@@ -164,7 +194,7 @@ def get_selected_repos(args, all_repositories: List[Repository]) -> List[Reposit
|
|||||||
if os.path.abspath(str(repo.get("directory", ""))) == cwd
|
if os.path.abspath(str(repo.get("directory", ""))) == cwd
|
||||||
]
|
]
|
||||||
if by_dir:
|
if by_dir:
|
||||||
return by_dir
|
return _maybe_filter_ignored(args, by_dir)
|
||||||
|
|
||||||
# No specific match -> empty list
|
# No specific match -> empty list
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "package-manager"
|
name = "package-manager"
|
||||||
version = "0.7.2"
|
version = "0.7.6"
|
||||||
description = "Kevin's package-manager tool (pkgmgr)"
|
description = "Kevin's package-manager tool (pkgmgr)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -97,11 +97,32 @@ if [[ "${IN_CONTAINER}" -eq 1 && "${EUID:-0}" -eq 0 ]]; then
|
|||||||
useradd -m -r -g nixbld -s /usr/bin/bash nix
|
useradd -m -r -g nixbld -s /usr/bin/bash nix
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create /nix directory and hand it to nix user (prevents installer sudo prompt)
|
# Ensure /nix exists and is writable by the "nix" user.
|
||||||
|
#
|
||||||
|
# In some base images (or previous runs), /nix may already exist and be
|
||||||
|
# owned by root. In that case the Nix single-user installer will abort with:
|
||||||
|
#
|
||||||
|
# "directory /nix exists, but is not writable by you"
|
||||||
|
#
|
||||||
|
# To keep container runs idempotent and robust, we always enforce
|
||||||
|
# ownership nix:nixbld here.
|
||||||
if [[ ! -d /nix ]]; then
|
if [[ ! -d /nix ]]; then
|
||||||
echo "[init-nix] Creating /nix with owner nix:nixbld..."
|
echo "[init-nix] Creating /nix with owner nix:nixbld..."
|
||||||
mkdir -m 0755 /nix
|
mkdir -m 0755 /nix
|
||||||
chown nix:nixbld /nix
|
chown nix:nixbld /nix
|
||||||
|
else
|
||||||
|
current_owner="$(stat -c '%U' /nix 2>/dev/null || echo '?')"
|
||||||
|
current_group="$(stat -c '%G' /nix 2>/dev/null || echo '?')"
|
||||||
|
if [[ "${current_owner}" != "nix" || "${current_group}" != "nixbld" ]]; then
|
||||||
|
echo "[init-nix] /nix already exists with owner ${current_owner}:${current_group} – fixing to nix:nixbld..."
|
||||||
|
chown -R nix:nixbld /nix
|
||||||
|
else
|
||||||
|
echo "[init-nix] /nix already exists with correct owner nix:nixbld."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -w /nix ]]; then
|
||||||
|
echo "[init-nix] WARNING: /nix is still not writable after chown; Nix installer may fail."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run Nix single-user installer as "nix"
|
# Run Nix single-user installer as "nix"
|
||||||
|
|||||||
309
tests/unit/pkgmgr/actions/repos/test_pull_with_verification.py
Normal file
309
tests/unit/pkgmgr/actions/repos/test_pull_with_verification.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import io
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
from pkgmgr.actions.repository.pull import pull_with_verification
|
||||||
|
|
||||||
|
|
||||||
|
class TestPullWithVerification(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Comprehensive unit tests for pull_with_verification().
|
||||||
|
|
||||||
|
These tests verify:
|
||||||
|
- Preview mode behaviour
|
||||||
|
- Verification logic (prompting, bypassing, skipping)
|
||||||
|
- subprocess.run invocation
|
||||||
|
- Repository directory existence checks
|
||||||
|
- Handling of extra git pull arguments
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _setup_mocks(self, mock_exists, mock_get_repo_id, mock_get_repo_dir,
|
||||||
|
mock_verify, exists=True, verified_ok=True,
|
||||||
|
errors=None, verified_info=True):
|
||||||
|
"""Helper to configure repetitive mock behavior."""
|
||||||
|
repo = {
|
||||||
|
"name": "pkgmgr",
|
||||||
|
"verified": {"gpg_keys": ["ABCDEF"]} if verified_info else None,
|
||||||
|
}
|
||||||
|
mock_exists.return_value = exists
|
||||||
|
mock_get_repo_id.return_value = "pkgmgr"
|
||||||
|
mock_get_repo_dir.return_value = "/fake/base/pkgmgr"
|
||||||
|
mock_verify.return_value = (
|
||||||
|
verified_ok,
|
||||||
|
errors or [],
|
||||||
|
"deadbeef", # commit hash
|
||||||
|
"ABCDEF", # signing key
|
||||||
|
)
|
||||||
|
return repo
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
@patch("pkgmgr.actions.repository.pull.subprocess.run")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.verify_repository")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.os.path.exists")
|
||||||
|
@patch("builtins.input")
|
||||||
|
def test_preview_mode_non_interactive(
|
||||||
|
self,
|
||||||
|
mock_input,
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
mock_subprocess,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Preview mode must NEVER request user input and must NEVER execute git.
|
||||||
|
It must only print the preview command.
|
||||||
|
"""
|
||||||
|
repo = self._setup_mocks(
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
exists=True,
|
||||||
|
verified_ok=False,
|
||||||
|
errors=["bad signature"],
|
||||||
|
verified_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
buf = io.StringIO()
|
||||||
|
with patch("sys.stdout", new=buf):
|
||||||
|
pull_with_verification(
|
||||||
|
selected_repos=[repo],
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
all_repos=[repo],
|
||||||
|
extra_args=["--ff-only"],
|
||||||
|
no_verification=False,
|
||||||
|
preview=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
output = buf.getvalue()
|
||||||
|
self.assertIn(
|
||||||
|
"[Preview] In '/fake/base/pkgmgr': git pull --ff-only",
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_input.assert_not_called()
|
||||||
|
mock_subprocess.assert_not_called()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
@patch("pkgmgr.actions.repository.pull.subprocess.run")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.verify_repository")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.os.path.exists")
|
||||||
|
@patch("builtins.input")
|
||||||
|
def test_verification_failure_user_declines(
|
||||||
|
self,
|
||||||
|
mock_input,
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
mock_subprocess,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If verification fails and preview=False, the user is prompted.
|
||||||
|
If the user declines ('n'), no git command is executed.
|
||||||
|
"""
|
||||||
|
repo = self._setup_mocks(
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
verified_ok=False,
|
||||||
|
errors=["signature invalid"],
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_input.return_value = "n"
|
||||||
|
|
||||||
|
buf = io.StringIO()
|
||||||
|
with patch("sys.stdout", new=buf):
|
||||||
|
pull_with_verification(
|
||||||
|
selected_repos=[repo],
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
all_repos=[repo],
|
||||||
|
extra_args=[],
|
||||||
|
no_verification=False,
|
||||||
|
preview=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_input.assert_called_once()
|
||||||
|
mock_subprocess.assert_not_called()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
@patch("pkgmgr.actions.repository.pull.subprocess.run")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.verify_repository")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.os.path.exists")
|
||||||
|
@patch("builtins.input")
|
||||||
|
def test_verification_failure_user_accepts_runs_git(
|
||||||
|
self,
|
||||||
|
mock_input,
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
mock_subprocess,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If verification fails and the user accepts ('y'),
|
||||||
|
then the git pull should be executed.
|
||||||
|
"""
|
||||||
|
repo = self._setup_mocks(
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
verified_ok=False,
|
||||||
|
errors=["invalid"],
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_input.return_value = "y"
|
||||||
|
mock_subprocess.return_value = MagicMock(returncode=0)
|
||||||
|
|
||||||
|
pull_with_verification(
|
||||||
|
selected_repos=[repo],
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
all_repos=[repo],
|
||||||
|
extra_args=[],
|
||||||
|
no_verification=False,
|
||||||
|
preview=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_subprocess.assert_called_once()
|
||||||
|
mock_input.assert_called_once()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
@patch("pkgmgr.actions.repository.pull.subprocess.run")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.verify_repository")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.os.path.exists")
|
||||||
|
@patch("builtins.input")
|
||||||
|
def test_verification_success_no_prompt(
|
||||||
|
self,
|
||||||
|
mock_input,
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
mock_subprocess,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If verification is successful, the user should NOT be prompted,
|
||||||
|
and git pull should run immediately.
|
||||||
|
"""
|
||||||
|
repo = self._setup_mocks(
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
verified_ok=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_subprocess.return_value = MagicMock(returncode=0)
|
||||||
|
|
||||||
|
pull_with_verification(
|
||||||
|
selected_repos=[repo],
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
all_repos=[repo],
|
||||||
|
extra_args=["--rebase"],
|
||||||
|
no_verification=False,
|
||||||
|
preview=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_input.assert_not_called()
|
||||||
|
mock_subprocess.assert_called_once()
|
||||||
|
cmd = mock_subprocess.call_args[0][0]
|
||||||
|
self.assertIn("git pull --rebase", cmd)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
@patch("pkgmgr.actions.repository.pull.subprocess.run")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.verify_repository")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.os.path.exists")
|
||||||
|
@patch("builtins.input")
|
||||||
|
def test_directory_missing_skips_repo(
|
||||||
|
self,
|
||||||
|
mock_input,
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
mock_subprocess,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If the repository directory does not exist, the repo must be skipped
|
||||||
|
silently and no git command executed.
|
||||||
|
"""
|
||||||
|
repo = self._setup_mocks(
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
exists=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
buf = io.StringIO()
|
||||||
|
with patch("sys.stdout", new=buf):
|
||||||
|
pull_with_verification(
|
||||||
|
selected_repos=[repo],
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
all_repos=[repo],
|
||||||
|
extra_args=[],
|
||||||
|
no_verification=False,
|
||||||
|
preview=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
output = buf.getvalue()
|
||||||
|
self.assertIn("not found", output)
|
||||||
|
|
||||||
|
mock_input.assert_not_called()
|
||||||
|
mock_subprocess.assert_not_called()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
@patch("pkgmgr.actions.repository.pull.subprocess.run")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.verify_repository")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
|
||||||
|
@patch("pkgmgr.actions.repository.pull.os.path.exists")
|
||||||
|
@patch("builtins.input")
|
||||||
|
def test_no_verification_flag_skips_prompt(
|
||||||
|
self,
|
||||||
|
mock_input,
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
mock_subprocess,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If no_verification=True, verification failures must NOT prompt.
|
||||||
|
Git pull should run directly.
|
||||||
|
"""
|
||||||
|
repo = self._setup_mocks(
|
||||||
|
mock_exists,
|
||||||
|
mock_get_repo_id,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
verified_ok=False,
|
||||||
|
errors=["invalid"],
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_subprocess.return_value = MagicMock(returncode=0)
|
||||||
|
|
||||||
|
pull_with_verification(
|
||||||
|
selected_repos=[repo],
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
all_repos=[repo],
|
||||||
|
extra_args=[],
|
||||||
|
no_verification=True,
|
||||||
|
preview=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_input.assert_not_called()
|
||||||
|
mock_subprocess.assert_called_once()
|
||||||
29
tests/unit/pkgmgr/core/repository/test_ignored.py
Normal file
29
tests/unit/pkgmgr/core/repository/test_ignored.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from pkgmgr.core.repository.ignored import filter_ignored
|
||||||
|
|
||||||
|
|
||||||
|
class TestFilterIgnored(unittest.TestCase):
|
||||||
|
def test_filter_ignored_removes_repos_with_ignore_true(self) -> None:
|
||||||
|
repos = [
|
||||||
|
{"provider": "github.com", "account": "user", "repository": "a", "ignore": True},
|
||||||
|
{"provider": "github.com", "account": "user", "repository": "b", "ignore": False},
|
||||||
|
{"provider": "github.com", "account": "user", "repository": "c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
result = filter_ignored(repos)
|
||||||
|
|
||||||
|
identifiers = {(r["provider"], r["account"], r["repository"]) for r in result}
|
||||||
|
self.assertNotIn(("github.com", "user", "a"), identifiers)
|
||||||
|
self.assertIn(("github.com", "user", "b"), identifiers)
|
||||||
|
self.assertIn(("github.com", "user", "c"), identifiers)
|
||||||
|
|
||||||
|
def test_filter_ignored_empty_list_returns_empty_list(self) -> None:
|
||||||
|
self.assertEqual(filter_ignored([]), [])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
180
tests/unit/pkgmgr/core/repository/test_selected.py
Normal file
180
tests/unit/pkgmgr/core/repository/test_selected.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pkgmgr.core.repository.selected import get_selected_repos
|
||||||
|
|
||||||
|
|
||||||
|
def _repo(
|
||||||
|
provider: str,
|
||||||
|
account: str,
|
||||||
|
repository: str,
|
||||||
|
ignore: bool | None = None,
|
||||||
|
directory: str | None = None,
|
||||||
|
):
|
||||||
|
repo = {
|
||||||
|
"provider": provider,
|
||||||
|
"account": account,
|
||||||
|
"repository": repository,
|
||||||
|
}
|
||||||
|
if ignore is not None:
|
||||||
|
repo["ignore"] = ignore
|
||||||
|
if directory is not None:
|
||||||
|
repo["directory"] = directory
|
||||||
|
return repo
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetSelectedRepos(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.repo_ignored = _repo(
|
||||||
|
"github.com",
|
||||||
|
"user",
|
||||||
|
"ignored-repo",
|
||||||
|
ignore=True,
|
||||||
|
directory="/repos/github.com/user/ignored-repo",
|
||||||
|
)
|
||||||
|
self.repo_visible = _repo(
|
||||||
|
"github.com",
|
||||||
|
"user",
|
||||||
|
"visible-repo",
|
||||||
|
ignore=False,
|
||||||
|
directory="/repos/github.com/user/visible-repo",
|
||||||
|
)
|
||||||
|
self.all_repos = [self.repo_ignored, self.repo_visible]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 1) Explizite Identifier – ignorierte Repos dürfen ausgewählt werden
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def test_identifiers_bypass_ignore_filter(self) -> None:
|
||||||
|
args = SimpleNamespace(
|
||||||
|
identifiers=["ignored-repo"], # matches by repository name
|
||||||
|
all=False,
|
||||||
|
category=[],
|
||||||
|
string="",
|
||||||
|
tag=[],
|
||||||
|
include_ignored=False, # should be ignored for explicit identifiers
|
||||||
|
)
|
||||||
|
|
||||||
|
selected = get_selected_repos(args, self.all_repos)
|
||||||
|
|
||||||
|
self.assertEqual(len(selected), 1)
|
||||||
|
self.assertIs(selected[0], self.repo_ignored)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2) Filter-only Modus – ignorierte Repos werden rausgefiltert
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def test_filter_mode_excludes_ignored_by_default(self) -> None:
|
||||||
|
# string-Filter, der beide Repos matchen würde
|
||||||
|
args = SimpleNamespace(
|
||||||
|
identifiers=[],
|
||||||
|
all=False,
|
||||||
|
category=[],
|
||||||
|
string="repo", # substring in beiden Namen
|
||||||
|
tag=[],
|
||||||
|
include_ignored=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected = get_selected_repos(args, self.all_repos)
|
||||||
|
|
||||||
|
self.assertEqual(len(selected), 1)
|
||||||
|
self.assertIs(selected[0], self.repo_visible)
|
||||||
|
|
||||||
|
def test_filter_mode_can_include_ignored_when_flag_set(self) -> None:
|
||||||
|
args = SimpleNamespace(
|
||||||
|
identifiers=[],
|
||||||
|
all=False,
|
||||||
|
category=[],
|
||||||
|
string="repo",
|
||||||
|
tag=[],
|
||||||
|
include_ignored=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected = get_selected_repos(args, self.all_repos)
|
||||||
|
|
||||||
|
# Beide Repos sollten erscheinen, weil include_ignored=True
|
||||||
|
self.assertEqual({r["repository"] for r in selected}, {"ignored-repo", "visible-repo"})
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 3) --all Modus – ignorierte Repos werden per Default entfernt
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def test_all_mode_excludes_ignored_by_default(self) -> None:
|
||||||
|
args = SimpleNamespace(
|
||||||
|
identifiers=[],
|
||||||
|
all=True,
|
||||||
|
category=[],
|
||||||
|
string="",
|
||||||
|
tag=[],
|
||||||
|
include_ignored=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected = get_selected_repos(args, self.all_repos)
|
||||||
|
|
||||||
|
self.assertEqual(len(selected), 1)
|
||||||
|
self.assertIs(selected[0], self.repo_visible)
|
||||||
|
|
||||||
|
def test_all_mode_can_include_ignored_when_flag_set(self) -> None:
|
||||||
|
args = SimpleNamespace(
|
||||||
|
identifiers=[],
|
||||||
|
all=True,
|
||||||
|
category=[],
|
||||||
|
string="",
|
||||||
|
tag=[],
|
||||||
|
include_ignored=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected = get_selected_repos(args, self.all_repos)
|
||||||
|
|
||||||
|
self.assertEqual(len(selected), 2)
|
||||||
|
self.assertCountEqual(
|
||||||
|
[r["repository"] for r in selected],
|
||||||
|
["ignored-repo", "visible-repo"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4) CWD-Modus – Repo anhand des aktuellen Verzeichnisses auswählen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def test_cwd_selection_excludes_ignored_by_default(self) -> None:
|
||||||
|
# Wir lassen CWD auf das Verzeichnis des ignorierten Repos zeigen.
|
||||||
|
cwd = os.path.abspath(self.repo_ignored["directory"])
|
||||||
|
|
||||||
|
args = SimpleNamespace(
|
||||||
|
identifiers=[],
|
||||||
|
all=False,
|
||||||
|
category=[],
|
||||||
|
string="",
|
||||||
|
tag=[],
|
||||||
|
include_ignored=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("os.getcwd", return_value=cwd):
|
||||||
|
selected = get_selected_repos(args, self.all_repos)
|
||||||
|
|
||||||
|
# Da das einzige Repo für dieses Verzeichnis ignoriert ist,
|
||||||
|
# sollte die Auswahl leer sein.
|
||||||
|
self.assertEqual(selected, [])
|
||||||
|
|
||||||
|
def test_cwd_selection_can_include_ignored_when_flag_set(self) -> None:
|
||||||
|
cwd = os.path.abspath(self.repo_ignored["directory"])
|
||||||
|
|
||||||
|
args = SimpleNamespace(
|
||||||
|
identifiers=[],
|
||||||
|
all=False,
|
||||||
|
category=[],
|
||||||
|
string="",
|
||||||
|
tag=[],
|
||||||
|
include_ignored=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("os.getcwd", return_value=cwd):
|
||||||
|
selected = get_selected_repos(args, self.all_repos)
|
||||||
|
|
||||||
|
self.assertEqual(len(selected), 1)
|
||||||
|
self.assertIs(selected[0], self.repo_ignored)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user