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 a026a0be4c88 [SPARK-59808][INFRA] Remove JIRA fix versions and reopen 
tickets when merging reverts
a026a0be4c88 is described below

commit a026a0be4c88e7ace59d1da7c18ab73b0d81edde
Author: Szehon Ho <[email protected]>
AuthorDate: Tue Sep 29 16:49:26 2026 -0700

    [SPARK-59808][INFRA] Remove JIRA fix versions and reopen tickets when 
merging reverts
    
    ### What changes were proposed in this pull request?
    
    After merging or backporting a `Revert "..."` PR, offer to remove the 
corresponding unreleased JIRA fix versions and then optionally reopen the 
ticket. For example, accepting the suggested versions (`<Enter>` denotes a 
blank response):
    
    ```text
    Revert merged into: master, branch-4.x
    JIRA issue SPARK-12345 has fix version(s): ['5.0.0', '4.3.0', '3.5.2']
    Available unreleased fix version(s) to remove: ['4.3.0', '5.0.0']
    Enter comma-separated fix version(s) to remove [5.0.0,4.3.0], or 'skip': 
<Enter>
    Remove fix version(s) ['5.0.0', '4.3.0'] from SPARK-12345? (y/N): y
    Successfully removed fixVersions=['5.0.0', '4.3.0'] from SPARK-12345!
    Reopen JIRA issue SPARK-12345 so the change can be reworked? (y/N): y
    Successfully reopened SPARK-12345!
    ```
    
    The committer can select different fix versions or skip their removal. 
Other fix versions are preserved; released and archived versions are excluded 
from removal.
    
    Resolved or closed tickets with a Fixed resolution receive a separate 
reopening prompt, including when fix-version removal is skipped or no versions 
remain to remove. Spark's JIRA Reopen Issue workflow clears the resolution. The 
committer can decline reopening when a revert affects only some releases. 
Already-active tickets need no reopening, and `--dry-run` logs both operations 
without writing to JIRA.
    
    An unavailable reopening transition or an exception during transition 
lookup or reopening prints a manual-reopen message and lets processing continue.
    
    ### Why are the changes needed?
    
    Spark release issue lists use JIRA fix versions. Reverted PRs currently 
leave those versions behind, so release notes can list changes absent from the 
release. Removing stale fix versions avoids inaccurate release notes, and 
reopening the ticket makes the outstanding work visible.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, the committer prompts shown above for fix-version removal and optional 
ticket reopening.
    
    ### How was this patch tested?
    
    All 219 doctest examples pass with Python 3.14.5, including mocked JIRA 
coverage for version protection, skipped cleanup, reopening resolved and closed 
tickets, declining reopening, unavailable transitions, API exceptions, and dry 
runs:
    
    ```sh
    python3 -m doctest dev/merge_spark_pr.py
    ```
    
    Ruff 0.14.8 lint and formatting checks and `git diff --check` also pass. 
Read-only inspection of Spark's JIRA confirmed the Reopen Issue transition and 
resolution clearing in an existing issue's history.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: OpenAI Codex (GPT-6)
    
    Closes #59082 from szehon-ho/codex/revert-jira-fix-versions.
    
    Authored-by: Szehon Ho <[email protected]>
    Signed-off-by: Szehon Ho <[email protected]>
---
 dev/merge_spark_pr.py | 345 ++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 333 insertions(+), 12 deletions(-)

diff --git a/dev/merge_spark_pr.py b/dev/merge_spark_pr.py
index f8624bdf9bb2..3154beef38f5 100755
--- a/dev/merge_spark_pr.py
+++ b/dev/merge_spark_pr.py
@@ -91,7 +91,7 @@ GITHUB_OAUTH_KEY = os.environ.get("GITHUB_OAUTH_KEY")
 # conflicts and the computed merge hash stay realistic -- but every outbound 
effect is routed
 # through a DryRun* client (see Git/GitHub/Jira below) that logs a "DRY-RUN: 
would ..." line
 # instead of executing it: the git push to PUSH_REMOTE_NAME, the GitHub PR 
close/comment, and all
-# JIRA writes (component and fixVersion updates, assignment, and the resolve 
transition).
+# JIRA writes (component and fixVersion updates, assignment, and 
resolve/reopen transitions).
 DRY_RUN_ENV = bool(os.environ.get("DRY_RUN"))
 
 
