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

zhengruifeng 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 df728ff60ffe [SPARK-57795][INFRA] Post a merge summary comment on the 
PR in merge_spark_pr.py
df728ff60ffe is described below

commit df728ff60ffe3544e5f16fd88914a43dc2fbd518
Author: Ruifeng Zheng <[email protected]>
AuthorDate: Wed Jul 1 18:16:43 2026 +0800

    [SPARK-57795][INFRA] Post a merge summary comment on the PR in 
merge_spark_pr.py
    
    ### What changes were proposed in this pull request?
    
    After a PR is merged (and any cherry-picks are done), 
`dev/merge_spark_pr.py` now posts a single comment on the PR recording every 
branch the change landed on and a link to the resulting commit, e.g.:
    
    ```
    **Merge summary** (posted by `merge_spark_pr.py`):
    - merged into master https://github.com/apache/spark/commit/0e1101aa
    - merged into branch-4.x https://github.com/apache/spark/commit/964f8da1
    - merged into branch-4.2 https://github.com/apache/spark/commit/9a6bd1ef
    ```
    
    New helpers `comment_pr` (POST `issues/{pr}/comments`) and 
`post_merge_comment` build and post the comment. `_do_cherry_pick` now returns 
`(ref, hash)` and `cherry_pick` returns a list of such pairs so the exact 
backport commits can be listed. The comment is posted from both the normal 
merge path and the already-merged backport path, and is skipped when 
`GITHUB_OAUTH_KEY` is unset.
    
    ### Why are the changes needed?
    
    A merged PR shows up on GitHub as Closed, and the master merge commit is 
folded into the close event rather than a "referenced in commit" link, so it is 
not obvious from the PR page which branches actually received the change. An 
explicit comment makes the merge fully traceable from the PR.
    
    It is also helpful when backporting the PR in a separate run: a common 
workflow is to merge into master first and later re-run `merge_spark_pr.py` to 
backport (the already-merged path). The comment records the master merge 
commit, so it is easy to find exactly what to cherry-pick in that later run, 
and each backport run appends its own summary so the full set of landed 
branches stays visible on the PR.
    
    The comment is purely human-facing for now (nothing parses it). It is 
