HyukjinKwon commented on code in PR #84:
URL: https://github.com/apache/spark-connect-rust/pull/84#discussion_r3885994150


##########
.github/workflows/release.yml:
##########
@@ -57,118 +92,107 @@ concurrency:
   group: release
   cancel-in-progress: false
 
+env:
+  # The release source is always fetched from this canonical apache repository.
+  UPSTREAM_REPO: apache/spark-connect-rust
+
 jobs:
-  validate-version:
-    name: Validate version
+  resolve:
+    name: Resolve release mode
     runs-on: ubuntu-latest
+    # In the canonical apache repo, only dry runs are allowed (release-version 
and
+    # rc-count empty). A fork may perform real runs. This mirrors apache/spark.
+    if: >-
+      github.repository != 'apache/spark-connect-rust' ||
+      (inputs.release-version == '' && inputs.rc-count == '')
+    outputs:
+      mode: ${{ steps.r.outputs.mode }}
+      version: ${{ steps.r.outputs.version }}
+      rc_tag: ${{ steps.r.outputs.rc_tag }}
+      branch: ${{ steps.r.outputs.branch }}
     steps:
-      - uses: actions/checkout@v4
-      - name: Cargo and pyproject versions agree (and match the tag, if any)
+      - name: Determine mode and validate inputs
+        id: r
+        env:
+          IN_BRANCH: ${{ inputs.branch }}
+          IN_VERSION: ${{ inputs.release-version }}
+          IN_RC: ${{ inputs.rc-count }}
+          IN_FINALIZE: ${{ inputs.finalize }}
         run: |
           set -euo pipefail
-          CARGO_VER=$(grep -m1 '^version' Cargo.toml | awk -F'"' '{print $2}')
-          PYPROJECT_VER=$(grep -m1 '^version' pyproject.toml | awk -F'"' 
'{print $2}')
-          echo "Cargo.toml=${CARGO_VER}  pyproject.toml=${PYPROJECT_VER}"
-          if [[ "${CARGO_VER}" != "${PYPROJECT_VER}" ]]; then
-            echo "::error::Cargo.toml (${CARGO_VER}) and pyproject.toml 
(${PYPROJECT_VER}) versions differ"
-            exit 1
-          fi
-          # Internal path-dependency version pins (e.g. spark-connect-core -> 
proto)
-          # must track the workspace version, or `cargo publish` embeds a stale
-          # requirement into the published crate.
-          BAD=$(grep -rEn 'path = "\.\./spark-connect[^"]*"' 
crates/*/Cargo.toml \
-                | grep -E 'version = "' \
-                | grep -v "version = \"${CARGO_VER}\"" || true)
-          if [[ -n "${BAD}" ]]; then
-            echo "::error::internal path-dep version pin(s) do not match 
${CARGO_VER}:"
-            echo "${BAD}"
+          BRANCH="${IN_BRANCH:-master}"
+          RV="${IN_VERSION:-}"
+          RC="${IN_RC:-}"
+          FIN="${IN_FINALIZE:-false}"
+
+          # release-version and rc-count must be provided together (or 
neither).
+          if { [ -n "$RV" ] && [ -z "$RC" ]; } || { [ -z "$RV" ] && [ -n "$RC" 
]; }; then
+            echo "::error::Provide BOTH 'release-version' and 'rc-count', or 
leave BOTH empty for a dry run."
             exit 1
           fi
-          if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
-            TAG="${GITHUB_REF##*/}"
-            if [[ "${TAG}" != "v${CARGO_VER}" ]]; then
-              echo "::error::Pushed tag ${TAG} does not match version 
v${CARGO_VER}"
+
+          if [ "$FIN" = "true" ]; then
+            if [ -z "$RV" ] || [ -z "$RC" ]; then
+              echo "::error::finalize=true requires 'release-version' and 
'rc-count' naming the RC to promote."
               exit 1
             fi
+            MODE=finalize
+          elif [ -n "$RV" ]; then
+            MODE=rc
+          else
+            MODE=dryrun
           fi
 
