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 c2f349d9695 Infer the RC from the target version during release.
(#59455) (#59469)
c2f349d9695 is described below
commit c2f349d9695c787dae3359dc5826faafa7d94dcb
Author: Ephraim Anierobi <[email protected]>
AuthorDate: Mon Dec 15 18:07:08 2025 +0100
Infer the RC from the target version during release. (#59455) (#59469)
This reduces the risk of error by ensuring a release is only
performed when a corresponding RC exists and that the latest
RC is selected automatically, rather than relying on manually supplied
input.
(cherry picked from commit a9f095e6fc4a81bc259eaab44ae87105e4a43812)
---
dev/README_RELEASE_AIRFLOW.md | 12 +-
.../doc/images/output_release-management.txt | 2 +-
.../output_release-management_start-release.svg | 36 +++---
.../output_release-management_start-release.txt | 2 +-
.../src/airflow_breeze/commands/release_command.py | 89 ++++++++++---
.../commands/release_management_commands_config.py | 2 +-
dev/breeze/tests/test_release_command.py | 142 +++++++++++++++++++++
7 files changed, 241 insertions(+), 44 deletions(-)
diff --git a/dev/README_RELEASE_AIRFLOW.md b/dev/README_RELEASE_AIRFLOW.md
index 598cc4e9096..3005b9d2c12 100644
--- a/dev/README_RELEASE_AIRFLOW.md
+++ b/dev/README_RELEASE_AIRFLOW.md
@@ -1003,25 +1003,19 @@ The best way of doing this is to svn cp between the two
repos (this avoids havin
```shell script
export VERSION=3.1.3
-export VERSION_SUFFIX=rc1
-export VERSION_RC=${VERSION}${VERSION_SUFFIX}
export TASK_SDK_VERSION=1.1.3
-export TASK_SDK_VERSION_RC=${TASK_SDK_VERSION}${VERSION_SUFFIX}
export PREVIOUS_RELEASE=3.1.2
# cd to the airflow repo directory and set the environment variable below
export AIRFLOW_REPO_ROOT=$(pwd)
# start the release process by running the below command
breeze release-management start-release \
- --release-candidate ${VERSION_RC} \
+ --version ${VERSION} \
--previous-release ${PREVIOUS_RELEASE} \
- --task-sdk-release-candidate ${TASK_SDK_VERSION_RC}
+ --task-sdk-version ${TASK_SDK_VERSION}
```
-Note: The `--task-sdk-release-candidate` parameter is optional. If you are
releasing Airflow without a corresponding Task SDK release, you can omit this
parameter.
+Note: The `--task-sdk-version` parameter is optional. If you are releasing
Airflow without a corresponding Task SDK release, you can omit this parameter.
-```Dockerfile
-ARG AIRFLOW_EXTRAS=".....,<provider>,...."
-```
4. Make sure to update Airflow version in ``v3-*-test`` branch after
cherry-picking to X.Y.1 in
``airflow/__init__.py``
diff --git a/dev/breeze/doc/images/output_release-management.txt
b/dev/breeze/doc/images/output_release-management.txt
index 74e81545457..0543e36d195 100644
--- a/dev/breeze/doc/images/output_release-management.txt
+++ b/dev/breeze/doc/images/output_release-management.txt
@@ -1 +1 @@
-adf95164020fa37164c778e10dcdaa36
+718c25395e194f1839a38424f73838f5
diff --git a/dev/breeze/doc/images/output_release-management_start-release.svg
b/dev/breeze/doc/images/output_release-management_start-release.svg
index 41227a891b6..22acf18ad1b 100644
--- a/dev/breeze/doc/images/output_release-management_start-release.svg
+++ b/dev/breeze/doc/images/output_release-management_start-release.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 440.4"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 464.79999999999995"
xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -45,7 +45,7 @@
<defs>
<clipPath id="breeze-release-management-start-release-clip-terminal">
- <rect x="0" y="0" width="1463.0" height="389.4" />
+ <rect x="0" y="0" width="1463.0" height="413.79999999999995" />
</clipPath>
<clipPath id="breeze-release-management-start-release-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -92,9 +92,12 @@
<clipPath id="breeze-release-management-start-release-line-14">
<rect x="0" y="343.1" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-release-management-start-release-line-15">
+ <rect x="0" y="367.5" width="1464" height="24.65"/>
+ </clipPath>
</defs>
- <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="438.4" rx="8"/><text
class="breeze-release-management-start-release-title" fill="#c5c8c6"
text-anchor="middle" x="740"
y="27">Command: release-management start-release</text>
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="462.8" rx="8"/><text
class="breeze-release-management-start-release-title" fill="#c5c8c6"
text-anchor="middle" x="740"
y="27">Command: release-management start-release</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -107,19 +110,20 @@
<text class="breeze-release-management-start-release-r1" x="1464" y="20"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-0)">
</text><text class="breeze-release-management-start-release-r2" x="12.2"
y="44.4" textLength="73.2"
clip-path="url(#breeze-release-management-start-release-line-1)">Usage:</text><text
class="breeze-release-management-start-release-r3" x="97.6" y="44.4"
textLength="475.8"
clip-path="url(#breeze-release-management-start-release-line-1)">breeze release-management start-release</text><text
class="breeze-release-management-start-release-r1" x="585.6" y="44.4"
textLength="12.2" clip- [...]
</text><text class="breeze-release-management-start-release-r1" x="1464"
y="68.8" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-2)">
-</text><text class="breeze-release-management-start-release-r1" x="12.2"
y="93.2" textLength="1305.4"
clip-path="url(#breeze-release-management-start-release-line-3)">Start the process of releasing an Airflow version. This command will guide you through the release process.</text><text
class="breeze-release-management-start-release-r1" x="1464" y="93.2"
textLength="12.2" clip-path="url(#breeze-release-managem [...]
-</text><text class="breeze-release-management-start-release-r1" x="1464"
y="117.6" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-4)">
-</text><text class="breeze-release-management-start-release-r5" x="0" y="142"
textLength="24.4"
clip-path="url(#breeze-release-management-start-release-line-5)">╭─</text><text
class="breeze-release-management-start-release-r5" x="24.4" y="142"
textLength="256.2"
clip-path="url(#breeze-release-management-start-release-line-5)"> Start release flags </text><text
class="breeze-release-management-start-release-r5" x="280.6" y="142"
textLength="1159" clip-path="url(#breeze- [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="166.4" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-6)">│</text><text
class="breeze-release-management-start-release-r6" x="24.4" y="166.4"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-6)">*</text><text
class="breeze-release-management-start-release-r4" x="61" y="166.4"
textLength="231.8"
clip-path="url(#breeze-release-management-start-release-line- [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="190.8" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-7)">│</text><text
class="breeze-release-management-start-release-r6" x="24.4" y="190.8"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-7)">*</text><text
class="breeze-release-management-start-release-r4" x="61" y="190.8"
textLength="219.6"
clip-path="url(#breeze-release-management-start-release-line- [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="215.2" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-8)">│</text><text
class="breeze-release-management-start-release-r4" x="61" y="215.2"
textLength="341.6"
clip-path="url(#breeze-release-management-start-release-line-8)">--task-sdk-release-candidate</text><text
class="breeze-release-management-start-release-r1" x="451.4" y="215.2"
textLength="488" clip-path="url(#breeze-release-man [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="239.6" textLength="1464"
clip-path="url(#breeze-release-management-start-release-line-9)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-release-management-start-release-r1" x="1464" y="239.6"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-9)">
-</text><text class="breeze-release-management-start-release-r5" x="0" y="264"
textLength="24.4"
clip-path="url(#breeze-release-management-start-release-line-10)">╭─</text><text
class="breeze-release-management-start-release-r5" x="24.4" y="264"
textLength="195.2"
clip-path="url(#breeze-release-management-start-release-line-10)"> Common options </text><text
class="breeze-release-management-start-release-r5" x="219.6" y="264"
textLength="1220" clip-path="url(#breeze-release- [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="288.4" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-11)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="288.4"
textLength="97.6"
clip-path="url(#breeze-release-management-start-release-line-11)">--answer</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="288.4"
textLength="24.4" clip-path="url(#breeze-release-management-start-re [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="312.8" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-12)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="312.8"
textLength="109.8"
clip-path="url(#breeze-release-management-start-release-line-12)">--dry-run</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="312.8"
textLength="24.4" clip-path="url(#breeze-release-management-start- [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="337.2" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-13)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="337.2"
textLength="109.8"
clip-path="url(#breeze-release-management-start-release-line-13)">--verbose</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="337.2"
textLength="24.4" clip-path="url(#breeze-release-management-start- [...]
-</text><text class="breeze-release-management-start-release-r5" x="0"
y="361.6" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-14)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="361.6"
textLength="73.2"
clip-path="url(#breeze-release-management-start-release-line-14)">--help</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="361.6"
textLength="24.4" clip-path="url(#breeze-release-management-start-rele [...]
-</text><text class="breeze-release-management-start-release-r5" x="0" y="386"
textLength="1464"
clip-path="url(#breeze-release-management-start-release-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-release-management-start-release-r1" x="1464" y="386"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-15)">
+</text><text class="breeze-release-management-start-release-r1" x="12.2"
y="93.2" textLength="1439.6"
clip-path="url(#breeze-release-management-start-release-line-3)">Start the process of releasing an Airflow version. This command will guide you through the release process. The latest</text><text
class="breeze-release-management-start-release-r1" x="1464" y="93.2"
textLength="12.2" clip-path="url(#b [...]
+</text><text class="breeze-release-management-start-release-r1" x="12.2"
y="117.6" textLength="1110.2"
clip-path="url(#breeze-release-management-start-release-line-4)">release candidate for the given version will be automatically found from SVN dev directory.</text><text
class="breeze-release-management-start-release-r1" x="1464" y="117.6"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-4)">
+</text><text class="breeze-release-management-start-release-r1" x="1464"
y="142" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-5)">
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="166.4" textLength="24.4"
clip-path="url(#breeze-release-management-start-release-line-6)">╭─</text><text
class="breeze-release-management-start-release-r5" x="24.4" y="166.4"
textLength="256.2"
clip-path="url(#breeze-release-management-start-release-line-6)"> Start release flags </text><text
class="breeze-release-management-start-release-r5" x="280.6" y="166.4"
textLength="1159" clip-path="url(#b [...]
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="190.8" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-7)">│</text><text
class="breeze-release-management-start-release-r6" x="24.4" y="190.8"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-7)">*</text><text
class="breeze-release-management-start-release-r4" x="61" y="190.8"
textLength="109.8"
clip-path="url(#breeze-release-management-start-release-line- [...]
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="215.2" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-8)">│</text><text
class="breeze-release-management-start-release-r6" x="24.4" y="215.2"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-8)">*</text><text
class="breeze-release-management-start-release-r4" x="61" y="215.2"
textLength="219.6"
clip-path="url(#breeze-release-management-start-release-line- [...]
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="239.6" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-9)">│</text><text
class="breeze-release-management-start-release-r4" x="61" y="239.6"
textLength="219.6"
clip-path="url(#breeze-release-management-start-release-line-9)">--task-sdk-version</text><text
class="breeze-release-management-start-release-r1" x="329.4" y="239.6"
textLength="427" clip-path="url(#breeze-release-management-st [...]
+</text><text class="breeze-release-management-start-release-r5" x="0" y="264"
textLength="1464"
clip-path="url(#breeze-release-management-start-release-line-10)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-release-management-start-release-r1" x="1464" y="264"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-10)">
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="288.4" textLength="24.4"
clip-path="url(#breeze-release-management-start-release-line-11)">╭─</text><text
class="breeze-release-management-start-release-r5" x="24.4" y="288.4"
textLength="195.2"
clip-path="url(#breeze-release-management-start-release-line-11)"> Common options </text><text
class="breeze-release-management-start-release-r5" x="219.6" y="288.4"
textLength="1220" clip-path="url(#breeze-re [...]
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="312.8" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-12)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="312.8"
textLength="97.6"
clip-path="url(#breeze-release-management-start-release-line-12)">--answer</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="312.8"
textLength="24.4" clip-path="url(#breeze-release-management-start-re [...]
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="337.2" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-13)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="337.2"
textLength="109.8"
clip-path="url(#breeze-release-management-start-release-line-13)">--dry-run</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="337.2"
textLength="24.4" clip-path="url(#breeze-release-management-start- [...]
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="361.6" textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-14)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="361.6"
textLength="109.8"
clip-path="url(#breeze-release-management-start-release-line-14)">--verbose</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="361.6"
textLength="24.4" clip-path="url(#breeze-release-management-start- [...]
+</text><text class="breeze-release-management-start-release-r5" x="0" y="386"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-15)">│</text><text
class="breeze-release-management-start-release-r4" x="24.4" y="386"
textLength="73.2"
clip-path="url(#breeze-release-management-start-release-line-15)">--help</text><text
class="breeze-release-management-start-release-r9" x="158.6" y="386"
textLength="24.4" clip-path="url(#breeze-release-management-start-release-li
[...]
+</text><text class="breeze-release-management-start-release-r5" x="0"
y="410.4" textLength="1464"
clip-path="url(#breeze-release-management-start-release-line-16)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-release-management-start-release-r1" x="1464" y="410.4"
textLength="12.2"
clip-path="url(#breeze-release-management-start-release-line-16)">
</text>
</g>
</g>
diff --git a/dev/breeze/doc/images/output_release-management_start-release.txt
b/dev/breeze/doc/images/output_release-management_start-release.txt
index 5920f348902..9576601cabf 100644
--- a/dev/breeze/doc/images/output_release-management_start-release.txt
+++ b/dev/breeze/doc/images/output_release-management_start-release.txt
@@ -1 +1 @@
-a860ddc98626bc82dc298e2bd0081f97
+481eebefa96f86a5a385c295c2cbbdd0
diff --git a/dev/breeze/src/airflow_breeze/commands/release_command.py
b/dev/breeze/src/airflow_breeze/commands/release_command.py
index b05ddf92a6d..9115c40f21c 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_command.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_command.py
@@ -17,6 +17,7 @@
from __future__ import annotations
import os
+import re
import click
@@ -44,6 +45,47 @@ def clone_asf_repo(working_dir):
)
+def find_latest_release_candidate(version, svn_dev_repo, component="airflow"):
+ """
+ Find the latest release candidate for a given version from SVN dev
directory.
+
+ :param version: The base version (e.g., "3.0.5")
+ :param svn_dev_repo: Path to the SVN dev repository
+ :param component: Component name ("airflow" or "task-sdk")
+ :return: The latest release candidate string (e.g., "3.0.5rc3") or None if
not found
+ """
+ if component == "task-sdk":
+ search_dir = f"{svn_dev_repo}/task-sdk"
+ else:
+ search_dir = svn_dev_repo
+
+ if not os.path.exists(search_dir):
+ return None
+
+ # Pattern to match release candidates for this version (e.g., "3.0.5rc1",
"3.0.5rc2")
+ pattern = re.compile(rf"^{re.escape(version)}rc(\d+)$")
+ matching_rcs = []
+
+ try:
+ entries = os.listdir(search_dir)
+ for entry in entries:
+ match = pattern.match(entry)
+ if match:
+ rc_number = int(match.group(1))
+ matching_rcs.append((rc_number, entry))
+
+ if not matching_rcs:
+ return None
+
+ # Sort by RC number and return the latest
+ matching_rcs.sort(key=lambda x: x[0], reverse=True)
+ latest_rc = matching_rcs[0][1]
+ console_print(f"Found latest {component} release candidate:
{latest_rc}")
+ return latest_rc
+ except OSError:
+ return None
+
+
def create_version_dir(version, task_sdk_version=None):
if confirm_action(f"Create SVN version directory for Airflow {version}?"):
run_command(["svn", "mkdir", f"{version}"], check=True)
@@ -269,35 +311,27 @@ def push_tag_for_final_version(version,
release_candidate, task_sdk_version=None
name="start-release",
short_help="Start Airflow release process",
help="Start the process of releasing an Airflow version. "
- "This command will guide you through the release process. ",
+ "This command will guide you through the release process. "
+ "The latest release candidate for the given version will be automatically
found from SVN dev directory.",
)
[email protected]("--release-candidate", required=True, help="Airflow release
candidate e.g. 3.0.5rc1")
[email protected]("--version", required=True, help="Airflow release version e.g.
3.0.5")
@click.option("--previous-release", required=True, help="Previous Airflow
release e.g. 3.0.4")
[email protected]("--task-sdk-release-candidate", required=False, help="Task SDK
release candidate e.g. 1.0.5rc1")
[email protected]("--task-sdk-version", required=False, help="Task SDK release
version e.g. 1.0.5")
@option_answer
@option_dry_run
@option_verbose
-def airflow_release(release_candidate, previous_release,
task_sdk_release_candidate):
- if "rc" not in release_candidate:
- exit("Release candidate must contain 'rc'")
+def airflow_release(version, previous_release, task_sdk_version):
+ if "rc" in version:
+ exit("Version must not contain 'rc' - use the final version (e.g.,
3.0.5)")
if "rc" in previous_release:
exit("Previous release must not contain 'rc'")
- version = release_candidate[:-3]
- task_sdk_version = None
- if task_sdk_release_candidate:
- if "rc" not in task_sdk_release_candidate:
- exit("Task SDK release candidate must contain 'rc'")
- task_sdk_version = task_sdk_release_candidate[:-3]
-
os.chdir(AIRFLOW_ROOT_PATH)
airflow_repo_root = os.getcwd()
console_print()
- console_print("Airflow Release candidate:", release_candidate)
console_print("Airflow Release Version:", version)
console_print("Previous Airflow release:", previous_release)
- if task_sdk_release_candidate:
- console_print("Task SDK Release candidate:",
task_sdk_release_candidate)
+ if task_sdk_version:
console_print("Task SDK Release Version:", task_sdk_version)
console_print("Airflow repo root:", airflow_repo_root)
console_print()
@@ -317,6 +351,29 @@ def airflow_release(release_candidate, previous_release,
task_sdk_release_candid
console_print("SVN dev repo root:", svn_dev_repo)
console_print("SVN release repo root:", svn_release_repo)
+ # Find the latest release candidate for the given version
+ console_print()
+ console_print("Finding latest release candidate from SVN dev directory...")
+ release_candidate = find_latest_release_candidate(version, svn_dev_repo,
component="airflow")
+ if not release_candidate:
+ exit(f"No release candidate found for version {version} in SVN dev
directory")
+
+ task_sdk_release_candidate = None
+ if task_sdk_version:
+ task_sdk_release_candidate = find_latest_release_candidate(
+ task_sdk_version, svn_dev_repo, component="task-sdk"
+ )
+ if not task_sdk_release_candidate:
+ exit(f"No Task SDK release candidate found for version
{task_sdk_version} in SVN dev directory")
+
+ console_print()
+ console_print("Airflow Release candidate:", release_candidate)
+ console_print("Airflow Release Version:", version)
+ if task_sdk_release_candidate:
+ console_print("Task SDK Release candidate:",
task_sdk_release_candidate)
+ console_print("Task SDK Release Version:", task_sdk_version)
+ console_print()
+
# Create the version directory
confirm_action("Confirm that the above repo exists. Continue?", abort=True)
diff --git
a/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py
b/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py
index 5e00c23b866..9ce0b13224a 100644
---
a/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py
+++
b/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py
@@ -457,7 +457,7 @@ RELEASE_MANAGEMENT_PARAMETERS: dict[str, list[dict[str, str
| list[str]]]] = {
"breeze release-management start-release": [
{
"name": "Start release flags",
- "options": ["--release-candidate", "--previous-release",
"--task-sdk-release-candidate"],
+ "options": ["--version", "--previous-release",
"--task-sdk-version"],
}
],
"breeze release-management update-constraints": [
diff --git a/dev/breeze/tests/test_release_command.py
b/dev/breeze/tests/test_release_command.py
new file mode 100644
index 00000000000..686e3df1ea5
--- /dev/null
+++ b/dev/breeze/tests/test_release_command.py
@@ -0,0 +1,142 @@
+# 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
+
+from unittest.mock import patch
+
+from airflow_breeze.commands.release_command import
find_latest_release_candidate
+
+
+class TestFindLatestReleaseCandidate:
+ """Test the find_latest_release_candidate function."""
+
+ def test_find_latest_rc_single_candidate(self, tmp_path):
+ """Test finding release candidate when only one exists."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+
+ # Create a single RC directory
+ (svn_dev_repo / "3.0.5rc1").mkdir()
+
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result == "3.0.5rc1"
+
+ def test_find_latest_rc_multiple_candidates(self, tmp_path):
+ """Test finding latest release candidate when multiple exist."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+
+ # Create multiple RC directories
+ (svn_dev_repo / "3.0.5rc1").mkdir()
+ (svn_dev_repo / "3.0.5rc2").mkdir()
+ (svn_dev_repo / "3.0.5rc3").mkdir()
+ (svn_dev_repo / "3.0.5rc10").mkdir() # Test that rc10 > rc3
+
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result == "3.0.5rc10"
+
+ def test_find_latest_rc_ignores_other_versions(self, tmp_path):
+ """Test that function ignores RCs for other versions."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+
+ # Create RCs for different versions
+ (svn_dev_repo / "3.0.4rc1").mkdir()
+ (svn_dev_repo / "3.0.5rc1").mkdir()
+ (svn_dev_repo / "3.0.5rc2").mkdir()
+ (svn_dev_repo / "3.0.6rc1").mkdir()
+
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result == "3.0.5rc2"
+
+ def test_find_latest_rc_ignores_non_rc_directories(self, tmp_path):
+ """Test that function ignores directories that don't match RC
pattern."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+
+ # Create RC directory and non-RC directories
+ (svn_dev_repo / "3.0.5rc1").mkdir()
+ (svn_dev_repo / "3.0.5").mkdir() # Final release directory
+ (svn_dev_repo / "some-other-dir").mkdir()
+
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result == "3.0.5rc1"
+
+ def test_find_latest_rc_no_match(self, tmp_path):
+ """Test that function returns None when no matching RC found."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+
+ # Create RCs for different version
+ (svn_dev_repo / "3.0.4rc1").mkdir()
+
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result is None
+
+ def test_find_latest_rc_directory_not_exists(self, tmp_path):
+ """Test that function returns None when directory doesn't exist."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ # Don't create the directory
+
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result is None
+
+ def test_find_latest_rc_empty_directory(self, tmp_path):
+ """Test that function returns None when directory is empty."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result is None
+
+ def test_find_latest_rc_task_sdk_component(self, tmp_path):
+ """Test finding release candidate for task-sdk component."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ task_sdk_dir = svn_dev_repo / "task-sdk"
+ task_sdk_dir.mkdir(parents=True)
+
+ # Create multiple Task SDK RC directories
+ (task_sdk_dir / "1.0.5rc1").mkdir()
+ (task_sdk_dir / "1.0.5rc2").mkdir()
+ (task_sdk_dir / "1.0.5rc3").mkdir()
+
+ result = find_latest_release_candidate("1.0.5", str(svn_dev_repo),
component="task-sdk")
+ assert result == "1.0.5rc3"
+
+ def test_find_latest_rc_task_sdk_ignores_airflow_rcs(self, tmp_path):
+ """Test that task-sdk component ignores airflow RCs."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+ task_sdk_dir = svn_dev_repo / "task-sdk"
+ task_sdk_dir.mkdir()
+
+ # Create airflow RC (should be ignored)
+ (svn_dev_repo / "3.0.5rc1").mkdir()
+ # Create task-sdk RC
+ (task_sdk_dir / "1.0.5rc1").mkdir()
+
+ result = find_latest_release_candidate("1.0.5", str(svn_dev_repo),
component="task-sdk")
+ assert result == "1.0.5rc1"
+
+ def test_find_latest_rc_handles_oserror(self, tmp_path):
+ """Test that function handles OSError gracefully."""
+ svn_dev_repo = tmp_path / "dev" / "airflow"
+ svn_dev_repo.mkdir(parents=True)
+
+ with patch("os.listdir", side_effect=OSError("Permission denied")):
+ result = find_latest_release_candidate("3.0.5", str(svn_dev_repo),
component="airflow")
+ assert result is None