mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-04 21:00:40 +08:00
Merge pull request #1153 from Codium-ai/tr/protections2
Tr/protections2
This commit is contained in:
@ -130,18 +130,6 @@ class AzureDevopsProvider(GitProvider):
|
|||||||
def get_pr_description_full(self) -> str:
|
def get_pr_description_full(self) -> str:
|
||||||
return self.pr.description
|
return self.pr.description
|
||||||
|
|
||||||
def delete_comment(self, comment):
|
|
||||||
try:
|
|
||||||
self.azure_devops_client.delete_comment(
|
|
||||||
repository_id=self.repo_slug,
|
|
||||||
pull_request_id=self.pr_num,
|
|
||||||
thread_id=comment.thread_id,
|
|
||||||
comment_id=comment.id,
|
|
||||||
project=self.workspace_slug,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
get_logger().exception(f"Failed to delete comment, error: {e}")
|
|
||||||
|
|
||||||
def edit_comment(self, comment, body: str):
|
def edit_comment(self, comment, body: str):
|
||||||
try:
|
try:
|
||||||
self.azure_devops_client.update_comment(
|
self.azure_devops_client.update_comment(
|
||||||
|
@ -208,9 +208,6 @@ class GitProvider(ABC):
|
|||||||
def get_comment_url(self, comment) -> str:
|
def get_comment_url(self, comment) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def delete_comment(self, comment):
|
|
||||||
comment.delete()
|
|
||||||
|
|
||||||
#### labels operations ####
|
#### labels operations ####
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def publish_labels(self, labels):
|
def publish_labels(self, labels):
|
||||||
|
@ -452,8 +452,8 @@ class GithubProvider(GitProvider):
|
|||||||
|
|
||||||
def edit_comment_from_comment_id(self, comment_id: int, body: str):
|
def edit_comment_from_comment_id(self, comment_id: int, body: str):
|
||||||
try:
|
try:
|
||||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
|
||||||
# self.pr.get_issue_comment(comment_id).edit(body)
|
# self.pr.get_issue_comment(comment_id).edit(body)
|
||||||
|
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||||
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
||||||
"PATCH", f"{self.base_url}/repos/{self.repo}/issues/comments/{comment_id}",
|
"PATCH", f"{self.base_url}/repos/{self.repo}/issues/comments/{comment_id}",
|
||||||
input={"body": body}
|
input={"body": body}
|
||||||
@ -463,8 +463,8 @@ class GithubProvider(GitProvider):
|
|||||||
|
|
||||||
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):
|
||||||
try:
|
try:
|
||||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
|
||||||
# self.pr.get_issue_comment(comment_id).edit(body)
|
# self.pr.get_issue_comment(comment_id).edit(body)
|
||||||
|
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||||
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
||||||
"POST", f"{self.base_url}/repos/{self.repo}/pulls/{self.pr_num}/comments/{comment_id}/replies",
|
"POST", f"{self.base_url}/repos/{self.repo}/pulls/{self.pr_num}/comments/{comment_id}/replies",
|
||||||
input={"body": body}
|
input={"body": body}
|
||||||
@ -490,6 +490,7 @@ class GithubProvider(GitProvider):
|
|||||||
)
|
)
|
||||||
for comment in file_comments:
|
for comment in file_comments:
|
||||||
comment['commit_id'] = self.last_commit_id.sha
|
comment['commit_id'] = self.last_commit_id.sha
|
||||||
|
comment['body'] = self.limit_output_characters(comment['body'], self.max_comment_chars)
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
for existing_comment in existing_comments:
|
for existing_comment in existing_comments:
|
||||||
@ -592,7 +593,7 @@ class GithubProvider(GitProvider):
|
|||||||
)
|
)
|
||||||
return data_patch.get("id", None)
|
return data_patch.get("id", None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().exception(f"Failed to add eyes reaction, error: {e}")
|
get_logger().warning(f"Failed to add eyes reaction, error: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def remove_reaction(self, issue_comment_id: int, reaction_id: str) -> bool:
|
def remove_reaction(self, issue_comment_id: int, reaction_id: str) -> bool:
|
||||||
@ -829,8 +830,11 @@ class GithubProvider(GitProvider):
|
|||||||
"""
|
"""
|
||||||
line_start = component_range.line_start + 1
|
line_start = component_range.line_start + 1
|
||||||
line_end = component_range.line_end + 1
|
line_end = component_range.line_end + 1
|
||||||
|
# link = (f"https://github.com/{self.repo}/blob/{self.last_commit_id.sha}/{filepath}/"
|
||||||
|
# f"#L{line_start}-L{line_end}")
|
||||||
link = (f"{self.base_url_html}/{self.repo}/blob/{self.last_commit_id.sha}/{filepath}/"
|
link = (f"{self.base_url_html}/{self.repo}/blob/{self.last_commit_id.sha}/{filepath}/"
|
||||||
f"#L{line_start}-L{line_end}")
|
f"#L{line_start}-L{line_end}")
|
||||||
|
|
||||||
return link
|
return link
|
||||||
|
|
||||||
def get_pr_id(self):
|
def get_pr_id(self):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import traceback
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@ -35,6 +36,7 @@ async def polling_loop():
|
|||||||
user_id = git_provider.get_user_id()
|
user_id = git_provider.get_user_id()
|
||||||
agent = PRAgent()
|
agent = PRAgent()
|
||||||
get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False)
|
get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False)
|
||||||
|
get_settings().set("pr_description.publish_description_as_comment", True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
deployment_type = get_settings().github.deployment_type
|
deployment_type = get_settings().github.deployment_type
|
||||||
@ -92,7 +94,8 @@ async def polling_loop():
|
|||||||
comment_body = comment['body'] if 'body' in comment else ''
|
comment_body = comment['body'] if 'body' in comment else ''
|
||||||
commenter_github_user = comment['user']['login'] \
|
commenter_github_user = comment['user']['login'] \
|
||||||
if 'user' in comment else ''
|
if 'user' in comment else ''
|
||||||
get_logger().info(f"Commenter: {commenter_github_user}\nComment: {comment_body}")
|
get_logger().info(f"Polling, pr_url: {pr_url}",
|
||||||
|
artifact={"comment": comment_body})
|
||||||
user_tag = "@" + user_id
|
user_tag = "@" + user_id
|
||||||
if user_tag not in comment_body:
|
if user_tag not in comment_body:
|
||||||
continue
|
continue
|
||||||
@ -100,7 +103,8 @@ async def polling_loop():
|
|||||||
comment_id = comment['id']
|
comment_id = comment['id']
|
||||||
git_provider.set_pr(pr_url)
|
git_provider.set_pr(pr_url)
|
||||||
success = await agent.handle_request(pr_url, rest_of_comment,
|
success = await agent.handle_request(pr_url, rest_of_comment,
|
||||||
notify=lambda: git_provider.add_eyes_reaction(comment_id)) # noqa E501
|
notify=lambda: git_provider.add_eyes_reaction(
|
||||||
|
comment_id)) # noqa E501
|
||||||
if not success:
|
if not success:
|
||||||
git_provider.set_pr(pr_url)
|
git_provider.set_pr(pr_url)
|
||||||
|
|
||||||
@ -108,7 +112,8 @@ async def polling_loop():
|
|||||||
print(f"Failed to fetch notifications. Status code: {response.status}")
|
print(f"Failed to fetch notifications. Status code: {response.status}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().error(f"Exception during processing of a notification: {e}")
|
get_logger().error(f"Polling exception during processing of a notification: {e}",
|
||||||
|
artifact={"traceback": traceback.format_exc()})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -52,6 +52,7 @@ class PRCodeSuggestions:
|
|||||||
self.ai_handler.main_pr_language = self.main_language
|
self.ai_handler.main_pr_language = self.main_language
|
||||||
self.patches_diff = None
|
self.patches_diff = None
|
||||||
self.prediction = None
|
self.prediction = None
|
||||||
|
self.pr_url = pr_url
|
||||||
self.cli_mode = cli_mode
|
self.cli_mode = cli_mode
|
||||||
self.vars = {
|
self.vars = {
|
||||||
"title": self.git_provider.pr.title,
|
"title": self.git_provider.pr.title,
|
||||||
@ -81,6 +82,10 @@ class PRCodeSuggestions:
|
|||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
try:
|
try:
|
||||||
|
if not self.git_provider.get_files():
|
||||||
|
get_logger().info(f"PR has no files: {self.pr_url}, skipping code suggestions")
|
||||||
|
return None
|
||||||
|
|
||||||
get_logger().info('Generating code suggestions for PR...')
|
get_logger().info('Generating code suggestions for PR...')
|
||||||
relevant_configs = {'pr_code_suggestions': dict(get_settings().pr_code_suggestions),
|
relevant_configs = {'pr_code_suggestions': dict(get_settings().pr_code_suggestions),
|
||||||
'config': dict(get_settings().config)}
|
'config': dict(get_settings().config)}
|
||||||
@ -159,6 +164,8 @@ class PRCodeSuggestions:
|
|||||||
self.push_inline_code_suggestions(data)
|
self.push_inline_code_suggestions(data)
|
||||||
if self.progress_response:
|
if self.progress_response:
|
||||||
self.progress_response.delete()
|
self.progress_response.delete()
|
||||||
|
else:
|
||||||
|
get_logger().info('Code suggestions generated for PR, but not published since publish_output is False.')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().error(f"Failed to generate code suggestions for PR, error: {e}")
|
get_logger().error(f"Failed to generate code suggestions for PR, error: {e}")
|
||||||
if self.progress_response:
|
if self.progress_response:
|
||||||
@ -177,6 +184,7 @@ class PRCodeSuggestions:
|
|||||||
final_update_message=True,
|
final_update_message=True,
|
||||||
max_previous_comments=4,
|
max_previous_comments=4,
|
||||||
progress_response=None):
|
progress_response=None):
|
||||||
|
|
||||||
if isinstance(self.git_provider, AzureDevopsProvider): # get_latest_commit_url is not supported yet
|
if isinstance(self.git_provider, AzureDevopsProvider): # get_latest_commit_url is not supported yet
|
||||||
if progress_response:
|
if progress_response:
|
||||||
self.git_provider.edit_comment(progress_response, pr_comment)
|
self.git_provider.edit_comment(progress_response, pr_comment)
|
||||||
@ -256,7 +264,7 @@ class PRCodeSuggestions:
|
|||||||
get_logger().info(f"Persistent mode - updating comment {comment_url} to latest {name} message")
|
get_logger().info(f"Persistent mode - updating comment {comment_url} to latest {name} message")
|
||||||
if progress_response: # publish to 'progress_response' comment, because it refreshes immediately
|
if progress_response: # publish to 'progress_response' comment, because it refreshes immediately
|
||||||
self.git_provider.edit_comment(progress_response, pr_comment_updated)
|
self.git_provider.edit_comment(progress_response, pr_comment_updated)
|
||||||
self.git_provider.delete_comment(comment)
|
self.git_provider.remove_comment(comment)
|
||||||
else:
|
else:
|
||||||
self.git_provider.edit_comment(comment, pr_comment_updated)
|
self.git_provider.edit_comment(comment, pr_comment_updated)
|
||||||
return
|
return
|
||||||
@ -361,12 +369,14 @@ class PRCodeSuggestions:
|
|||||||
one_sentence_summary_list = []
|
one_sentence_summary_list = []
|
||||||
for i, suggestion in enumerate(data['code_suggestions']):
|
for i, suggestion in enumerate(data['code_suggestions']):
|
||||||
try:
|
try:
|
||||||
needed_keys = ['one_sentence_summary', 'label', 'relevant_file', 'relevant_lines_start', 'relevant_lines_end']
|
needed_keys = ['one_sentence_summary', 'label', 'relevant_file', 'relevant_lines_start',
|
||||||
|
'relevant_lines_end']
|
||||||
is_valid_keys = True
|
is_valid_keys = True
|
||||||
for key in needed_keys:
|
for key in needed_keys:
|
||||||
if key not in suggestion:
|
if key not in suggestion:
|
||||||
is_valid_keys = False
|
is_valid_keys = False
|
||||||
get_logger().debug(f"Skipping suggestion {i + 1}, because it does not contain '{key}':\n'{suggestion}")
|
get_logger().debug(
|
||||||
|
f"Skipping suggestion {i + 1}, because it does not contain '{key}':\n'{suggestion}")
|
||||||
break
|
break
|
||||||
if not is_valid_keys:
|
if not is_valid_keys:
|
||||||
continue
|
continue
|
||||||
@ -529,7 +539,7 @@ class PRCodeSuggestions:
|
|||||||
get_logger().error(f"Error getting PR diff for suggestion {i} in call {j}, error: {e}")
|
get_logger().error(f"Error getting PR diff for suggestion {i} in call {j}, error: {e}")
|
||||||
self.data = data
|
self.data = data
|
||||||
else:
|
else:
|
||||||
get_logger().error(f"Error getting PR diff")
|
get_logger().warning(f"Empty PR diff list")
|
||||||
self.data = data = None
|
self.data = data = None
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -508,7 +508,8 @@ extra_file_yaml =
|
|||||||
|
|
||||||
def _prepare_file_labels(self):
|
def _prepare_file_labels(self):
|
||||||
file_label_dict = {}
|
file_label_dict = {}
|
||||||
if not self.data or 'pr_files' not in self.data:
|
if (not self.data or not isinstance(self.data, dict) or
|
||||||
|
'pr_files' not in self.data or not self.data['pr_files']):
|
||||||
return file_label_dict
|
return file_label_dict
|
||||||
for file in self.data['pr_files']:
|
for file in self.data['pr_files']:
|
||||||
try:
|
try:
|
||||||
|
@ -96,6 +96,10 @@ class PRReviewer:
|
|||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
try:
|
try:
|
||||||
|
if not self.git_provider.get_files():
|
||||||
|
get_logger().info(f"PR has no files: {self.pr_url}, skipping review")
|
||||||
|
return None
|
||||||
|
|
||||||
if self.incremental.is_incremental and not self._can_run_incremental_review():
|
if self.incremental.is_incremental and not self._can_run_incremental_review():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -158,7 +162,7 @@ class PRReviewer:
|
|||||||
get_logger().debug(f"PR diff", diff=self.patches_diff)
|
get_logger().debug(f"PR diff", diff=self.patches_diff)
|
||||||
self.prediction = await self._get_prediction(model)
|
self.prediction = await self._get_prediction(model)
|
||||||
else:
|
else:
|
||||||
get_logger().error(f"Error getting PR diff")
|
get_logger().warning(f"Empty diff for PR: {self.pr_url}")
|
||||||
self.prediction = None
|
self.prediction = None
|
||||||
|
|
||||||
async def _get_prediction(self, model: str) -> str:
|
async def _get_prediction(self, model: str) -> str:
|
||||||
|
Reference in New Issue
Block a user