-  publish-crates:
-    name: Publish Rust crates to crates.io
-    needs: validate-version
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      - name: Install Rust toolchain
-        run: |
-          rustup toolchain install stable --profile minimal
-          rustup default stable
-      - name: Install protoc
-        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
-      - name: cargo publish (dependency-ordered; dry-run unless releasing)
-        env:
-          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
-          # true only for an explicit dry-run dispatch; tag pushes and an 
explicit
-          # dry_run=false dispatch both evaluate to "false" and publish for 
real.
-          DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && 
inputs.dry_run }}
-        run: |
-          set -euo pipefail
-          # Leaf-first; cargo (>=1.90) orders and verify-builds these together,
-          # resolving siblings from the local workspace.
-          CRATES=(apache-spark-connect-proto apache-spark-connect-core 
apache-spark-connect)
-          VER=$(grep -m1 '^version' Cargo.toml | awk -F'"' '{print $2}')
+          RC_TAG=""
+          [ -n "$RV" ] && RC_TAG="v${RV}-rc${RC}"
 
-          if [[ "${DRY_RUN}" == "true" ]]; then
-            # Rehearse the real publish. Because siblings resolve from the 
local
-            # workspace, this works even before anything exists on crates.io.
-            pkgs=(); for c in "${CRATES[@]}"; do pkgs+=(-p "$c"); done
-            echo "::group::cargo publish --dry-run ${pkgs[*]}"
-            cargo publish --dry-run "${pkgs[@]}"
-            echo "::endgroup::"
-            echo "Dry run only — nothing published."
-            exit 0
-          fi
+          {
+            echo "mode=$MODE"
+            echo "version=$RV"
+            echo "rc_tag=$RC_TAG"
+            echo "branch=$BRANCH"
+          } >> "$GITHUB_OUTPUT"
+          echo "Resolved: mode=$MODE version='${RV}' rc='${RC}' 
tag='${RC_TAG}' branch='${BRANCH}'"
 
