diff --git a/committer-tools/README.md b/committer-tools/README.md index 92558d4a296..94f714f959a 100644 --- a/committer-tools/README.md +++ b/committer-tools/README.md @@ -46,11 +46,13 @@ See: https://cli.github.com/ brew install gh ``` -## Find Reviewers +## Find Reviewers and Update to PR body The reviewers.py script is used to simplify the process of producing our "Reviewers:" -Git trailer. It parses the Git log to gather a set of "Authors" and "Reviewers". -Some simple string prefix matching is done to find candidates. +Git trailer to PR body. It parses the Git log to gather a set of "Authors" and "Reviewers". +Some simple string prefix matching is done to find candidates. +After entering the pull request number, the script updates the "Reviewers:" trailer accordingly. +If the PR body already contains a "Reviewers:" trailer, the script replaces it with the updated list of reviewers. Usage: diff --git a/committer-tools/reviewers.py b/committer-tools/reviewers.py index ccac5e3182c..a3539f9d9c3 100755 --- a/committer-tools/reviewers.py +++ b/committer-tools/reviewers.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import json +import shlex +import subprocess +import tempfile # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -35,6 +39,42 @@ def prompt_for_user(): return clean_input +def update_trailers(body, trailer): + with tempfile.NamedTemporaryFile() as fp: + fp.write(body.encode()) + fp.flush() + cmd = f"git interpret-trailers --if-exists replace --trailer '{trailer}' {fp.name} " + p = subprocess.run(shlex.split(cmd), capture_output=True, text=True) + fp.close() + + return p.stdout + + +def append_message_to_pr_body(pr: int , message: str): + try: + pr_url = f"https://github.com/apache/kafka/pull/{pr}" + cmd_get_pr = f"gh pr view {pr_url} --json title,body" + result = subprocess.run(shlex.split(cmd_get_pr), capture_output=True, text=True, check=True) + current_pr_body = json.loads(result.stdout).get("body", {}).strip() + "\n" + pr_title = json.loads(result.stdout).get("title", {}) + updated_pr_body = update_trailers(current_pr_body, message) + except subprocess.CalledProcessError as e: + print("Failed to retrieve PR body:", e.stderr) + return + + print(f"""New PR body will be:\n\n---\n{updated_pr_body}---\n""") + choice = input(f'Update the body of "{pr_title}"? (y/n): ').strip().lower() + if choice in ['n', 'no']: + return + + try: + cmd_edit_body = f"gh pr edit {pr_url} --body {shlex.quote(updated_pr_body)}" + subprocess.run(shlex.split(cmd_edit_body), check=True) + print("PR body updated successfully!") + except subprocess.CalledProcessError as e: + print("Failed to update PR body:", e.stderr) + + if __name__ == "__main__": print("Utility to help generate 'Reviewers' string for Pull Requests. Use Ctrl+D or Ctrl+C to exit") @@ -87,9 +127,12 @@ if __name__ == "__main__": continue if selected_reviewers: - out = "\n\nReviewers: " - out += ", ".join([f"{name} <{email}>" for name, email, _ in selected_reviewers]) - out += "\n" - print(out) - + reviewer_message = "Reviewers: " + reviewer_message += ", ".join([f"{name} <{email}>" for name, email, _ in selected_reviewers]) + print(f"\n\n{reviewer_message}\n") + try: + pr_number = int(input("\nPull Request (Ctrl+D or Ctrl+C to skip): ")) + append_message_to_pr_body(pr_number, reviewer_message) + except (EOFError, KeyboardInterrupt): + exit(0)