spark-test commented on code in PR #60031:
URL: https://github.com/apache/spark/pull/60031#discussion_r4479767730


##########
dev/merge_spark_pr.py:
##########
@@ -2658,61 +3091,25 @@ def main():
     if pr.get("draft", False) or "[WIP]" in title or "[DO-NOT-MERGE]" in title:
         fail("Cannot merge a draft PR #%s: %s" % (pr_num, title))
 
-    # e.g. 'Revert "[SPARK-56357][BUILD] Upgrade sbt to 1.12.8"'
-    is_revert_pr = title.startswith('Revert "') and title.endswith('"')
-    # e.g. 'Reapply "[SPARK-56357][BUILD] Upgrade sbt to 1.12.8"'
-    is_reapply_pr = title.startswith('Reapply "') and title.endswith('"')
-
-    # Normalized PR-title component tags, used later to reconcile JIRA 
components. Empty for
-    # Revert/Reapply PRs, whose titles are kept verbatim and not parsed for 
components.
-    title_components: List[str] = []
-
-    # Revert and Reapply PRs keep their title verbatim.
-    if not (is_revert_pr or is_reapply_pr):
-        # Parse; fail on a malformed title.
-        try:
-            parsed = Title.parse(title)
-        except ValueError as e:
-            fail("Malformed PR title: %s" % e)
-
-        # Normalize component tags via the registry and track primary.
-        components = []
-        has_primary = False
-        for tag in parsed.components:
-            c = Component.find(tag)
-            if c is not None and c.primary:
-                has_primary = True
-            components.append(Component.normalize(tag))
-        if not has_primary:
-            new_tags = prompt_for_components()
-            components = new_tags + components
-
-        # Deduplicate tags in insertion order.
-        components = list(dict.fromkeys(components))
-
-        # Move version tags (e.g. [4.X], [4.2]) to the head of components.
-        versions = [t for t in components if _VERSION_TAG_RE.match(t)]
-        if versions:
-            others = [t for t in components if not _VERSION_TAG_RE.match(t)]
-            components = versions + others
-
-        # Move FOLLOWUP to the last tag.
-        non_followup = [t for t in components if t != "FOLLOWUP"]
-        if len(non_followup) < len(components):
-            components = non_followup + ["FOLLOWUP"]
-
-        # Warn about tags that are neither known components nor version tags.
-        unknown = [
-            t for t in components if Component.find(t) is None and not 
_VERSION_TAG_RE.match(t)
-        ]
-        if unknown:
-            print_error("Title has unknown tag(s): %s" % ", ".join("[%s]" % t 
for t in unknown))
-
-        parsed.components = components
-        title_components = list(parsed.components)
-        title = str(parsed)
-        if title != pr["title"]:
-            print("Normalized title: %s" % title)
+    # Merged pull requests don't appear as merged in the GitHub API;
+    # Instead, they're closed by committers.
+    # A PR might have multiple merge commits, if it's reopened and merged 
again. We shall
+    # cherry-pick PRs in closed state with the latest merge hash.
+    # If the PR is still open (reopened), we shall not cherry-pick it but 
perform the normal
+    # merge as it could have been reverted earlier.
+    merge_hash, message = (None, None)
+    if pr["state"] == "closed":
+        merge_hash, message = find_merge_commit(pr_num, pr_events)
+    if merge_hash is not None:
+        print("Pull request %s has already been merged, assuming you want to 
backport" % pr_num)
+        commit_is_downloaded = (
+            git.run(["git", "rev-parse", "--quiet", "--verify", "%s^{commit}" 
% merge_hash]).strip()
+            != ""
+        )
+        if not commit_is_downloaded:
+            fail("Couldn't find any merge commit for #%s, you may need to 
update HEAD." % pr_num)
+        print("Found commit %s:\n%s" % (merge_hash, message))
+    title = title_for_run(pr_num, title, message)

Review Comment:
   **Nit (P3):** No doctest reaches this line or the 
`jira_title_for_backport()` call before the backport's JIRA update, and 
together they decide whether a backport reads the committed subject. Passing 
`None` here instead of `message`, dropping that call, or swapping its two title 
arguments would still pass every doctest, and the PR description itself lists 
four `main()` mutants that survive. With `None`, a backport goes back to 
preparing the current PR title, prompting for its components and updating JIRA 
from it. `main()` has never been doctested in this file, and moving the title 
handling into `prepare_merge_title()`, `title_for_run()` and 
`jira_title_for_backport()` already pinned most of it. Committing the mocked 
`main()` run that the description mentions, as a doctest, would pin these call 
sites too.
   
   This extends existing discussions 
[1](https://github.com/apache/spark/pull/60031#discussion_r4448436218), 
[2](https://github.com/apache/spark/pull/60031#discussion_r4470227689).
   
   **Verification:**
   - **Regression:** On a backport whose commit supplies a committed title and 
whose current PR title would update JIRA differently, update_jira_for_pr() 
receives the committed title by default, and the notice and question are 
printed right before the JIRA update. Passing None instead of the found commit 
message to title_for_run(), dropping the jira_title_for_backport() call, or 
swapping its two title arguments each makes this coverage fail.
   - **Behavior:** A merge run passes the prepared title, including a component 
entered at the prompt, to update_jira_for_pr(), and a backport whose commit 
supplies no committed title passes the prepared current PR title.



##########
dev/merge_spark_pr.py:
##########
@@ -670,6 +670,32 @@ def message_of(commit_hash):
     return None, None
 
 
+def committed_title(pr_num: Union[int, str], message: Optional[str]) -> 
Optional[str]:
+    """Return the subject of the merge commit ``message`` if ``merge_pr`` made 
it for this PR.
+
+    That is the title that was committed, and a backport run reads it for the 
JIRA update
+    instead of the current PR title, which may have been edited since the 
merge. It is
+    used only when the merge footer of ``message`` closes ``pr_num``; 
otherwise this
+    returns None, and the PR title is used as before, e.g. for a commit made 
with the GitHub
+    squash button, whose subject ends with ``(#N)``, or a commit of another PR.
+
+    >>> footer = "\\n\\nBody\\n\\nCloses #1 from a/b.\\n\\nAuthored-by: A 
<[email protected]>"
+    >>> committed_title(1, 'Revert "Revert "[SPARK-1][SQL] Fix"" \\r' + footer)
+    'Revert "Revert "[SPARK-1][SQL] Fix""'
+    >>> committed_title("1", "[SPARK-1][SQL] Fix" + footer)
+    '[SPARK-1][SQL] Fix'
+    >>> committed_title(1, 'Revert "[SPARK-1][SQL] Fix" (#1)\\n\\nBody') is 
None
+    True
+    >>> committed_title(1, "[SPARK-2][SQL] Other" + footer.replace("#1", 
"#2")) is None
+    True
+    >>> committed_title(1, None) is None
+    True
+    """
+    if message is None or not has_merge_footer(message, pr_num):

Review Comment:
   **Non-blocking (P2):** This returns None for every commit without this PR's 
merge footer, including a GitHub squash-button commit. `.asf.yaml` enables that 
button with `PR_TITLE_AND_DESC`, so the commit's subject is the PR title at 
merge time plus ` (#N)`, and its body is the PR description with no `Closes #N 
from ...` footer (e.g. 6bae835ccbc). `find_merge_commit()` takes the commit 
linked to the PR's `closed` event without a footer check, and the doctests here 
and in `title_for_run()` and `jira_title_for_backport()` treat such a commit as 
a backport input. Its backport then prepares the current PR title, and 
`jira_title_for_backport()` returns that title without a notice.
   
   So a backport of a squash-merged PR still updates JIRA from a title edited 
after the merge, which is the failure this PR fixes for footer commits. For 
example, if a PR is squash-merged as `Revert "[SPARK-1][SQL] X"` and later 
retitled `[SPARK-1][SQL] X`, its backport offers to resolve SPARK-1 or add fix 
versions to it instead of removing them, and nothing shows the committed title. 
The ` (#N)` suffix is all that separates the subject from the committed title. 
Such merges are rare (6 of the 50,881 commit subjects on master at the base of 
this PR end with ` (#N)`), and the later JIRA prompts still show the ticket.
   
   **Recommended change:** Teach committed_title() to recognize this PR's 
GitHub squash-button commit, whose subject is the PR title at merge time 
followed by ' (#N)' with this PR's number, and return that subject without the 
suffix. A squash-button backport then reads the committed title as a footer 
backport does: title_for_run() uses it without preparing the current PR title, 
and jira_title_for_backport() shows the notice and offers the current PR title 
when the two would update JIRA differently. Update the committed_title(), 
title_for_run(), jira_title_for_backport() and update_jira_for_pr() docstrings 
and the doctests whose squash-button cases now yield the stripped subject, and 
the PR description's sentences about squash-button commits.
   
   **Why this works:** The suffix names the PR the commit was squash-merged 
from, just as the footer names the PR merge_pr closed. Stripping it exposes the 
committed title, so jira_action() reads the committed ids, skip tags and 
direction, and the existing notice and choice handle a PR title edited after 
the merge.
   
   **Scope:** Extend backport committed-title detection to GitHub squash-button 
commits and update the related docstrings, doctests and PR description.
   
   **Compatibility:** Commits with this PR's merge footer, merge runs, and 
commits that belong to other PRs keep their current title handling.
   
   **Risks:** A squash-button backport whose PR title has no primary component 
no longer prompts for one, and one whose current PR title fails Title.parse() 
no longer stops, matching what the PR already does for footer commits; removing 
the suffix check restores the current fallback. A squash commit title the 
merger edited in GitHub's dialog becomes the default JIRA title, though the 
notice and choice still show the current PR title when the two would update 
JIRA differently. This assumes, as this PR's docstrings and the author's 
replies do, that GitHub links a squash-button merge commit to the PR's closed 
event, so that find_merge_commit() returns it for a backport. If it does not, a 
squash-merged PR never reaches the backport path and this change has no effect 
for it.
   
   **Constraints:** has_merge_footer() remains the first check, so commits with 
this PR's merge footer keep their current subject handling. Only a trailing ' 
(#N)' with this PR's own number is stripped; any other commit without the 
footer still falls back to the prepared PR title.
   
   **Exclusions:**
   - Running check_wrapped_title() on backports stays out of scope, as for 
footer commits, since a committed title cannot be retitled before merging.
   
   **Success:** A backport of this PR's squash-button commit whose subject is 
'T (#N)' updates JIRA from T by default. When the current PR title would update 
JIRA differently from T, the notice and the question about using the current PR 
title appear right before the JIRA update, as for footer commits. Footer 
commits, other PRs' commits and merge runs keep their current titles, prompts 
and JIRA actions.
   
   **Verification:**
   - **Regression:** A backport of this PR's squash-button commit 'T (#N)' 
whose current PR title would update JIRA differently from T, through other skip 
tags, other ticket ids or the other direction, updates JIRA from T by default 
and prints the notice and question right before the JIRA update; e.g. 'Revert 
"[SPARK-1][SQL] X" (#N)' with the current title '[SPARK-1][SQL] X' takes the 
revert path for SPARK-1.
   - **Behavior:** committed_title() returns the subject without its ' (#N)' 
suffix for this PR's squash-button message, still returns the stripped subject 
for a message with this PR's footer, and returns None for another PR's 
squash-button or footer commit and for a message with neither.
   - **Compatibility:** Merge runs and footer-commit backports produce the same 
titles, prompts and JIRA actions as before the change.



##########
dev/merge_spark_pr.py:
##########
@@ -2012,6 +2088,150 @@ def update_jira_for_pr(pr_num, title, merge_branches, 
title_components, is_rever
     )
 
 
+def _bracketed(tags):
+    """Format tags as in a title, e.g. ``[MINOR], [FOLLOWUP]``, for 
messages."""
+    return ", ".join("[%s]" % tag for tag in tags)
+
+
+def committed_title_notice(subject: str, pr_title: str) -> Optional[str]:
+    """Say that ``subject`` and ``pr_title`` would update JIRA differently, or 
return None.
+
+    Both are compared by :func:`jira_action`: whether JIRA is skipped, and if 
not, which
+    tickets and in which direction. The components are not compared: they 
differ whenever
+    one was entered at the merge prompt, and are only reconciled with an 
unresolved ticket.
+    A resolve of a ticket that is already resolved as Fixed only adds fix 
versions, and a

Review Comment:
   **Nit (P3):** For a ticket already resolved as Fixed, `resolve_jira_issue()` 
does more than add fix versions. It calls 
`maybe_reconcile_jira_affects_versions()` when every inferred fix version is 
already present, when the committer declines the addition, when no new version 
is chosen, and before `add_fix_versions()`, and that call can prompt for and 
change the Affects Version/s. The comparison here is unaffected, since that 
step does not depend on the title. The sentence could say, e.g., that such a 
resolve adds fix versions and may offer to correct the Affects Version/s, 
without reconciling components.
   
   **Verification:**
   - **Inspection:** The docstring's description of resolving a ticket already 
resolved as Fixed matches resolve_jira_issue(): fix versions are added, Affects 
Version/s may be corrected, and components are not reconciled.



-- 
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