This is an automated email from the ASF dual-hosted git repository.

shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 6425fb72203 Improve error message when publish-docs ref is a branch 
not a tag (#66250)
6425fb72203 is described below

commit 6425fb72203a394a8d76354ebeca482cfe341762
Author: Shahar Epstein <[email protected]>
AuthorDate: Tue May 12 17:58:26 2026 +0300

    Improve error message when publish-docs ref is a branch not a tag (#66250)
---
 .../airflow_breeze/commands/workflow_commands.py   |  24 ++++-
 dev/breeze/tests/test_workflow_commands.py         | 101 +++++++++++++++++++++
 2 files changed, 122 insertions(+), 3 deletions(-)

diff --git a/dev/breeze/src/airflow_breeze/commands/workflow_commands.py 
b/dev/breeze/src/airflow_breeze/commands/workflow_commands.py
index 5822a8ceb08..1620a09c90b 100644
--- a/dev/breeze/src/airflow_breeze/commands/workflow_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/workflow_commands.py
@@ -145,10 +145,28 @@ def workflow_run_publish(
         tag_respo = json.loads(stdout)
 
         if not tag_respo.get("ref"):
-            console_print(
-                f"[red]Error: Ref {ref} does not exists in repo apache/airflow 
.[/red]",
+            # Check whether the ref exists as a branch (common case for -docs 
branches)
+            branch_result = run_gh_command(
+                ["gh", "api", f"repos/apache/airflow/git/refs/heads/{ref}"],
+                capture_output=True,
+                check=False,
             )
-            console_print("\nYou can add --skip-tag-validation to skip this 
validation.")
+            branch_stdout = branch_result.stdout.decode("utf-8")
+            branch_respo = json.loads(branch_stdout)
+
+            if branch_respo.get("ref"):
+                console_print(
+                    f"[red]Error: Ref {ref} exists as a branch but not as a 
tag.[/red]",
+                )
+                console_print(
+                    "\nTo publish docs from a branch (e.g. a 
`providers/YYYY-MM-DD-docs` "
+                    "post-release fix branch), add 
[bold]--skip-tag-validation[/bold] to your command."
+                )
+            else:
+                console_print(
+                    f"[red]Error: Ref {ref} does not exist as a tag or branch 
in repo apache/airflow.[/red]",
+                )
+                console_print("\nYou can add --skip-tag-validation to skip 
this validation.")
             sys.exit(1)
 
     console_print(
diff --git a/dev/breeze/tests/test_workflow_commands.py 
b/dev/breeze/tests/test_workflow_commands.py
new file mode 100644
index 00000000000..8a30e2a40e4
--- /dev/null
+++ b/dev/breeze/tests/test_workflow_commands.py
@@ -0,0 +1,101 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import json
+from subprocess import CompletedProcess
+from unittest.mock import MagicMock, patch
+
+import pytest
+from click.testing import CliRunner
+
+from airflow_breeze.commands.workflow_commands import workflow_run_publish
+
+
+def _make_gh_response(ref: str | None) -> CompletedProcess:
+    """Return a fake CompletedProcess whose stdout mimics a gh api response."""
+    body = {"ref": ref} if ref else {"message": "Not Found"}
+    return MagicMock(spec=CompletedProcess, stdout=json.dumps(body).encode())
+
+
+class TestPublishDocsTagValidation:
+    """Tests for the ref validation logic in `breeze workflow-run 
publish-docs`."""
+
+    @pytest.fixture
+    def runner(self):
+        return CliRunner()
+
+    def _invoke(self, runner: CliRunner, ref: str, extra_args: list[str] | 
None = None) -> object:
+        args = ["--ref", ref, "--site-env", "staging", "apache-airflow"]
+        if extra_args:
+            args = extra_args + args
+        return runner.invoke(workflow_run_publish, args, 
catch_exceptions=False)
+
+    @patch("airflow_breeze.commands.workflow_commands.run_gh_command")
+    def test_ref_is_valid_tag_passes_validation(self, mock_run_gh_command, 
runner):
+        """When the ref exists as a tag, validation passes and the workflow is 
triggered."""
+        tag_response = _make_gh_response("refs/tags/providers/2026-04-26")
+        mock_run_gh_command.return_value = tag_response
+
+        with 
patch("airflow_breeze.commands.workflow_commands.trigger_workflow_and_monitor") 
as mock_trigger:
+            result = self._invoke(runner, "providers/2026-04-26")
+
+        assert result.exit_code == 0
+        mock_trigger.assert_called()
+        # Only one gh api call (tag check), no branch check needed
+        mock_run_gh_command.assert_called_once()
+
+    @patch("airflow_breeze.commands.workflow_commands.run_gh_command")
+    def test_ref_is_branch_shows_actionable_message(self, mock_run_gh_command, 
runner):
+        """When the ref exists as a branch but not a tag, the error names the 
branch
+        and points to --skip-tag-validation."""
+        tag_response = _make_gh_response(None)
+        branch_response = _make_gh_response("refs/heads/main")
+        mock_run_gh_command.side_effect = [tag_response, branch_response]
+
+        result = self._invoke(runner, "main")
+
+        assert result.exit_code == 1
+        assert "exists as a branch but not as a tag" in result.output
+        assert "--skip-tag-validation" in result.output
+        # Both tag and branch checks must have been made
+        assert mock_run_gh_command.call_count == 2
+        calls = mock_run_gh_command.call_args_list
+        assert "refs/tags/main" in calls[0][0][0][2]
+        assert "refs/heads/main" in calls[1][0][0][2]
+
+    @patch("airflow_breeze.commands.workflow_commands.run_gh_command")
+    def test_ref_not_found_anywhere_shows_generic_message(self, 
mock_run_gh_command, runner):
+        """When the ref exists neither as a tag nor a branch, a generic error 
is shown."""
+        mock_run_gh_command.return_value = _make_gh_response(None)
+
+        result = self._invoke(runner, "nonexistent-ref-xyz")
+
+        assert result.exit_code == 1
+        assert "does not exist as a tag or branch" in result.output
+        assert "--skip-tag-validation" in result.output
+        assert mock_run_gh_command.call_count == 2
+
+    @patch("airflow_breeze.commands.workflow_commands.run_gh_command")
+    def test_skip_tag_validation_bypasses_checks(self, mock_run_gh_command, 
runner):
+        """With --skip-tag-validation, no gh api calls are made and the 
workflow proceeds."""
+        with 
patch("airflow_breeze.commands.workflow_commands.trigger_workflow_and_monitor") 
as mock_trigger:
+            result = self._invoke(runner, "some-branch", 
extra_args=["--skip-tag-validation"])
+
+        assert result.exit_code == 0
+        mock_run_gh_command.assert_not_called()
+        mock_trigger.assert_called()

Reply via email to