diff --git a/pkgmgr/core/repository/selected.py b/pkgmgr/core/repository/selected.py index 4a8ad20..d19d221 100644 --- a/pkgmgr/core/repository/selected.py +++ b/pkgmgr/core/repository/selected.py @@ -8,6 +8,7 @@ import re from typing import Any, Dict, List, Sequence from pkgmgr.core.repository.resolve import resolve_repos +from pkgmgr.core.repository.ignored import filter_ignored Repository = Dict[str, Any] @@ -88,7 +89,7 @@ def _apply_filters( if not _match_pattern(ident_str, string_pattern): continue - # Category filter: nur echte Kategorien, KEINE Tags + # Category filter: only real categories, NOT tags if category_patterns: cats: List[str] = [] cats.extend(map(str, repo.get("category_files", []))) @@ -106,7 +107,7 @@ def _apply_filters( if not ok: continue - # Tag filter: ausschließlich YAML-Tags + # Tag filter: YAML tags only if tag_patterns: tags: List[str] = list(map(str, repo.get("tags", []))) if not tags: @@ -124,16 +125,38 @@ def _apply_filters( 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]: """ Compute the list of repositories selected by CLI arguments. Modes: - If identifiers are given: select via resolve_repos() from all_repositories. - - Else if any of --category/--string/--tag is used: start from all_repositories - and apply filters. - - Else if --all is set: select all_repositories. - - Else: try to select the repository of the current working directory. + Ignored repositories are *not* filtered here, so explicit identifiers + always win. + - Else if any of --category/--string/--tag is used: start from + 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 [] 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) - # 1) Explicit identifiers win + # 1) Explicit identifiers win and bypass ignore filtering if identifiers: base = resolve_repos(identifiers, all_repositories) return _apply_filters(base, string_pattern, category_patterns, tag_patterns) # 2) Filter-only mode: start from all repositories 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 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 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 by_dir: - return by_dir + return _maybe_filter_ignored(args, by_dir) # No specific match -> empty list return [] diff --git a/tests/unit/pkgmgr/core/repository/test_ignored.py b/tests/unit/pkgmgr/core/repository/test_ignored.py new file mode 100644 index 0000000..cb547ea --- /dev/null +++ b/tests/unit/pkgmgr/core/repository/test_ignored.py @@ -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() diff --git a/tests/unit/pkgmgr/core/test_resolve_command.py b/tests/unit/pkgmgr/core/repository/test_resolve.py similarity index 100% rename from tests/unit/pkgmgr/core/test_resolve_command.py rename to tests/unit/pkgmgr/core/repository/test_resolve.py diff --git a/tests/unit/pkgmgr/core/repository/test_selected.py b/tests/unit/pkgmgr/core/repository/test_selected.py new file mode 100644 index 0000000..905d047 --- /dev/null +++ b/tests/unit/pkgmgr/core/repository/test_selected.py @@ -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()