From 80b04482c4e77d26b15c875f5ac6a567cdfe8c34 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 5 Apr 2026 09:34:15 -0500 Subject: [PATCH 1/3] test(sync[dry-run]) xfail for implicit show-unchanged with explicit patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: when filtering to specific repos, the user expects all matched repos to appear — needing --show-unchanged to see the 4 unchanged out of 5 matched is unintuitive. Regression test documents the desired behavior before the fix. what: - add xfail field to DryRunPlanFixture (defaults False, zero churn on existing fixtures) - add pytest.xfail() guard in test_sync_dry_run_plan_human when xfail=True - add unchanged-implicit-filter fixture: --dry-run with explicit name pattern, pre-synced repo, expects ✓ row without --show-unchanged flag --- tests/test_cli.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 670f08b1..1fca55ce 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1627,6 +1627,7 @@ class DryRunPlanFixture(t.NamedTuple): plan_entries: list[PlanEntry] | None = None plan_summary: PlanSummary | None = None set_no_color: bool = True + xfail: bool = False DRY_RUN_PLAN_FIXTURES: list[DryRunPlanFixture] = [ @@ -1651,6 +1652,13 @@ class DryRunPlanFixture(t.NamedTuple): pre_sync=True, expected_contains=["Plan: 0 to clone (+)", "✓ my_git_repo"], ), + DryRunPlanFixture( + test_id="unchanged-implicit-filter", + cli_args=["sync", "--dry-run", "my_git_repo"], + pre_sync=True, + expected_contains=["Plan: 0 to clone (+)", "✓ my_git_repo"], + xfail=True, + ), DryRunPlanFixture( test_id="long-format", cli_args=["sync", "--dry-run", "--long", "repo-long"], @@ -1729,6 +1737,7 @@ def test_sync_dry_run_plan_human( plan_entries: list[PlanEntry] | None, plan_summary: PlanSummary | None, set_no_color: bool, + xfail: bool, tmp_path: pathlib.Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, @@ -1737,6 +1746,10 @@ def test_sync_dry_run_plan_human( git_repo: GitSync, ) -> None: """Validate human-readable plan output variants.""" + if xfail: + pytest.xfail( + "explicit filter patterns do not yet auto-show unchanged repos", + ) if set_no_color: monkeypatch.setenv("NO_COLOR", "1") From 83b1e3c23d8ff9a34b3936a41204bb79f627395d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 5 Apr 2026 09:36:27 -0500 Subject: [PATCH 2/3] feat(sync[dry-run]) auto-show all matched repos when filtering explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: vcspull sync tanstack-* shows 5 matched repos but only the 1 needing action — the user has already scoped the command to specific repos and shouldn't need --show-unchanged to confirm the filter worked. what: - add has_explicit_patterns field to PlanRenderOptions - set it true in sync() when repo_patterns passed without --all - fold it into the show_unchanged OR at both _filter_entries_for_display call sites (human and JSON/NDJSON paths) - add PLAN_TIP_MESSAGE_FILTERED: suppress --show-unchanged hint when unchanged rows are already visible due to explicit filtering - wire request.applymarker into test_sync_dry_run_plan_human to support per-fixture xfail without stopping test execution --- src/vcspull/cli/_output.py | 1 + src/vcspull/cli/sync.py | 15 ++++++++++++--- tests/test_cli.py | 7 +++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/vcspull/cli/_output.py b/src/vcspull/cli/_output.py index c2add1d2..77c72eed 100644 --- a/src/vcspull/cli/_output.py +++ b/src/vcspull/cli/_output.py @@ -154,6 +154,7 @@ class PlanRenderOptions: """Rendering options for human plan output.""" show_unchanged: bool = False + has_explicit_patterns: bool = False summary_only: bool = False long: bool = False verbosity: int = 0 diff --git a/src/vcspull/cli/sync.py b/src/vcspull/cli/sync.py index 73ec66ca..d7d3df37 100644 --- a/src/vcspull/cli/sync.py +++ b/src/vcspull/cli/sync.py @@ -79,6 +79,7 @@ PLAN_TIP_MESSAGE = ( "Tip: run without --dry-run to apply. Use --show-unchanged to include ✓ rows." ) +PLAN_TIP_MESSAGE_FILTERED = "Tip: run without --dry-run to apply." DEFAULT_PLAN_CONCURRENCY = max(1, min(32, (os.cpu_count() or 4) * 2)) ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*m") @@ -426,7 +427,8 @@ def _render_plan( entry.name.lower(), ), ), - show_unchanged=render_options.show_unchanged, + show_unchanged=render_options.show_unchanged + or render_options.has_explicit_patterns, ) if not display_entries: @@ -499,7 +501,12 @@ def _render_plan( formatter.emit_text(f" {colors.muted(msg)}") if dry_run: - formatter.emit_text(colors.muted(PLAN_TIP_MESSAGE)) + tip = ( + PLAN_TIP_MESSAGE_FILTERED + if render_options.has_explicit_patterns + else PLAN_TIP_MESSAGE + ) + formatter.emit_text(colors.muted(tip)) def _emit_plan_output( @@ -525,7 +532,8 @@ def _emit_plan_output( display_entries = _filter_entries_for_display( plan.entries, - show_unchanged=render_options.show_unchanged, + show_unchanged=render_options.show_unchanged + or render_options.has_explicit_patterns, ) for entry in display_entries: @@ -1469,6 +1477,7 @@ def _sync_impl( verbosity_level = clamp(verbosity, 0, 2) render_options = PlanRenderOptions( show_unchanged=show_unchanged, + has_explicit_patterns=not sync_all and bool(repo_patterns), summary_only=summary_only, long=long_view, verbosity=verbosity_level, diff --git a/tests/test_cli.py b/tests/test_cli.py index 1fca55ce..dc4382c7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1741,14 +1741,17 @@ def test_sync_dry_run_plan_human( tmp_path: pathlib.Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, + request: pytest.FixtureRequest, user_path: pathlib.Path, config_path: pathlib.Path, git_repo: GitSync, ) -> None: """Validate human-readable plan output variants.""" if xfail: - pytest.xfail( - "explicit filter patterns do not yet auto-show unchanged repos", + request.applymarker( + pytest.mark.xfail( + reason="explicit filter patterns do not yet auto-show unchanged repos", + ), ) if set_no_color: monkeypatch.setenv("NO_COLOR", "1") From 6417c6feb48df646b07e02cac9013053e339ab67 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 5 Apr 2026 09:37:07 -0500 Subject: [PATCH 3/3] =?UTF-8?q?test(sync[dry-run])=20release=20xfail=20?= =?UTF-8?q?=E2=80=94=20implicit=20filter=20auto-show-unchanged=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: unchanged-implicit-filter now passes unconditionally. what: - remove xfail=True from unchanged-implicit-filter fixture --- tests/test_cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index dc4382c7..a770f897 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1657,7 +1657,6 @@ class DryRunPlanFixture(t.NamedTuple): cli_args=["sync", "--dry-run", "my_git_repo"], pre_sync=True, expected_contains=["Plan: 0 to clone (+)", "✓ my_git_repo"], - xfail=True, ), DryRunPlanFixture( test_id="long-format",