@@ -318,7 +318,7 @@ def compute_merge_default_fix_versions(merge_branches, 
unreleased_version_names)
                     % (line_major, line_major)
                 )
             continue
-        prefix = b.replace("branch-", "")
+        prefix = b.replace("branch-", "") + "."
         found_versions = [n for n in names if n.startswith(prefix)]
         chosen = _semver_max_version(found_versions)
         if chosen:
@@ -353,6 +353,37 @@ def compute_merge_default_fix_versions(merge_branches, 
unreleased_version_names)
     return list(dict.fromkeys(v for _, v in filtered)), warnings
 
 
+def compute_revert_fix_versions(merge_branches, unreleased_version_names, 
existing_version_names):
+    """Suggest recorded Fix Versions to remove for the branches receiving a 
revert.
+
+    Infer each branch separately so that redundant versions recorded by 
separate merge
+    runs are included in the removal candidates.
+
+    >>> versions = ["5.0.0", "4.3.0", "4.2.0", "3.5.2"]
+    >>> compute_revert_fix_versions(
+    ...     ["master", "branch-4.x", "branch-4.2"], versions, versions)
+    (['5.0.0', '4.3.0', '4.2.0'], [])
+    >>> compute_revert_fix_versions(["master"], versions, versions)
+    (['5.0.0'], [])
+    >>> compute_revert_fix_versions(["branch-4.2"], versions, ["4.3.0", 
"3.5.2"])
+    ([], [])
+    >>> compute_revert_fix_versions(["branch-4.x"], versions, ["4.3.0", 
"4.2.0"])
+    (['4.3.0'], [])
+    >>> compute_revert_fix_versions(
+    ...     ["branch-4.2"], ["4.2.1", "4.20.0"], ["4.2.1", "4.20.0"])
+    (['4.2.1'], [])
+    """
+    candidates = set()
+    warnings = []
+    for branch in merge_branches:
+        versions, branch_warnings = compute_merge_default_fix_versions(
+            [branch], unreleased_version_names
+        )
+        candidates.update(versions)
+        warnings.extend(branch_warnings)
+    return [v for v in existing_version_names if v in candidates], warnings
+
+
 def additional_fix_versions(inferred_versions, existing_versions):
     """Return inferred Fix Versions not already present on a JIRA issue.
 
@@ -1434,9 +1465,7 @@ def get_jira_issue(prompt, default_jira_id=""):
             resolution = issue.fields.resolution
             resolution_name = resolution.name if resolution is not None else 
None
             print("JIRA issue %s already has status '%s' (%s)" % (jira_id, 
status, resolution_name))
-            # Only a ticket an earlier merge resolved as Fixed can 
legitimately gain
-            # another Fix Version. Duplicate / Won't Fix / Invalid tickets 
must not be
-            # touched.
+            # Only Fixed resolutions permit updates to resolved or closed 
tickets.
             if resolution_name != "Fixed":
                 return None
         if get_input("Check if the JIRA information is as expected (y/N): ", 
["y", "n", ""]) == "y":
@@ -1594,6 +1623,72 @@ def resolve_jira_issue(
     )
 
 
+def revert_jira_issue(merge_branches, default_jira_id=""):
+    issue = get_jira_issue("Enter the JIRA id for the reverted change", 
default_jira_id)
+    if issue is None:
+        return
+
+    remove_jira_fix_versions(issue, merge_branches)
+    if issue.fields.status.name not in ("Resolved", "Closed"):
+        return
+
+    prompt = "Reopen JIRA issue %s so the change can be reworked? (y/N): " % 
issue.key
+    if get_input(prompt, ["y", "n", ""]) != "y":
+        return
+    try:
+        reopen = next(
+            (t for t in asf_jira.transitions(issue.key) if t["name"] == 
"Reopen Issue"), None
+        )
+        if reopen is None:
+            print_error(
+                "No Reopen Issue transition available for %s; reopen it 
manually." % issue.key
+            )
+            return
+        jira_ops.reopen_issue(issue, reopen["id"])
+    except Exception as e:
+        print_error("Failed to reopen %s; reopen it manually: %s" % 
(issue.key, e))
+
+
+def remove_jira_fix_versions(issue, merge_branches):
+    versions = asf_jira.project_versions("SPARK")
+    unreleased_names = [
+        v.name
+        for v in versions
+        if not v.raw["released"] and not v.raw["archived"] and 
parse_version(v.name) is not None
+    ]
+    existing_names = [v.name for v in issue.fields.fixVersions]
+    available_names = set(existing_names).intersection(unreleased_names)
+    if not available_names:
+        print("JIRA issue %s has no unreleased fix versions to remove." % 
issue.key)
+        return
+
+    defaults, warnings = compute_revert_fix_versions(
+        merge_branches, unreleased_names, existing_names
+    )
+    for warning in warnings:
+        print_error(warning)
+    print("Revert merged into: %s" % ", ".join(merge_branches))
+    print("JIRA issue %s has fix version(s): %s" % (issue.key, existing_names))
+    print("Available unreleased fix version(s) to remove: %s" % 
sorted(available_names))
+    while True:
+        raw = bold_input(
+            "Enter comma-separated fix version(s) to remove [%s], or 'skip': " 
% ",".join(defaults)
+        )
+        if raw.strip().lower() == "skip":
+            return
+        removals = fix_versions_from_input(raw, ",".join(defaults))
+        if not removals:
+            print("No fix version selected; update %s manually if needed." % 
issue.key)
+            return
+        if set(removals).issubset(available_names):
+            break
+        print_error("Select only unreleased fix versions currently recorded on 
this JIRA.")
+
+    prompt = "Remove fix version(s) %s from %s? (y/N): " % (removals, 
issue.key)
+    if get_input(prompt, ["y", "n", ""]) == "y":
+        jira_ops.remove_fix_versions(issue, removals)
+
+
 def choose_jira_assignee(issue):
     """
     Prompt the user to choose who to assign the issue to in jira, given a list 
