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

szehon-ho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new a0ce466faaed [SPARK-59017][INFRA] Prevent merge script skipping or 
hanging at the Fix Version prompt when no version can be inferred
a0ce466faaed is described below

commit a0ce466faaed64663efee442a108d4abb748a9ee
Author: Szehon Ho <[email protected]>
AuthorDate: Wed Aug 26 13:33:54 2026 -0700

    [SPARK-59017][INFRA] Prevent merge script skipping or hanging at the Fix 
Version prompt when no version can be inferred
    
    ### What changes were proposed in this pull request?
    
    Two fixes in `dev/merge_spark_pr.py`, both on the path where Fix Version 
inference produces nothing:
    
    1. Blank input at the Fix Version prompt with no default now exits with a 
message instead of re-prompting forever.
    2. On an already-resolved JIRA, `already contains all inferred fix 
versions` is printed only when something was in fact inferred. Otherwise the 
committer reaches the prompt.
    
    ### Why are the changes needed?
    
    Take a backport PR opened directly against `branch-4.2`, merged at a moment 
when JIRA has no unreleased `4.2.x` version -- either the line is EOL, or 
`4.2.2` has not been created yet after `4.2.1` shipped. Inference finds nothing 
and warns the committer to enter a version manually. What follows is wrong in 
both directions.
    
    **The linked JIRA is already resolved by the earlier master merge**, which 
is the normal state for a backport. Before:
    
    ```
    JIRA issue SPARK-58880 already has status 'Resolved' (Fixed)
    Check if the JIRA information is as expected (y/N): y
    Target version for branch-4.2 is not found on JIRA, it may be archived or 
not created. Skipping it.
    JIRA issue SPARK-58880 already contains all inferred fix versions; no 
update needed.
    ```
    
    The ticket has `['5.0.0']` and nothing was inferred, so the last line is 
false and contradicts the warning above it. The prompt never appears, and the 
merge records no Fix Version. After:
    
    ```
    Target version for branch-4.2 is not found on JIRA, it may be archived or 
not created. Skipping it.
    JIRA issue SPARK-58880 has fix version(s) ['5.0.0']; no additional fix 
version could be inferred.
    Enter comma-separated additional fix version(s) []:
    No fix version entered; update SPARK-58880 manually.
    ```
    
    **The JIRA is not yet resolved.** The committer reaches the prompt with an 
empty default, and pressing Enter never escapes, because `""` can never match a 
known version -- even though the retry message offers exactly that. Before:
    
    ```
    Target version for branch-4.2 is not found on JIRA, it may be archived or 
not created. Skipping it.
    Enter comma-separated fix version(s) []:
    Specified version(s) [] not found in the available versions, try again (or 
leave blank and fix manually).
    Enter comma-separated fix version(s) []:
    Specified version(s) [] not found in the available versions, try again (or 
leave blank and fix manually).
    Enter comma-separated fix version(s) []:
    ^C
    ```
    
    Only Ctrl-C exits, and the merge to `branch-4.2` has already been pushed by 
then, since the JIRA step runs in a `finally` block. After:
    
    ```
    Target version for branch-4.2 is not found on JIRA, it may be archived or 
not created. Skipping it.
    Enter comma-separated fix version(s) []:
    No fix version entered; update SPARK-58880 manually.
    ```
    
    In both cases the committer still has to record the Fix Version by hand. 
The point of the change is that the script now says so, instead of claiming the 
ticket is already correct or trapping them in a loop.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. Committer-facing only.
    
    ### How was this patch tested?
    
    New doctests on two extracted helpers, `fix_version_additions` and 
`fix_versions_from_input`, which cover both failure modes directly. 
`fix_versions_from_input("", "")` returns `[]` where the previous inline 
expression produced `[""]` and looped, and `fix_version_additions` separates 
"nothing inferred" from "all inferred already present", which the old code 
could not distinguish:
    
    ```
    master  :  "".replace(" ","").split(",")             -> ['']         never 
a known version -> loops
    patched :  fix_versions_from_input("", "")            -> []
    
    master  :  additional_fix_versions([], ["5.0.0"])     -> []          
identical to the up-to-date case
    patched :  fix_version_additions([], ["5.0.0"])       -> ([], False)
    patched :  fix_version_additions(["5.0.0"], ["5.0.0"]) -> ([], True)
    ```
    
    Doctests go from 80 to 88, and they run on every invocation of the script 
since `__main__` calls `doctest.testmod()` before `main()`. I also drove 
`resolve_jira_issue` with a stubbed JIRA client to produce the transcripts 
above and to confirm that the cases where a version is inferable are unchanged, 
including the resolved-ticket path that proposes additions and asks for 
confirmation.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Cursor (Claude Opus 5)
    
    Closes #58300 from szehon-ho/SPARK-59017.
    
    Authored-by: Szehon Ho <[email protected]>
    Signed-off-by: Szehon Ho <[email protected]>
