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

uros-b 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 d23432eed28d [SPARK-58734][INFRA] Skip a declined cherry-pick instead 
of aborting the merge in merge_spark_pr.py
d23432eed28d is described below

commit d23432eed28d9637a04dff45c235cb16f2ece1e5
Author: Uros Bojanic <[email protected]>
AuthorDate: Thu Aug 13 08:45:51 2026 +0000

    [SPARK-58734][INFRA] Skip a declined cherry-pick instead of aborting the 
merge in merge_spark_pr.py
    
    ### What changes were proposed in this pull request?
    
    This PR changes how `dev/merge_spark_pr.py` handles a committer declining 
to resolve a conflicting backport cherry-pick.
    
    Previously, when a cherry-pick onto a maintenance branch conflicted, the 
script prompted `Would you like to manually fix-up this merge?`. Answering `N` 
went through `continue_maybe(..., cherry=True)`, which aborted the cherry-pick 
and then called `fail("Okay, exiting")` — terminating the whole run via 
`sys.exit(-1)`. As a result the committer never reached the JIRA-resolution 
step, and the process exited non-zero, even though the merge into the target 
branch (and any earlier cherry-pi [...]
    
    This PR makes declining a cherry-pick fix-up skip only that one branch and 
continue:
    - Add a `SkipCherryPick` exception. When a cherry-pick fix-up prompt is 
declined, `continue_maybe` aborts the cherry-pick, restores the working tree 
(`clean_up()`), and raises `SkipCherryPick` instead of calling `fail()`.
    - `cherry_pick()` catches `SkipCherryPick` and returns only the picks that 
actually landed (empty, or — in the Upstream-First two-branch path — just the 
sibling branch that was already pushed).
    - The two merge/backport loops in `main()` already consume the returned 
list generically (`merged_refs + []` is a no-op), so no loop changes are 
needed: after a skip they simply offer the next branch and still proceed to 
resolve the associated JIRA.
    
    Hard aborts elsewhere are unchanged (e.g. declining the push prompt, or 
choosing `[a]bort` at the Upstream-First prompt, still exit).
    
    ### Why are the changes needed?
    
    Backport cherry-pick conflicts are routine, and by the time one occurs the 
merge into the target branch has already been pushed. Aborting the entire 
script on a declined fix-up means the committer:
    - skips JIRA resolution (the ticket is left Open, which is easy to miss and 
tedious to reconcile after the fact), and
    - gets a non-zero exit for what is a normal "do not backport that one 
branch" decision.
    
    Declining a single conflicting backport should skip just that branch and 
let the merge finish cleanly.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. This changes a committer-only developer tool (`dev/merge_spark_pr.py`); 
it is not part of any Spark release artifact.
    
    ### How was this patch tested?
    
    - The module's inline doctests still pass (run by `doctest.testmod()` at 
startup): 76 attempted, 0 failed.
    - `python3 -m py_compile dev/merge_spark_pr.py` is clean.
    - Manually traced the affected control-flow paths:
      - single-branch pick declined -> `cherry_pick` returns `[]`, the loop 
re-prompts and JIRA resolution still runs;
      - Upstream-First `[b]oth` path with the second pick declined -> the 
already-pushed sibling branch is still returned and recorded in the merge 
comment / `merged_refs`;
      - declining non-cherry prompts, and `[a]bort` at the Upstream-First 
prompt, still hard-exit as before.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code with Claude Opus 4.8
    
    Closes #57959 from uros-b/merge-script-cherrypick-skip.
    
    Authored-by: Uros Bojanic <[email protected]>
    Signed-off-by: Uros Bojanic <[email protected]>
---
 dev/merge_spark_pr.py | 44 +++++++++++++++++++++++++++++++++++++-------
 1 file changed, 37 insertions(+), 7 deletions(-)

diff --git a/dev/merge_spark_pr.py b/dev/merge_spark_pr.py
index 73258ff3816b..bb2b9733c976 100755
--- a/dev/merge_spark_pr.py
+++ b/dev/merge_spark_pr.py
@@ -556,6 +556,16 @@ def run_cmd(cmd):
         return subprocess.check_output(cmd.split(" ")).decode("utf-8")
 
 
