From 1ff871c58b2b70e00565cd2294976ee0c75fb788 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 5 Apr 2026 09:03:01 -0500 Subject: [PATCH 1/3] test(filter_repos) add xfail regression for full-path matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: filter_repos() only compares r["path"].parent against the path pattern, so full repo paths (e.g. from zsh glob expansion of tanstack-*) and workspace roots with trailing slashes never match — discoverable via discover but not via sync. what: - add test_filter_dir_exact_repo_path (xfail): full repo path like /home/me/myproject/github_projects/kaptan should filter to that repo - add test_filter_dir_workspace_root_trailing_slash (xfail): workspace root with trailing slash should match all repos under it --- tests/test_repo.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_repo.py b/tests/test_repo.py index f6ccd49ac..7865ca08d 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -4,6 +4,7 @@ import typing as t +import pytest from libvcs import BaseSync, GitSync, HgSync, SvnSync from libvcs._internal.shortcuts import create_project @@ -24,6 +25,32 @@ def test_filter_dir() -> None: assert r["name"] == "kaptan" +@pytest.mark.xfail( + reason="filter_repos matches parent dir only; full paths not matched" +) +def test_filter_dir_exact_repo_path() -> None: + """`filter_repos` filter by exact full repo path (e.g. after zsh glob expansion).""" + repo_list = filter_repos( + fixtures.config_dict_expanded, + path="/home/me/myproject/github_projects/kaptan", + ) + assert len(repo_list) == 1 + assert repo_list[0]["name"] == "kaptan" + + +@pytest.mark.xfail( + reason="filter_repos parent-dir fnmatch fails with trailing slash", +) +def test_filter_dir_workspace_root_trailing_slash() -> None: + """`filter_repos` filter by workspace root path with trailing slash.""" + repo_list = filter_repos( + fixtures.config_dict_expanded, + path="/home/me/myproject/github_projects/", + ) + assert len(repo_list) == 1 + assert repo_list[0]["name"] == "kaptan" + + def test_filter_name() -> None: """`filter_repos` filter by name.""" repo_list = filter_repos(fixtures.config_dict_expanded, name=".vim") From 7e167aa852fbcdfaea588e16bc578c9e3de899cd Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 5 Apr 2026 09:04:09 -0500 Subject: [PATCH 2/3] fix(filter_repos) match full repo path and normalize trailing slash why: vcspull sync ~/study/typescript/tanstack-* passes zsh-expanded absolute paths like /home/d/study/typescript/tanstack-router; filter_repos only matched against r["path"].parent (/home/d/study/typescript), so the full path never matched. Tilde-prefixed paths and workspace roots with trailing slashes also failed silently. what: - also match fnmatch against r["path"] (full repo path) in addition to parent - expand ~ and $HOME in pattern before comparing against absolute stored paths - normalize trailing slash via pathlib.Path so /foo/bar/ matches parent /foo/bar --- src/vcspull/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vcspull/config.py b/src/vcspull/config.py index 2d3ffc9a0..8fabc460c 100644 --- a/src/vcspull/config.py +++ b/src/vcspull/config.py @@ -685,11 +685,17 @@ def filter_repos( repo_list: list[ConfigDict] = [] if path: + path_str = str(path) + if "~" in path_str or "$" in path_str: + path_str = str(expand_dir(pathlib.Path(path_str))) + else: + path_str = str(pathlib.Path(path_str)) repo_list.extend( [ r for r in config - if fnmatch.fnmatch(str(pathlib.Path(r["path"]).parent), str(path)) + if fnmatch.fnmatch(str(pathlib.Path(r["path"]).parent), path_str) + or fnmatch.fnmatch(str(r["path"]), path_str) ], ) From 617c05e28baf1d55e361ce51f452120d6ad76349 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 5 Apr 2026 09:05:00 -0500 Subject: [PATCH 3/3] =?UTF-8?q?test(filter=5Frepos)=20release=20xfail=20?= =?UTF-8?q?=E2=80=94=20full-path=20and=20trailing-slash=20matching=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: filter_repos now matches against both r["path"].parent and r["path"], and normalizes trailing slashes, so the regression tests pass unconditionally. what: - remove @pytest.mark.xfail from test_filter_dir_exact_repo_path - remove @pytest.mark.xfail from test_filter_dir_workspace_root_trailing_slash - remove unused pytest import --- tests/test_repo.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/test_repo.py b/tests/test_repo.py index 7865ca08d..e19e91f40 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -4,7 +4,6 @@ import typing as t -import pytest from libvcs import BaseSync, GitSync, HgSync, SvnSync from libvcs._internal.shortcuts import create_project @@ -25,9 +24,6 @@ def test_filter_dir() -> None: assert r["name"] == "kaptan" -@pytest.mark.xfail( - reason="filter_repos matches parent dir only; full paths not matched" -) def test_filter_dir_exact_repo_path() -> None: """`filter_repos` filter by exact full repo path (e.g. after zsh glob expansion).""" repo_list = filter_repos( @@ -38,9 +34,6 @@ def test_filter_dir_exact_repo_path() -> None: assert repo_list[0]["name"] == "kaptan" -@pytest.mark.xfail( - reason="filter_repos parent-dir fnmatch fails with trailing slash", -) def test_filter_dir_workspace_root_trailing_slash() -> None: """`filter_repos` filter by workspace root path with trailing slash.""" repo_list = filter_repos(