---
 dev/merge_spark_pr.py | 78 +++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 66 insertions(+), 12 deletions(-)

diff --git a/dev/merge_spark_pr.py b/dev/merge_spark_pr.py
index 366181a4b380..08441aa8cc08 100755
--- a/dev/merge_spark_pr.py
+++ b/dev/merge_spark_pr.py
@@ -350,6 +350,49 @@ def additional_fix_versions(inferred_versions, 
existing_versions):
     return [version for version in inferred_versions if version not in 
existing]
 
 
+def fix_version_additions(inferred_versions, existing_versions):
+    """Return (additions, all_inferred_present).
+
+    An empty ``additions`` list is ambiguous on its own: either the issue 
already carries
+    every inferred version, or nothing was inferred. Only the first means 
there is nothing
+    to do; the second still needs the committer prompted.
+
+    >>> fix_version_additions(["4.3.0"], ["5.0.0"])
+    (['4.3.0'], False)
+    >>> fix_version_additions(["5.0.0"], ["5.0.0"])
+    ([], True)
+    >>> fix_version_additions([], ["5.0.0"])
+    ([], False)
+    """
+    additions = additional_fix_versions(inferred_versions, existing_versions)
+    return additions, bool(inferred_versions) and not additions
+
+
+def fix_versions_from_input(raw_input, default_fix_versions):
+    """Resolve the Fix Version prompt's raw input into a list of version names.
+
+    Blank falls back to the inferred default. With no default to fall back on, 
blank means
+    skip: the empty list, not [""], which no known version can match.
+
+    >>> fix_versions_from_input("4.2.2", "")
+    ['4.2.2']
+    >>> fix_versions_from_input("", "5.0.0")
+    ['5.0.0']
+    >>> fix_versions_from_input("5.0.0, 4.3.0", "")
+    ['5.0.0', '4.3.0']
+    >>> fix_versions_from_input("", "")
+    []
+    >>> fix_versions_from_input("   ", "")
+    []
+    """
+    if raw_input == "":
+        raw_input = default_fix_versions
+    stripped = raw_input.replace(" ", "")
+    if stripped == "":
+        return []
+    return stripped.split(",")
+
+
 def red(text):
     return "\033[91m%s\033[0m" % text
 
@@ -1252,19 +1295,28 @@ def resolve_jira_issue(
     if is_resolved:
         # A later backport run must preserve the versions recorded by the 
original merge and
         # only add versions inferred from the newly discovered branches.
-        default_fix_list = additional_fix_versions(default_fix_list, 
existing_fix_version_names)
-        if not default_fix_list:
+        default_fix_list, all_inferred_present = fix_version_additions(
+            default_fix_list, existing_fix_version_names
+        )
+        if all_inferred_present:
             print(
                 "JIRA issue %s already contains all inferred fix versions; no 
update needed."
                 % issue.key
             )
             return
-        print(
-            "JIRA issue %s has fix version(s) %s; inferred addition(s): %s"
-            % (issue.key, existing_fix_version_names, default_fix_list)
-        )
-        if get_input("Add these fix version(s)? (y/N): ", ["y", "n", ""]) != 
"y":
-            return
+        if default_fix_list:
+            print(
+                "JIRA issue %s has fix version(s) %s; inferred addition(s): %s"
+                % (issue.key, existing_fix_version_names, default_fix_list)
+            )
+            if get_input("Add these fix version(s)? (y/N): ", ["y", "n", ""]) 
!= "y":
+                return
+        else:
+            # Nothing inferred, so there is nothing to confirm; fall through 
to the prompt.
+            print(
+                "JIRA issue %s has fix version(s) %s; no additional fix 
version could be "
+                "inferred." % (issue.key, existing_fix_version_names)
+            )
     default_fix_versions = ",".join(default_fix_list)
 
     available_versions = set(list(map(lambda v: v.name, versions)))
@@ -1273,10 +1325,12 @@ def resolve_jira_issue(
             prompt = "Enter comma-separated fix version(s) [%s]: "
             if is_resolved:
                 prompt = "Enter comma-separated additional fix version(s) 
[%s]: "
-            fix_versions = bold_input(prompt % default_fix_versions)
-            if fix_versions == "":
-                fix_versions = default_fix_versions
-            fix_versions = fix_versions.replace(" ", "").split(",")
+            fix_versions = fix_versions_from_input(
+                bold_input(prompt % default_fix_versions), default_fix_versions
+            )
+            if not fix_versions:
+                print("No fix version entered; update %s manually." % 
issue.key)
+                return
             if set(fix_versions).issubset(available_versions):
                 break
             else:


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

Reply via email to