Repository: TobikoData/sqlmesh
Pull request #4444 (2025-05-17 00:14:48)

Fix: CLI output for restatement plans
The CLI output for restatement plans has a number of issues:
1. The virtual update progress bar is created even though no virtual updates will occur
2. The physical layer SKIP message appears when no physical layer updates occur even though the user does not expect physical layer updates
3. The "no changes to plan" message is always produced even though the user knows there aren't changes

This PR fixes the first two issues and replaces the "no changes" message with `Restating models`.


Patch:
diff --git a/sqlmesh/core/console.py b/sqlmesh/core/console.py
index 5bf03ee0..bdd92c24 100644
--- a/sqlmesh/core/console.py
+++ b/sqlmesh/core/console.py
@@ -1161,7 +1161,7 @@ class TerminalConsole(Console):
         default_catalog: t.Optional[str],
     ) -> None:
         """Indicates that a new snapshot promotion progress has begun."""
-        if self.promotion_progress is None:
+        if snapshots and self.promotion_progress is None:
             self.promotion_progress = make_progress_bar(
                 "Updating virtual layer ", self.console, justify="left"
             )
@@ -1782,10 +1782,13 @@ class TerminalConsole(Console):
         """Get the user's change category for the directly modified models."""
         plan = plan_builder.build()
 
-        self.show_environment_difference_summary(
-            plan.context_diff,
-            no_diff=no_diff,
-        )
+        if plan.restatements:
+            self._print("\n[bold]Restating models\n")
+        else:
+            self.show_environment_difference_summary(
+                plan.context_diff,
+                no_diff=no_diff,
+            )
 
         if plan.context_diff.has_changes:
             self.show_model_difference_summary(
@@ -3458,7 +3461,8 @@ class DebuggerTerminalConsole(TerminalConsole):
         environment_naming_info: EnvironmentNamingInfo,
         default_catalog: t.Optional[str],
     ) -> None:
-        self._write(f"Starting promotion for {len(snapshots)} snapshots")
+        if snapshots:
+            self._write(f"Starting promotion for {len(snapshots)} snapshots")
 
     def update_promotion_progress(self, snapshot: SnapshotInfoLike, promoted: bool) -> None:
         self._write(f"Promoting {snapshot.name}")
diff --git a/sqlmesh/core/plan/evaluator.py b/sqlmesh/core/plan/evaluator.py
index ffdc3c0d..b48cef9e 100644
--- a/sqlmesh/core/plan/evaluator.py
+++ b/sqlmesh/core/plan/evaluator.py
@@ -133,7 +133,9 @@ class BuiltInPlanEvaluator(PlanEvaluator):
             push_completion_status = self._push(plan, snapshots, deployability_index_for_creation)
             if push_completion_status.is_nothing_to_do:
                 self.console.log_status_update(
-                    "\n[green]SKIP: No physical layer updates to perform[/green]\n"
+                    ""
+                    if plan.restatements
+                    else "\n[green]SKIP: No physical layer updates to perform[/green]\n"
                 )
             update_intervals_for_new_snapshots(plan.new_snapshots, self.state_sync)
             self._restate(plan, snapshots_by_name)
diff --git a/sqlmesh/integrations/github/cicd/controller.py b/sqlmesh/integrations/github/cicd/controller.py
index 18063574..bf0d4a40 100644
--- a/sqlmesh/integrations/github/cicd/controller.py
+++ b/sqlmesh/integrations/github/cicd/controller.py
@@ -457,10 +457,13 @@ class GithubController:
         try:
             # Clear out any output that might exist from prior steps
             self._console.clear_captured_outputs()
-            self._console.show_environment_difference_summary(
-                context_diff=plan.context_diff,
-                no_diff=False,
-            )
+            if plan.restatements:
+                self._console._print("\n**Restating models**\n")
+            else:
+                self._console.show_environment_difference_summary(
+                    context_diff=plan.context_diff,
+                    no_diff=False,
+                )
             if plan.context_diff.has_changes:
                 self._console.show_model_difference_summary(
                     context_diff=plan.context_diff,


Test patch:
diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py
index c0b1722c..ef0480ec 100644
--- a/tests/cli/test_cli.py
+++ b/tests/cli/test_cli.py
@@ -248,10 +248,10 @@ def test_plan_restate_model(runner, tmp_path):
     )
     assert result.exit_code == 0
     assert_duckdb_test(result)
-    assert "No changes to plan: project files match the `prod` environment" in result.output
+    assert "Restating models" in result.output
     assert "sqlmesh_example.full_model   [full refresh" in result.output
     assert_model_batches_executed(result)
-    assert_virtual_layer_updated(result)
+    assert "Virtual layer updated" not in result.output
 
 
 @pytest.mark.parametrize("flag", ["--skip-backfill", "--dry-run"])