mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-04 12:50:38 +08:00
Merge branch 'main' of https://github.com/Codium-ai/pr-agent into fix_bitbucket_improve_issue
This commit is contained in:
@ -21,20 +21,43 @@ verbosity_level=2
|
|||||||
```
|
```
|
||||||
This is useful for debugging or experimenting with the different tools.
|
This is useful for debugging or experimenting with the different tools.
|
||||||
|
|
||||||
### Working from pre-built repo (GitHub Action/GitHub App/Docker)
|
### Working from GitHub App (pre-built repo)
|
||||||
When running PR-Agent from a pre-built repo, the default configuration file will be loaded.
|
When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be loaded.
|
||||||
|
|
||||||
To edit the configuration, you have two options:
|
#### GitHub app default tools
|
||||||
1. Place a local configuration file in the root of your local repo. The local file will be used instead of the default one.
|
The `[github_app]` section of the configuration file defines GitHub app specific configurations.
|
||||||
2. For online usage, just add `--config_path=<value>` to you command, to edit a specific configuration value.
|
The important parameter is `pr_commands`, which is a list of tools that will be run automatically when a new PR is opened:
|
||||||
|
```
|
||||||
|
[github_app]
|
||||||
|
pr_commands = [
|
||||||
|
"/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true",
|
||||||
|
"/auto_review",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
This means that when a new PR is opened, PR-Agent will run the `describe` and `auto_review` tools.
|
||||||
|
For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to `true`.
|
||||||
|
|
||||||
|
However, you can override the default tool parameters by uploading a local configuration file called `.pr_agent.toml` to the root of your repo.
|
||||||
|
For example, if your local `.pr_agent.toml` file contains:
|
||||||
|
```
|
||||||
|
[pr_description]
|
||||||
|
add_original_user_description = false
|
||||||
|
keep_original_user_title = false
|
||||||
|
```
|
||||||
|
Then when a new PR is opened, PR-Agent will run the `describe` tool with the above parameters.
|
||||||
|
|
||||||
|
Note that a local `.pr_agent.toml` file enables you to edit and customize the default parameters of any tool, not just the ones that are run automatically.
|
||||||
|
|
||||||
|
#### Online usage
|
||||||
|
For online usage (calling tools by comments on a PR like `/ask ...`), just add `--config_path=<value>` to any command, to edit a specific configuration value.
|
||||||
For example if you want to edit `pr_reviewer` configurations, you can run:
|
For example if you want to edit `pr_reviewer` configurations, you can run:
|
||||||
```
|
```
|
||||||
/review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ...
|
/review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Any configuration value in `configuration.toml` file can be similarly edited.
|
Any configuration value in `configuration.toml` file can be similarly edited.
|
||||||
|
|
||||||
### General configuration parameters
|
|
||||||
|
### General configuration walkthrough
|
||||||
|
|
||||||
#### Changing a model
|
#### Changing a model
|
||||||
See [here](pr_agent/algo/__init__.py) for the list of available models.
|
See [here](pr_agent/algo/__init__.py) for the list of available models.
|
||||||
|
@ -2,7 +2,8 @@ FROM python:3.10 as base
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD pyproject.toml .
|
ADD pyproject.toml .
|
||||||
RUN pip install . && rm pyproject.toml
|
ADD requirements.txt .
|
||||||
|
RUN pip install . && rm pyproject.toml requirements.txt
|
||||||
ENV PYTHONPATH=/app
|
ENV PYTHONPATH=/app
|
||||||
ADD pr_agent pr_agent
|
ADD pr_agent pr_agent
|
||||||
ADD github_action/entrypoint.sh /
|
ADD github_action/entrypoint.sh /
|
||||||
|
@ -309,7 +309,9 @@ Example IAM permissions to that user to allow access to CodeCommit:
|
|||||||
"codecommit:Get*",
|
"codecommit:Get*",
|
||||||
"codecommit:List*",
|
"codecommit:List*",
|
||||||
"codecommit:PostComment*",
|
"codecommit:PostComment*",
|
||||||
"codecommit:PutCommentReaction"
|
"codecommit:PutCommentReaction",
|
||||||
|
"codecommit:UpdatePullRequestDescription",
|
||||||
|
"codecommit:UpdatePullRequestTitle"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,9 @@ Examples for invoking the different tools via the CLI:
|
|||||||
"<pr_url>" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50).
|
"<pr_url>" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50).
|
||||||
|
|
||||||
In the [configuration](./CONFIGURATION.md) file you can select your git provider (GitHub, Gitlab, Bitbucket), and further configure the different tools.
|
In the [configuration](./CONFIGURATION.md) file you can select your git provider (GitHub, Gitlab, Bitbucket), and further configure the different tools.
|
||||||
|
Options that are available in the configuration file can be specified at run time when calling actions. Two examples:
|
||||||
|
- /review --pr_reviewer.extra_instructions="focus on the file: ..."
|
||||||
|
- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..."
|
||||||
|
|
||||||
## Try it now
|
## Try it now
|
||||||
|
|
||||||
@ -176,7 +179,7 @@ Here are some advantages of PR-Agent:
|
|||||||
|
|
||||||
## Similar Projects
|
## Similar Projects
|
||||||
|
|
||||||
- [CodiumAI - Meaningful tests for busy devs](https://github.com/Codium-ai/codiumai-vscode-release)
|
- [CodiumAI - Meaningful tests for busy devs](https://github.com/Codium-ai/codiumai-vscode-release) (although various capabilities are much more advanced in the CodiumAI IDE plugins)
|
||||||
- [Aider - GPT powered coding in your terminal](https://github.com/paul-gauthier/aider)
|
- [Aider - GPT powered coding in your terminal](https://github.com/paul-gauthier/aider)
|
||||||
- [openai-pr-reviewer](https://github.com/coderabbitai/openai-pr-reviewer)
|
- [openai-pr-reviewer](https://github.com/coderabbitai/openai-pr-reviewer)
|
||||||
- [CodeReview BOT](https://github.com/anc95/ChatGPT-CodeReview)
|
- [CodeReview BOT](https://github.com/anc95/ChatGPT-CodeReview)
|
||||||
|
@ -64,7 +64,7 @@ class CodeCommitClient:
|
|||||||
"""
|
"""
|
||||||
Get the differences between two commits in CodeCommit.
|
Get the differences between two commits in CodeCommit.
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
- repo_name: Name of the repository
|
- repo_name: Name of the repository
|
||||||
- destination_commit: Commit hash you want to merge into (the "before" hash) (usually on the main or master branch)
|
- destination_commit: Commit hash you want to merge into (the "before" hash) (usually on the main or master branch)
|
||||||
- source_commit: Commit hash of the code you are adding (the "after" branch)
|
- source_commit: Commit hash of the code you are adding (the "after" branch)
|
||||||
@ -73,8 +73,8 @@ class CodeCommitClient:
|
|||||||
- List of CodeCommitDifferencesResponse objects
|
- List of CodeCommitDifferencesResponse objects
|
||||||
|
|
||||||
Boto3 Documentation:
|
Boto3 Documentation:
|
||||||
aws codecommit get-differences
|
- aws codecommit get-differences
|
||||||
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_differences.html
|
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_differences.html
|
||||||
"""
|
"""
|
||||||
if self.boto_client is None:
|
if self.boto_client is None:
|
||||||
self._connect_boto_client()
|
self._connect_boto_client()
|
||||||
@ -101,7 +101,7 @@ class CodeCommitClient:
|
|||||||
"""
|
"""
|
||||||
Retrieve a file from CodeCommit.
|
Retrieve a file from CodeCommit.
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
- repo_name: Name of the repository
|
- repo_name: Name of the repository
|
||||||
- file_path: Path to the file you are retrieving
|
- file_path: Path to the file you are retrieving
|
||||||
- sha_hash: Commit hash of the file you are retrieving
|
- sha_hash: Commit hash of the file you are retrieving
|
||||||
@ -110,8 +110,8 @@ class CodeCommitClient:
|
|||||||
- File contents
|
- File contents
|
||||||
|
|
||||||
Boto3 Documentation:
|
Boto3 Documentation:
|
||||||
aws codecommit get_file
|
- aws codecommit get_file
|
||||||
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_file.html
|
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_file.html
|
||||||
"""
|
"""
|
||||||
if not file_path:
|
if not file_path:
|
||||||
return ""
|
return ""
|
||||||
@ -137,15 +137,15 @@ class CodeCommitClient:
|
|||||||
"""
|
"""
|
||||||
Get a information about a CodeCommit PR.
|
Get a information about a CodeCommit PR.
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
- pr_number: The PR number you are requesting
|
- pr_number: The PR number you are requesting
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- CodeCommitPullRequestResponse object
|
- CodeCommitPullRequestResponse object
|
||||||
|
|
||||||
Boto3 Documentation:
|
Boto3 Documentation:
|
||||||
aws codecommit get_pull_request
|
- aws codecommit get_pull_request
|
||||||
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_pull_request.html
|
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_pull_request.html
|
||||||
"""
|
"""
|
||||||
if self.boto_client is None:
|
if self.boto_client is None:
|
||||||
self._connect_boto_client()
|
self._connect_boto_client()
|
||||||
@ -164,11 +164,48 @@ class CodeCommitClient:
|
|||||||
|
|
||||||
return CodeCommitPullRequestResponse(response.get("pullRequest", {}))
|
return CodeCommitPullRequestResponse(response.get("pullRequest", {}))
|
||||||
|
|
||||||
|
def publish_description(self, pr_number: int, pr_title: str, pr_body: str):
|
||||||
|
"""
|
||||||
|
Set the title and description on a pull request
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- pr_number: the AWS CodeCommit pull request number
|
||||||
|
- pr_title: title of the pull request
|
||||||
|
- pr_body: body of the pull request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- None
|
||||||
|
|
||||||
|
Boto3 Documentation:
|
||||||
|
- aws codecommit update_pull_request_title
|
||||||
|
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/update_pull_request_title.html
|
||||||
|
- aws codecommit update_pull_request_description
|
||||||
|
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/update_pull_request_description.html
|
||||||
|
"""
|
||||||
|
if self.boto_client is None:
|
||||||
|
self._connect_boto_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.boto_client.update_pull_request_title(pullRequestId=str(pr_number), title=pr_title)
|
||||||
|
self.boto_client.update_pull_request_description(pullRequestId=str(pr_number), description=pr_body)
|
||||||
|
except botocore.exceptions.ClientError as e:
|
||||||
|
if e.response["Error"]["Code"] == 'PullRequestDoesNotExistException':
|
||||||
|
raise ValueError(f"PR number does not exist: {pr_number}") from e
|
||||||
|
if e.response["Error"]["Code"] == 'InvalidTitleException':
|
||||||
|
raise ValueError(f"Invalid title for PR number: {pr_number}") from e
|
||||||
|
if e.response["Error"]["Code"] == 'InvalidDescriptionException':
|
||||||
|
raise ValueError(f"Invalid description for PR number: {pr_number}") from e
|
||||||
|
if e.response["Error"]["Code"] == 'PullRequestAlreadyClosedException':
|
||||||
|
raise ValueError(f"PR is already closed: PR number: {pr_number}") from e
|
||||||
|
raise ValueError(f"Boto3 client error calling publish_description") from e
|
||||||
|
except Exception as 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):
|
||||||
"""
|
"""
|
||||||
Publish a comment to a pull request
|
Publish a comment to a pull request
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
- repo_name: name of the repository
|
- repo_name: name of the repository
|
||||||
- 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)
|
||||||
@ -179,8 +216,8 @@ class CodeCommitClient:
|
|||||||
- None
|
- None
|
||||||
|
|
||||||
Boto3 Documentation:
|
Boto3 Documentation:
|
||||||
aws codecommit post_comment_for_pull_request
|
- aws codecommit post_comment_for_pull_request
|
||||||
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_pull_request.html
|
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_pull_request.html
|
||||||
"""
|
"""
|
||||||
if self.boto_client is None:
|
if self.boto_client is None:
|
||||||
self._connect_boto_client()
|
self._connect_boto_client()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
@ -153,17 +154,27 @@ class CodeCommitProvider(GitProvider):
|
|||||||
return self.diff_files
|
return self.diff_files
|
||||||
|
|
||||||
def publish_description(self, pr_title: str, pr_body: str):
|
def publish_description(self, pr_title: str, pr_body: str):
|
||||||
return "" # not implemented yet
|
try:
|
||||||
|
self.codecommit_client.publish_description(
|
||||||
|
pr_number=self.pr_num,
|
||||||
|
pr_title=pr_title,
|
||||||
|
pr_body=CodeCommitProvider._add_additional_newlines(pr_body),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"CodeCommit Cannot publish description for PR: {self.pr_num}") from e
|
||||||
|
|
||||||
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:
|
if is_temporary:
|
||||||
logging.info(pr_comment)
|
logging.info(pr_comment)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
pr_comment = CodeCommitProvider._remove_markdown_html(pr_comment)
|
||||||
|
pr_comment = CodeCommitProvider._add_additional_newlines(pr_comment)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.codecommit_client.publish_comment(
|
self.codecommit_client.publish_comment(
|
||||||
repo_name=self.repo_name,
|
repo_name=self.repo_name,
|
||||||
pr_number=str(self.pr_num),
|
pr_number=self.pr_num,
|
||||||
destination_commit=self.pr.destination_commit,
|
destination_commit=self.pr.destination_commit,
|
||||||
source_commit=self.pr.source_commit,
|
source_commit=self.pr.source_commit,
|
||||||
comment=pr_comment,
|
comment=pr_comment,
|
||||||
@ -200,7 +211,7 @@ class CodeCommitProvider(GitProvider):
|
|||||||
Returns a dictionary of languages, containing the percentage of each language used in the PR.
|
Returns a dictionary of languages, containing the percentage of each language used in the PR.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary where each key is a language name and the corresponding value is the percentage of that language in the PR.
|
- dict: A dictionary where each key is a language name and the corresponding value is the percentage of that language in the PR.
|
||||||
"""
|
"""
|
||||||
commit_files = self.get_files()
|
commit_files = self.get_files()
|
||||||
filenames = [ item.filename for item in commit_files ]
|
filenames = [ item.filename for item in commit_files ]
|
||||||
@ -251,11 +262,20 @@ class CodeCommitProvider(GitProvider):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
|
def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
|
||||||
|
"""
|
||||||
|
Parse the CodeCommit PR URL and return the repository name and PR number.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- pr_url: the full AWS CodeCommit pull request URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- Tuple[str, int]: A tuple containing the repository name and PR number.
|
||||||
|
"""
|
||||||
# Example PR URL:
|
# Example PR URL:
|
||||||
# https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/__MY_REPO__/pull-requests/123456"
|
# https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/__MY_REPO__/pull-requests/123456"
|
||||||
parsed_url = urlparse(pr_url)
|
parsed_url = urlparse(pr_url)
|
||||||
|
|
||||||
if "us-east-1.console.aws.amazon.com" not in parsed_url.netloc:
|
if not CodeCommitProvider._is_valid_codecommit_hostname(parsed_url.netloc):
|
||||||
raise ValueError(f"The provided URL is not a valid CodeCommit URL: {pr_url}")
|
raise ValueError(f"The provided URL is not a valid CodeCommit URL: {pr_url}")
|
||||||
|
|
||||||
path_parts = parsed_url.path.strip("/").split("/")
|
path_parts = parsed_url.path.strip("/").split("/")
|
||||||
@ -278,6 +298,22 @@ class CodeCommitProvider(GitProvider):
|
|||||||
|
|
||||||
return repo_name, pr_number
|
return repo_name, pr_number
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_valid_codecommit_hostname(hostname: str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the provided hostname is a valid AWS CodeCommit hostname.
|
||||||
|
|
||||||
|
This is not an exhaustive check of AWS region names,
|
||||||
|
but instead uses a regex to check for matching AWS region patterns.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- hostname: the hostname to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- bool: True if the hostname is valid, False otherwise.
|
||||||
|
"""
|
||||||
|
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.pr_num)
|
||||||
|
|
||||||
@ -306,13 +342,52 @@ class CodeCommitProvider(GitProvider):
|
|||||||
return "" # not implemented yet
|
return "" # not implemented yet
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_edit_type(codecommit_change_type):
|
def _add_additional_newlines(body: str) -> str:
|
||||||
|
"""
|
||||||
|
Replace single newlines in a PR body with double newlines.
|
||||||
|
|
||||||
|
CodeCommit Markdown does not seem to render as well as GitHub Markdown,
|
||||||
|
so we add additional newlines to the PR body to make it more readable in CodeCommit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- body: the PR body
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- str: the PR body with the double newlines added
|
||||||
|
"""
|
||||||
|
return re.sub(r'(?<!\n)\n(?!\n)', '\n\n', body)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _remove_markdown_html(comment: str) -> str:
|
||||||
|
"""
|
||||||
|
Remove the HTML tags from a PR comment.
|
||||||
|
|
||||||
|
CodeCommit Markdown does not seem to render as well as GitHub Markdown,
|
||||||
|
so we remove the HTML tags from the PR comment to make it more readable in CodeCommit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- comment: the PR comment
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- str: the PR comment with the HTML tags removed
|
||||||
|
"""
|
||||||
|
comment = comment.replace("<details>", "")
|
||||||
|
comment = comment.replace("</details>", "")
|
||||||
|
comment = comment.replace("<summary>", "")
|
||||||
|
comment = comment.replace("</summary>", "")
|
||||||
|
return comment
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_edit_type(codecommit_change_type: str):
|
||||||
"""
|
"""
|
||||||
Convert the CodeCommit change type string to the EDIT_TYPE enum.
|
Convert the CodeCommit change type string to the EDIT_TYPE enum.
|
||||||
The CodeCommit change type string is returned from the get_differences SDK method.
|
The CodeCommit change type string is returned from the get_differences SDK method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- codecommit_change_type: the CodeCommit change type string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An EDIT_TYPE enum representing the modified, added, deleted, or renamed file in the PR diff.
|
- An EDIT_TYPE enum representing the modified, added, deleted, or renamed file in the PR diff.
|
||||||
"""
|
"""
|
||||||
t = codecommit_change_type.upper()
|
t = codecommit_change_type.upper()
|
||||||
edit_type = None
|
edit_type = None
|
||||||
@ -333,6 +408,12 @@ class CodeCommitProvider(GitProvider):
|
|||||||
The returned extensions will include the dot "." prefix,
|
The returned extensions will include the dot "." prefix,
|
||||||
to accommodate for the dots in the existing language_extension_map settings.
|
to accommodate for the dots in the existing language_extension_map settings.
|
||||||
Filenames with no extension will return an empty string for the extension.
|
Filenames with no extension will return an empty string for the extension.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- filenames: a list of filenames
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- list: A list of file extensions, including the dot "." prefix.
|
||||||
"""
|
"""
|
||||||
extensions = []
|
extensions = []
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
@ -349,6 +430,12 @@ class CodeCommitProvider(GitProvider):
|
|||||||
Return a dictionary containing the programming language name (as the key),
|
Return a dictionary containing the programming language name (as the key),
|
||||||
and the percentage that language is used (as the value),
|
and the percentage that language is used (as the value),
|
||||||
given a list of file extensions.
|
given a list of file extensions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- extensions: a list of file extensions
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: A dictionary where each key is a language name and the corresponding value is the percentage of that language in the PR.
|
||||||
"""
|
"""
|
||||||
total_files = len(extensions)
|
total_files = len(extensions)
|
||||||
if total_files == 0:
|
if total_files == 0:
|
||||||
|
@ -86,11 +86,11 @@ class GitProvider(ABC):
|
|||||||
def get_pr_description_full(self) -> str:
|
def get_pr_description_full(self) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_pr_description(self) -> str:
|
def get_pr_description(self, *, full: bool = True) -> str:
|
||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
from pr_agent.algo.pr_processing import clip_tokens
|
from pr_agent.algo.pr_processing import clip_tokens
|
||||||
max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None)
|
max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None)
|
||||||
description = self.get_pr_description_full()
|
description = self.get_pr_description_full() if full else self.get_user_description()
|
||||||
if max_tokens:
|
if max_tokens:
|
||||||
return clip_tokens(description, max_tokens)
|
return clip_tokens(description, max_tokens)
|
||||||
return description
|
return description
|
||||||
|
@ -12,6 +12,7 @@ from starlette_context import context
|
|||||||
from starlette_context.middleware import RawContextMiddleware
|
from starlette_context.middleware import RawContextMiddleware
|
||||||
|
|
||||||
from pr_agent.agent.pr_agent import PRAgent
|
from pr_agent.agent.pr_agent import PRAgent
|
||||||
|
from pr_agent.algo.utils import update_settings_from_args
|
||||||
from pr_agent.config_loader import get_settings, global_settings
|
from pr_agent.config_loader import get_settings, global_settings
|
||||||
from pr_agent.git_providers import get_git_provider
|
from pr_agent.git_providers import get_git_provider
|
||||||
from pr_agent.servers.utils import verify_signature
|
from pr_agent.servers.utils import verify_signature
|
||||||
@ -123,8 +124,13 @@ async def handle_request(body: Dict[str, Any], event: str):
|
|||||||
return {}
|
return {}
|
||||||
logging.info(f"Performing review because of event={event} and action={action}")
|
logging.info(f"Performing review because of event={event} and action={action}")
|
||||||
for command in get_settings().github_app.pr_commands:
|
for command in get_settings().github_app.pr_commands:
|
||||||
logging.info(f"Performing command: {command}")
|
split_command = command.split(" ")
|
||||||
await agent.handle_request(api_url, command)
|
command = split_command[0]
|
||||||
|
args = split_command[1:]
|
||||||
|
other_args = update_settings_from_args(args)
|
||||||
|
new_command = ' '.join([command] + other_args)
|
||||||
|
logging.info(f"Performing command: {new_command}")
|
||||||
|
await agent.handle_request(api_url, new_command)
|
||||||
|
|
||||||
logging.info("event or action does not require handling")
|
logging.info("event or action does not require handling")
|
||||||
return {}
|
return {}
|
||||||
|
@ -36,7 +36,7 @@ class PRDescription:
|
|||||||
self.vars = {
|
self.vars = {
|
||||||
"title": self.git_provider.pr.title,
|
"title": self.git_provider.pr.title,
|
||||||
"branch": self.git_provider.get_pr_branch(),
|
"branch": self.git_provider.get_pr_branch(),
|
||||||
"description": self.git_provider.get_pr_description(),
|
"description": self.git_provider.get_pr_description(full=False),
|
||||||
"language": self.main_pr_language,
|
"language": self.main_pr_language,
|
||||||
"diff": "", # empty diff for initial calculation
|
"diff": "", # empty diff for initial calculation
|
||||||
"extra_instructions": get_settings().pr_description.extra_instructions,
|
"extra_instructions": get_settings().pr_description.extra_instructions,
|
||||||
|
@ -13,7 +13,7 @@ atlassian-python-api==3.39.0
|
|||||||
GitPython~=3.1.32
|
GitPython~=3.1.32
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
starlette-context==0.3.6
|
starlette-context==0.3.6
|
||||||
litellm~=0.1.445
|
litellm~=0.1.504
|
||||||
boto3~=1.28.25
|
boto3~=1.28.25
|
||||||
google-cloud-storage==2.10.0
|
google-cloud-storage==2.10.0
|
||||||
ujson==5.8.0
|
ujson==5.8.0
|
||||||
|
@ -26,11 +26,48 @@ class TestCodeCommitFile:
|
|||||||
|
|
||||||
class TestCodeCommitProvider:
|
class TestCodeCommitProvider:
|
||||||
def test_parse_pr_url(self):
|
def test_parse_pr_url(self):
|
||||||
|
# Test that the _parse_pr_url() function can extract the repo name and PR number from a CodeCommit URL
|
||||||
url = "https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/my_test_repo/pull-requests/321"
|
url = "https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/my_test_repo/pull-requests/321"
|
||||||
repo_name, pr_number = CodeCommitProvider._parse_pr_url(url)
|
repo_name, pr_number = CodeCommitProvider._parse_pr_url(url)
|
||||||
assert repo_name == "my_test_repo"
|
assert repo_name == "my_test_repo"
|
||||||
assert pr_number == 321
|
assert pr_number == 321
|
||||||
|
|
||||||
|
def test_is_valid_codecommit_hostname(self):
|
||||||
|
# Test the various AWS regions
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("af-south-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-east-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-northeast-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-northeast-2.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-northeast-3.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-south-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-south-2.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-2.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-3.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-4.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("ca-central-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-central-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-central-2.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-north-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-south-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-south-2.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-west-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-west-2.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("eu-west-3.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("il-central-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("me-central-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("me-south-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("sa-east-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("us-east-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("us-east-2.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("us-gov-east-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("us-gov-west-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("us-west-1.console.aws.amazon.com")
|
||||||
|
assert CodeCommitProvider._is_valid_codecommit_hostname("us-west-2.console.aws.amazon.com")
|
||||||
|
# Test non-AWS regions
|
||||||
|
assert not CodeCommitProvider._is_valid_codecommit_hostname("no-such-region.console.aws.amazon.com")
|
||||||
|
assert not CodeCommitProvider._is_valid_codecommit_hostname("console.aws.amazon.com")
|
||||||
|
|
||||||
# Test that an error is raised when an invalid CodeCommit URL is provided to the set_pr() method of the CodeCommitProvider class.
|
# Test that an error is raised when an invalid CodeCommit URL is provided to the set_pr() method of the CodeCommitProvider class.
|
||||||
# Generated by CodiumAI
|
# Generated by CodiumAI
|
||||||
def test_invalid_codecommit_url(self):
|
def test_invalid_codecommit_url(self):
|
||||||
@ -106,6 +143,7 @@ class TestCodeCommitProvider:
|
|||||||
assert percentages == {}
|
assert percentages == {}
|
||||||
|
|
||||||
def test_get_edit_type(self):
|
def test_get_edit_type(self):
|
||||||
|
# Test that the _get_edit_type() function can convert a CodeCommit letter to an EDIT_TYPE enum
|
||||||
assert CodeCommitProvider._get_edit_type("A") == EDIT_TYPE.ADDED
|
assert CodeCommitProvider._get_edit_type("A") == EDIT_TYPE.ADDED
|
||||||
assert CodeCommitProvider._get_edit_type("D") == EDIT_TYPE.DELETED
|
assert CodeCommitProvider._get_edit_type("D") == EDIT_TYPE.DELETED
|
||||||
assert CodeCommitProvider._get_edit_type("M") == EDIT_TYPE.MODIFIED
|
assert CodeCommitProvider._get_edit_type("M") == EDIT_TYPE.MODIFIED
|
||||||
@ -117,3 +155,18 @@ class TestCodeCommitProvider:
|
|||||||
assert CodeCommitProvider._get_edit_type("r") == EDIT_TYPE.RENAMED
|
assert CodeCommitProvider._get_edit_type("r") == EDIT_TYPE.RENAMED
|
||||||
|
|
||||||
assert CodeCommitProvider._get_edit_type("X") is None
|
assert CodeCommitProvider._get_edit_type("X") is None
|
||||||
|
|
||||||
|
def test_add_additional_newlines(self):
|
||||||
|
# a short string to test adding double newlines
|
||||||
|
input = "abc\ndef\n\n___\nghi\njkl\nmno\n\npqr\n"
|
||||||
|
expect = "abc\n\ndef\n\n___\n\nghi\n\njkl\n\nmno\n\npqr\n\n"
|
||||||
|
assert CodeCommitProvider._add_additional_newlines(input) == expect
|
||||||
|
# a test example from a real PR
|
||||||
|
input = "## PR Type:\nEnhancement\n\n___\n## PR Description:\nThis PR introduces a new feature to the script, allowing users to filter servers by name.\n\n___\n## PR Main Files Walkthrough:\n`foo`: The foo script has been updated to include a new command line option `-f` or `--filter`.\n`bar`: The bar script has been updated to list stopped servers.\n"
|
||||||
|
expect = "## PR Type:\n\nEnhancement\n\n___\n\n## PR Description:\n\nThis PR introduces a new feature to the script, allowing users to filter servers by name.\n\n___\n\n## PR Main Files Walkthrough:\n\n`foo`: The foo script has been updated to include a new command line option `-f` or `--filter`.\n\n`bar`: The bar script has been updated to list stopped servers.\n\n"
|
||||||
|
assert CodeCommitProvider._add_additional_newlines(input) == expect
|
||||||
|
|
||||||
|
def test_remove_markdown_html(self):
|
||||||
|
input = "## PR Feedback\n<details><summary>Code feedback:</summary>\nfile foo\n</summary>\n"
|
||||||
|
expect = "## PR Feedback\nCode feedback:\nfile foo\n\n"
|
||||||
|
assert CodeCommitProvider._remove_markdown_html(input) == expect
|
Reference in New Issue
Block a user