mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-03 20:30:41 +08:00
implemented 'improve' command for CodeCommit
This commit is contained in:
@ -101,8 +101,8 @@ See the [usage guide](./Usage.md) for instructions how to run the different tool
|
|||||||
| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark:
|
| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark:
|
||||||
| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: |
|
| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: |
|
||||||
| | Improve Code | :white_check_mark: | :white_check_mark: | | | |
|
| | Improve Code | :white_check_mark: | :white_check_mark: | | :white_check_mark: | |
|
||||||
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | | |
|
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | :white_check_mark: | |
|
||||||
| | Reflect and Review | :white_check_mark: | | | | :white_check_mark: |
|
| | Reflect and Review | :white_check_mark: | | | | :white_check_mark: |
|
||||||
| | Update CHANGELOG.md | :white_check_mark: | | | | |
|
| | Update CHANGELOG.md | :white_check_mark: | | | | |
|
||||||
| | | | | | | |
|
| | | | | | | |
|
||||||
|
@ -90,7 +90,11 @@ class CodeCommitClient:
|
|||||||
):
|
):
|
||||||
differences.extend(page.get("differences", []))
|
differences.extend(page.get("differences", []))
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
raise ValueError(f"Failed to retrieve differences from CodeCommit PR #{self.pr_num}") from e
|
if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException':
|
||||||
|
raise ValueError(f"CodeCommit cannot retrieve differences: Repository does not exist: {repo_name}") from e
|
||||||
|
raise ValueError(f"CodeCommit cannot retrieve differences for {source_commit}..{destination_commit}") from e
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"CodeCommit cannot retrieve differences for {source_commit}..{destination_commit}") from e
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
for json in differences:
|
for json in differences:
|
||||||
@ -122,6 +126,8 @@ class CodeCommitClient:
|
|||||||
try:
|
try:
|
||||||
response = self.boto_client.get_file(repositoryName=repo_name, commitSpecifier=sha_hash, filePath=file_path)
|
response = self.boto_client.get_file(repositoryName=repo_name, commitSpecifier=sha_hash, filePath=file_path)
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
|
if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException':
|
||||||
|
raise ValueError(f"CodeCommit cannot retrieve PR: Repository does not exist: {repo_name}") from e
|
||||||
# if the file does not exist, but is flagged as optional, then return an empty string
|
# if the file does not exist, but is flagged as optional, then return an empty string
|
||||||
if optional and e.response["Error"]["Code"] == 'FileDoesNotExistException':
|
if optional and e.response["Error"]["Code"] == 'FileDoesNotExistException':
|
||||||
return ""
|
return ""
|
||||||
@ -133,11 +139,12 @@ class CodeCommitClient:
|
|||||||
|
|
||||||
return response.get("fileContent", "")
|
return response.get("fileContent", "")
|
||||||
|
|
||||||
def get_pr(self, pr_number: int):
|
def get_pr(self, repo_name: str, pr_number: int):
|
||||||
"""
|
"""
|
||||||
Get a information about a CodeCommit PR.
|
Get a information about a CodeCommit PR.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
- repo_name: Name of the repository
|
||||||
- pr_number: The PR number you are requesting
|
- pr_number: The PR number you are requesting
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -155,6 +162,8 @@ class CodeCommitClient:
|
|||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
if e.response["Error"]["Code"] == 'PullRequestDoesNotExistException':
|
if e.response["Error"]["Code"] == 'PullRequestDoesNotExistException':
|
||||||
raise ValueError(f"CodeCommit cannot retrieve PR: PR number does not exist: {pr_number}") from e
|
raise ValueError(f"CodeCommit cannot retrieve PR: PR number does not exist: {pr_number}") from e
|
||||||
|
if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException':
|
||||||
|
raise ValueError(f"CodeCommit cannot retrieve PR: Repository does not exist: {repo_name}") from e
|
||||||
raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}: boto client error") from e
|
raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}: boto client error") from e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}") from e
|
raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}") from e
|
||||||
@ -201,7 +210,7 @@ class CodeCommitClient:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Error calling publish_description") from e
|
raise ValueError(f"Error calling publish_description") from e
|
||||||
|
|
||||||
def publish_comment(self, repo_name: str, pr_number: int, destination_commit: str, source_commit: str, comment: str):
|
def publish_comment(self, repo_name: str, pr_number: int, destination_commit: str, source_commit: str, comment: str, annotation_file: str = None, annotation_line: int = None):
|
||||||
"""
|
"""
|
||||||
Publish a comment to a pull request
|
Publish a comment to a pull request
|
||||||
|
|
||||||
@ -210,7 +219,13 @@ class CodeCommitClient:
|
|||||||
- pr_number: number of the pull request
|
- pr_number: number of the pull request
|
||||||
- destination_commit: The commit hash you want to merge into (the "before" hash) (usually on the main or master branch)
|
- destination_commit: The commit hash you want to merge into (the "before" hash) (usually on the main or master branch)
|
||||||
- source_commit: The commit hash of the code you are adding (the "after" branch)
|
- source_commit: The commit hash of the code you are adding (the "after" branch)
|
||||||
- pr_comment: comment
|
- comment: The comment you want to publish
|
||||||
|
- annotation_file: The file you want to annotate (optional)
|
||||||
|
- annotation_line: The line number you want to annotate (optional)
|
||||||
|
|
||||||
|
Comment annotations for CodeCommit are different than GitHub.
|
||||||
|
CodeCommit only designates the starting line number for the comment.
|
||||||
|
It does not support the ending line number to highlight a range of lines.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- None
|
- None
|
||||||
@ -223,13 +238,30 @@ class CodeCommitClient:
|
|||||||
self._connect_boto_client()
|
self._connect_boto_client()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.boto_client.post_comment_for_pull_request(
|
# If the comment has code annotations,
|
||||||
pullRequestId=str(pr_number),
|
# then set the file path and line number in the location dictionary
|
||||||
repositoryName=repo_name,
|
if annotation_file and annotation_line:
|
||||||
beforeCommitId=destination_commit,
|
self.boto_client.post_comment_for_pull_request(
|
||||||
afterCommitId=source_commit,
|
pullRequestId=str(pr_number),
|
||||||
content=comment,
|
repositoryName=repo_name,
|
||||||
)
|
beforeCommitId=destination_commit,
|
||||||
|
afterCommitId=source_commit,
|
||||||
|
content=comment,
|
||||||
|
location={
|
||||||
|
"filePath": annotation_file,
|
||||||
|
"filePosition": annotation_line,
|
||||||
|
"relativeFileVersion": "AFTER",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# The comment does not have code annotations
|
||||||
|
self.boto_client.post_comment_for_pull_request(
|
||||||
|
pullRequestId=str(pr_number),
|
||||||
|
repositoryName=repo_name,
|
||||||
|
beforeCommitId=destination_commit,
|
||||||
|
afterCommitId=source_commit,
|
||||||
|
content=comment,
|
||||||
|
)
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException':
|
if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException':
|
||||||
raise ValueError(f"Repository does not exist: {repo_name}") from e
|
raise ValueError(f"Repository does not exist: {repo_name}") from e
|
||||||
|
@ -180,10 +180,37 @@ class CodeCommitProvider(GitProvider):
|
|||||||
comment=pr_comment,
|
comment=pr_comment,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"CodeCommit Cannot post comment for PR: {self.pr_num}") from e
|
raise ValueError(f"CodeCommit Cannot publish comment for PR: {self.pr_num}") from e
|
||||||
|
|
||||||
def publish_code_suggestions(self, code_suggestions: list) -> bool:
|
def publish_code_suggestions(self, code_suggestions: list) -> bool:
|
||||||
return [""] # not implemented yet
|
counter = 1
|
||||||
|
for suggestion in code_suggestions:
|
||||||
|
# Verify that each suggestion has the required keys
|
||||||
|
if not all(key in suggestion for key in ["body", "relevant_file", "relevant_lines_start"]):
|
||||||
|
logging.warning(f"Skipping code suggestion #{counter}: Each suggestion must have 'body', 'relevant_file', 'relevant_lines_start' keys")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Publish the code suggestion to CodeCommit
|
||||||
|
try:
|
||||||
|
logging.debug(f"Code Suggestion #{counter} in file: {suggestion['relevant_file']}: {suggestion['relevant_lines_start']}")
|
||||||
|
self.codecommit_client.publish_comment(
|
||||||
|
repo_name=self.repo_name,
|
||||||
|
pr_number=self.pr_num,
|
||||||
|
destination_commit=self.pr.destination_commit,
|
||||||
|
source_commit=self.pr.source_commit,
|
||||||
|
comment=suggestion["body"],
|
||||||
|
annotation_file=suggestion["relevant_file"],
|
||||||
|
annotation_line=suggestion["relevant_lines_start"],
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"CodeCommit Cannot publish code suggestions for PR: {self.pr_num}") from e
|
||||||
|
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
# The calling function passes in a list of code suggestions, and this function publishes each suggestion one at a time.
|
||||||
|
# If we were to return False here, the calling function will attempt to publish the same list of code suggestions again, one at a time.
|
||||||
|
# Since this function publishes the suggestions one at a time anyway, we always return True here to avoid the retry.
|
||||||
|
return True
|
||||||
|
|
||||||
def publish_labels(self, labels):
|
def publish_labels(self, labels):
|
||||||
return [""] # not implemented yet
|
return [""] # not implemented yet
|
||||||
@ -195,6 +222,7 @@ class CodeCommitProvider(GitProvider):
|
|||||||
return "" # not implemented yet
|
return "" # not implemented yet
|
||||||
|
|
||||||
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):
|
||||||
|
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_compared_commit.html
|
||||||
raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet")
|
raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet")
|
||||||
|
|
||||||
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
@ -255,9 +283,11 @@ class CodeCommitProvider(GitProvider):
|
|||||||
return self.codecommit_client.get_file(self.repo_name, settings_filename, self.pr.source_commit, optional=True)
|
return self.codecommit_client.get_file(self.repo_name, settings_filename, self.pr.source_commit, optional=True)
|
||||||
|
|
||||||
def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
|
def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
|
||||||
|
logging.info("CodeCommit provider does not support eyes reaction yet")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
|
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
|
||||||
|
logging.info("CodeCommit provider does not support removing reactions yet")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -315,16 +345,16 @@ class CodeCommitProvider(GitProvider):
|
|||||||
return re.match(r"^[a-z]{2}-(gov-)?[a-z]+-\d\.console\.aws\.amazon\.com$", hostname) is not None
|
return re.match(r"^[a-z]{2}-(gov-)?[a-z]+-\d\.console\.aws\.amazon\.com$", hostname) is not None
|
||||||
|
|
||||||
def _get_pr(self):
|
def _get_pr(self):
|
||||||
response = self.codecommit_client.get_pr(self.pr_num)
|
response = self.codecommit_client.get_pr(self.repo_name, self.pr_num)
|
||||||
|
|
||||||
if len(response.targets) == 0:
|
if len(response.targets) == 0:
|
||||||
raise ValueError(f"No files found in CodeCommit PR: {self.pr_num}")
|
raise ValueError(f"No files found in CodeCommit PR: {self.pr_num}")
|
||||||
|
|
||||||
# TODO: implement support for multiple commits in one CodeCommit PR
|
# TODO: implement support for multiple targets in one CodeCommit PR
|
||||||
# for now, we are only using the first commit in the PR
|
# for now, we are only using the first target in the PR
|
||||||
if len(response.targets) > 1:
|
if len(response.targets) > 1:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"Multiple commits in one PR is not supported for CodeCommit yet. Continuing, using the first commit only..."
|
"Multiple targets in one PR is not supported for CodeCommit yet. Continuing, using the first target only..."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return our object that mimics PullRequest class from the PyGithub library
|
# Return our object that mimics PullRequest class from the PyGithub library
|
||||||
|
@ -125,7 +125,7 @@ class TestCodeCommitProvider:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pr = api.get_pr(321)
|
pr = api.get_pr("my_test_repo", 321)
|
||||||
|
|
||||||
assert pr.title == "My PR"
|
assert pr.title == "My PR"
|
||||||
assert pr.description == "My PR description"
|
assert pr.description == "My PR description"
|
||||||
|
Reference in New Issue
Block a user