intentionally a good future basis for programmatically reconstructing which 
branches received the change, because it records each branch's own resulting 
commit hash captured after the cherry-pick completes -- correct even when the 
committer manually resolves conflicts. That is more reliable than the 
alternatives: `git branch -r --contains <master_merge_hash>` never finds the 
backports (a cherry-pick is a new commit, [...]
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. Committer tooling only.
    
    ### How was this patch tested?
    
    `python -m py_compile` and the module doctests pass.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (claude-opus-4-8)
    
    Closes #56917 from zhengruifeng/merge-spark-pr-comment-dev5.
    
    Authored-by: Ruifeng Zheng <[email protected]>
    Signed-off-by: Ruifeng Zheng <[email protected]>
---
 dev/merge_spark_pr.py | 66 +++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 59 insertions(+), 7 deletions(-)

diff --git a/dev/merge_spark_pr.py b/dev/merge_spark_pr.py
index 9e44e5fd84c6..f16740f2720c 100755
--- a/dev/merge_spark_pr.py
+++ b/dev/merge_spark_pr.py
@@ -81,6 +81,7 @@ GITHUB_OAUTH_KEY = os.environ.get("GITHUB_OAUTH_KEY")
 
 
 GITHUB_BASE = "https://github.com/apache/spark/pull";
+GITHUB_COMMIT_BASE = "https://github.com/apache/spark/commit";
 GITHUB_API_BASE = "https://api.github.com/repos/apache/spark";
 JIRA_BASE = "https://issues.apache.org/jira/browse";
 JIRA_API_BASE = "https://issues.apache.org/jira";
@@ -421,6 +422,42 @@ def close_pr(pr_num):
         return None
 
 
+def comment_pr(pr_num, body):
+    url = "%s/issues/%s/comments" % (GITHUB_API_BASE, pr_num)
+    data = json.dumps({"body": body}).encode("utf-8")
+    request = Request(url, data=data, method="POST")
+    request.add_header("Content-Type", "application/json")
+    request.add_header("Accept", "application/vnd.github+json")
+    if GITHUB_OAUTH_KEY:
+        request.add_header("Authorization", "token %s" % GITHUB_OAUTH_KEY)
+    try:
+        return json.load(urlopen(request))
+    except HTTPError as e:
+        print_error("Failed to comment on PR #%s: HTTP %s %s" % (pr_num, 
e.code, e.reason))
+        return None
+
+
+def post_merge_comment(pr_num, merged_commits):
+    """Post a comment on the PR recording every branch the change landed on 
and a
+    link to the resulting commit, so the merge is traceable from the PR page.
+
+    ``merged_commits`` is an ordered list of (branch, commit_hash) pairs, the 
merge
+    sink first followed by each cherry-pick target.
+    """
+    if not merged_commits:
+        return
+    lines = [
+        "- merged into %s %s/%s" % (ref, GITHUB_COMMIT_BASE, commit_hash)
+        for ref, commit_hash in merged_commits
+    ]
+    body = "**Merge summary** (posted by `merge_spark_pr.py`):\n" + 
"\n".join(lines)
+    print("Posting merge comment on PR #%s:\n%s" % (pr_num, body))
+    if not GITHUB_OAUTH_KEY:
+        print_error("GITHUB_OAUTH_KEY is not set; skipping the merge comment.")
+        return
+    comment_pr(pr_num, body)
+
+
 def fail(msg):
     print_error(msg)
     clean_up()
@@ -531,7 +568,10 @@ 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."""
+    """Cherry-pick `merge_hash` onto `pick_ref` and push.
+
+    Returns the (pushed ref, pushed commit hash) pair.
+    """
     pick_branch_name = "%s_PICK_PR_%s_%s" % (BRANCH_PREFIX, pr_num, 
pick_ref.upper())
 
     run_cmd("git fetch %s %s:%s" % (PUSH_REMOTE_NAME, pick_ref, 
pick_branch_name))
@@ -559,7 +599,7 @@ def _do_cherry_pick(pr_num, merge_hash, pick_ref):
 
     print("Pull request #%s picked into %s!" % (pr_num, pick_ref))
     print("Pick hash: %s" % pick_hash)
-    return pick_ref
+    return pick_ref, pick_hash
 
 
 def _upstream_first_sibling(target_ref, pick_ref, branch_names, 
already_picked):
@@ -599,8 +639,9 @@ def cherry_pick(pr_num, merge_hash, default_branch, 
branch_names, target_ref, al
     types a branch-M.N target while branch-M.x is also a known release branch 
AND
     has not already received this commit, prompt to confirm whether to pick 
into
     BOTH (the policy-compliant default) or branch-M.N only (treated as a
-    maintenance-only bugfix). Returns the list of refs actually picked into, so
-    the main loop can advance its remaining-branches list correctly.
+    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.
     """
     while True:
         pick_ref = bold_input(f"Enter a branch name [{default_branch}]: ")
@@ -1455,7 +1496,10 @@ def main():
 
         print("Found commit %s:\n%s" % (merge_hash, message))
         default = branch_names[0]
-        cherry_pick(pr_num, merge_hash, default, branch_names, target_ref, 
already_picked=())
+        picked = cherry_pick(
+            pr_num, merge_hash, default, branch_names, target_ref, 
already_picked=()
+        )
+        post_merge_comment(pr_num, picked)
         sys.exit(0)
 
     if not bool(pr["mergeable"]):
@@ -1499,6 +1543,10 @@ def main():
 
     merge_hash = merge_pr(pr_num, target_ref, title, body, pr_repo_desc, 
pr_author, co_authors)
 
+    # Ordered (branch, commit_hash) pairs for the merge comment: the merge 
sink first,
+    # then each cherry-pick target as it is picked.
+    merged_commits = [(target_ref, merge_hash)]
+
     # The "Closes #N" keyword in the commit message only auto-closes the PR 
when the commit
     # lands on the default branch. For merges into other branches (e.g. 
branch-X.Y backport
     # PRs), GitHub leaves the PR open, so close it explicitly through the API.
@@ -1524,11 +1572,15 @@ def main():
             target_ref,
             already_picked=tuple(merged_refs),
         )
-        merged_refs = merged_refs + picked
-        for b in picked:
+        picked_refs = [ref for ref, _ in picked]
+        merged_refs = merged_refs + picked_refs
+        merged_commits = merged_commits + picked
+        for b in picked_refs:
             if b in remaining_branches:
                 remaining_branches.remove(b)
 
+    post_merge_comment(pr_num, merged_commits)
+
     # asf_jira is guaranteed to be set here: initialize_jira() fails fast 
otherwise.
     continue_maybe("Would you like to update an associated JIRA?")
     jira_comment = "Issue resolved by pull request %s\n[%s/%s]" % (


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

Reply via email to