+class SkipCherryPick(Exception):
+    """Signals that the committer declined to resolve a conflicting 
cherry-pick.
+
+    A backport conflict is routine, and by the time one is hit the merge into 
the target
+    branch (and any earlier cherry-picks) has already been pushed. Declining 
it must skip
+    only that one branch -- letting the caller offer another branch and, 
crucially, still
+    resolve the JIRA -- instead of aborting the whole merge the way a hard 
`fail()` would.
+    """
+
+
 def continue_maybe(prompt, cherry=False):
     if get_input(f"{prompt} (y/N): ", ["y", "n", ""]) != "y":
         if cherry:
@@ -563,6 +573,9 @@ def continue_maybe(prompt, cherry=False):
                 run_cmd("git cherry-pick --abort")
             except Exception:
                 print_error("Unable to abort and get back to the state before 
cherry-pick")
+            print("Skipping this cherry-pick; the merge continues.")
+            clean_up()
+            raise SkipCherryPick()
         fail("Okay, exiting")
 
 
@@ -654,7 +667,9 @@ def merge_pr(pr_num, target_ref, title, body, pr_repo_desc, 
pr_author, co_author
 def _do_cherry_pick(pr_num, merge_hash, pick_ref):
     """Cherry-pick `merge_hash` onto `pick_ref` and push.
 
-    Returns the (pushed ref, pushed commit hash) pair.
+    Returns the (pushed ref, pushed commit hash) pair. Raises `SkipCherryPick` 
if the
+    cherry-pick conflicts and the committer declines to resolve it, after 
attempting to
+    abort the cherry-pick and restore the working tree.
     """
     pick_branch_name = "%s_PICK_PR_%s_%s" % (BRANCH_PREFIX, pr_num, 
pick_ref.upper())
 
@@ -764,7 +779,9 @@ def cherry_pick(pr_num, merge_hash, default_branch, 
branch_names, target_ref, al
     BOTH (the policy-compliant default) or branch-M.N only (treated as a
     maintenance-only bugfix). Returns the list of (ref, commit_hash) pairs 
actually
     picked into, so the main loop can advance its remaining-branches list 
correctly
-    and record each backport commit for the merge comment.
+    and record each backport commit for the merge comment. The list is empty 
(or, for the
+    Upstream-First two-branch path, holds only what landed) when the committer 
declines to
+    resolve a conflict, so the caller simply offers the next branch and still 
resolves JIRA.
     """
     while True:
         pick_ref = bold_input(f"Enter a branch name [{default_branch}]: ")
@@ -796,17 +813,30 @@ def cherry_pick(pr_num, merge_hash, default_branch, 
branch_names, target_ref, al
             {"b": ["b", "both", ""], "o": ["o", "only"], "a": ["a", "abort"]},
         )
         if choice == "b":
-            picked_x = _do_cherry_pick(pr_num, merge_hash, sibling_x)
-            picked_n = _do_cherry_pick(pr_num, merge_hash, pick_ref)
-            return [picked_x, picked_n]
+            # Preserve any pick that was already pushed: if the branch-M.N 
pick is skipped after
+            # branch-M.x landed, still return branch-M.x so it's recorded and 
JIRA/comment
+            # reflect it.
+            picked = []
+            try:
+                picked.append(_do_cherry_pick(pr_num, merge_hash, sibling_x))
+                picked.append(_do_cherry_pick(pr_num, merge_hash, pick_ref))
+            except SkipCherryPick:
+                pass
+            return picked
         elif choice == "o":
-            return [_do_cherry_pick(pr_num, merge_hash, pick_ref)]
+            try:
+                return [_do_cherry_pick(pr_num, merge_hash, pick_ref)]
+            except SkipCherryPick:
+                return []
         elif choice == "a":
             fail("Aborted by user at Upstream-First policy prompt.")
         else:
             fail("Unrecognized choice %r; aborting." % choice)
 
-    return [_do_cherry_pick(pr_num, merge_hash, pick_ref)]
+    try:
+        return [_do_cherry_pick(pr_num, merge_hash, pick_ref)]
+    except SkipCherryPick:
+        return []
 
 
 # Common words carry no signal when comparing a PR title to a JIRA summary, so 
they are


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

Reply via email to