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 fadf6e93..ea9bf7d1 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 3c932e45..f35bf959 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,12 @@ 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"], + ), DryRunPlanFixture( test_id="long-format", cli_args=["sync", "--dry-run", "--long", "repo-long"], @@ -1724,14 +1731,22 @@ 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, + request: pytest.FixtureRequest, user_path: pathlib.Path, config_path: pathlib.Path, git_repo: GitSync, ) -> None: """Validate human-readable plan output variants.""" + if xfail: + 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")