mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-02 11:50:37 +08:00
persistent release notes
This commit is contained in:
@ -138,6 +138,34 @@ class GitProvider(ABC):
|
|||||||
final_update_message=True):
|
final_update_message=True):
|
||||||
self.publish_comment(pr_comment)
|
self.publish_comment(pr_comment)
|
||||||
|
|
||||||
|
def publish_persistent_comment_full(self, pr_comment: str,
|
||||||
|
initial_header: str,
|
||||||
|
update_header: bool = True,
|
||||||
|
name='review',
|
||||||
|
final_update_message=True):
|
||||||
|
try:
|
||||||
|
prev_comments = list(self.get_issue_comments())
|
||||||
|
for comment in prev_comments:
|
||||||
|
if comment.body.startswith(initial_header):
|
||||||
|
latest_commit_url = self.get_latest_commit_url()
|
||||||
|
comment_url = self.get_comment_url(comment)
|
||||||
|
if update_header:
|
||||||
|
updated_header = f"{initial_header}\n\n#### ({name.capitalize()} updated until commit {latest_commit_url})\n"
|
||||||
|
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
|
||||||
|
else:
|
||||||
|
pr_comment_updated = pr_comment
|
||||||
|
get_logger().info(f"Persistent mode - updating comment {comment_url} to latest {name} message")
|
||||||
|
# response = self.mr.notes.update(comment.id, {'body': pr_comment_updated})
|
||||||
|
self.edit_comment(comment, pr_comment_updated)
|
||||||
|
if final_update_message:
|
||||||
|
self.publish_comment(
|
||||||
|
f"**[Persistent {name}]({comment_url})** updated to latest commit {latest_commit_url}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
get_logger().exception(f"Failed to update persistent review, error: {e}")
|
||||||
|
pass
|
||||||
|
self.publish_comment(pr_comment)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
pass
|
pass
|
||||||
|
@ -231,24 +231,7 @@ class GithubProvider(GitProvider):
|
|||||||
update_header: bool = True,
|
update_header: bool = True,
|
||||||
name='review',
|
name='review',
|
||||||
final_update_message=True):
|
final_update_message=True):
|
||||||
prev_comments = list(self.pr.get_issue_comments())
|
self.publish_persistent_comment_full(pr_comment, initial_header, update_header, name, final_update_message)
|
||||||
for comment in prev_comments:
|
|
||||||
body = comment.body
|
|
||||||
if body.startswith(initial_header):
|
|
||||||
latest_commit_url = self.get_latest_commit_url()
|
|
||||||
comment_url = self.get_comment_url(comment)
|
|
||||||
if update_header:
|
|
||||||
updated_header = f"{initial_header}\n\n#### ({name.capitalize()} updated until commit {latest_commit_url})\n"
|
|
||||||
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
|
|
||||||
else:
|
|
||||||
pr_comment_updated = pr_comment
|
|
||||||
get_logger().info(f"Persistent mode- updating comment {comment_url} to latest review message")
|
|
||||||
response = comment.edit(pr_comment_updated)
|
|
||||||
if final_update_message:
|
|
||||||
self.publish_comment(
|
|
||||||
f"**[Persistent {name}]({comment_url})** updated to latest commit {latest_commit_url}")
|
|
||||||
return
|
|
||||||
self.publish_comment(pr_comment)
|
|
||||||
|
|
||||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
||||||
if is_temporary and not get_settings().config.publish_output_progress:
|
if is_temporary and not get_settings().config.publish_output_progress:
|
||||||
|
@ -173,26 +173,7 @@ class GitLabProvider(GitProvider):
|
|||||||
update_header: bool = True,
|
update_header: bool = True,
|
||||||
name='review',
|
name='review',
|
||||||
final_update_message=True):
|
final_update_message=True):
|
||||||
try:
|
self.publish_persistent_comment_full(pr_comment, initial_header, update_header, name, final_update_message)
|
||||||
for comment in self.mr.notes.list(get_all=True)[::-1]:
|
|
||||||
if comment.body.startswith(initial_header):
|
|
||||||
latest_commit_url = self.get_latest_commit_url()
|
|
||||||
comment_url = self.get_comment_url(comment)
|
|
||||||
if update_header:
|
|
||||||
updated_header = f"{initial_header}\n\n#### ({name.capitalize()} updated until commit {latest_commit_url})\n"
|
|
||||||
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
|
|
||||||
else:
|
|
||||||
pr_comment_updated = pr_comment
|
|
||||||
get_logger().info(f"Persistent mode - updating comment {comment_url} to latest {name} message")
|
|
||||||
response = self.mr.notes.update(comment.id, {'body': pr_comment_updated})
|
|
||||||
if final_update_message:
|
|
||||||
self.publish_comment(
|
|
||||||
f"**[Persistent {name}]({comment_url})** updated to latest commit {latest_commit_url}")
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
get_logger().exception(f"Failed to update persistent review, error: {e}")
|
|
||||||
pass
|
|
||||||
self.publish_comment(pr_comment)
|
|
||||||
|
|
||||||
def publish_comment(self, mr_comment: str, is_temporary: bool = False):
|
def publish_comment(self, mr_comment: str, is_temporary: bool = False):
|
||||||
comment = self.mr.notes.create({'body': mr_comment})
|
comment = self.mr.notes.create({'body': mr_comment})
|
||||||
@ -203,6 +184,11 @@ class GitLabProvider(GitProvider):
|
|||||||
def edit_comment(self, comment, body: str):
|
def edit_comment(self, comment, body: str):
|
||||||
self.mr.notes.update(comment.id,{'body': body} )
|
self.mr.notes.update(comment.id,{'body': body} )
|
||||||
|
|
||||||
|
def edit_comment_from_comment_id(self, comment_id: int, body: str):
|
||||||
|
comment = self.mr.notes.get(comment_id)
|
||||||
|
comment.body = body
|
||||||
|
comment.save()
|
||||||
|
|
||||||
def reply_to_comment_from_comment_id(self, comment_id: int, body: str):
|
def reply_to_comment_from_comment_id(self, comment_id: int, body: str):
|
||||||
discussion = self.mr.discussions.get(comment_id)
|
discussion = self.mr.discussions.get(comment_id)
|
||||||
discussion.notes.create({'body': body})
|
discussion.notes.create({'body': body})
|
||||||
@ -219,6 +205,10 @@ class GitLabProvider(GitProvider):
|
|||||||
def create_inline_comments(self, comments: list[dict]):
|
def create_inline_comments(self, comments: list[dict]):
|
||||||
raise NotImplementedError("Gitlab provider does not support publishing inline comments yet")
|
raise NotImplementedError("Gitlab provider does not support publishing inline comments yet")
|
||||||
|
|
||||||
|
def get_comment_body_from_comment_id(self, comment_id: int):
|
||||||
|
comment = self.mr.notes.get(comment_id)
|
||||||
|
return comment
|
||||||
|
|
||||||
def send_inline_comment(self,body: str,edit_type: str,found: bool,relevant_file: str,relevant_line_in_file: int,
|
def send_inline_comment(self,body: str,edit_type: str,found: bool,relevant_file: str,relevant_line_in_file: int,
|
||||||
source_line_no: int, target_file: str,target_line_no: int) -> None:
|
source_line_no: int, target_file: str,target_line_no: int) -> None:
|
||||||
if not found:
|
if not found:
|
||||||
@ -381,7 +371,7 @@ class GitLabProvider(GitProvider):
|
|||||||
return self.mr.description
|
return self.mr.description
|
||||||
|
|
||||||
def get_issue_comments(self):
|
def get_issue_comments(self):
|
||||||
raise NotImplementedError("GitLab provider does not support issue comments yet")
|
return self.mr.notes.list(get_all=True)[::-1]
|
||||||
|
|
||||||
def get_repo_settings(self):
|
def get_repo_settings(self):
|
||||||
try:
|
try:
|
||||||
|
@ -92,7 +92,8 @@ commitable_code_suggestions = false
|
|||||||
extra_instructions = ""
|
extra_instructions = ""
|
||||||
rank_suggestions = false
|
rank_suggestions = false
|
||||||
enable_help_text=false
|
enable_help_text=false
|
||||||
persistent_comment=false
|
persistent_comment=true
|
||||||
|
max_history_len=4
|
||||||
# enable to apply suggestion 💎
|
# enable to apply suggestion 💎
|
||||||
apply_suggestions_checkbox=true
|
apply_suggestions_checkbox=true
|
||||||
# suggestions scoring
|
# suggestions scoring
|
||||||
|
@ -17,6 +17,7 @@ from pr_agent.log import get_logger
|
|||||||
from pr_agent.servers.help import HelpMessage
|
from pr_agent.servers.help import HelpMessage
|
||||||
from pr_agent.tools.pr_description import insert_br_after_x_chars
|
from pr_agent.tools.pr_description import insert_br_after_x_chars
|
||||||
import difflib
|
import difflib
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class PRCodeSuggestions:
|
class PRCodeSuggestions:
|
||||||
@ -132,11 +133,12 @@ class PRCodeSuggestions:
|
|||||||
|
|
||||||
if get_settings().pr_code_suggestions.persistent_comment:
|
if get_settings().pr_code_suggestions.persistent_comment:
|
||||||
final_update_message = False
|
final_update_message = False
|
||||||
self.git_provider.publish_persistent_comment(pr_body,
|
self.publish_persistent_comment_with_history(pr_body,
|
||||||
initial_header="## PR Code Suggestions ✨",
|
initial_header="## PR Code Suggestions ✨",
|
||||||
update_header=True,
|
update_header=True,
|
||||||
name="suggestions",
|
name="suggestions",
|
||||||
final_update_message=final_update_message, )
|
final_update_message = final_update_message,
|
||||||
|
max_previous_comments = get_settings().pr_code_suggestions.max_history_len)
|
||||||
if self.progress_response:
|
if self.progress_response:
|
||||||
self.progress_response.delete()
|
self.progress_response.delete()
|
||||||
else:
|
else:
|
||||||
@ -160,6 +162,101 @@ class PRCodeSuggestions:
|
|||||||
self.git_provider.publish_comment(f"Failed to generate code suggestions for PR")
|
self.git_provider.publish_comment(f"Failed to generate code suggestions for PR")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
def publish_persistent_comment_with_history(self, pr_comment: str,
|
||||||
|
initial_header: str,
|
||||||
|
update_header: bool = True,
|
||||||
|
name='review',
|
||||||
|
final_update_message=True,
|
||||||
|
max_previous_comments=4):
|
||||||
|
history_header = f"#### Previous Suggestions\n"
|
||||||
|
last_commit_num = self.git_provider.get_latest_commit_url().split('/')[-1][:7]
|
||||||
|
latest_suggestion_header = f"Latest Suggestions up to {last_commit_num}"
|
||||||
|
latest_commit_html_comment = f"<!-- {last_commit_num} -->"
|
||||||
|
found_comment = None
|
||||||
|
|
||||||
|
if max_previous_comments > 0:
|
||||||
|
try:
|
||||||
|
prev_comments = list(self.git_provider.get_issue_comments())
|
||||||
|
for comment in prev_comments:
|
||||||
|
if comment.body.startswith(initial_header):
|
||||||
|
prev_suggestions = comment.body
|
||||||
|
found_comment = comment
|
||||||
|
comment_url = self.git_provider.get_comment_url(comment)
|
||||||
|
|
||||||
|
if history_header.strip() not in comment.body:
|
||||||
|
# no history section
|
||||||
|
# extract everything between <table> and </table> in comment.body including <table> and </table>
|
||||||
|
table_index = comment.body.find("<table>")
|
||||||
|
if table_index == -1:
|
||||||
|
self.git_provider.edit_comment(comment, pr_comment)
|
||||||
|
continue
|
||||||
|
# find http link from comment.body[:table_index]
|
||||||
|
up_to_commit_txt = self.extract_link(comment.body[:table_index])
|
||||||
|
prev_suggestion_table = comment.body[table_index:comment.body.rfind("</table>") + len("</table>")]
|
||||||
|
|
||||||
|
tick = "✅ " if "✅" in prev_suggestion_table else ""
|
||||||
|
# surround with details tag
|
||||||
|
prev_suggestion_table = f"<details><summary>{tick}{name.capitalize()}{up_to_commit_txt}</summary>\n<br>{prev_suggestion_table}\n\n</details>"
|
||||||
|
|
||||||
|
new_suggestion_table = pr_comment.replace(initial_header, "").strip()
|
||||||
|
|
||||||
|
pr_comment_updated = f"{initial_header}\n{latest_commit_html_comment}\n\n"
|
||||||
|
pr_comment_updated += f"{latest_suggestion_header}\n{new_suggestion_table}\n\n___\n\n"
|
||||||
|
pr_comment_updated += f"{history_header}{prev_suggestion_table}\n"
|
||||||
|
else:
|
||||||
|
# get the text of the previous suggestions until the latest commit
|
||||||
|
sections = prev_suggestions.split(history_header.strip())
|
||||||
|
latest_table = sections[0].strip()
|
||||||
|
prev_suggestion_table = sections[1].replace(history_header, "").strip()
|
||||||
|
|
||||||
|
# get text after the latest_suggestion_header in comment.body
|
||||||
|
table_ind = latest_table.find("<table>")
|
||||||
|
up_to_commit_txt = self.extract_link(latest_table[:table_ind])
|
||||||
|
|
||||||
|
latest_table = latest_table[table_ind:latest_table.rfind("</table>") + len("</table>")]
|
||||||
|
# enforce max_previous_comments
|
||||||
|
count = prev_suggestions.count(f"\n<details><summary>{name.capitalize()}")
|
||||||
|
count += prev_suggestions.count(f"\n<details><summary>✅ {name.capitalize()}")
|
||||||
|
if count >= max_previous_comments:
|
||||||
|
# remove the oldest suggestion
|
||||||
|
prev_suggestion_table = prev_suggestion_table[:prev_suggestion_table.rfind(f"<details><summary>{name.capitalize()} up to commit")]
|
||||||
|
|
||||||
|
tick = "✅ " if "✅" in latest_table else ""
|
||||||
|
# Add to the prev_suggestions section
|
||||||
|
last_prev_table = f"\n<details><summary>{tick}{name.capitalize()}{up_to_commit_txt}</summary>\n<br>{latest_table}\n\n</details>"
|
||||||
|
prev_suggestion_table = last_prev_table + "\n" + prev_suggestion_table
|
||||||
|
new_suggestion_table = pr_comment.replace(initial_header, "").strip()
|
||||||
|
|
||||||
|
pr_comment_updated = f"{initial_header}\n"
|
||||||
|
pr_comment_updated += f"{latest_commit_html_comment}\n\n"
|
||||||
|
pr_comment_updated += f"{latest_suggestion_header}\n\n{new_suggestion_table}\n\n"
|
||||||
|
pr_comment_updated += "___\n\n"
|
||||||
|
pr_comment_updated += f"{history_header}\n"
|
||||||
|
pr_comment_updated += f"{prev_suggestion_table}\n"
|
||||||
|
|
||||||
|
get_logger().info(f"Persistent mode - updating comment {comment_url} to latest {name} message")
|
||||||
|
|
||||||
|
self.git_provider.edit_comment(comment, pr_comment_updated)
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
get_logger().exception(f"Failed to update persistent review, error: {e}")
|
||||||
|
pass
|
||||||
|
|
||||||
|
body = pr_comment.replace(initial_header, "").strip()
|
||||||
|
pr_comment = f"{initial_header}\n\n{latest_commit_html_comment}\n\n{body}\n\n"
|
||||||
|
if found_comment is not None:
|
||||||
|
self.git_provider.edit_comment(found_comment, pr_comment)
|
||||||
|
else:
|
||||||
|
self.git_provider.publish_comment(pr_comment)
|
||||||
|
|
||||||
|
def extract_link(self, s):
|
||||||
|
r = re.compile(r"<!--.*?-->")
|
||||||
|
match = r.search(s)
|
||||||
|
|
||||||
|
up_to_commit_txt = ""
|
||||||
|
if match:
|
||||||
|
up_to_commit_txt = f" up to commit {match.group(0)[4:-3].strip()}"
|
||||||
|
return up_to_commit_txt
|
||||||
|
|
||||||
async def _prepare_prediction(self, model: str) -> dict:
|
async def _prepare_prediction(self, model: str) -> dict:
|
||||||
self.patches_diff = get_pr_diff(self.git_provider,
|
self.patches_diff = get_pr_diff(self.git_provider,
|
||||||
|
Reference in New Issue
Block a user