mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-04 21:00:40 +08:00
Merge remote-tracking branch 'origin/main' into tr/review_extra_labels
This commit is contained in:
@ -16,16 +16,18 @@ The `review` tool can also be triggered automatically every time a new PR is ope
|
|||||||
|
|
||||||
Under the section 'pr_reviewer', the [configuration file](./../pr_agent/settings/configuration.toml#L16) contains options to customize the 'review' tool:
|
Under the section 'pr_reviewer', the [configuration file](./../pr_agent/settings/configuration.toml#L16) contains options to customize the 'review' tool:
|
||||||
|
|
||||||
|
#### enable\\disable features
|
||||||
- `require_focused_review`: if set to true, the tool will add a section - 'is the PR a focused one'. Default is false.
|
- `require_focused_review`: if set to true, the tool will add a section - 'is the PR a focused one'. Default is false.
|
||||||
- `require_score_review`: if set to true, the tool will add a section that scores the PR. Default is false.
|
- `require_score_review`: if set to true, the tool will add a section that scores the PR. Default is false.
|
||||||
- `require_tests_review`: if set to true, the tool will add a section that checks if the PR contains tests. Default is true.
|
- `require_tests_review`: if set to true, the tool will add a section that checks if the PR contains tests. Default is true.
|
||||||
- `require_security_review`: if set to true, the tool will add a section that checks if the PR contains security issues. Default is true.
|
- `require_security_review`: if set to true, the tool will add a section that checks if the PR contains security issues. Default is true.
|
||||||
- `require_estimate_effort_to_review`: if set to true, the tool will add a section that estimates thed effort needed to review the PR. Default is true.
|
- `require_estimate_effort_to_review`: if set to true, the tool will add a section that estimates thed effort needed to review the PR. Default is true.
|
||||||
|
#### general options
|
||||||
- `num_code_suggestions`: number of code suggestions provided by the 'review' tool. Default is 4.
|
- `num_code_suggestions`: number of code suggestions provided by the 'review' tool. Default is 4.
|
||||||
- `inline_code_comments`: if set to true, the tool will publish the code suggestions as comments on the code diff. Default is false.
|
- `inline_code_comments`: if set to true, the tool will publish the code suggestions as comments on the code diff. Default is false.
|
||||||
- `automatic_review`: if set to false, no automatic reviews will be done. Default is true.
|
- `automatic_review`: if set to false, no automatic reviews will be done. Default is true.
|
||||||
- `remove_previous_review_comment`: if set to true, the tool will remove the previous review comment before adding a new one. Default is false.
|
- `remove_previous_review_comment`: if set to true, the tool will remove the previous review comment before adding a new one. Default is false.
|
||||||
- `persistent_comment`: if set to true, the review comment will be persistent. Default is true.
|
- `persistent_comment`: if set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.
|
||||||
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
|
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
|
||||||
- To enable `custom labels`, apply the configuration changes described [here](./GENERATE_CUSTOM_LABELS.md#configuration-changes)
|
- To enable `custom labels`, apply the configuration changes described [here](./GENERATE_CUSTOM_LABELS.md#configuration-changes)
|
||||||
#### Incremental Mode
|
#### Incremental Mode
|
||||||
|
@ -282,7 +282,7 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
|
|||||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||||
|
|
||||||
for file in diff_files:
|
for file in diff_files:
|
||||||
if file.filename.strip() == relevant_file:
|
if file.filename and (file.filename.strip() == relevant_file):
|
||||||
patch = file.patch
|
patch = file.patch
|
||||||
patch_lines = patch.splitlines()
|
patch_lines = patch.splitlines()
|
||||||
|
|
||||||
|
@ -153,17 +153,29 @@ class BitbucketProvider(GitProvider):
|
|||||||
self.diff_files = diff_files
|
self.diff_files = diff_files
|
||||||
return diff_files
|
return diff_files
|
||||||
|
|
||||||
def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
|
def get_latest_commit_url(self):
|
||||||
|
return self.pr.data['source']['commit']['links']['html']['href']
|
||||||
|
|
||||||
|
def get_comment_url(self, comment):
|
||||||
|
return comment.data['links']['html']['href']
|
||||||
|
|
||||||
|
def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool = True):
|
||||||
try:
|
try:
|
||||||
for comment in self.pr.comments():
|
for comment in self.pr.comments():
|
||||||
body = comment.raw
|
body = comment.raw
|
||||||
if initial_text in body:
|
if initial_header in body:
|
||||||
if updated_text:
|
latest_commit_url = self.get_latest_commit_url()
|
||||||
pr_comment_updated = pr_comment.replace(initial_text, updated_text)
|
comment_url = self.get_comment_url(comment)
|
||||||
|
if update_header:
|
||||||
|
updated_header = f"{initial_header}\n\n### (review updated until commit {latest_commit_url})\n"
|
||||||
|
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
|
||||||
else:
|
else:
|
||||||
pr_comment_updated = pr_comment
|
pr_comment_updated = pr_comment
|
||||||
|
get_logger().info(f"Persistent mode- updating comment {comment_url} to latest review message")
|
||||||
d = {"content": {"raw": pr_comment_updated}}
|
d = {"content": {"raw": pr_comment_updated}}
|
||||||
response = comment._update_data(comment.put(None, data=d))
|
response = comment._update_data(comment.put(None, data=d))
|
||||||
|
self.publish_comment(
|
||||||
|
f"**[Persistent review]({comment_url})** updated to latest commit {latest_commit_url}")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().exception(f"Failed to update persistent review, error: {e}")
|
get_logger().exception(f"Failed to update persistent review, error: {e}")
|
||||||
|
@ -40,45 +40,10 @@ class GitProvider(ABC):
|
|||||||
def publish_description(self, pr_title: str, pr_body: str):
|
def publish_description(self, pr_title: str, pr_body: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
|
|
||||||
self.publish_comment(pr_comment)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def publish_inline_comments(self, comments: list[dict]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def publish_code_suggestions(self, code_suggestions: list) -> bool:
|
def publish_code_suggestions(self, code_suggestions: list) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def publish_labels(self, labels):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_labels(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def remove_initial_comment(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def remove_comment(self, comment):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
pass
|
pass
|
||||||
@ -117,11 +82,54 @@ class GitProvider(ABC):
|
|||||||
return description.split("## User Description:", 1)[1].strip()
|
return description.split("## User Description:", 1)[1].strip()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_issue_comments(self):
|
def get_repo_settings(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_pr_id(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
#### comments operations ####
|
||||||
|
@abstractmethod
|
||||||
|
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool):
|
||||||
|
self.publish_comment(pr_comment)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_repo_settings(self):
|
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def publish_inline_comments(self, comments: list[dict]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def remove_initial_comment(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def remove_comment(self, comment):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_issue_comments(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_comment_url(self, comment) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
#### labels operations ####
|
||||||
|
@abstractmethod
|
||||||
|
def publish_labels(self, labels):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_labels(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -132,11 +140,12 @@ class GitProvider(ABC):
|
|||||||
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
|
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
#### commits operations ####
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_commit_messages(self):
|
def get_commit_messages(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_pr_id(self):
|
def get_latest_commit_url(self) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def get_main_pr_language(languages, files) -> str:
|
def get_main_pr_language(languages, files) -> str:
|
||||||
|
@ -154,16 +154,28 @@ class GithubProvider(GitProvider):
|
|||||||
def publish_description(self, pr_title: str, pr_body: str):
|
def publish_description(self, pr_title: str, pr_body: str):
|
||||||
self.pr.edit(title=pr_title, body=pr_body)
|
self.pr.edit(title=pr_title, body=pr_body)
|
||||||
|
|
||||||
def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
|
def get_latest_commit_url(self) -> str:
|
||||||
|
return self.last_commit_id.html_url
|
||||||
|
|
||||||
|
def get_comment_url(self, comment) -> str:
|
||||||
|
return comment.html_url
|
||||||
|
|
||||||
|
def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool = True):
|
||||||
prev_comments = list(self.pr.get_issue_comments())
|
prev_comments = list(self.pr.get_issue_comments())
|
||||||
for comment in prev_comments:
|
for comment in prev_comments:
|
||||||
body = comment.body
|
body = comment.body
|
||||||
if body.startswith(initial_text):
|
if body.startswith(initial_header):
|
||||||
if updated_text:
|
latest_commit_url = self.get_latest_commit_url()
|
||||||
pr_comment_updated = pr_comment.replace(initial_text, updated_text)
|
comment_url = self.get_comment_url(comment)
|
||||||
|
if update_header:
|
||||||
|
updated_header = f"{initial_header}\n\n### (review updated until commit {latest_commit_url})\n"
|
||||||
|
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
|
||||||
else:
|
else:
|
||||||
pr_comment_updated = pr_comment
|
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)
|
response = comment.edit(pr_comment_updated)
|
||||||
|
self.publish_comment(
|
||||||
|
f"**[Persistent review]({comment_url})** updated to latest commit {latest_commit_url}")
|
||||||
return
|
return
|
||||||
self.publish_comment(pr_comment)
|
self.publish_comment(pr_comment)
|
||||||
|
|
||||||
|
@ -136,15 +136,27 @@ class GitLabProvider(GitProvider):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().exception(f"Could not update merge request {self.id_mr} description: {e}")
|
get_logger().exception(f"Could not update merge request {self.id_mr} description: {e}")
|
||||||
|
|
||||||
def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
|
def get_latest_commit_url(self):
|
||||||
|
return self.mr.commits().next().web_url
|
||||||
|
|
||||||
|
def get_comment_url(self, comment):
|
||||||
|
return f"{self.mr.web_url}#note_{comment.id}"
|
||||||
|
|
||||||
|
def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool = True):
|
||||||
try:
|
try:
|
||||||
for comment in self.mr.notes.list(get_all=True)[::-1]:
|
for comment in self.mr.notes.list(get_all=True)[::-1]:
|
||||||
if comment.body.startswith(initial_text):
|
if comment.body.startswith(initial_header):
|
||||||
if updated_text:
|
latest_commit_url = self.get_latest_commit_url()
|
||||||
pr_comment_updated = pr_comment.replace(initial_text, updated_text)
|
comment_url = self.get_comment_url(comment)
|
||||||
|
if update_header:
|
||||||
|
updated_header = f"{initial_header}\n\n### (review updated until commit {latest_commit_url})\n"
|
||||||
|
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
|
||||||
else:
|
else:
|
||||||
pr_comment_updated = pr_comment
|
pr_comment_updated = pr_comment
|
||||||
|
get_logger().info(f"Persistent mode- updating comment {comment_url} to latest review message")
|
||||||
response = self.mr.notes.update(comment.id, {'body': pr_comment_updated})
|
response = self.mr.notes.update(comment.id, {'body': pr_comment_updated})
|
||||||
|
self.publish_comment(
|
||||||
|
f"**[Persistent review]({comment_url})** updated to latest commit {latest_commit_url}")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().exception(f"Failed to update persistent review, error: {e}")
|
get_logger().exception(f"Failed to update persistent review, error: {e}")
|
||||||
|
@ -158,6 +158,9 @@ class PRDescription:
|
|||||||
user=user_prompt
|
user=user_prompt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_settings().config.verbosity_level >= 2:
|
||||||
|
get_logger().info(f"\nAI response:\n{response}")
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _prepare_data(self):
|
def _prepare_data(self):
|
||||||
|
@ -121,8 +121,8 @@ class PRReviewer:
|
|||||||
# publish the review
|
# publish the review
|
||||||
if get_settings().pr_reviewer.persistent_comment and not self.incremental.is_incremental:
|
if get_settings().pr_reviewer.persistent_comment and not self.incremental.is_incremental:
|
||||||
self.git_provider.publish_persistent_comment(pr_comment,
|
self.git_provider.publish_persistent_comment(pr_comment,
|
||||||
initial_text="## PR Analysis",
|
initial_header="## PR Analysis",
|
||||||
updated_text="## PR Analysis (updated)")
|
update_header=True)
|
||||||
else:
|
else:
|
||||||
self.git_provider.publish_comment(pr_comment)
|
self.git_provider.publish_comment(pr_comment)
|
||||||
|
|
||||||
@ -178,6 +178,9 @@ class PRReviewer:
|
|||||||
user=user_prompt
|
user=user_prompt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_settings().config.verbosity_level >= 2:
|
||||||
|
get_logger().info(f"\nAI response:\n{response}")
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _prepare_pr_review(self) -> str:
|
def _prepare_pr_review(self) -> str:
|
||||||
|
Reference in New Issue
Block a user