2023-04-28 02:32:19 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2025-03-12 21:41:05 +08:00
|
|
|
import json
|
|
|
|
import shlex
|
|
|
|
import subprocess
|
|
|
|
import tempfile
|
2023-04-28 05:04:56 +08:00
|
|
|
#
|
|
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
|
|
# this work for additional information regarding copyright ownership.
|
|
|
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
|
|
|
# (the "License"); you may not use this file except in compliance with
|
|
|
|
# the License. You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
#
|
|
|
|
|
|
|
|
|
2023-04-28 02:32:19 +08:00
|
|
|
from collections import defaultdict
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
|
def prompt_for_user():
|
|
|
|
while True:
|
|
|
|
try:
|
2023-09-06 15:28:23 +08:00
|
|
|
user_input = input("\nName or email (case insensitive): ")
|
2023-04-28 02:32:19 +08:00
|
|
|
except (KeyboardInterrupt, EOFError):
|
|
|
|
return None
|
|
|
|
clean_input = user_input.strip().lower()
|
|
|
|
if clean_input != "":
|
|
|
|
return clean_input
|
|
|
|
|
|
|
|
|
2025-03-12 21:41:05 +08:00
|
|
|
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""")
|
MINOR: Improve the prompt in committer-tools/reviewers.py (#19942)
Previously, the confirmation prompt for updating the PR body treated any
input other than 'n' as approval, which could lead to unintended
actions.
With this change, the update will only proceed if the user enters 'y',
'Y', or presses Enter. For any other input, the operation is canceled
and an Abort. message is printed. This makes the prompt behavior clearer
and more predictable.
Reviewers: TengYao Chi <frankvicky@apache.org>, PoAn Yang
<payang@apache.org>, Kuan-Po Tseng <brandboat@gmail.com>, Ken Huang
<s7133700@gmail.com>, Lan Ding <isDing_L@163.com>
2025-06-10 17:37:57 +08:00
|
|
|
choice = input(f'Update the body of "{pr_title}"? [Y/n] ').strip().lower()
|
|
|
|
if choice not in ['', 'y']:
|
|
|
|
print("Abort.")
|
2025-03-12 21:41:05 +08:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2023-04-28 02:32:19 +08:00
|
|
|
if __name__ == "__main__":
|
|
|
|
print("Utility to help generate 'Reviewers' string for Pull Requests. Use Ctrl+D or Ctrl+C to exit")
|
2023-09-06 15:28:23 +08:00
|
|
|
|
2024-07-29 18:36:35 +08:00
|
|
|
command = r"git log | grep 'Reviewers\|Author'"
|
|
|
|
stream = os.popen(command)
|
2023-04-28 02:32:19 +08:00
|
|
|
lines = stream.readlines()
|
|
|
|
all_reviewers = defaultdict(int)
|
|
|
|
for line in lines:
|
2024-07-29 18:36:35 +08:00
|
|
|
stripped = line.strip().lstrip("Reviewers: ").lstrip("Author: ")
|
2023-04-28 02:32:19 +08:00
|
|
|
reviewers = stripped.split(",")
|
|
|
|
for reviewer in reviewers:
|
2023-09-06 15:28:23 +08:00
|
|
|
all_reviewers[reviewer.strip()] += 1
|
2023-04-28 02:32:19 +08:00
|
|
|
parsed_reviewers = []
|
|
|
|
|
|
|
|
for item in all_reviewers.items():
|
2024-07-29 18:36:35 +08:00
|
|
|
patterns = r"(?P<name>.*)\s<(?P<email>.*)>"
|
|
|
|
m = re.match(patterns, item[0])
|
2023-04-28 02:32:19 +08:00
|
|
|
if m is not None and len(m.groups()) == 2:
|
|
|
|
if item[1] > 2:
|
|
|
|
parsed_reviewers.append((m.group("name"), m.group("email"), item[1]))
|
2023-09-06 15:28:23 +08:00
|
|
|
|
2023-04-28 02:32:19 +08:00
|
|
|
selected_reviewers = []
|
|
|
|
while True:
|
2023-07-11 17:01:50 +08:00
|
|
|
if selected_reviewers:
|
2023-04-28 02:32:19 +08:00
|
|
|
print(f"Reviewers so far: {selected_reviewers}")
|
|
|
|
user_input = prompt_for_user()
|
|
|
|
if user_input is None:
|
|
|
|
break
|
|
|
|
candidates = []
|
|
|
|
for reviewer, email, count in parsed_reviewers:
|
|
|
|
if reviewer.lower().startswith(user_input) or email.lower().startswith(user_input):
|
|
|
|
candidates.append((reviewer, email, count))
|
|
|
|
if len(candidates) == 10:
|
|
|
|
break
|
2023-07-11 17:01:50 +08:00
|
|
|
if not candidates:
|
2023-04-28 02:32:19 +08:00
|
|
|
continue
|
|
|
|
|
|
|
|
print("\nPossible matches (in order of most recent):")
|
|
|
|
for i, candidate in zip(range(10), candidates):
|
|
|
|
print(f"[{i+1}] {candidate[0]} {candidate[1]} ({candidate[2]})")
|
|
|
|
|
|
|
|
try:
|
|
|
|
selection_input = input("\nMake a selection: ")
|
|
|
|
selected_candidate = candidates[int(selection_input)-1]
|
|
|
|
selected_reviewers.append(selected_candidate)
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
|
|
break
|
|
|
|
except (ValueError, IndexError):
|
|
|
|
print("Invalid selection")
|
|
|
|
continue
|
|
|
|
|
2023-07-11 17:01:50 +08:00
|
|
|
if selected_reviewers:
|
2025-03-12 21:41:05 +08:00
|
|
|
reviewer_message = "Reviewers: "
|
|
|
|
reviewer_message += ", ".join([f"{name} <{email}>" for name, email, _ in selected_reviewers])
|
|
|
|
print(f"\n\n{reviewer_message}\n")
|
2023-04-28 02:32:19 +08:00
|
|
|
|
2025-03-12 21:41:05 +08:00
|
|
|
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)
|