From b9011a253c7dacb24bf83d6b3fce6a11a66c1bd6 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 3c932e45c..bcf7f0ff5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1622,6 +1622,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] = [ @@ -1646,6 +1647,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"], @@ -1724,6 +1732,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, @@ -1732,6 +1741,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 4661995c0148761b1ed5cd1df0dda14c2b709a5b 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 c2add1d2d..77c72eedf 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 fadf6e93d..ea9bf7d10 100644 --- a/src/vcspull/cli/sync.py +++ b/src/vcspull/cli/sync.py @@ -74,6 +74,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") @@ -395,7 +396,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: @@ -468,7 +470,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( @@ -494,7 +501,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: @@ -659,6 +667,7 @@ def sync( 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 bcf7f0ff5..7d6df9728 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1736,14 +1736,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 d22bd289bb21cf2d3254d8a26d5bbe28a76e1140 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 7d6df9728..f35bf9599 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1652,7 +1652,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",