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

kaxilnaik 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 0810b0df301 Dev: Add Task SDK support to `start-rc-process` (#54960)
0810b0df301 is described below

commit 0810b0df301f427034ee7b99a90aa4166b82f806
Author: Kaxil Naik <kaxiln...@apache.org>
AuthorDate: Wed Aug 27 00:16:43 2025 +0100

    Dev: Add Task SDK support to `start-rc-process` (#54960)
    
    Added support to automate building Task SDK too and `--remote-name` 
parameter to allow passing a different remote (for example I use a different 
remote than origin). Also added some validations to prevent errors!
---
 dev/README_RELEASE_AIRFLOW.md                      |  74 +++---
 .../output_release-management_start-rc-process.svg |  30 ++-
 .../output_release-management_start-rc-process.txt |   2 +-
 .../commands/release_candidate_command.py          | 278 +++++++++++++++++++--
 .../commands/release_management_commands_config.py |   2 +
 5 files changed, 320 insertions(+), 66 deletions(-)

diff --git a/dev/README_RELEASE_AIRFLOW.md b/dev/README_RELEASE_AIRFLOW.md
index 3800c1b3489..70374c64908 100644
--- a/dev/README_RELEASE_AIRFLOW.md
+++ b/dev/README_RELEASE_AIRFLOW.md
@@ -272,10 +272,11 @@ The Release Candidate artifacts we vote upon should be 
the exact ones we vote ag
 export GPG_TTY=$(tty)
 
 # Set Version
-export VERSION=2.1.2rc3
-export VERSION_SUFFIX=rc3
+export VERSION=3.0.5rc1
+export VERSION_SUFFIX=rc1
 export VERSION_BRANCH=2-1
 export VERSION_WITHOUT_RC=${VERSION/rc?/}
+export TASK_SDK_VERSION=1.0.5rc1
 
 # Set AIRFLOW_REPO_ROOT to the path of your git repo
 export AIRFLOW_REPO_ROOT=$(pwd)
@@ -359,7 +360,23 @@ uv tool install -e ./dev/breeze
     ```shell script
     git checkout main
     git pull # Ensure that the script is up-to-date
-    breeze release-management start-rc-process --version ${VERSION} 
--previous-version <PREVIOUS_VERSION>
+    breeze release-management start-rc-process \
+        --version ${VERSION} \
+        --previous-version <PREVIOUS_VERSION> \
+        --task-sdk-version ${TASK_SDK_VERSION}
+   ```
+
+   **Testing the start-rc-process command:**
+   Before running the actual release command, you can safely test it using:
+
+   ```shell script
+   # Test with dry-run (shows what would be executed without doing it)
+   breeze release-management start-rc-process \
+       --version ${VERSION} \
+       --previous-version <PREVIOUS_VERSION> \
+       --task-sdk-version ${TASK_SDK_VERSION} \
+       --remote-name upstream \
+       --dry-run
    ```
 
 - Create issue in github for testing the release using this subject:
@@ -748,8 +765,8 @@ this is a valid key already.  To suppress the warning you 
may edit the key's tru
 by running `gpg --edit-key <key id> trust` and entering `5` to assign trust 
level `ultimate`.
 
 ```
-Checking apache-airflow-2.0.2rc4.tar.gz.asc
-gpg: assuming signed data in 'apache-airflow-2.0.2rc4.tar.gz'
+Checking apache-airflow-3.0.5rc4.tar.gz.asc
+gpg: assuming signed data in 'apache-airflow-3.0.5rc4.tar.gz'
 gpg: Signature made sob, 22 sie 2020, 20:28:28 CEST
 gpg:                using RSA key 12717556040EEF2EEAF1B9C275FCCD0A25FA0E4B
 gpg: Good signature from "Kaxil Naik <kaxiln...@gmail.com>" [unknown]
@@ -757,8 +774,8 @@ gpg: WARNING: This key is not certified with a trusted 
signature!
 gpg:          There is no indication that the signature belongs to the owner.
 Primary key fingerprint: 1271 7556 040E EF2E EAF1  B9C2 75FC CD0A 25FA 0E4B
 
-Checking apache_airflow-2.0.2rc4-py2.py3-none-any.whl.asc
-gpg: assuming signed data in 'apache_airflow-2.0.2rc4-py2.py3-none-any.whl'
+Checking apache_airflow-3.0.5rc4-py2.py3-none-any.whl.asc
+gpg: assuming signed data in 'apache_airflow-3.0.5rc4-py2.py3-none-any.whl'
 gpg: Signature made sob, 22 sie 2020, 20:28:31 CEST
 gpg:                using RSA key 12717556040EEF2EEAF1B9C275FCCD0A25FA0E4B
 gpg: Good signature from "Kaxil Naik <kaxiln...@gmail.com>" [unknown]
@@ -766,8 +783,8 @@ gpg: WARNING: This key is not certified with a trusted 
signature!
 gpg:          There is no indication that the signature belongs to the owner.
 Primary key fingerprint: 1271 7556 040E EF2E EAF1  B9C2 75FC CD0A 25FA 0E4B
 
-Checking apache-airflow-2.0.2rc4-source.tar.gz.asc
-gpg: assuming signed data in 'apache-airflow-2.0.2rc4-source.tar.gz'
+Checking apache-airflow-3.0.5rc4-source.tar.gz.asc
+gpg: assuming signed data in 'apache-airflow-3.0.5rc4-source.tar.gz'
 gpg: Signature made sob, 22 sie 2020, 20:28:25 CEST
 gpg:                using RSA key 12717556040EEF2EEAF1B9C275FCCD0A25FA0E4B
 gpg: Good signature from "Kaxil Naik <kaxiln...@gmail.com>" [unknown]
@@ -790,9 +807,9 @@ done
 You should get output similar to:
 
 ```
-Checking apache-airflow-2.0.2rc4.tar.gz.sha512
-Checking apache_airflow-2.0.2rc4-py2.py3-none-any.whl.sha512
-Checking apache-airflow-2.0.2rc4-source.tar.gz.sha512
+Checking apache-airflow-3.0.5rc4.tar.gz.sha512
+Checking apache_airflow-3.0.5rc4-py2.py3-none-any.whl.sha512
+Checking apache-airflow-3.0.5rc4-source.tar.gz.sha512
 ```
 
 
@@ -857,7 +874,7 @@ Once the vote has been passed, you will need to send a 
result vote to dev@airflo
 Subject:
 
 ```
-[RESULT][VOTE] Release Airflow 2.0.2 from 2.0.2rc3
+[RESULT][VOTE] Release Airflow 3.0.5 from 3.0.5rc1 & Task SDK 1.0.5 from 
1.0.5rc1
 ```
 
 Message:
@@ -865,26 +882,27 @@ Message:
 ```
 Hello,
 
-Apache Airflow 2.0.2 (based on RC3) has been accepted.
+The vote to release Apache Airflow version 3.0.5 based on 3.0.5rc3 & Task SDK 
1.0.5 from 1.0.5rc3 is now closed.
+
+The vote PASSED with 6 binding "+1", 4 non-binding "+1" and 0 "-1" votes:
 
-4 "+1" binding votes received:
+"+1" Binding votes:
 - Kaxil Naik
-- Bolke de Bruin
+- Jens Scheffler
+- Jarek Potiuk
 - Ash Berlin-Taylor
-- Tao Feng
-
-
-4 "+1" non-binding votes received:
+- Hussein Awala
+- Amogh Desai
 
-- Deng Xiaodong
-- Stefan Seelmann
-- Joshua Patchus
-- Felix Uellendall
+"+1" non-Binding votes:
+- Wei Lee
+- Pavankumar Gopidesu
+- Ankit Chaurasia
+- Rahul Vats
 
-Vote thread:
-https://lists.apache.org/thread.html/736404ca3d2b2143b296d0910630b9bd0f8b56a0c54e3a05f4c8b5fe@%3Cdev.airflow.apache.org%3E
+Vote thread: https://lists.apache.org/thread/f72gglg5vdxnfmjqtjlhwgvn2tnh4gx4
 
-I'll continue with the release process, and the release announcement will 
follow shortly.
+I will continue with the release process, and the release announcement will 
follow shortly.
 
 Cheers,
 <your name>
@@ -899,7 +917,7 @@ https://dist.apache.org/repos/dist/release/airflow/
 The best way of doing this is to svn cp between the two repos (this avoids 
having to upload the binaries again, and gives a clearer history in the svn 
commit logs):
 
 ```shell script
-export RC=2.0.2rc5
+export RC=3.0.5rc5
 export VERSION=${RC/rc?/}
 # cd to the airflow repo directory and set the environment variable below
 export AIRFLOW_REPO_ROOT=$(pwd)
diff --git 
a/dev/breeze/doc/images/output_release-management_start-rc-process.svg 
b/dev/breeze/doc/images/output_release-management_start-rc-process.svg
index cd966701475..45c7c863b53 100644
--- a/dev/breeze/doc/images/output_release-management_start-rc-process.svg
+++ b/dev/breeze/doc/images/output_release-management_start-rc-process.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 489.2" 
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-rc-process-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="389.4" />
+      <rect x="0" y="0" width="1463.0" height="438.2" />
     </clipPath>
     <clipPath id="breeze-release-management-start-rc-process-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -92,9 +92,15 @@
 <clipPath id="breeze-release-management-start-rc-process-line-14">
     <rect x="0" y="343.1" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-release-management-start-rc-process-line-15">
+    <rect x="0" y="367.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-release-management-start-rc-process-line-16">
+    <rect x="0" y="391.9" 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-rc-process-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;release-management&#160;start-rc-process</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="487.2" rx="8"/><text 
class="breeze-release-management-start-rc-process-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;release-management&#160;start-rc-process</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -112,14 +118,16 @@
 </text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="142" textLength="24.4" 
clip-path="url(#breeze-release-management-start-rc-process-line-5)">╭─</text><text
 class="breeze-release-management-start-rc-process-r5" x="24.4" y="142" 
textLength="292.8" 
clip-path="url(#breeze-release-management-start-rc-process-line-5)">&#160;Start&#160;RC&#160;process&#160;flags&#160;</text><text
 class="breeze-release-management-start-rc-process-r5" x="317.2" y="142" 
textLength="1122.4 [...]
 </text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="166.4" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-6)">│</text><text
 class="breeze-release-management-start-rc-process-r6" x="24.4" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-6)">*</text><text
 class="breeze-release-management-start-rc-process-r4" x="61" y="166.4" 
textLength="109.8" clip-path="url(#breeze-release-management-star [...]
 </text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="190.8" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-7)">│</text><text
 class="breeze-release-management-start-rc-process-r6" x="24.4" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-7)">*</text><text
 class="breeze-release-management-start-rc-process-r4" x="61" y="190.8" 
textLength="219.6" clip-path="url(#breeze-release-management-star [...]
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="215.2" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-8)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="61" y="215.2" 
textLength="170.8" 
clip-path="url(#breeze-release-management-start-rc-process-line-8)">--github-token</text><text
 class="breeze-release-management-start-rc-process-r1" x="329.4" y="215.2" 
textLength="878.4" clip-path="url(#breeze-release- [...]
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="239.6" textLength="1464" 
clip-path="url(#breeze-release-management-start-rc-process-line-9)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-start-rc-process-r1" x="1464" y="239.6" 
textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-9)">
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="264" textLength="24.4" 
clip-path="url(#breeze-release-management-start-rc-process-line-10)">╭─</text><text
 class="breeze-release-management-start-rc-process-r5" x="24.4" y="264" 
textLength="195.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-10)">&#160;Common&#160;options&#160;</text><text
 class="breeze-release-management-start-rc-process-r5" x="219.6" y="264" 
textLength="1220" clip-path="url(# [...]
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="288.4" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-11)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="288.4" 
textLength="97.6" 
clip-path="url(#breeze-release-management-start-rc-process-line-11)">--answer</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="288.4" 
textLength="24.4" clip-path="url(#breeze-release-mana [...]
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="312.8" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-12)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="312.8" 
textLength="109.8" 
clip-path="url(#breeze-release-management-start-rc-process-line-12)">--dry-run</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="312.8" 
textLength="24.4" clip-path="url(#breeze-release-ma [...]
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="337.2" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-13)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="337.2" 
textLength="109.8" 
clip-path="url(#breeze-release-management-start-rc-process-line-13)">--verbose</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="337.2" 
textLength="24.4" clip-path="url(#breeze-release-ma [...]
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="361.6" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-14)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="361.6" 
textLength="73.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-14)">--help</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="361.6" 
textLength="24.4" clip-path="url(#breeze-release-manage [...]
-</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="386" textLength="1464" 
clip-path="url(#breeze-release-management-start-rc-process-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-start-rc-process-r1" x="1464" y="386" 
textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-15)">
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="215.2" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-8)">│</text><text
 class="breeze-release-management-start-rc-process-r6" x="24.4" y="215.2" 
textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-8)">*</text><text
 class="breeze-release-management-start-rc-process-r4" x="61" y="215.2" 
textLength="219.6" clip-path="url(#breeze-release-management-star [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="239.6" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-9)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="61" y="239.6" 
textLength="170.8" 
clip-path="url(#breeze-release-management-start-rc-process-line-9)">--github-token</text><text
 class="breeze-release-management-start-rc-process-r1" x="329.4" y="239.6" 
textLength="878.4" clip-path="url(#breeze-release- [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="264" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-10)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="61" y="264" 
textLength="158.6" 
clip-path="url(#breeze-release-management-start-rc-process-line-10)">--remote-name</text><text
 class="breeze-release-management-start-rc-process-r1" x="329.4" y="264" 
textLength="536.8" clip-path="url(#breeze-release-manag [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="288.4" textLength="1464" 
clip-path="url(#breeze-release-management-start-rc-process-line-11)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-start-rc-process-r1" x="1464" y="288.4" 
textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-11)">
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="312.8" textLength="24.4" 
clip-path="url(#breeze-release-management-start-rc-process-line-12)">╭─</text><text
 class="breeze-release-management-start-rc-process-r5" x="24.4" y="312.8" 
textLength="195.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-12)">&#160;Common&#160;options&#160;</text><text
 class="breeze-release-management-start-rc-process-r5" x="219.6" y="312.8" 
textLength="1220" clip-path= [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="337.2" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-13)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="337.2" 
textLength="97.6" 
clip-path="url(#breeze-release-management-start-rc-process-line-13)">--answer</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="337.2" 
textLength="24.4" clip-path="url(#breeze-release-mana [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="361.6" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-14)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="361.6" 
textLength="109.8" 
clip-path="url(#breeze-release-management-start-rc-process-line-14)">--dry-run</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="361.6" 
textLength="24.4" clip-path="url(#breeze-release-ma [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="386" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-15)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="386" 
textLength="109.8" 
clip-path="url(#breeze-release-management-start-rc-process-line-15)">--verbose</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="386" 
textLength="24.4" clip-path="url(#breeze-release-manageme [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="410.4" textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-16)">│</text><text
 class="breeze-release-management-start-rc-process-r4" x="24.4" y="410.4" 
textLength="73.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-16)">--help</text><text
 class="breeze-release-management-start-rc-process-r9" x="158.6" y="410.4" 
textLength="24.4" clip-path="url(#breeze-release-manage [...]
+</text><text class="breeze-release-management-start-rc-process-r5" x="0" 
y="434.8" textLength="1464" 
clip-path="url(#breeze-release-management-start-rc-process-line-17)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-start-rc-process-r1" x="1464" y="434.8" 
textLength="12.2" 
clip-path="url(#breeze-release-management-start-rc-process-line-17)">
 </text>
     </g>
     </g>
diff --git 
a/dev/breeze/doc/images/output_release-management_start-rc-process.txt 
b/dev/breeze/doc/images/output_release-management_start-rc-process.txt
index b3b458160d9..60bca552ac9 100644
--- a/dev/breeze/doc/images/output_release-management_start-rc-process.txt
+++ b/dev/breeze/doc/images/output_release-management_start-rc-process.txt
@@ -1 +1 @@
-e213b793839b778288f40a3af9e076f6
+8c938ba1642769000bab086687b60815
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 87aa6dad66a..f71992b6c9d 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
@@ -32,7 +32,173 @@ from airflow_breeze.utils.run_utils import run_command
 from airflow_breeze.utils.shared_options import get_dry_run
 
 
-def merge_pr(version_branch):
+def validate_remote_tracks_apache_airflow(remote_name):
+    """Validate that the specified remote tracks the apache/airflow 
repository."""
+    console_print(f"[info]Validating remote '{remote_name}' tracks 
apache/airflow...")
+
+    result = run_command(
+        ["git", "remote", "get-url", remote_name],
+        check=False,
+        capture_output=True,
+        text=True,
+        dry_run_override=False,
+    )
+
+    if result.returncode != 0:
+        console_print(f"[error]Remote '{remote_name}' does not exist!")
+        console_print("Available remotes:")
+        run_command(["git", "remote", "-v"])
+        exit(1)
+
+    remote_url = result.stdout.strip()
+
+    # Check if it's the apache/airflow repository
+    apache_patterns = [
+        "https://github.com/apache/airflow";,
+        "https://github.com/apache/airflow.git";,
+        "g...@github.com:apache/airflow",
+        "g...@github.com:apache/airflow.git",
+        "ssh://g...@github.com/apache/airflow",
+        "ssh://g...@github.com/apache/airflow.git",
+    ]
+
+    is_apache_repo = any(pattern in remote_url for pattern in apache_patterns)
+
+    if not is_apache_repo:
+        console_print(f"[error]Remote '{remote_name}' does not track 
apache/airflow!")
+        console_print(f"Remote URL: {remote_url}")
+        console_print("Expected patterns: apache/airflow")
+        if not confirm_action("Do you want to continue anyway? This is NOT 
recommended for releases."):
+            exit(1)
+    console_print(f"[success]Remote '{remote_name}' correctly tracks 
apache/airflow")
+
+
+def validate_git_status():
+    """Validate that git working directory is clean."""
+    console_print("[info]Validating git status...")
+
+    # Check if working directory is clean
+    result = run_command(
+        ["git", "status", "--porcelain"],
+        check=True,
+        capture_output=True,
+        text=True,
+    )
+
+    if result.stdout.strip():
+        console_print("[error]Working directory is not clean!")
+        run_command(["git", "status"])
+        if not confirm_action("Do you want to continue with uncommitted 
changes? This is NOT recommended."):
+            exit(1)
+    console_print("[success]Working directory is clean")
+
+
+def validate_version_branches_exist(version_branch, remote_name):
+    """Validate that the required version branches exist."""
+    console_print(f"[info]Validating version branches exist for 
{version_branch}...")
+
+    # Check if test branch exists
+    test_branch = f"v{version_branch}-test"
+    stable_branch = f"v{version_branch}-stable"
+
+    # Fetch to get latest remote branches
+    run_command(["git", "fetch", remote_name], check=True)
+
+    # Check remote branches
+    result = run_command(
+        ["git", "branch", "-r"],
+        check=True,
+        capture_output=True,
+        text=True,
+        dry_run_override=False,
+    )
+    remote_branches = result.stdout
+
+    test_branch_exists = f"{remote_name}/{test_branch}" in remote_branches
+    stable_branch_exists = f"{remote_name}/{stable_branch}" in remote_branches
+
+    if not test_branch_exists:
+        console_print(f"[error]Test branch '{remote_name}/{test_branch}' does 
not exist!")
+        console_print("Available remote branches:")
+        run_command(["git", "branch", "-r"])
+        exit(1)
+    console_print(f"[success]Test branch '{remote_name}/{test_branch}' exists")
+
+    if not stable_branch_exists:
+        console_print(f"[error]Stable branch '{remote_name}/{stable_branch}' 
does not exist!")
+        console_print("Available remote branches:")
+        run_command(["git", "branch", "-r"])
+        exit(1)
+    console_print(f"[success]Stable branch '{remote_name}/{stable_branch}' 
exists")
+
+
+def validate_tag_does_not_exist(version, remote_name):
+    """Validate that the release tag doesn't already exist."""
+    console_print(f"[info]Checking if tag '{version}' already exists...")
+
+    # Check if tag exists locally
+    local_result = run_command(
+        ["git", "tag", "-l", version],
+        check=True,
+        capture_output=True,
+        text=True,
+    )
+
+    # Check if tag exists on remote using ls-remote with --exit-code
+    remote_result = run_command(
+        ["git", "ls-remote", "--exit-code", "--tags", remote_name, 
f"refs/tags/{version}"],
+        check=False,
+    )
+
+    tag_exists_locally = bool(local_result.stdout.strip())
+    tag_exists_remotely = remote_result.returncode == 0
+
+    if not tag_exists_locally and not tag_exists_remotely:
+        console_print(f"[success]Tag '{version}' does not exist yet")
+        return
+
+    location = []
+    if tag_exists_locally:
+        location.append("locally")
+    if tag_exists_remotely:
+        location.append("remotely")
+
+    console_print(f"[error]Tag '{version}' already exists {' and 
'.join(location)}!")
+
+    if tag_exists_locally:
+        console_print(f"Use 'git tag -d {version}' to delete it locally if 
needed")
+    if tag_exists_remotely:
+        console_print(f"Use 'git push {remote_name} --delete {version}' to 
delete it remotely if needed")
+
+    if not confirm_action("Do you want to continue anyway? This may cause 
issues."):
+        exit(1)
+
+
+def validate_on_correct_branch_for_tagging(version_branch):
+    """Validate that we're on the correct branch for tagging (stable 
branch)."""
+    console_print("[info]Validating we're on the correct branch for 
tagging...")
+
+    expected_branch = f"v{version_branch}-stable"
+
+    # Check current branch
+    result = run_command(
+        ["git", "branch", "--show-current"],
+        check=True,
+        capture_output=True,
+        text=True,
+        dry_run_override=False,
+    )
+    current_branch = result.stdout.strip()
+
+    if current_branch != expected_branch:
+        console_print(f"[error]Currently on branch '{current_branch}', 
expected '{expected_branch}'!")
+        console_print("Tags should be created on the stable branch after 
merging the sync PR.")
+        console_print("Make sure the PR merge step completed successfully.")
+        exit(1)
+    console_print(f"[success]On correct branch '{expected_branch}' for 
tagging")
+
+
+def merge_pr(version_branch, remote_name):
     if confirm_action("Do you want to merge the Sync PR?"):
         run_command(
             [
@@ -43,7 +209,7 @@ def merge_pr(version_branch):
             check=True,
         )
         run_command(
-            ["git", "reset", "--hard", f"origin/v{version_branch}-stable"],
+            ["git", "reset", "--hard", 
f"{remote_name}/v{version_branch}-stable"],
             check=True,
         )
         run_command(
@@ -52,18 +218,18 @@ def merge_pr(version_branch):
         )
         if confirm_action("Do you want to push the changes? Pushing the 
changes closes the PR"):
             run_command(
-                ["git", "push", "origin", f"v{version_branch}-stable"],
+                ["git", "push", remote_name, f"v{version_branch}-stable"],
                 check=True,
             )
 
 
-def git_tag(version):
+def git_tag(version, message):
     if confirm_action(f"Tag {version}?"):
         run_command(
-            ["git", "tag", "-s", f"{version}", "-m", f"Apache Airflow 
{version}"],
+            ["git", "tag", "-s", f"{version}", "-m", message],
             check=True,
         )
-        console_print("[success]Tagged")
+        console_print(f"[success]Tagged {version}!")
 
 
 def git_clean():
@@ -116,12 +282,27 @@ def create_artifacts_with_hatch(source_date_epoch: int):
     AIRFLOW_DIST_PATH.mkdir(exist_ok=True)
     env_copy = os.environ.copy()
     env_copy["SOURCE_DATE_EPOCH"] = str(source_date_epoch)
+    # Build Airflow packages
     run_command(
         ["hatch", "build", "-c", "-t", "custom", "-t", "sdist", "-t", 
"wheel"], check=True, env=env_copy
     )
-    console_print("[success]Successfully prepared Airflow packages:")
+    # Build Task SDK packages
+    run_command(
+        [
+            "breeze",
+            "release-management",
+            "prepare-task-sdk-distributions",
+            "--distribution-format",
+            "both",
+            "--use-local-hatch",
+        ],
+        check=True,
+    )
+    console_print("[success]Successfully prepared Airflow and Task SDK 
packages:")
     for file in sorted(AIRFLOW_DIST_PATH.glob("apache_airflow*")):
         console_print(print(file.name))
+    for file in sorted(AIRFLOW_DIST_PATH.glob("*task*")):
+        console_print(print(file.name))
     console_print()
 
 
@@ -137,7 +318,17 @@ def create_artifacts_with_docker():
         ],
         check=True,
     )
-    console_print("[success]Artifacts created")
+    run_command(
+        [
+            "breeze",
+            "release-management",
+            "prepare-task-sdk-distributions",
+            "--distribution-format",
+            "both",
+        ],
+        check=True,
+    )
+    console_print("[success]Airflow and Task SDK artifacts created")
 
 
 def sign_the_release(repo_root):
@@ -147,10 +338,10 @@ def sign_the_release(repo_root):
         console_print("[success]Release signed")
 
 
-def tag_and_push_constraints(version, version_branch):
+def tag_and_push_constraints(version, version_branch, remote_name):
     if confirm_action("Do you want to tag and push constraints?"):
         run_command(
-            ["git", "checkout", f"origin/constraints-{version_branch}"],
+            ["git", "checkout", f"{remote_name}/constraints-{version_branch}"],
             check=True,
         )
         run_command(
@@ -165,7 +356,7 @@ def tag_and_push_constraints(version, version_branch):
             check=True,
         )
         run_command(
-            ["git", "push", "origin", "tag", f"constraints-{version}"],
+            ["git", "push", remote_name, "tag", f"constraints-{version}"],
             check=True,
         )
         console_print("[success]Constraints tagged and pushed")
@@ -187,13 +378,18 @@ def clone_asf_repo(version, repo_root):
         console_print("[success]Cloned ASF repo successfully")
 
 
-def move_artifacts_to_svn(version, repo_root):
+def move_artifacts_to_svn(version, task_sdk_version, repo_root):
     if confirm_action("Do you want to move artifacts to SVN?"):
         os.chdir(f"{repo_root}/asf-dist/dev/airflow")
         run_command(["svn", "mkdir", f"{version}"], check=True)
-        run_command(f"mv {repo_root}/dist/* {version}/", check=True, 
shell=True)
+        run_command(f"mv {repo_root}/dist/*{version}* {version}/", check=True, 
shell=True)
+        run_command(
+            f"mv {repo_root}/dist/*{task_sdk_version}* 
task-sdk/{task_sdk_version}/", check=True, shell=True
+        )
         console_print("[success]Moved artifacts to SVN:")
         run_command(["ls"])
+        run_command([f"ls {version}"])
+        run_command([f"ls task-sdk/{task_sdk_version}"])
 
 
 def push_artifacts_to_asf_repo(version, repo_root):
@@ -237,6 +433,19 @@ def prepare_pypi_packages(version, version_suffix, 
repo_root):
             ],
             check=True,
         )
+        # Task SDK
+        run_command(
+            [
+                "breeze",
+                "release-management",
+                "prepare-task-sdk-distributions",
+                "--version-suffix",
+                f"{version_suffix}",
+                "--distribution-format",
+                "both",
+            ],
+            check=True,
+        )
         files_to_check = []
         for files in Path(AIRFLOW_DIST_PATH).glob("apache_airflow*"):
             if "-sources" not in files.name:
@@ -273,8 +482,8 @@ def push_packages_to_pypi(version):
         )
 
 
-def push_release_candidate_tag_to_github(version):
-    if confirm_action("Do you want to push release candidate tag to GitHub?"):
+def push_release_candidate_tag_to_github(version, remote_name):
+    if confirm_action("Do you want to push release candidate tags to GitHub?"):
         console_print(
             """
         This step should only be done now and not before, because it triggers 
an automated
@@ -283,7 +492,7 @@ def push_release_candidate_tag_to_github(version):
         """
         )
         confirm_action(f"Confirm that {version} is pushed to PyPI(not PyPI 
test). Is it pushed?", abort=True)
-        run_command(["git", "push", "origin", "tag", f"{version}"], check=True)
+        run_command(["git", "push", remote_name, "tag", f"{version}"], 
check=True)
         console_print("[success]Release candidate tag pushed to GitHub")
 
 
@@ -345,13 +554,15 @@ def prepare_airflow_tarball(version: str):
 )
 @click.option("--version", required=True, help="The release candidate version 
e.g. 2.4.3rc1")
 @click.option("--previous-version", required=True, help="Previous version 
released e.g. 2.4.2")
+@click.option("--task-sdk-version", required=True, help="The task SDK version 
e.g. 1.0.6rc1.")
 @click.option(
     "--github-token", help="GitHub token to use in generating issue for 
testing of release candidate"
 )
+@click.option("--remote-name", default="origin", help="Git remote name to push 
to (default: origin)")
 @option_answer
 @option_dry_run
 @option_verbose
-def publish_release_candidate(version, previous_version, github_token):
+def publish_release_candidate(version, previous_version, task_sdk_version, 
github_token, remote_name):
     from packaging.version import Version
 
     airflow_version = Version(version)
@@ -368,29 +579,43 @@ def publish_release_candidate(version, previous_version, 
github_token):
     version_suffix = airflow_version.pre[0] + str(airflow_version.pre[1])
     version_branch = str(airflow_version.release[0]) + "-" + 
str(airflow_version.release[1])
     version_without_rc = airflow_version.base_version
+
+    task_sdk_version_obj = Version(task_sdk_version)
+    task_sdk_version_without_rc = task_sdk_version_obj.base_version
+
     os.chdir(AIRFLOW_ROOT_PATH)
     airflow_repo_root = os.getcwd()
 
+    validate_remote_tracks_apache_airflow(remote_name)
+    validate_git_status()
+    validate_version_branches_exist(version_branch, remote_name)
+    validate_tag_does_not_exist(version, remote_name)
+    validate_tag_does_not_exist(f"task-sdk/{task_sdk_version}", remote_name)
+
     # List the above variables and ask for confirmation
     console_print()
     console_print(f"Previous version: {previous_version}")
-    console_print(f"version: {version}")
+    console_print(f"Airflow version: {version}")
+    console_print(f"Task SDK version: {task_sdk_version}")
     console_print(f"version_suffix: {version_suffix}")
     console_print(f"version_branch: {version_branch}")
     console_print(f"version_without_rc: {version_without_rc}")
+    console_print(f"task_sdk_version_without_rc: 
{task_sdk_version_without_rc}")
     console_print(f"airflow_repo_root: {airflow_repo_root}")
+    console_print(f"remote_name: {remote_name}")
     console_print()
-    console_print("Below are your git remotes. We will push to origin:")
+    console_print(f"Below are your git remotes. We will push to 
{remote_name}:")
     run_command(["git", "remote", "-v"])
     console_print()
     confirm_action("Verify that the above information is correct. Do you want 
to continue?", abort=True)
-    # Final confirmation
-    confirm_action("Pushes will be made to origin. Do you want to continue?", 
abort=True)
     # Merge the sync PR
-    merge_pr(version_branch)
+    merge_pr(version_branch, remote_name)
     #
     # # Tag & clean the repo
-    git_tag(version)
+    # Validate we're on the correct branch before tagging
+    validate_on_correct_branch_for_tagging(version_branch)
+    git_tag(version, f"Apache Airflow {version}")
+    git_tag(f"task-sdk/{task_sdk_version}", f"Airflow Task SDK 
{task_sdk_version}")
     git_clean()
     source_date_epoch = get_source_date_epoch(AIRFLOW_ROOT_PATH)
     shutil.rmtree(AIRFLOW_DIST_PATH, ignore_errors=True)
@@ -407,11 +632,11 @@ def publish_release_candidate(version, previous_version, 
github_token):
     # Sign the release
     sign_the_release(airflow_repo_root)
     # Tag and push constraints
-    tag_and_push_constraints(version, version_branch)
+    tag_and_push_constraints(version, version_branch, remote_name)
     # Clone the asf repo
     clone_asf_repo(version, airflow_repo_root)
     # Move artifacts to SVN
-    move_artifacts_to_svn(version, airflow_repo_root)
+    move_artifacts_to_svn(version, task_sdk_version, airflow_repo_root)
     # Push the artifacts to the asf repo
     push_artifacts_to_asf_repo(version, airflow_repo_root)
 
@@ -428,7 +653,8 @@ def publish_release_candidate(version, previous_version, 
github_token):
     push_packages_to_pypi(version)
 
     # Push the release candidate tag to gitHub
-    push_release_candidate_tag_to_github(version)
+    push_release_candidate_tag_to_github(version, remote_name)
+    push_release_candidate_tag_to_github(f"task-sdk/{task_sdk_version}", 
remote_name)
     # Create issue for testing
     os.chdir(airflow_repo_root)
 
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 7dee7bca4db..09eae51533c 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
@@ -421,7 +421,9 @@ RELEASE_MANAGEMENT_PARAMETERS: dict[str, list[dict[str, str 
| list[str]]]] = {
             "options": [
                 "--version",
                 "--previous-version",
+                "--task-sdk-version",
                 "--github-token",
+                "--remote-name",
             ],
         }
     ],


Reply via email to