of candidates,
@@ -1654,7 +1749,7 @@ class Jira:
     """ASF JIRA writes used by the merge script -- the single seam for JIRA 
mutations.
 
     The pre-write JIRA lookups stay on the module-level asf_jira client; the 
writes (components,
-    fix versions, the resolve transition, assignment, and the contributor-role 
grant) go through
+    fix versions, workflow transitions, assignment, and the contributor-role 
grant) go through
     here, so DryRunJira can log them. Two Production writes (add_fix_versions, 
resolve_issue) also
     read the issue back afterward to print the updated summary; DryRunJira 
skips the write and that
     read-back alike. main() builds Jira(asf_jira) normally and 
DryRunJira(asf_jira) for a dry run,
@@ -1691,6 +1786,10 @@ class Jira:
             % (issue.key, [v["name"] for v in new_version_jsons])
         )
 
+    def remove_fix_versions(self, issue, version_names):
+        issue.update(update={"fixVersions": [{"remove": {"name": name}} for 
name in version_names]})
+        print("Successfully removed fixVersions=%s from %s!" % (version_names, 
issue.key))
+
     def resolve_issue(
         self, issue, resolve_id, fix_version_jsons, comment, resolution_id, 
fix_version_names
     ):
@@ -1707,6 +1806,11 @@ class Jira:
             print("Unable to fetch JIRA issue %s after resolving" % issue.key)
         print("Successfully resolved %s with fixVersions=%s!" % (issue.key, 
fix_version_names))
 
+    def reopen_issue(self, issue, reopen_id):
+        # Spark's Reopen Issue workflow clears the resolution.
+        self._client.transition_issue(issue.key, reopen_id)
+        print("Successfully reopened %s!" % issue.key)
+
     def assign(self, issue_key, assignee):
         # Shorthand for jira.client.JIRA.assign_issue. The library's own 
assign_issue re-searches
         # users and blindly picks the first of 20 candidates when unmatched; 
here the assignee is
@@ -1736,6 +1840,9 @@ class DryRunJira(Jira):
             % ([v["name"] for v in new_version_jsons], issue.key)
         )
 
+    def remove_fix_versions(self, issue, version_names):
+        print("DRY-RUN: would remove fixVersions=%s from JIRA %s." % 
(version_names, issue.key))
+
     def resolve_issue(
         self, issue, resolve_id, fix_version_jsons, comment, resolution_id, 
fix_version_names
     ):
@@ -1744,6 +1851,9 @@ class DryRunJira(Jira):
             % (issue.key, fix_version_names, comment)
         )
 
+    def reopen_issue(self, issue, reopen_id):
+        print("DRY-RUN: would reopen JIRA %s." % issue.key)
+
     def assign(self, issue_key, assignee):
         print("DRY-RUN: would assign JIRA %s to '%s'." % (issue_key, assignee))
         return True
@@ -1775,11 +1885,12 @@ def resolve_jira_issues(title, merge_branches, comment, 
title_components=()):
         )
 
 
-def update_jira_for_pr(pr_num, title, merge_branches, title_components):
+def update_jira_for_pr(pr_num, title, merge_branches, title_components, 
is_revert=False):
     skip_jira_title_tags = ("MINOR", "TRIVIAL", "FOLLOWUP")
     tags = set(title_components)
+    jira_title = title[len('Revert "') : -1] if is_revert else title
     try:
-        parsed = Title.parse(title)
+        parsed = Title.parse(jira_title)
         tags.update(parsed.leading)
         tags.update(parsed.components)
     except ValueError:
@@ -1796,6 +1907,12 @@ def update_jira_for_pr(pr_num, title, merge_branches, 
title_components):
     # asf_jira is guaranteed to be set here: initialize_jira() fails fast 
otherwise.
     print()
     continue_maybe("Would you like to update an associated JIRA?")
+    if is_revert:
+        jira_ids = list(dict.fromkeys(re.findall("SPARK-[0-9]{4,5}", 
jira_title)))
+        for jira_id in jira_ids or [""]:
+            revert_jira_issue(merge_branches, jira_id)
+        return
+
     jira_comment = "Issue resolved by pull request %s\n[%s/%s]" % (
         pr_num,
         GITHUB_BASE,
@@ -2417,9 +2534,8 @@ def main():
             # pushes have already landed.
             if picked_commits:
                 post_merge_comment(pr_num, picked_commits)
-            # Backport mode may be the first chance to resolve a JIRA after an 
interrupted
-            # original merge. If it was already resolved, add any newly 
inferred fix versions.
-            update_jira_for_pr(pr_num, title, picked_refs, title_components)
+            # Reconcile JIRA with every branch that contains the merge or 
revert.
+            update_jira_for_pr(pr_num, title, picked_refs, title_components, 
is_revert=is_revert_pr)
         sys.exit(0)
 
     if not bool(pr["mergeable"]):
@@ -2538,10 +2654,215 @@ def main():
             post_merge_comment(pr_num, merged_commits)
         # This is deliberately in the finally block: once the target branch 
has been pushed,
         # cancelling a later cherry-pick must not bypass the JIRA update 
decision.
-        update_jira_for_pr(pr_num, title, merged_refs, title_components)
+        update_jira_for_pr(pr_num, title, merged_refs, title_components, 
is_revert=is_revert_pr)
 
 
 __test__ = {
+    "revert_jira_issue": """
+    A revert removes the versions for its branches while preserving released, 
archived,
+    and unrelated versions. JIRA receives individual removals so concurrent 
additions survive.
+
+    >>> from contextlib import redirect_stdout
+    >>> from io import StringIO
+    >>> from types import SimpleNamespace
+    >>> from unittest.mock import Mock, call, patch
+    >>> def version(name, released=False, archived=False):
+    ...     return SimpleNamespace(
+    ...         name=name, raw={"name": name, "released": released, 
"archived": archived})
+    >>> versions = [version("5.0.0"), version("4.3.0"), version("4.2.1", 
released=True),
+    ...             version("4.2.0", archived=True), version("3.5.2")]
+    >>> issue = Mock(key="SPARK-1234")
+    >>> issue.fields.fixVersions = versions
+    >>> issue.fields.status.name = "Open"
+    >>> client = Mock()
+    >>> client.project_versions.return_value = versions
+    >>> writer = Jira(client)
+    >>> def run_revert(answers):
+    ...     issue.reset_mock()
+    ...     client.reset_mock()
+    ...     output = StringIO()
+    ...     with (
+    ...         patch.dict(revert_jira_issue.__globals__, asf_jira=client,
+    ...                    jira_ops=writer, 
get_jira_issue=Mock(return_value=issue)),
+    ...         patch("builtins.input", side_effect=answers),
+    ...         redirect_stdout(output),
+    ...     ):
+    ...         revert_jira_issue(["master", "branch-4.x"], "SPARK-1234")
+    ...     return output.getvalue()
+    >>> output = run_revert(["", "y"])
+    >>> issue.update.assert_called_once_with(update={"fixVersions": [
+    ...     {"remove": {"name": "5.0.0"}}, {"remove": {"name": "4.3.0"}}]})
+    >>> client.mock_calls
+    [call.project_versions('SPARK')]
+
+    The committer can narrow the selection, decline confirmation, or skip the 
update.
+
+    >>> output = run_revert(["5.0.0", "y"])
+    >>> issue.update.assert_called_once_with(
+    ...     update={"fixVersions": [{"remove": {"name": "5.0.0"}}]})
+    >>> for answers in (["", "n"], ["", ""], ["skip"]):
+    ...     output = run_revert(answers)
+    ...     issue.update.assert_not_called()
+
+    Released, archived, and unrecorded versions are rejected even when entered 
manually.
+
+    >>> output = run_revert(["4.2.1", "4.2.0", "9.9.9", "", "y"])
+    >>> output.count("Select only unreleased fix versions currently recorded 
on this JIRA.")
+    3
+    >>> issue.update.assert_called_once_with(update={"fixVersions": [
+    ...     {"remove": {"name": "5.0.0"}}, {"remove": {"name": "4.3.0"}}]})
+
+    Dry runs log the removal without writing to JIRA.
+
+    >>> writer = DryRunJira(client)
+    >>> output = run_revert(["", "y"])
+    >>> "DRY-RUN: would remove fixVersions=['5.0.0', '4.3.0'] from JIRA 
SPARK-1234." in output
+    True
+    >>> issue.update.assert_not_called()
+    >>> client.mock_calls
+    [call.project_versions('SPARK')]
+
+    Removing the last fix versions is allowed. A rerun with no matching 
versions is a no-op.
+
+    >>> writer = Jira(client)
+    >>> issue.fields.fixVersions = versions[:2]
+    >>> output = run_revert(["", "y"])
+    >>> issue.update.assert_called_once_with(update={"fixVersions": [
+    ...     {"remove": {"name": "5.0.0"}}, {"remove": {"name": "4.3.0"}}]})
+    >>> issue.fields.fixVersions = versions[2:]
+    >>> output = run_revert([""])
+    >>> issue.update.assert_not_called()
+    >>> issue.fields.fixVersions = []
+    >>> output = run_revert([])
+    >>> "no unreleased fix versions to remove" in output
+    True
+    >>> issue.update.assert_not_called()
+
+    Resolved and closed tickets can be reopened even when no fix versions 
remain.
+
+    >>> client.transitions.return_value = [
+    ...     {"id": "701", "name": "Close Issue"}, {"id": "3", "name": "Reopen 
Issue"}]
+    >>> for status in ("Resolved", "Closed"):
+    ...     issue.fields.status.name = status
+    ...     output = run_revert(["y"])
+    ...     client.transitions.assert_called_once_with("SPARK-1234")
+    ...     client.transition_issue.assert_called_once_with("SPARK-1234", "3")
+    ...     issue.update.assert_not_called()
+
+    Reopening is optional and independent of fix-version removal.
+
+    >>> for answer in ("n", ""):
+    ...     output = run_revert([answer])
+    ...     client.transitions.assert_not_called()
+    ...     client.transition_issue.assert_not_called()
+    >>> issue.fields.fixVersions = versions
+    >>> output = run_revert(["", "y", "y"])
+    >>> issue.update.assert_called_once_with(update={"fixVersions": [
+    ...     {"remove": {"name": "5.0.0"}}, {"remove": {"name": "4.3.0"}}]})
+    >>> client.transition_issue.assert_called_once_with("SPARK-1234", "3")
+    >>> for answers in (["skip", "y"], ["", "n", "y"]):
+    ...     output = run_revert(answers)
+    ...     issue.update.assert_not_called()
+    ...     client.transition_issue.assert_called_once_with("SPARK-1234", "3")
+    >>> output = run_revert(["", "y", "n"])
+    >>> issue.update.assert_called_once()
+    >>> client.transition_issue.assert_not_called()
+
+    Dry runs log both updates and leave JIRA untouched.
+
+    >>> writer = DryRunJira(client)
+    >>> output = run_revert(["", "y", "y"])
+    >>> "DRY-RUN: would remove fixVersions=" in output
+    True
+    >>> "DRY-RUN: would reopen JIRA SPARK-1234." in output
+    True
+    >>> issue.update.assert_not_called()
+    >>> client.transition_issue.assert_not_called()
+
+    Unavailable transitions report that manual action is needed.
+
+    >>> writer = Jira(client)
+    >>> client.transitions.return_value = [{"id": "701", "name": "Close 
Issue"}]
+    >>> output = run_revert(["skip", "y"])
+    >>> "No Reopen Issue transition available for SPARK-1234; reopen it 
manually." in output
+    True
+    >>> client.transition_issue.assert_not_called()
+
+    Lookup and transition failures are nonfatal and request manual reopening.
+
+    >>> client.transitions.side_effect = RuntimeError("lookup failed")
+    >>> output = run_revert(["skip", "y"])
+    >>> "Failed to reopen SPARK-1234; reopen it manually: lookup failed" in 
output
+    True
+    >>> client.transition_issue.assert_not_called()
+    >>> client.transitions.side_effect = None
+    >>> client.transitions.return_value = [{"id": "3", "name": "Reopen Issue"}]
+    >>> client.transition_issue.side_effect = RuntimeError("permission denied")
+    >>> output = run_revert(["skip", "y"])
+    >>> "Failed to reopen SPARK-1234; reopen it manually: permission denied" 
in output
+    True
+    >>> client.transition_issue.assert_called_once_with("SPARK-1234", "3")
+    >>> client.transition_issue.side_effect = None
+
+    Active tickets require no reopening prompt or transition lookup.
+
+    >>> issue.fields.fixVersions = []
+    >>> for status in ("Open", "In Progress", "Reopened"):
+    ...     issue.fields.status.name = status
+    ...     output = run_revert([])
+    ...     client.transitions.assert_not_called()
+    ...     client.transition_issue.assert_not_called()
+    """,
+    "update_jira_for_revert": """
+    Reverts update each referenced JIRA once and bypass the resolve/add path.
+
+    >>> from contextlib import redirect_stdout
+    >>> from io import StringIO
+    >>> from unittest.mock import Mock, call, patch
+    >>> reverter, resolver, confirm = Mock(), Mock(), Mock()
+    >>> def update(title, branches, is_revert=False):
+    ...     reverter.reset_mock()
+    ...     resolver.reset_mock()
+    ...     confirm.reset_mock()
+    ...     with (
+    ...         patch.dict(update_jira_for_pr.__globals__, 
revert_jira_issue=reverter,
+    ...                    resolve_jira_issues=resolver, 
continue_maybe=confirm),
+    ...         redirect_stdout(StringIO()),
+    ...     ):
+    ...         update_jira_for_pr("123", title, branches, [], 
is_revert=is_revert)
+    >>> title = 'Revert "[SPARK-1234][SPARK-5678][SQL] Fix SPARK-1234"'
+    >>> update(title, ["master", "branch-4.x"], is_revert=True)
+    >>> assert reverter.call_args_list == [
+    ...     call(['master', 'branch-4.x'], 'SPARK-1234'),
+    ...     call(['master', 'branch-4.x'], 'SPARK-5678')]
+    >>> resolver.assert_not_called()
+
+    Backport-only runs use the same revert path. A missing JIRA id is entered 
interactively.
+
+    >>> update('Revert "[SPARK-1234][SQL] Fix"', ["branch-4.2"], 
is_revert=True)
+    >>> reverter.assert_called_once_with(["branch-4.2"], "SPARK-1234")
+    >>> update('Revert "Fix"', ["master"], is_revert=True)
+    >>> reverter.assert_called_once_with(["master"], "")
+
+    Wrapped skip tags preserve the existing policy for MINOR, TRIVIAL, and 
FOLLOWUP changes.
+
+    >>> for title in ('Revert "[MINOR][SQL] Fix"', 'Revert "[TRIVIAL][SQL] 
Fix"',
+    ...               'Revert "[SPARK-1234][SQL][FOLLOWUP] Fix"'):
+    ...     update(title, ["master"], is_revert=True)
+    ...     reverter.assert_not_called()
+    ...     resolver.assert_not_called()
+    ...     confirm.assert_not_called()
+
+    Ordinary merges and reapplies still resolve the issue or add fix versions.
+
+    >>> for title in ('[SPARK-1234][SQL] Fix', 'Reapply "[SPARK-1234][SQL] 
Fix"'):
+    ...     update(title, ["master"])
+    ...     reverter.assert_not_called()
+    ...     resolver.assert_called_once_with(
+    ...         title, ["master"],
+    ...         "Issue resolved by pull request 
123\\n[https://github.com/apache/spark/pull/123]";,
+    ...         [])
+    """,
     "reconcile_jira_affects_versions": """
     Blank input accepts the suggested version and appends it to the existing 
versions:
 


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

Reply via email to