-          # Real publish. Skip any crate whose current version is already on
-          # crates.io so a re-run after a partial failure is idempotent.
-          pkgs=()
-          for c in "${CRATES[@]}"; do
-            if curl -sf -H "User-Agent: spark-connect-rust-release 
([email protected])" \
-                 "https://crates.io/api/v1/crates/${c}/${VER}"; >/dev/null; then
-              echo "${c} ${VER} is already on crates.io — skipping"
-            else
-              pkgs+=(-p "$c")
-            fi
-          done
-          if [[ ${#pkgs[@]} -eq 0 ]]; then
-            echo "All crates already published at ${VER}; nothing to do."
-            exit 0
+      - name: Check out release source (for the version assertion)
+        if: steps.r.outputs.mode == 'rc'
+        uses: actions/checkout@v4
+        with:
+          repository: apache/spark-connect-rust
+          ref: ${{ steps.r.outputs.branch }}
+
+      - name: Assert the branch version equals release-version
+        if: steps.r.outputs.mode == 'rc'
+        run: |
+          set -euo pipefail
+          V="${{ steps.r.outputs.version }}"
+          CARGO_VER=$(grep -m1 '^version' Cargo.toml | awk -F'"' '{print $2}')
+          PYPROJECT_VER=$(grep -m1 '^version' pyproject.toml | awk -F'"' 
'{print $2}')
+          echo "branch '${{ steps.r.outputs.branch }}': Cargo=${CARGO_VER} 
pyproject=${PYPROJECT_VER}; release-version=${V}"
+          if [ "$CARGO_VER" != "$V" ] || [ "$PYPROJECT_VER" != "$V" ]; then

Review Comment:
   Fixed in 6026a95. Re-added the path-dep pin check (`grep 'path = 
"../spark-connect…"' | grep -E 'version = "' | grep -v "version = 
\"$CARGO_VER\""`) to both the `resolve` **Validate versions** step and the 
finalize **Validate the RC source versions** step. Verified against the real 
crate files: it passes when pins match, and catches a simulated stale 
`apache-spark-connect-proto = 4.1.0` pin (step exits 1).



##########
.github/workflows/release.yml:
##########
@@ -57,118 +92,107 @@ concurrency:
   group: release
   cancel-in-progress: false
 
+env:
+  # The release source is always fetched from this canonical apache repository.
+  UPSTREAM_REPO: apache/spark-connect-rust
+
 jobs:
-  validate-version:
-    name: Validate version
+  resolve:
+    name: Resolve release mode
     runs-on: ubuntu-latest
+    # In the canonical apache repo, only dry runs are allowed (release-version 
and
+    # rc-count empty). A fork may perform real runs. This mirrors apache/spark.
+    if: >-
+      github.repository != 'apache/spark-connect-rust' ||
+      (inputs.release-version == '' && inputs.rc-count == '')
+    outputs:
+      mode: ${{ steps.r.outputs.mode }}
+      version: ${{ steps.r.outputs.version }}
+      rc_tag: ${{ steps.r.outputs.rc_tag }}
+      branch: ${{ steps.r.outputs.branch }}

Review Comment:
   Fixed in 6026a95. `resolve` now resolves the branch to a single commit with 
`git ls-remote` and outputs `sha`; `build-wheels`, `build-sdist`, 
`dryrun-crates`, and `publish-rc` all check out that SHA (`ref: ${{ 
needs.resolve.outputs.sha }}`), so the wheels, sdist, and RC tag can't come 
from different commits. `finalize` still checks out the RC tag, which is 
already immutable.



##########
.github/workflows/release.yml:
##########
@@ -233,28 +257,197 @@ jobs:
           name: wheels-sdist
           path: dist/*.tar.gz
 
-  publish-wheels:
-    name: Publish wheel + sdist to PyPI
-    needs: [build-wheels, build-sdist]
-    if: github.event_name == 'push' || (github.event_name == 
'workflow_dispatch' && inputs.dry_run == false)
+  # ---- Dry run: rehearse the crates.io packaging without publishing ----
+  dryrun-crates:
+    name: Dry-run crates.io packaging
+    needs: resolve
+    if: needs.resolve.outputs.mode == 'dryrun'

Review Comment:
   Fixed in 6026a95. The Cargo↔pyproject agreement and path-dep pin checks now 
run for **both** `dryrun` and `rc` (the **Validate versions** step is gated 
`mode == 'dryrun' || mode == 'rc'`); only the `release-version` equality check 
stays rc-only. So the scheduled dry run now fails on a 
`Cargo.toml`/`pyproject.toml` mismatch.



##########
.github/workflows/release.yml:
##########
@@ -17,36 +17,71 @@
 # under the License.
 #
 
-# Release workflow for the Rust crates (crates.io) and the Python wheel (PyPI).
+# Release workflow for pyspark-client-rust (the Rust crates on crates.io + the
+# Python wheel on PyPI). Modeled on Apache Spark's release workflow: it is
+# DISPATCHED FROM A COMMITTER'S FORK (which holds the publish tokens), takes 
the
+# same inputs, and follows the same release-candidate-then-finalize flow.
 #
-# Following the Apache Spark model, releases are cut by a committer from THEIR
-# OWN FORK, not from the canonical apache/ repository:
+# Inputs (identical to apache/spark's release workflow):
+#   branch          apache/spark-connect-rust branch the release source is 
fetched
+#                   from (always from apache, never the fork's own copy). 
Default
+#                   master. Unused by finalize, which uses the RC tag as its 
source.
+#   release-version Final version, e.g. 4.2.0. Leave empty (with rc-count) for 
a dry
+#                   run. Must equal the Cargo.toml/pyproject.toml version on 
`branch`
+#                   (asserted) -- the RC number never appears in the artifact 
version.
+#   rc-count        RC number, e.g. 1. Leave empty (with release-version) for 
a dry run.
+#   finalize        true => convert the named RC into the OFFICIAL release
+#                   (IRREVERSIBLE). Default false.
 #
-#   1. Enable GitHub Actions on your fork.
-#   2. Add the release secrets to the fork:
-#        - CARGO_REGISTRY_TOKEN  (crates.io API token)
-#        - PYPI_API_TOKEN        (PyPI API token)
-#   3. Bump the version in Cargo.toml and pyproject.toml (they must match).
-#   4. Dispatch this workflow with dry_run=true to rehearse (default), then 
push
-#      a matching `v<version>` tag (or dispatch with dry_run=false) to publish.
+# The three modes are derived from the inputs (see the `resolve` job):
 #
-# A dry run fully rehearses BOTH sides without publishing: it packages and
-# verify-builds every crate in dependency order (`cargo publish --dry-run`) and
-# builds every wheel and the sdist. A tag push (or an explicit dry_run=false
-# dispatch) additionally publishes to crates.io and PyPI. Both publish steps 
are
-# idempotent, so a re-run after a partial failure skips what already landed.
+#   * Dry run  (release-version AND rc-count empty): build every wheel + sdist 
and
+#     `cargo publish --dry-run`. Creates no tag, publishes nothing. This is the
+#     testable path and also runs on a schedule to keep the release path 
healthy.
+#
+#   * Cut an RC (release-version + rc-count, finalize=false): build the wheels 
+
+#     sdist -- versioned as the FINAL release-version, since the RC number 
lives
+#     only in the tag/release name, never in the artifact version -- then
+#     auto-create and push the tag `v<version>-rc<rc>` to this fork and attach 
the
+#     artifacts to a GitHub *pre-release* of that tag. crates.io/PyPI are NOT
+#     touched, so an RC stays fully deletable.
+#
+#   * Finalize (finalize=true, naming an existing RC): check out the RC tag,
+#     `twine upload` the EXACT wheels+sdist from that RC's GitHub pre-release 
to
+#     PyPI, `cargo publish` the identical source to crates.io, then delete 
every
+#     `v<version>-rc*` GitHub pre-release (and its tag) from this fork. GitHub
+#     Releases are only RC staging; the official release lives on PyPI + 
crates.io.
+#
+# Required fork secrets for a real run: CARGO_REGISTRY_TOKEN (crates.io) and
+# PYPI_API_TOKEN (PyPI). Without them, only dry runs work. The default 
GITHUB_TOKEN
+# creates/deletes the RC tags and GitHub Releases on the fork.
+#
+# To drop an RC without finalizing: delete its GitHub pre-release and the
+# `v<version>-rc<n>` tag from the fork (nothing was published to 
PyPI/crates.io).
 
 name: Release
 
 on:
+  schedule:
+    # Periodic dry run (runs only in the repo hosting the workflow; GitHub 
does not
+    # run scheduled workflows in forks), to catch release-path breakage early.
+    - cron: '0 7 */2 * *'
   workflow_dispatch:
     inputs:
-      dry_run:
-        description: "Build and validate artifacts but do not publish"
-        type: boolean
-        default: true
-  push:
-    tags: ["v*"]
+      branch:
+        description: 'Branch to release. Leave release-version/rc-count empty 
to launch a dryrun. Dispatch this workflow only in the forked repository.'
+        required: true
+        default: master
+      release-version:
+        description: 'Release version (e.g. 4.2.0). Leave it empty to launch a 
dryrun.'
+        required: false
+      rc-count:
+        description: 'RC number (e.g. 1). Leave it empty to launch a dryrun.'
+        required: false
+      finalize:

Review Comment:
   Fixed in 6026a95. Added `type: boolean` to the `finalize` input, so it 
renders as a checkbox and only a real `true` finalizes — a typo like 
`yes`/`True` can no longer silently fall through to an RC re-cut.



##########
.github/workflows/release.yml:
##########
@@ -233,28 +257,197 @@ jobs:
           name: wheels-sdist
           path: dist/*.tar.gz
 
-  publish-wheels:
-    name: Publish wheel + sdist to PyPI
-    needs: [build-wheels, build-sdist]
-    if: github.event_name == 'push' || (github.event_name == 
'workflow_dispatch' && inputs.dry_run == false)
+  # ---- Dry run: rehearse the crates.io packaging without publishing ----
+  dryrun-crates:
+    name: Dry-run crates.io packaging
+    needs: resolve
+    if: needs.resolve.outputs.mode == 'dryrun'
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/setup-python@v5
+      - uses: actions/checkout@v4
         with:
-          python-version: "3.11"
+          repository: apache/spark-connect-rust
+          ref: ${{ needs.resolve.outputs.branch }}
+      - name: Install Rust toolchain
+        run: |
+          rustup toolchain install stable --profile minimal
+          rustup default stable
+      - name: Install protoc
+        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
+      - name: cargo publish --dry-run (dependency-ordered)
+        run: |
+          set -euo pipefail
+          cargo publish --dry-run \
+            -p apache-spark-connect-proto \
+            -p apache-spark-connect-core \
+            -p apache-spark-connect
+
+  # ---- Cut an RC: tag + GitHub pre-release with the artifacts (no 
crates.io/PyPI) ----
+  publish-rc:
+    name: Cut RC GitHub pre-release
+    needs: [resolve, build-wheels, build-sdist]
+    if: needs.resolve.outputs.mode == 'rc'
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write  # push the RC tag + create the GitHub Release on the 
fork
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          repository: apache/spark-connect-rust
+          ref: ${{ needs.resolve.outputs.branch }}
+          fetch-depth: 0  # full history so the tag push carries all reachable 
objects
       - uses: actions/download-artifact@v4
         with:
-          # Matches every wheels-<target> artifact plus wheels-sdist.
           pattern: wheels-*
           path: dist
           merge-multiple: true
-      - name: Publish to PyPI
-        # Fork-based release: authenticate with the committer's PyPI token via 
twine
-        # (avoids third-party publish actions; ASF Actions policy allowlists 
actions/*).
+      - name: Create and push the RC tag to this fork
+        env:
+          GH_TOKEN: ${{ github.token }}
+          TAG: ${{ needs.resolve.outputs.rc_tag }}
+          VERSION: ${{ needs.resolve.outputs.version }}
+        run: |
+          set -euo pipefail
+          git config user.name "${{ github.actor }}"
+          git config user.email "${{ github.actor }}@users.noreply.github.com"
+          if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
+            echo "Local tag ${TAG} already exists; reusing."
+          else
+            git tag -a "${TAG}" -m "Release candidate ${TAG} of 
pyspark-client-rust ${VERSION}"
+          fi
+          # Push the RC tag (and the objects it reaches) to THIS fork, so the 
GitHub
+          # pre-release below can hang off it. Force so re-cutting the same RC 
is idempotent.
+          git push --force 
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"; 
"refs/tags/${TAG}"
+      - name: Create the GitHub pre-release with the artifacts
+        env:
+          GH_TOKEN: ${{ github.token }}
+          TAG: ${{ needs.resolve.outputs.rc_tag }}
+          VERSION: ${{ needs.resolve.outputs.version }}
+        run: |
+          set -euo pipefail
+          ls -l dist
+          NOTES="Release candidate ${TAG} of pyspark-client-rust 
${VERSION}."$'\n\n'"> Not published to PyPI or crates.io. Install directly from 
a wheel/sdist asset below, e.g. \`pip install <asset-url>\`. This pre-release 
is deleted when the RC is finalized (or dropped)."
+          # Idempotent: a re-run updates the existing release and clobbers its 
assets.
+          if gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 
2>&1; then
+            gh release upload "${TAG}" dist/* --repo "${GITHUB_REPOSITORY}" 
--clobber
+          else
+            gh release create "${TAG}" dist/* --repo "${GITHUB_REPOSITORY}" \
+              --prerelease --title "${TAG}" --notes "${NOTES}"
+          fi
+
+  # ---- Finalize: promote the named RC into the official release 
(IRREVERSIBLE) ----
+  finalize:
+    name: Finalize release to PyPI + crates.io (IRREVERSIBLE)
+    needs: resolve
+    if: needs.resolve.outputs.mode == 'finalize'
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write  # delete the RC pre-releases + tags on the fork
+    steps:
+      - name: Abort window
+        run: |
+          echo 
"=============================================================================="
+          echo " CONVERTING RC ${{ needs.resolve.outputs.rc_tag }} INTO THE 
OFFICIAL RELEASE"
+          echo " ${{ needs.resolve.outputs.version }} -> PyPI + crates.io. 
THIS IS IRREVERSIBLE."
+          echo " Cancel this workflow now if you did not intend to finalize."
+          echo " Continuing in 60 seconds..."
+          echo 
"=============================================================================="
+          sleep 60
+      - name: Verify the RC pre-release exists
+        env:
+          GH_TOKEN: ${{ github.token }}
+          TAG: ${{ needs.resolve.outputs.rc_tag }}
+        run: |
+          set -euo pipefail
+          if ! gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" 
>/dev/null 2>&1; then
+            echo "::error::RC GitHub pre-release ${TAG} not found on 
${GITHUB_REPOSITORY}. Cut the RC first."
+            exit 1
+          fi
+      - name: Check out the RC tag (the exact released source)
+        uses: actions/checkout@v4
+        with:
+          # The RC tag lives on the fork where the RC was cut (this 
repository).
+          repository: ${{ github.repository }}
+          ref: ${{ needs.resolve.outputs.rc_tag }}
+          fetch-depth: 0
+      - name: Assert the RC source version equals release-version
+        run: |
+          set -euo pipefail
+          V="${{ needs.resolve.outputs.version }}"
+          CARGO_VER=$(grep -m1 '^version' Cargo.toml | awk -F'"' '{print $2}')
+          PYPROJECT_VER=$(grep -m1 '^version' pyproject.toml | awk -F'"' 
'{print $2}')
+          echo "RC tag source: Cargo=${CARGO_VER} pyproject=${PYPROJECT_VER}; 
release-version=${V}"
+          if [ "$CARGO_VER" != "$V" ] || [ "$PYPROJECT_VER" != "$V" ]; then
+            echo "::error::RC tag ${{ needs.resolve.outputs.rc_tag }} declares 
Cargo=${CARGO_VER} / pyproject=${PYPROJECT_VER}, not ${V}."
+            exit 1
+          fi
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.11"
+      - name: Download the RC artifacts (the exact tested wheels + sdist)
+        env:
+          GH_TOKEN: ${{ github.token }}
+          TAG: ${{ needs.resolve.outputs.rc_tag }}
+        run: |
+          set -euo pipefail
+          mkdir -p dist
+          gh release download "${TAG}" --repo "${GITHUB_REPOSITORY}" --dir 
dist --pattern '*'
+          ls -l dist
+      - name: Publish wheels + sdist to PyPI
         env:
           TWINE_USERNAME: __token__
           TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
         run: |
+          set -euo pipefail
           python -m pip install --upgrade twine
           # --skip-existing makes a re-run after a partial upload safe.
           twine upload --skip-existing dist/*
+      - name: Install Rust toolchain
+        run: |
+          rustup toolchain install stable --profile minimal
+          rustup default stable
+      - name: Install protoc
+        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
+      - name: Publish crates to crates.io (from the identical RC source)
+        env:
+          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+        run: |
+          set -euo pipefail
+          CRATES=(apache-spark-connect-proto apache-spark-connect-core 
apache-spark-connect)
+          VER="${{ needs.resolve.outputs.version }}"
+          # Skip any crate already on crates.io at this version so a re-run is 
idempotent.
+          pkgs=()
+          for c in "${CRATES[@]}"; do
+            if curl -sf -H "User-Agent: spark-connect-rust-release 
([email protected])" \
+                 "https://crates.io/api/v1/crates/${c}/${VER}"; >/dev/null; then
+              echo "${c} ${VER} is already on crates.io — skipping"
+            else
+              pkgs+=(-p "$c")
+            fi
+          done
+          if [ ${#pkgs[@]} -eq 0 ]; then
+            echo "All crates already published at ${VER}; nothing to do."
+          else
+            # cargo publishes in dependency order and waits for the index 
between crates.
+            cargo publish "${pkgs[@]}"
+          fi
+      - name: Remove all RC GitHub pre-releases + tags for this version
+        env:
+          GH_TOKEN: ${{ github.token }}
+          VERSION: ${{ needs.resolve.outputs.version }}
+        run: |
+          set -euo pipefail
+          # Every v<version>-rcN pre-release is RC staging; the official 
release now lives
+          # on PyPI + crates.io, so delete them all (and their tags) from this 
fork.
+          # Escape dots: grep -E treats '.' as any char, so an unescaped 4.2.0 
would also
+          # match tags like v4x2x0-rcN.
+          ESC_V="${VERSION//./\\.}"
+          mapfile -t RCS < <(gh release list --repo "${GITHUB_REPOSITORY}" 
--limit 200 \
+            --json tagName --jq '.[].tagName' | grep -E "^v${ESC_V}-rc[0-9]+$" 
|| true)

Review Comment:
   Fixed in 6026a95. Split into `ALL_TAGS=$(gh release list … --jq 
'.[].tagName')` (which fails loudly under `set -e`) and then `grep … || true` 
only around the match. Verified: a `gh` failure now aborts the step, while `|| 
true` still absorbs grep's no-match. (Also escaped the version dots so `4.2.0` 
doesn't also match `v4x2x0-rcN`.)



##########
.github/workflows/release.yml:
##########
@@ -57,118 +92,107 @@ concurrency:
   group: release
   cancel-in-progress: false
 
+env:
+  # The release source is always fetched from this canonical apache repository.
+  UPSTREAM_REPO: apache/spark-connect-rust

Review Comment:
   Fixed in 6026a95. `UPSTREAM_REPO` is now referenced by all five upstream 
checkouts and the `git ls-remote` in `resolve`. The one remaining literal is 
the `resolve` job-level `if:`, because the `env` context isn't available in a 
job-level `if:` — I added a comment there noting it must match `UPSTREAM_REPO`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to