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

ephraimanierobi pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-1-test by this push:
     new dbf8a81dce0 [v3-1-test] Fix Old RC removal logic and add test for the 
function (#59438) (#59456)
dbf8a81dce0 is described below

commit dbf8a81dce0d5c6ccd27536dd5f42ba1cb7c56ef
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Dec 16 09:19:28 2025 +0100

    [v3-1-test] Fix Old RC removal logic and add test for the function (#59438) 
(#59456)
    
    * Fix Old RC removal logic and add test for the function
    
    Old RC releases should be removed when a new RC is built. This helps
    us avoid the situation we had with 3.1.4. The step to remove the old RC 
wasn't working before
    because we were checking old releases against Airflow 2 instead of 3
    using if name.startswith("2"). The fix here is to use pattern to select
    all old RCs whether they are version 2 or 3.
    
    * fixup! Update issue templates regarding Airflow 2 and misc. (#59384)
    (cherry picked from commit 737b3bafcd1bce6f1be7336adcce397deaadf98b)
    
    Co-authored-by: Ephraim Anierobi <[email protected]>
---
 .../commands/release_candidate_command.py          |  10 +-
 dev/breeze/tests/test_release_candidate_command.py | 159 +++++++++++++++++++++
 2 files changed, 165 insertions(+), 4 deletions(-)

diff --git 
a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py 
b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
index e993209fe3b..37c67da5d55 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
@@ -17,6 +17,7 @@
 from __future__ import annotations
 
 import os
+import re
 import shutil
 import sys
 from datetime import date
@@ -49,6 +50,8 @@ from airflow_breeze.utils.reproducible import 
get_source_date_epoch, repack_dete
 from airflow_breeze.utils.run_utils import run_command
 from airflow_breeze.utils.shared_options import get_dry_run
 
+RC_PATTERN = 
re.compile(r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)rc(?P<rc>\d+)$")
+
 
 def validate_remote_tracks_apache_airflow(remote_name):
     """Validate that the specified remote tracks the apache/airflow 
repository."""
@@ -586,8 +589,6 @@ def push_release_candidate_tag_to_github(version, 
remote_name):
 
 
 def remove_old_releases(version, repo_root):
-    if confirm_action("In beta release we do not remove old RCs. Is this a 
beta release?"):
-        return
     if not confirm_action("Do you want to look for old RCs to remove?"):
         return
 
@@ -598,11 +599,12 @@ def remove_old_releases(version, repo_root):
         if entry.name == version:
             # Don't remove the current RC
             continue
-        if entry.is_dir() and entry.name.startswith("2."):
+        if entry.is_dir() and RC_PATTERN.match(entry.name):
             old_releases.append(entry.name)
     old_releases.sort()
-
+    console_print(f"The following old releases should be removed: 
{old_releases}")
     for old_release in old_releases:
+        console_print(f"Removing old release {old_release}")
         if confirm_action(f"Remove old RC {old_release}?"):
             run_command(["svn", "rm", old_release], check=True)
             run_command(
diff --git a/dev/breeze/tests/test_release_candidate_command.py 
b/dev/breeze/tests/test_release_candidate_command.py
new file mode 100644
index 00000000000..3070f9420a4
--- /dev/null
+++ b/dev/breeze/tests/test_release_candidate_command.py
@@ -0,0 +1,159 @@
+# 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 pytest
+
+
+class FakeDirEntry:
+    def __init__(self, name: str, *, is_dir: bool):
+        self.name = name
+        self._is_dir = is_dir
+
+    def is_dir(self) -> bool:
+        return self._is_dir
+
+
[email protected]
+def rc_cmd():
+    """Lazy import the rc command module."""
+    import airflow_breeze.commands.release_candidate_command as module
+
+    return module
+
+
+def test_remove_old_releases_only_collects_rc_directories(monkeypatch, rc_cmd):
+    version = "2.10.0rc3"
+    repo_root = "/repo/root"
+
+    # Arrange: entries include current RC, old RC directories, a matching 
"file", and non-RC directory.
+    entries = [
+        FakeDirEntry(version, is_dir=True),  # current RC: should be skipped
+        FakeDirEntry("2.10.0rc2", is_dir=True),  # old RC dir: should be 
included
+        FakeDirEntry("2.10.0rc1", is_dir=True),  # old RC dir: should be 
included
+        FakeDirEntry("2.10.0rc0", is_dir=False),  # matches pattern but not a 
directory: excluded
+        FakeDirEntry("not-a-rc", is_dir=True),  # directory but not matching 
pattern: excluded
+    ]
+
+    chdir_calls: list[str] = []
+    console_messages: list[str] = []
+    run_command_calls: list[list[str]] = []
+
+    def fake_confirm_action(prompt: str, **_kwargs) -> bool:
+        # First prompt decides whether we scan. We want to.
+        if prompt == "Do you want to look for old RCs to remove?":
+            return True
+        # For each candidate, we decline removal to avoid running svn commands.
+        if prompt.startswith("Remove old RC "):
+            return False
+        raise AssertionError(f"Unexpected confirm prompt: {prompt}")
+
+    monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: 
chdir_calls.append(path))
+    monkeypatch.setattr(rc_cmd.os, "scandir", lambda: iter(entries))
+    monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
+    monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": 
console_messages.append(str(msg)))
+    monkeypatch.setattr(rc_cmd, "run_command", lambda cmd, **_kwargs: 
run_command_calls.append(cmd))
+
+    # Act
+    rc_cmd.remove_old_releases(version=version, repo_root=repo_root)
+
+    # Assert: only directory entries matching RC_PATTERN, excluding current 
version, and sorted.
+    assert f"{repo_root}/asf-dist/dev/airflow" in chdir_calls
+    assert repo_root in chdir_calls
+    assert "The following old releases should be removed: ['2.10.0rc1', 
'2.10.0rc2']" in console_messages
+    assert run_command_calls == []
+
+
+def test_remove_old_releases_returns_early_when_user_declines(monkeypatch, 
rc_cmd):
+    version = "2.10.0rc3"
+    repo_root = "/repo/root"
+
+    confirm_prompts: list[str] = []
+
+    def fake_confirm_action(prompt: str, **_kwargs) -> bool:
+        confirm_prompts.append(prompt)
+        return False
+
+    def should_not_be_called(*_args, **_kwargs):
+        raise AssertionError("This should not have been called when user 
declines the initial prompt.")
+
+    monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
+    monkeypatch.setattr(rc_cmd.os, "chdir", should_not_be_called)
+    monkeypatch.setattr(rc_cmd.os, "scandir", should_not_be_called)
+    monkeypatch.setattr(rc_cmd, "console_print", should_not_be_called)
+    monkeypatch.setattr(rc_cmd, "run_command", should_not_be_called)
+
+    rc_cmd.remove_old_releases(version=version, repo_root=repo_root)
+
+    assert confirm_prompts == ["Do you want to look for old RCs to remove?"]
+
+
+def test_remove_old_releases_removes_confirmed_old_releases(monkeypatch, 
rc_cmd):
+    version = "3.1.5rc3"
+    repo_root = "/repo/root"
+
+    # Unsorted on purpose to verify sorting before prompting/removing.
+    entries = [
+        FakeDirEntry("3.1.5rc2", is_dir=True),
+        FakeDirEntry(version, is_dir=True),
+        FakeDirEntry("3.1.0rc1", is_dir=True),
+    ]
+
+    chdir_calls: list[str] = []
+    console_messages: list[str] = []
+    run_command_calls: list[tuple[list[str], dict]] = []
+    confirm_prompts: list[str] = []
+
+    def fake_confirm_action(prompt: str, **_kwargs) -> bool:
+        confirm_prompts.append(prompt)
+        if prompt == "Do you want to look for old RCs to remove?":
+            return True
+        if prompt == "Remove old RC 3.1.0rc1?":
+            return True
+        if prompt == "Remove old RC 3.1.5rc2?":
+            return False
+        raise AssertionError(f"Unexpected confirm prompt: {prompt}")
+
+    monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: 
chdir_calls.append(path))
+    monkeypatch.setattr(rc_cmd.os, "scandir", lambda: iter(entries))
+    monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
+    monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": 
console_messages.append(str(msg)))
+
+    def fake_run_command(cmd: list[str], **kwargs):
+        run_command_calls.append((cmd, kwargs))
+
+    monkeypatch.setattr(rc_cmd, "run_command", fake_run_command)
+
+    rc_cmd.remove_old_releases(version=version, repo_root=repo_root)
+
+    assert chdir_calls == [f"{repo_root}/asf-dist/dev/airflow", repo_root]
+    assert confirm_prompts == [
+        "Do you want to look for old RCs to remove?",
+        "Remove old RC 3.1.0rc1?",
+        "Remove old RC 3.1.5rc2?",
+    ]
+    assert "The following old releases should be removed: ['3.1.0rc1', 
'3.1.5rc2']" in console_messages
+    assert "Removing old release 3.1.0rc1" in console_messages
+    assert "Removing old release 3.1.5rc2" in console_messages
+    assert "[success]Old releases removed" in console_messages
+
+    # Only rc1 was confirmed, so we should run rm+commit for rc1 only.
+    assert run_command_calls == [
+        (["svn", "rm", "3.1.0rc1"], {"check": True}),
+        (["svn", "commit", "-m", "Remove old release: 3.1.0rc1"], {"check": 
True}),
+    ]

Reply via email to