Compare commits
139 Commits
v0.7
...
fix/repo_s
Author | SHA1 | Date | |
---|---|---|---|
e7258e732b | |||
18ee9d66b0 | |||
8755f635b4 | |||
e2417ebe88 | |||
264dea2a8b | |||
da98fd712f | |||
e6548f4fe1 | |||
e66fed2468 | |||
1b3fb49f9c | |||
66a5f06b45 | |||
51c817ba29 | |||
8f9f09ecbf | |||
d77a71bf47 | |||
70c0ef5ce1 | |||
92e9012fb6 | |||
43dc648b05 | |||
6dee18b24a | |||
baa0e95227 | |||
b27f57d05d | |||
fd8c90041c | |||
ea6253e2e8 | |||
2a270886ea | |||
2945c36899 | |||
1bab26f1c5 | |||
72eecbbf61 | |||
989c56220b | |||
e387086890 | |||
088f256415 | |||
d92f5284df | |||
f3e794e50b | |||
44239f1a79 | |||
428e6382bd | |||
d13a92515b | |||
eaf7cfbcf2 | |||
abb633db0f | |||
ca11cfa54e | |||
8df57941c6 | |||
af45dcc7df | |||
18c33ae6fc | |||
896d65a43a | |||
02ea2c7a40 | |||
fdbb7f176c | |||
5386ec359d | |||
85f64ad895 | |||
589d329a3c | |||
3585a4ebff | |||
706f6bf44d | |||
54f29fcf38 | |||
e941fa9ec0 | |||
4479c5f11b | |||
b2369c66d8 | |||
ab5ac8ffa8 | |||
ccc7f1e10a | |||
8d075b76ae | |||
32a8b0e9bc | |||
69c6acf89d | |||
e07412c098 | |||
8cec3ffde3 | |||
25bc54785b | |||
8b033ccc94 | |||
2003b6915b | |||
175218c779 | |||
f96c6cbc95 | |||
4dbb70c9d5 | |||
73cc7c2d71 | |||
902975b660 | |||
813fa8571e | |||
b5c505f727 | |||
26b9e8a235 | |||
c90e72cb75 | |||
55f022d93e | |||
71b532d4d5 | |||
c18ae77299 | |||
2f0fa246c0 | |||
a371f7ab84 | |||
e1149862b2 | |||
b8f93516ce | |||
d15d374cdc | |||
cae0f627e2 | |||
7fbdc3aead | |||
0551922839 | |||
043d453cab | |||
663ae92bdf | |||
96824aa9e2 | |||
5cca299b16 | |||
cd3527f7d4 | |||
bb12c75431 | |||
4accddcaa7 | |||
bb8a0f10f4 | |||
c3cbaaf09e | |||
a6e65e867f | |||
0df8071673 | |||
b17a4d9551 | |||
dc7db4cbdd | |||
4e94fcc372 | |||
4c72cfbff4 | |||
ac89867ac7 | |||
34ed598c20 | |||
e7aee84ea8 | |||
388684e2e8 | |||
8f81c18647 | |||
ba78475944 | |||
75dd5688fa | |||
aa32024078 | |||
9167c20512 | |||
a7fb5d98b1 | |||
fda47bb5cf | |||
9c4f849066 | |||
3e2e2d6c6e | |||
56cc804fcf | |||
62746294e3 | |||
d384b0644e | |||
3e07fe618f | |||
be54fb5bf8 | |||
46ec3c0754 | |||
5e608cc7e7 | |||
04162564ca | |||
992f51a019 | |||
2bc25b7435 | |||
fcd9821d10 | |||
911ad299e2 | |||
fbfa186733 | |||
7545b25823 | |||
1370a051f1 | |||
dcbd3132d1 | |||
f7abdc6ae8 | |||
a2ca43afcd | |||
43af4aa182 | |||
e343ce8468 | |||
7b2c01181b | |||
978c56c128 | |||
4043dfff9e | |||
67052aa714 | |||
caee7cbf50 | |||
9bee3055c2 | |||
9bd5140ea4 | |||
12bd9e8b42 | |||
ca8997b616 | |||
ed1816a2d7 |
18
Dockerfile.bitbucket_pipeline
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM python:3.10 as base
|
||||||
|
|
||||||
|
ENV OPENAI_API_KEY=${OPENAI_API_KEY} \
|
||||||
|
BITBUCKET_BEARER_TOKEN=${BITBUCKET_BEARER_TOKEN} \
|
||||||
|
BITBUCKET_PR_ID=${BITBUCKET_PR_ID} \
|
||||||
|
BITBUCKET_REPO_SLUG=${BITBUCKET_REPO_SLUG} \
|
||||||
|
BITBUCKET_WORKSPACE=${BITBUCKET_WORKSPACE}
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
ADD pyproject.toml .
|
||||||
|
ADD requirements.txt .
|
||||||
|
RUN pip install . && rm pyproject.toml requirements.txt
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
ADD pr_agent pr_agent
|
||||||
|
ADD bitbucket_pipeline/entrypoint.sh /
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
@ -1 +1 @@
|
|||||||
FROM codiumai/pr-agent:0.7-github_action
|
FROM codiumai/pr-agent:github_action
|
||||||
|
92
INSTALL.md
@ -16,6 +16,7 @@ There are several ways to use PR-Agent:
|
|||||||
- [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function)
|
- [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function)
|
||||||
- [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup)
|
- [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup)
|
||||||
- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server)
|
- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server)
|
||||||
|
- [Method 9: Run as a Bitbucket Pipeline](INSTALL.md#method-9-run-as-a-bitbucket-pipeline)
|
||||||
---
|
---
|
||||||
|
|
||||||
### Method 1: Use Docker image (no installation required)
|
### Method 1: Use Docker image (no installation required)
|
||||||
@ -39,7 +40,7 @@ For other git providers, update CONFIG.GIT_PROVIDER accordingly, and check the `
|
|||||||
```
|
```
|
||||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> ask "<your question>"
|
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> ask "<your question>"
|
||||||
```
|
```
|
||||||
Note: If you want to ensure you're running a specific version of the Docker image, consider using the image's digest.
|
Note: If you want to ensure you're running a specific version of the Docker image, consider using the image's digest.
|
||||||
The digest is a unique identifier for a specific version of an image. You can pull and run an image using its digest by referencing it like so: repository@sha256:digest. Always ensure you're using the correct and trusted digest for your operations.
|
The digest is a unique identifier for a specific version of an image. You can pull and run an image using its digest by referencing it like so: repository@sha256:digest. Always ensure you're using the correct and trusted digest for your operations.
|
||||||
|
|
||||||
1. To request a review for a PR using a specific digest, run the following command:
|
1. To request a review for a PR using a specific digest, run the following command:
|
||||||
@ -88,17 +89,17 @@ chmod 600 pr_agent/settings/.secrets.toml
|
|||||||
|
|
||||||
```
|
```
|
||||||
export PYTHONPATH=[$PYTHONPATH:]<PATH to pr_agent folder>
|
export PYTHONPATH=[$PYTHONPATH:]<PATH to pr_agent folder>
|
||||||
python pr_agent/cli.py --pr_url <pr_url> /review
|
python3 -m pr_agent.cli --pr_url <pr_url> review
|
||||||
python pr_agent/cli.py --pr_url <pr_url> /ask <your question>
|
python3 -m pr_agent.cli --pr_url <pr_url> ask <your question>
|
||||||
python pr_agent/cli.py --pr_url <pr_url> /describe
|
python3 -m pr_agent.cli --pr_url <pr_url> describe
|
||||||
python pr_agent/cli.py --pr_url <pr_url> /improve
|
python3 -m pr_agent.cli --pr_url <pr_url> improve
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Method 3: Run as a GitHub Action
|
### Method 3: Run as a GitHub Action
|
||||||
|
|
||||||
You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action.
|
You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action.
|
||||||
|
|
||||||
1. Add the following file to your repository under `.github/workflows/pr_agent.yml`:
|
1. Add the following file to your repository under `.github/workflows/pr_agent.yml`:
|
||||||
|
|
||||||
@ -122,7 +123,7 @@ jobs:
|
|||||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
```
|
```
|
||||||
** if you want to pin your action to a specific commit for stability reasons
|
** if you want to pin your action to a specific release (v0.7 for example) for stability reasons, use:
|
||||||
```yaml
|
```yaml
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@ -139,7 +140,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: PR Agent action step
|
- name: PR Agent action step
|
||||||
id: pragent
|
id: pragent
|
||||||
uses: Codium-ai/pr-agent@<commit_sha>
|
uses: Codium-ai/pr-agent@v0.7
|
||||||
env:
|
env:
|
||||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -152,10 +153,10 @@ OPENAI_KEY: <your key>
|
|||||||
|
|
||||||
The GITHUB_TOKEN secret is automatically created by GitHub.
|
The GITHUB_TOKEN secret is automatically created by GitHub.
|
||||||
|
|
||||||
3. Merge this change to your main branch.
|
3. Merge this change to your main branch.
|
||||||
When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools.
|
When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools.
|
||||||
|
|
||||||
4. You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](./Usage.md) file. Some examples:
|
4. You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](pr_agent/settings/configuration.toml) file. Some examples:
|
||||||
```yaml
|
```yaml
|
||||||
env:
|
env:
|
||||||
# ... previous environment values
|
# ... previous environment values
|
||||||
@ -220,12 +221,12 @@ git clone https://github.com/Codium-ai/pr-agent.git
|
|||||||
- Copy your app's webhook secret to the webhook_secret field.
|
- Copy your app's webhook secret to the webhook_secret field.
|
||||||
- Set deployment_type to 'app' in [configuration.toml](./pr_agent/settings/configuration.toml)
|
- Set deployment_type to 'app' in [configuration.toml](./pr_agent/settings/configuration.toml)
|
||||||
|
|
||||||
> The .secrets.toml file is not copied to the Docker image by default, and is only used for local development.
|
> The .secrets.toml file is not copied to the Docker image by default, and is only used for local development.
|
||||||
> If you want to use the .secrets.toml file in your Docker image, you can add remove it from the .dockerignore file.
|
> If you want to use the .secrets.toml file in your Docker image, you can add remove it from the .dockerignore file.
|
||||||
> In most production environments, you would inject the secrets file as environment variables or as mounted volumes.
|
> In most production environments, you would inject the secrets file as environment variables or as mounted volumes.
|
||||||
> For example, in order to inject a secrets file as a volume in a Kubernetes environment you can update your pod spec to include the following,
|
> For example, in order to inject a secrets file as a volume in a Kubernetes environment you can update your pod spec to include the following,
|
||||||
> assuming you have a secret named `pr-agent-settings` with a key named `.secrets.toml`:
|
> assuming you have a secret named `pr-agent-settings` with a key named `.secrets.toml`:
|
||||||
```
|
```
|
||||||
volumes:
|
volumes:
|
||||||
- name: settings-volume
|
- name: settings-volume
|
||||||
secret:
|
secret:
|
||||||
@ -321,7 +322,7 @@ Example IAM permissions to that user to allow access to CodeCommit:
|
|||||||
"codecommit:PostComment*",
|
"codecommit:PostComment*",
|
||||||
"codecommit:PutCommentReaction",
|
"codecommit:PutCommentReaction",
|
||||||
"codecommit:UpdatePullRequestDescription",
|
"codecommit:UpdatePullRequestDescription",
|
||||||
"codecommit:UpdatePullRequestTitle"
|
"codecommit:UpdatePullRequestTitle"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
}
|
}
|
||||||
@ -365,9 +366,68 @@ WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
|
|||||||
- Your OpenAI key.
|
- Your OpenAI key.
|
||||||
- In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token.
|
- In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token.
|
||||||
- Set deployment_type to 'gitlab' in [configuration.toml](./pr_agent/settings/configuration.toml)
|
- Set deployment_type to 'gitlab' in [configuration.toml](./pr_agent/settings/configuration.toml)
|
||||||
5. Create a webhook in GitLab. Set the URL to the URL of your app's server. Set the secret token to the generated secret from step 2.
|
5. Create a webhook in GitLab. Set the URL to the URL of your app's server. Set the secret token to the generated secret from step 2.
|
||||||
In the "Trigger" section, check the ‘comments’ and ‘merge request events’ boxes.
|
In the "Trigger" section, check the ‘comments’ and ‘merge request events’ boxes.
|
||||||
6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands.
|
6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Method 9: Run as a Bitbucket Pipeline
|
||||||
|
|
||||||
|
|
||||||
|
You can use our pre-build Bitbucket-Pipeline docker image to run as Bitbucket-Pipeline.
|
||||||
|
|
||||||
|
1. Add the following file in your repository bitbucket_pipelines.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
pipelines:
|
||||||
|
pull-requests:
|
||||||
|
'**':
|
||||||
|
- step:
|
||||||
|
name: PR Agent Pipeline
|
||||||
|
caches:
|
||||||
|
- pip
|
||||||
|
image: python:3.8
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
script:
|
||||||
|
- git clone https://github.com/Codium-ai/pr-agent.git
|
||||||
|
- cd pr-agent
|
||||||
|
- docker build -t bitbucket_runner:latest -f Dockerfile.bitbucket_pipeline .
|
||||||
|
- docker run -e OPENAI_API_KEY=$OPENAI_API_KEY -e BITBUCKET_BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN -e BITBUCKET_PR_ID=$BITBUCKET_PR_ID -e BITBUCKET_REPO_SLUG=$BITBUCKET_REPO_SLUG -e BITBUCKET_WORKSPACE=$BITBUCKET_WORKSPACE bitbucket_runner:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add the following secret to your repository under Repository settings > Pipelines > Repository variables.
|
||||||
|
OPENAI_API_KEY: <your key>
|
||||||
|
BITBUCKET_BEARER_TOKEN: <your token>
|
||||||
|
|
||||||
|
3. To get BITBUCKET_BEARER_TOKEN follow these steps
|
||||||
|
So here is my step by step tutorial
|
||||||
|
i) Insert your workspace name instead of {workspace_name} and go to the following link in order to create an OAuth consumer.
|
||||||
|
|
||||||
|
https://bitbucket.org/{workspace_name}/workspace/settings/api
|
||||||
|
|
||||||
|
set callback URL to http://localhost:8976 (doesn't need to be a real server there)
|
||||||
|
select permissions: repository -> read
|
||||||
|
|
||||||
|
ii) use consumer's Key as a {client_id} and open the following URL in the browser
|
||||||
|
|
||||||
|
https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=code
|
||||||
|
|
||||||
|
iii)
|
||||||
|
after you press "Grant access" in the browser it will redirect you to
|
||||||
|
|
||||||
|
http://localhost:8976?code=<CODE>
|
||||||
|
|
||||||
|
iv) use the code from the previous step and consumer's Key as a {client_id}, and consumer's Secret as {client_secret}
|
||||||
|
|
||||||
|
curl -X POST -u "{client_id}:{client_secret}" \
|
||||||
|
https://bitbucket.org/site/oauth2/access_token \
|
||||||
|
-d grant_type=authorization_code \
|
||||||
|
-d code={code} \
|
||||||
|
|
||||||
|
|
||||||
|
After completing this steps, you just to place this access token in the repository varibles.
|
||||||
|
|
||||||
|
|
||||||
=======
|
=======
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Git Patch Logic
|
# PR Compression Strategy
|
||||||
There are two scenarios:
|
There are two scenarios:
|
||||||
1. The PR is small enough to fit in a single prompt (including system and user prompt)
|
1. The PR is small enough to fit in a single prompt (including system and user prompt)
|
||||||
2. The PR is too large to fit in a single prompt (including system and user prompt)
|
2. The PR is too large to fit in a single prompt (including system and user prompt)
|
||||||
@ -16,7 +16,7 @@ We prioritize the languages of the repo based on the following criteria:
|
|||||||
## Small PR
|
## Small PR
|
||||||
In this case, we can fit the entire PR in a single prompt:
|
In this case, we can fit the entire PR in a single prompt:
|
||||||
1. Exclude binary files and non code files (e.g. images, pdfs, etc)
|
1. Exclude binary files and non code files (e.g. images, pdfs, etc)
|
||||||
2. We Expand the surrounding context of each patch to 6 lines above and below the patch
|
2. We Expand the surrounding context of each patch to 3 lines above and below the patch
|
||||||
## Large PR
|
## Large PR
|
||||||
|
|
||||||
### Motivation
|
### Motivation
|
||||||
@ -25,7 +25,7 @@ We want to be able to pack as much information as possible in a single LMM promp
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### PR compression strategy
|
#### Compression strategy
|
||||||
We prioritize additions over deletions:
|
We prioritize additions over deletions:
|
||||||
- Combine all deleted files into a single list (`deleted files`)
|
- Combine all deleted files into a single list (`deleted files`)
|
||||||
- File patches are a list of hunks, remove all hunks of type deletion-only from the hunks in the file patch
|
- File patches are a list of hunks, remove all hunks of type deletion-only from the hunks in the file patch
|
||||||
|
31
README.md
@ -9,6 +9,7 @@ Making pull requests less painful with an AI agent
|
|||||||
|
|
||||||
[](https://github.com/Codium-ai/pr-agent/blob/main/LICENSE)
|
[](https://github.com/Codium-ai/pr-agent/blob/main/LICENSE)
|
||||||
[](https://discord.com/channels/1057273017547378788/1126104260430528613)
|
[](https://discord.com/channels/1057273017547378788/1126104260430528613)
|
||||||
|
[](https://twitter.com/codiumai)
|
||||||
<a href="https://github.com/Codium-ai/pr-agent/commits/main">
|
<a href="https://github.com/Codium-ai/pr-agent/commits/main">
|
||||||
<img alt="GitHub" src="https://img.shields.io/github/last-commit/Codium-ai/pr-agent/main?style=for-the-badge" height="20">
|
<img alt="GitHub" src="https://img.shields.io/github/last-commit/Codium-ai/pr-agent/main?style=for-the-badge" height="20">
|
||||||
</a>
|
</a>
|
||||||
@ -17,20 +18,25 @@ Making pull requests less painful with an AI agent
|
|||||||
|
|
||||||
CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull requests faster and more efficiently. It automatically analyzes the pull request and can provide several types of commands:
|
CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull requests faster and more efficiently. It automatically analyzes the pull request and can provide several types of commands:
|
||||||
|
|
||||||
‣ **Auto Description (`/describe`)**: Automatically generating [PR description](https://github.com/Codium-ai/pr-agent/pull/229#issue-1860711415) - title, type, summary, code walkthrough and labels.
|
‣ **Auto Description ([`/describe`](./docs/DESCRIBE.md))**: Automatically generating PR description - title, type, summary, code walkthrough and labels.
|
||||||
\
|
\
|
||||||
‣ **Auto Review (`/review`)**: [Adjustable feedback](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695022908) about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content.
|
‣ **Auto Review ([`/review`](./docs/REVIEW.md))**: Adjustable feedback about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content.
|
||||||
\
|
\
|
||||||
‣ **Question Answering (`/ask ...`)**: Answering [free-text questions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021332) about the PR.
|
‣ **Question Answering ([`/ask ...`](./docs/ASK.md))**: Answering free-text questions about the PR.
|
||||||
\
|
\
|
||||||
‣ **Code Suggestions (`/improve`)**: [Committable code suggestions](https://github.com/Codium-ai/pr-agent/pull/229#discussion_r1306919276) for improving the PR.
|
‣ **Code Suggestions ([`/improve`](./docs/IMPROVE.md))**: Committable code suggestions for improving the PR.
|
||||||
\
|
\
|
||||||
‣ **Update Changelog (`/update_changelog`)**: Automatically updating the CHANGELOG.md file with the [PR changes](https://github.com/Codium-ai/pr-agent/pull/168#discussion_r1282077645).
|
‣ **Update Changelog ([`/update_changelog`](./docs/UPDATE_CHANGELOG.md))**: Automatically updating the CHANGELOG.md file with the PR changes.
|
||||||
\
|
\
|
||||||
‣ **Find similar issue (`/similar_issue`)**: Automatically retrieves and presents [similar issues](https://github.com/Alibaba-MIIL/ASL/issues/107).
|
‣ **Find similar issue ([`/similar_issue`](./docs/SIMILAR_ISSUE.md))**: Automatically retrieves and presents similar issues
|
||||||
|
\
|
||||||
|
‣ **Add Documentation ([`/add_docs`](./docs/ADD_DOCUMENTATION.md))**: Automatically adds documentation to un-documented functions/classes in the PR.
|
||||||
|
|
||||||
|
See the [Usage Guide](./Usage.md) for instructions how to run the different tools from _CLI_, _online usage_, Or by _automatically triggering_ them when a new PR is opened.
|
||||||
|
|
||||||
See the [usage guide](./Usage.md) for instructions how to run the different tools from [CLI](./Usage.md#working-from-a-local-repo-cli), or by [online usage](./Usage.md#online-usage), as well as additional details on optional commands and configurations.
|
See the [Tools Guide](./docs/TOOLS_GUIDE.md) for detailed description of the different tools.
|
||||||
|
|
||||||
|
See the [Release notes](./RELEASE_NOTES.md) for updates on the latest changes.
|
||||||
|
|
||||||
<h3>Example results:</h3>
|
<h3>Example results:</h3>
|
||||||
</div>
|
</div>
|
||||||
@ -100,13 +106,15 @@ See the [usage guide](./Usage.md) for instructions how to run the different tool
|
|||||||
| | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | Gerrit |
|
| | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | Gerrit |
|
||||||
|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:|:----------:|
|
|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:|:----------:|
|
||||||
| TOOLS | Review | :white_check_mark: | :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: | :white_check_mark: |
|
||||||
|
| | ⮑ Incremental | :white_check_mark: | | | | | |
|
||||||
| | Ask | :white_check_mark: | :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: | :white_check_mark: |
|
||||||
| | Auto-Description | :white_check_mark: | :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: | :white_check_mark: | :white_check_mark: |
|
||||||
| | Improve Code | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
|
| | Improve Code | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
|
||||||
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
|
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
|
||||||
| | Reflect and Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: |
|
| | Reflect and Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: |
|
||||||
| | Update CHANGELOG.md | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | |
|
| | Update CHANGELOG.md | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | |
|
||||||
| | Find similar issue | :white_check_mark: | | | | | |
|
| | Find similar issue | :white_check_mark: | | | | | |
|
||||||
|
| | Add Documentation | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: |
|
||||||
| | | | | | | |
|
| | | | | | | |
|
||||||
| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| | App / webhook | :white_check_mark: | :white_check_mark: | | | |
|
| | App / webhook | :white_check_mark: | :white_check_mark: | | | |
|
||||||
@ -120,7 +128,7 @@ See the [usage guide](./Usage.md) for instructions how to run the different tool
|
|||||||
| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| | Incremental PR Review | :white_check_mark: | | | | | |
|
| | Incremental PR Review | :white_check_mark: | | | | | |
|
||||||
|
|
||||||
Review the **[usage guide](./Usage.md)** section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs.
|
Review the [usage guide](./Usage.md) section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs.
|
||||||
|
|
||||||
## Try it now
|
## Try it now
|
||||||
|
|
||||||
@ -157,12 +165,13 @@ There are several ways to use PR-Agent:
|
|||||||
- [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function)
|
- [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function)
|
||||||
- [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup)
|
- [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup)
|
||||||
- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server)
|
- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server)
|
||||||
|
- [Method 9: Run as a Bitbucket Pipeline](INSTALL.md#method-9-run-as-a-bitbucket-pipeline)
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
The following diagram illustrates PR-Agent tools and their flow:
|
The following diagram illustrates PR-Agent tools and their flow:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Check out the [PR Compression strategy](./PR_COMPRESSION.md) page for more details on how we convert a code diff to a manageable LLM prompt
|
Check out the [PR Compression strategy](./PR_COMPRESSION.md) page for more details on how we convert a code diff to a manageable LLM prompt
|
||||||
|
|
||||||
@ -192,7 +201,7 @@ Here are some advantages of PR-Agent:
|
|||||||
- [x] Rank the PR (see [here](https://github.com/Codium-ai/pr-agent/pull/89))
|
- [x] Rank the PR (see [here](https://github.com/Codium-ai/pr-agent/pull/89))
|
||||||
- [ ] Enforcing CONTRIBUTING.md guidelines
|
- [ ] Enforcing CONTRIBUTING.md guidelines
|
||||||
- [ ] Performance (are there any performance issues)
|
- [ ] Performance (are there any performance issues)
|
||||||
- [ ] Documentation (is the PR properly documented)
|
- [x] Documentation (is the PR properly documented)
|
||||||
- [ ] ...
|
- [ ] ...
|
||||||
|
|
||||||
## Similar Projects
|
## Similar Projects
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
## [Version 0.8] - 2023-09-27
|
||||||
|
- codiumai/pr-agent:0.8
|
||||||
|
- codiumai/pr-agent:0.8-github_app
|
||||||
|
- codiumai/pr-agent:0.8-bitbucket-app
|
||||||
|
- codiumai/pr-agent:0.8-gitlab_webhook
|
||||||
|
- codiumai/pr-agent:0.8-github_polling
|
||||||
|
- codiumai/pr-agent:0.8-github_action
|
||||||
|
|
||||||
|
### Added::Algo
|
||||||
|
- GitHub Action: Can control which tools will run automatically when a new PR is created. (see usage guide: https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-action)
|
||||||
|
- Code suggestion tool: Will try to avoid an 'add comments' suggestion (see https://github.com/Codium-ai/pr-agent/pull/327)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Gitlab: Fixed a bug of improper usage of pr_id
|
||||||
|
|
||||||
|
|
||||||
## [Version 0.7] - 2023-09-20
|
## [Version 0.7] - 2023-09-20
|
||||||
|
|
||||||
### Docker Tags
|
### Docker Tags
|
||||||
@ -20,6 +36,3 @@
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Protection against no code suggestions generated.
|
- Protection against no code suggestions generated.
|
||||||
- Resilience to repositories where the languages cannot be automatically detected.
|
- Resilience to repositories where the languages cannot be automatically detected.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
154
Usage.md
@ -1,4 +1,4 @@
|
|||||||
## Usage guide
|
## Usage Guide
|
||||||
|
|
||||||
### Table of Contents
|
### Table of Contents
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
@ -6,19 +6,19 @@
|
|||||||
- [Online usage](#online-usage)
|
- [Online usage](#online-usage)
|
||||||
- [Working with GitHub App](#working-with-github-app)
|
- [Working with GitHub App](#working-with-github-app)
|
||||||
- [Working with GitHub Action](#working-with-github-action)
|
- [Working with GitHub Action](#working-with-github-action)
|
||||||
|
- [Changing a model](#changing-a-model)
|
||||||
|
- [Working with large PRs](#working-with-large-prs)
|
||||||
- [Appendix - additional configurations walkthrough](#appendix---additional-configurations-walkthrough)
|
- [Appendix - additional configurations walkthrough](#appendix---additional-configurations-walkthrough)
|
||||||
|
|
||||||
### Introduction
|
### Introduction
|
||||||
|
|
||||||
There are 3 basic ways to invoke CodiumAI PR-Agent:
|
See the **[installation guide](/INSTALL.md)** for instructions on how to setup PR-Agent. After installation, there are three basic ways to invoke CodiumAI PR-Agent:
|
||||||
1. Locally running a CLI command
|
1. Locally running a CLI command
|
||||||
2. Online usage - by [commenting](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR
|
2. Online usage - by [commenting](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR
|
||||||
3. Enabling PR-Agent tools to run automatically when a new PR is opened
|
3. Enabling PR-Agent tools to run automatically when a new PR is opened
|
||||||
|
|
||||||
See the [installation guide](/INSTALL.md) for instructions on how to setup your own PR-Agent.
|
|
||||||
|
|
||||||
Specifically, CLI commands can be issued by invoking a pre-built [docker image](/INSTALL.md#running-from-source), or by invoking a [locally cloned repo](INSTALL.md#method-2-run-from-source).
|
Specifically, CLI commands can be issued by invoking a pre-built [docker image](/INSTALL.md#running-from-source), or by invoking a [locally cloned repo](INSTALL.md#method-2-run-from-source).
|
||||||
|
|
||||||
For online usage, you will need to setup either a [GitHub App](INSTALL.md#method-5-run-as-a-github-app), or a [GitHub Action](INSTALL.md#method-3-run-as-a-github-action).
|
For online usage, you will need to setup either a [GitHub App](INSTALL.md#method-5-run-as-a-github-app), or a [GitHub Action](INSTALL.md#method-3-run-as-a-github-action).
|
||||||
GitHub App and GitHub Action also enable to run PR-Agent specific tool automatically when a new PR is opened.
|
GitHub App and GitHub Action also enable to run PR-Agent specific tool automatically when a new PR is opened.
|
||||||
|
|
||||||
@ -27,10 +27,22 @@ GitHub App and GitHub Action also enable to run PR-Agent specific tool automatic
|
|||||||
The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)**.
|
The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)**.
|
||||||
In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the [pr_reviewer](/pr_agent/settings/configuration.toml#L16) section in the configuration file.
|
In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the [pr_reviewer](/pr_agent/settings/configuration.toml#L16) section in the configuration file.
|
||||||
|
|
||||||
**git provider:**
|
The [Tools Guide](./docs/TOOLS_GUIDE.md) provides a detailed description of the different tools and their configurations.
|
||||||
|
|
||||||
|
#### Ignoring files from analysis
|
||||||
|
In some cases, you may want to exclude specific files or directories from the analysis performed by CodiumAI PR-Agent. This can be useful, for example, when you have files that are generated automatically or files that shouldn't be reviewed, like vendored code.
|
||||||
|
|
||||||
|
To ignore files or directories, edit the **[ignore.toml](/pr_agent/settings/ignore.toml)** configuration file. This setting is also exposed the following environment variables:
|
||||||
|
|
||||||
|
- `IGNORE.GLOB`
|
||||||
|
- `IGNORE.REGEX`
|
||||||
|
|
||||||
|
See [dynaconf envvars documentation](https://www.dynaconf.com/envvars/).
|
||||||
|
|
||||||
|
#### git provider
|
||||||
The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported:
|
The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported:
|
||||||
`
|
`
|
||||||
"github", "gitlab", "azure", "codecommit", "local"
|
"github", "gitlab", "azure", "codecommit", "local", "gerrit"
|
||||||
`
|
`
|
||||||
|
|
||||||
[//]: # (** online usage:**)
|
[//]: # (** online usage:**)
|
||||||
@ -47,15 +59,14 @@ The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configu
|
|||||||
|
|
||||||
### Working from a local repo (CLI)
|
### Working from a local repo (CLI)
|
||||||
When running from your local repo (CLI), your local configuration file will be used.
|
When running from your local repo (CLI), your local configuration file will be used.
|
||||||
|
|
||||||
Examples for invoking the different tools via the CLI:
|
Examples for invoking the different tools via the CLI:
|
||||||
|
|
||||||
- **Review**: `python cli.py --pr_url=<pr_url> review`
|
- **Review**: `python -m pr_agent.cli --pr_url=<pr_url> review`
|
||||||
- **Describe**: `python cli.py --pr_url=<pr_url> describe`
|
- **Describe**: `python -m pr_agent.cli --pr_url=<pr_url> describe`
|
||||||
- **Improve**: `python cli.py --pr_url=<pr_url> improve`
|
- **Improve**: `python -m pr_agent.cli --pr_url=<pr_url> improve`
|
||||||
- **Ask**: `python cli.py --pr_url=<pr_url> ask "Write me a poem about this PR"`
|
- **Ask**: `python -m pr_agent.cli --pr_url=<pr_url> ask "Write me a poem about this PR"`
|
||||||
- **Reflect**: `python cli.py --pr_url=<pr_url> reflect`
|
- **Reflect**: `python -m pr_agent.cli --pr_url=<pr_url> reflect`
|
||||||
- **Update Changelog**: `python cli.py --pr_url=<pr_url> update_changelog`
|
- **Update Changelog**: `python -m pr_agent.cli --pr_url=<pr_url> update_changelog`
|
||||||
|
|
||||||
`<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).
|
||||||
|
|
||||||
@ -63,7 +74,7 @@ Examples for invoking the different tools via the CLI:
|
|||||||
|
|
||||||
(1) in addition to editing your local configuration file, you can also change any configuration value by adding it to the command line:
|
(1) in addition to editing your local configuration file, you can also change any configuration value by adding it to the command line:
|
||||||
```
|
```
|
||||||
python cli.py --pr_url=<pr_url> /review --pr_reviewer.extra_instructions="focus on the file: ..."
|
python -m pr_agent.cli --pr_url=<pr_url> /review --pr_reviewer.extra_instructions="focus on the file: ..."
|
||||||
```
|
```
|
||||||
|
|
||||||
(2) You can print results locally, without publishing them, by setting in `configuration.toml`:
|
(2) You can print results locally, without publishing them, by setting in `configuration.toml`:
|
||||||
@ -93,15 +104,15 @@ For example if you want to edit the `review` tool 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 file](pr_agent/settings/configuration.toml) file can be similarly edited.
|
Any configuration value in [configuration file](pr_agent/settings/configuration.toml) file can be similarly edited. comment `/config` to see the list of available configurations.
|
||||||
|
|
||||||
|
|
||||||
### Working with GitHub App
|
### Working with GitHub App
|
||||||
When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configurations from a pre-built repo will be initially loaded.
|
When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configurations from a pre-built docker will be initially loaded.
|
||||||
|
|
||||||
#### GitHub app automatic tools
|
#### GitHub app automatic tools
|
||||||
The [github_app](pr_agent/settings/configuration.toml#L56) section defines GitHub app specific configurations.
|
The [github_app](pr_agent/settings/configuration.toml#L56) section defines GitHub app specific configurations.
|
||||||
An important parameter is `pr_commands`, which is a list of tools that will be **run automatically when a new PR is opened**:
|
An important parameter is `pr_commands`, which is a list of tools that will be **run automatically** when a new PR is opened:
|
||||||
```
|
```
|
||||||
[github_app]
|
[github_app]
|
||||||
pr_commands = [
|
pr_commands = [
|
||||||
@ -112,7 +123,7 @@ pr_commands = [
|
|||||||
This means that when a new PR is opened, PR-Agent will run the `describe` and `auto_review` tools.
|
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.
|
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.
|
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:
|
For example, if your local `.pr_agent.toml` file contains:
|
||||||
```
|
```
|
||||||
[pr_description]
|
[pr_description]
|
||||||
@ -121,12 +132,18 @@ keep_original_user_title = false
|
|||||||
```
|
```
|
||||||
When a new PR is opened, PR-Agent will run the `describe` tool with the above parameters.
|
When a new PR is opened, PR-Agent will run the `describe` tool with the above parameters.
|
||||||
|
|
||||||
|
To cancel the automatic run of all the tools, set:
|
||||||
|
```
|
||||||
|
[github_app]
|
||||||
|
pr_commands = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
#### Editing the prompts
|
#### Editing the prompts
|
||||||
The prompts for the various PR-Agent tools are defined in the `pr_agent/settings` folder.
|
The prompts for the various PR-Agent tools are defined in the `pr_agent/settings` folder.
|
||||||
|
In practice, the prompts are loaded and stored as a standard setting object.
|
||||||
In practice, the prompts are loaded and stored as a standard setting object.
|
|
||||||
Hence, editing them is similar to editing any other configuration value - just place the relevant key in `.pr_agent.toml`file, and override the default value.
|
Hence, editing them is similar to editing any other configuration value - just place the relevant key in `.pr_agent.toml`file, and override the default value.
|
||||||
|
|
||||||
For example, if you want to edit the prompts of the [describe](./pr_agent/settings/pr_description_prompts.toml) tool, you can add the following to your `.pr_agent.toml` file:
|
For example, if you want to edit the prompts of the [describe](./pr_agent/settings/pr_description_prompts.toml) tool, you can add the following to your `.pr_agent.toml` file:
|
||||||
@ -142,34 +159,47 @@ user="""
|
|||||||
Note that the new prompt will need to generate an output compatible with the relevant [post-process function](./pr_agent/tools/pr_description.py#L137).
|
Note that the new prompt will need to generate an output compatible with the relevant [post-process function](./pr_agent/tools/pr_description.py#L137).
|
||||||
|
|
||||||
### Working with GitHub Action
|
### Working with GitHub Action
|
||||||
TBD
|
You can configure settings in GitHub action by adding environment variables under the env section in `.github/workflows/pr_agent.yml` file. Some examples:
|
||||||
|
```yaml
|
||||||
|
env:
|
||||||
|
# ... previous environment values
|
||||||
|
OPENAI.ORG: "<Your organization name under your OpenAI account>"
|
||||||
|
PR_REVIEWER.REQUIRE_TESTS_REVIEW: "false" # Disable tests review
|
||||||
|
PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 6 # Increase number of code suggestions
|
||||||
|
github_action.auto_review: "true" # Enable auto review
|
||||||
|
github_action.auto_describe: "true" # Enable auto describe
|
||||||
|
github_action.auto_improve: "false" # Disable auto improve
|
||||||
|
```
|
||||||
|
specifically, `github_action.auto_review`, `github_action.auto_describe` and `github_action.auto_improve` are used to enable/disable automatic tools that run when a new PR is opened.
|
||||||
|
|
||||||
### Appendix - additional configurations walkthrough
|
If not set, the default option is that only the `review` tool will run automatically when a new PR is opened.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
To use a different model than the default (GPT-4), you need to edit [configuration file](pr_agent/settings/configuration.toml#L2).
|
||||||
|
For models and environments not from OPENAI, you might need to provide additional keys and other parameters. See below for instructions.
|
||||||
|
|
||||||
#### Azure
|
#### Azure
|
||||||
To use Azure, set:
|
To use Azure, set in your .secrets.toml:
|
||||||
```
|
```
|
||||||
api_key = "" # your azure api key
|
api_key = "" # your azure api key
|
||||||
api_type = "azure"
|
api_type = "azure"
|
||||||
api_version = '2023-05-15' # Check Azure documentation for the current API version
|
api_version = '2023-05-15' # Check Azure documentation for the current API version
|
||||||
api_base = "" # The base URL for your Azure OpenAI resource. e.g. "https://<your resource name>.openai.azure.com"
|
api_base = "" # The base URL for your Azure OpenAI resource. e.g. "https://<your resource name>.openai.azure.com"
|
||||||
deployment_id = "" # The deployment name you chose when you deployed the engine
|
openai.deployment_id = "" # The deployment name you chose when you deployed the engine
|
||||||
```
|
```
|
||||||
in your .secrets.toml
|
|
||||||
|
|
||||||
and
|
and
|
||||||
```
|
```
|
||||||
[config]
|
[config]
|
||||||
model="" # the OpenAI model you've deployed on Azure (e.g. gpt-3.5-turbo)
|
model="" # the OpenAI model you've deployed on Azure (e.g. gpt-3.5-turbo)
|
||||||
```
|
```
|
||||||
in the configuration.toml
|
in the configuration.toml
|
||||||
|
|
||||||
#### Huggingface
|
#### Huggingface
|
||||||
|
|
||||||
**Local**
|
**Local**
|
||||||
You can run Huggingface models locally through either [VLLM](https://docs.litellm.ai/docs/providers/vllm) or [Ollama](https://docs.litellm.ai/docs/providers/ollama)
|
You can run Huggingface models locally through either [VLLM](https://docs.litellm.ai/docs/providers/vllm) or [Ollama](https://docs.litellm.ai/docs/providers/ollama)
|
||||||
|
|
||||||
E.g. to use a new Huggingface model locally via Ollama, set:
|
E.g. to use a new Huggingface model locally via Ollama, set:
|
||||||
@ -189,7 +219,7 @@ MAX_TOKENS={
|
|||||||
model = "ollama/llama2"
|
model = "ollama/llama2"
|
||||||
|
|
||||||
[ollama] # in .secrets.toml
|
[ollama] # in .secrets.toml
|
||||||
api_base = ... # the base url for your huggingface inference endpoint
|
api_base = ... # the base url for your huggingface inference endpoint
|
||||||
```
|
```
|
||||||
|
|
||||||
**Inference Endpoints**
|
**Inference Endpoints**
|
||||||
@ -210,7 +240,7 @@ model = "huggingface/meta-llama/Llama-2-7b-chat-hf"
|
|||||||
|
|
||||||
[huggingface] # in .secrets.toml
|
[huggingface] # in .secrets.toml
|
||||||
key = ... # your huggingface api key
|
key = ... # your huggingface api key
|
||||||
api_base = ... # the base url for your huggingface inference endpoint
|
api_base = ... # the base url for your huggingface inference endpoint
|
||||||
```
|
```
|
||||||
(you can obtain a Llama2 key from [here](https://replicate.com/replicate/llama-2-70b-chat/api))
|
(you can obtain a Llama2 key from [here](https://replicate.com/replicate/llama-2-70b-chat/api))
|
||||||
|
|
||||||
@ -228,12 +258,50 @@ key = ...
|
|||||||
|
|
||||||
Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models.
|
Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models.
|
||||||
|
|
||||||
|
### Working with large PRs
|
||||||
|
|
||||||
|
The default mode of CodiumAI is to have a single call per tool, using GPT-4, which has a token limit of 8000 tokens.
|
||||||
|
This mode provide a very good speed-quality-cost tradeoff, and can handle most PRs successfully.
|
||||||
|
When the PR is above the token limit, it employs a [PR Compression strategy](./PR_COMPRESSION.md).
|
||||||
|
|
||||||
|
However, for very large PRs, or in case you want to emphasize quality over speed and cost, there are 2 possible solutions:
|
||||||
|
1) [Use a model](#changing-a-model) with larger context, like GPT-32K, or claude-100K. This solution will be applicable for all the tools.
|
||||||
|
2) For the `/improve` tool, there is an ['extended' mode](./docs/IMPROVE.md) (`/improve --extended`),
|
||||||
|
which divides the PR to chunks, and process each chunk separately. With this mode, regardless of the model, no compression will be done (but for large PRs, multiple model calls may occur)
|
||||||
|
|
||||||
|
### Appendix - additional configurations walkthrough
|
||||||
|
|
||||||
|
|
||||||
#### Extra instructions
|
#### Extra instructions
|
||||||
All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage:
|
All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage:
|
||||||
```
|
```
|
||||||
/update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..."
|
/update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..."
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Patch Extra Lines
|
||||||
|
By default, around any change in your PR, git patch provides 3 lines of context above and below the change.
|
||||||
|
```
|
||||||
|
@@ -12,5 +12,5 @@ def func1():
|
||||||
|
code line that already existed in the file...
|
||||||
|
code line that already existed in the file...
|
||||||
|
code line that already existed in the file....
|
||||||
|
-code line that was removed in the PR
|
||||||
|
+new code line added in the PR
|
||||||
|
code line that already existed in the file...
|
||||||
|
code line that already existed in the file...
|
||||||
|
code line that already existed in the file...
|
||||||
|
```
|
||||||
|
|
||||||
|
For the `review`, `describe`, `ask` and `add_docs` tools, if the token budget allows, PR-Agent tries to increase the number of lines of context, via the parameter:
|
||||||
|
```
|
||||||
|
[config]
|
||||||
|
patch_extra_lines=3
|
||||||
|
```
|
||||||
|
|
||||||
|
Increasing this number provides more context to the model, but will also increase the token budget.
|
||||||
|
If the PR is too large (see [PR Compression strategy](./PR_COMPRESSION.md)), PR-Agent automatically sets this number to 0, using the original git patch.
|
||||||
|
|
||||||
|
|
||||||
#### Azure DevOps provider
|
#### Azure DevOps provider
|
||||||
To use Azure DevOps provider use the following settings in configuration.toml:
|
To use Azure DevOps provider use the following settings in configuration.toml:
|
||||||
```
|
```
|
||||||
@ -248,25 +316,3 @@ And use the following settings (you have to replace the values) in .secrets.toml
|
|||||||
org = "https://dev.azure.com/YOUR_ORGANIZATION/"
|
org = "https://dev.azure.com/YOUR_ORGANIZATION/"
|
||||||
pat = "YOUR_PAT_TOKEN"
|
pat = "YOUR_PAT_TOKEN"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Similar issue tool
|
|
||||||
|
|
||||||
[Example usage](https://github.com/Alibaba-MIIL/ASL/issues/107)
|
|
||||||
|
|
||||||
<img src=./pics/similar_issue_tool.png width="768">
|
|
||||||
|
|
||||||
To enable usage of the '**similar issue**' tool, you need to set the following keys in `.secrets.toml` (or in the relevant environment variables):
|
|
||||||
```
|
|
||||||
[pinecone]
|
|
||||||
api_key = "..."
|
|
||||||
environment = "..."
|
|
||||||
```
|
|
||||||
These parameters can be obtained by registering to [Pinecone](https://app.pinecone.io/?sessionType=signup/).
|
|
||||||
|
|
||||||
- To invoke the 'similar issue' tool from **CLI**, run:
|
|
||||||
`python3 cli.py --issue_url=... similar_issue`
|
|
||||||
|
|
||||||
- To invoke the 'similar' issue tool via online usage, [comment](https://github.com/Codium-ai/pr-agent/issues/178#issuecomment-1716934893) on a PR:
|
|
||||||
`/similar_issue`
|
|
||||||
|
|
||||||
- You can also enable the 'similar issue' tool to run automatically when a new issue is opened, by adding it to the [pr_commands list in the github_app section](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L66)
|
|
||||||
|
2
bitbucket_pipeline/entrypoint.sh
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
python /app/pr_agent/servers/bitbucket_pipeline_runner.py
|
15
docs/ADD_DOCUMENTATION.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Add Documentation Tool
|
||||||
|
The `add_docs` tool scans the PR code changes, and automatically suggests documentation for the undocumented code components (functions, classes, etc.).
|
||||||
|
|
||||||
|
It can be invoked manually by commenting on any PR:
|
||||||
|
```
|
||||||
|
/add_docs
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/add_docs_comment.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/add_docs.png width="768"></kbd>
|
||||||
|
|
||||||
|
### Configuration options
|
||||||
|
- `docs_style`: The exact style of the documentation (for python docstring). you can choose between: `google`, `numpy`, `sphinx`, `restructuredtext`, `plain`. Default is `sphinx`.
|
||||||
|
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
|
11
docs/ASK.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# ASK Tool
|
||||||
|
|
||||||
|
The `ask` tool answers questions about the PR, based on the PR code changes.
|
||||||
|
It can be invoked manually by commenting on any PR:
|
||||||
|
```
|
||||||
|
/ask "..."
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/ask_comment.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/ask.png width="768"></kbd>
|
51
docs/DESCRIBE.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Describe Tool
|
||||||
|
|
||||||
|
The `describe` tool scans the PR code changes, and automatically generates PR description - title, type, summary, code walkthrough and labels.
|
||||||
|
It can be invoked manually by commenting on any PR:
|
||||||
|
```
|
||||||
|
/describe
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/describe_comment.png width="768"></kbd>
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/describe.png width="768"></kbd>
|
||||||
|
|
||||||
|
The `describe` tool can also be triggered automatically every time a new PR is opened. See examples for automatic triggers for [GitHub App](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) and [GitHub Action](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-action)
|
||||||
|
|
||||||
|
### Configuration options
|
||||||
|
|
||||||
|
Under the section 'pr_description', the [configuration file](./../pr_agent/settings/configuration.toml#L28) contains options to customize the 'describe' tool:
|
||||||
|
|
||||||
|
- `publish_labels`: if set to true, the tool will publish the labels to the PR. Default is true.
|
||||||
|
|
||||||
|
- `publish_description_as_comment`: if set to true, the tool will publish the description as a comment to the PR. If false, it will overwrite the origianl description. Default is false.
|
||||||
|
|
||||||
|
- `add_original_user_description`: if set to true, the tool will add the original user description to the generated description. Default is false.
|
||||||
|
|
||||||
|
- `keep_original_user_title`: if set to true, the tool will keep the original PR title, and won't change it. Default is false.
|
||||||
|
|
||||||
|
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
|
||||||
|
|
||||||
|
#### Markers template
|
||||||
|
|
||||||
|
markers enable to easily integrate user's content and auto-generated content, with a template-like mechanism.
|
||||||
|
|
||||||
|
- `use_description_markers`: if set to true, the tool will use markers template. It replaces every marker of the form `pr_agent:marker_name` with the relevant content. Default is false.
|
||||||
|
|
||||||
|
For example, if the PR original description was:
|
||||||
|
```
|
||||||
|
User content...
|
||||||
|
|
||||||
|
## PR Type:
|
||||||
|
pr_agent:pr_type
|
||||||
|
|
||||||
|
## PR Description:
|
||||||
|
pr_agent:summary
|
||||||
|
|
||||||
|
## PR Walkthrough:
|
||||||
|
pr_agent:walkthrough
|
||||||
|
```
|
||||||
|
The marker `pr_agent:pr_type` will be replaced with the PR type, `pr_agent:summary` will be replaced with the PR summary, and `pr_agent:walkthrough` will be replaced with the PR walkthrough.
|
||||||
|
|
||||||
|
- `include_generated_by_header`: if set to true, the tool will add a dedicated header: 'Generated by PR Agent at ...' to any automatic content. Default is true.
|
34
docs/IMPROVE.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Improve Tool
|
||||||
|
|
||||||
|
The `improve` tool scans the PR code changes, and automatically generate committable suggestions for improving the PR code.
|
||||||
|
It can be invoked manually by commenting on any PR:
|
||||||
|
```
|
||||||
|
/improve
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/improve_comment.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/improve.png width="768"></kbd>
|
||||||
|
|
||||||
|
The `improve` tool can also be triggered automatically every time a new PR is opened. See examples for automatic triggers for [GitHub App](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) and [GitHub Action](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-action)
|
||||||
|
|
||||||
|
An extended mode, which does not involve PR Compression and provides more comprehensive suggestions, can be invoked by commenting on any PR:
|
||||||
|
```
|
||||||
|
/improve --extended
|
||||||
|
```
|
||||||
|
Note that the extended mode divides the PR code changes into chunks, up to the token limits, where each chunk is handled separately (multiple calls to GPT-4).
|
||||||
|
Hence, the total number of suggestions is proportional to the number of chunks, i.e. the size of the PR.
|
||||||
|
|
||||||
|
### Configuration options
|
||||||
|
|
||||||
|
Under the section 'pr_code_suggestions', the [configuration file](./../pr_agent/settings/configuration.toml#L40) contains options to customize the 'improve' tool:
|
||||||
|
|
||||||
|
- `num_code_suggestions`: number of code suggestions provided by the 'improve' tool. Default is 4.
|
||||||
|
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
|
||||||
|
- `rank_suggestions`: if set to true, the tool will rank the suggestions, based on importance. Default is false.
|
||||||
|
|
||||||
|
#### params for '/improve --extended' mode
|
||||||
|
- `num_code_suggestions_per_chunk`: number of code suggestions provided by the 'improve' tool, per chunk. Default is 8.
|
||||||
|
- `rank_extended_suggestions`: if set to true, the tool will rank the suggestions, based on importance. Default is true.
|
||||||
|
- `max_number_of_calls`: maximum number of chunks. Default is 5.
|
||||||
|
- `final_clip_factor`: factor to remove suggestions with low confidence. Default is 0.9.
|
46
docs/REVIEW.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Review Tool
|
||||||
|
|
||||||
|
The `review` tool scans the PR code changes, and automatically generates a PR review.
|
||||||
|
It can be invoked manually by commenting on any PR:
|
||||||
|
```
|
||||||
|
/review
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/review_comment.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/describe.png width="768"></kbd>
|
||||||
|
|
||||||
|
The `review` tool can also be triggered automatically every time a new PR is opened. See examples for automatic triggers for [GitHub App](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) and [GitHub Action](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-action)
|
||||||
|
|
||||||
|
### Configuration options
|
||||||
|
|
||||||
|
Under the section 'pr_reviewer', the [configuration file](./../pr_agent/settings/configuration.toml#L16) contains options to customize the 'review' tool:
|
||||||
|
|
||||||
|
- `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_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_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.
|
||||||
|
- `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.
|
||||||
|
- `automatic_review`: if set to false, no automatic reviews will be done. Default is true.
|
||||||
|
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
|
||||||
|
#### Incremental Mode
|
||||||
|
For an incremental review, which only considers changes since the last PR-Agent review, this can be useful when working on the PR in an iterative manner, and you want to focus on the changes since the last review instead of reviewing the entire PR again, the following command can be used:
|
||||||
|
```
|
||||||
|
/improve -i
|
||||||
|
```
|
||||||
|
Note that the incremental mode is only available for GitHub.
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/incremental_review.png width="768"></kbd>
|
||||||
|
|
||||||
|
#### PR Reflection
|
||||||
|
By invoking:
|
||||||
|
```
|
||||||
|
/reflect_and_review
|
||||||
|
```
|
||||||
|
The tool will first ask the author questions about the PR, and will guide the review based on his answers.
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/reflection_questions.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/reflection_answers.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/reflection_insights.png width="768"></kbd>
|
31
docs/SIMILAR_ISSUE.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Similar Issue Tool
|
||||||
|
The similar issue tool retrieves the most similar issues to the current issue.
|
||||||
|
It can be invoked manually by commenting on any PR:
|
||||||
|
```
|
||||||
|
/similar_issue
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/similar_issue_original_issue.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/similar_issue_comment.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/similar_issue.png width="768"></kbd>
|
||||||
|
|
||||||
|
Note that to perform retrieval, the `similar_issue` tool indexes all the repo previous issues (once).
|
||||||
|
|
||||||
|
To enable usage of the '**similar issue**' tool, you need to set the following keys in `.secrets.toml` (or in the relevant environment variables):
|
||||||
|
```
|
||||||
|
[pinecone]
|
||||||
|
api_key = "..."
|
||||||
|
environment = "..."
|
||||||
|
```
|
||||||
|
These parameters can be obtained by registering to [Pinecone](https://app.pinecone.io/?sessionType=signup/).
|
||||||
|
|
||||||
|
|
||||||
|
### How to use:
|
||||||
|
- To invoke the 'similar issue' tool from **CLI**, run:
|
||||||
|
`python3 cli.py --issue_url=... similar_issue`
|
||||||
|
|
||||||
|
- To invoke the 'similar' issue tool via online usage, [comment](https://github.com/Codium-ai/pr-agent/issues/178#issuecomment-1716934893) on a PR:
|
||||||
|
`/similar_issue`
|
||||||
|
|
||||||
|
- You can also enable the 'similar issue' tool to run automatically when a new issue is opened, by adding it to the [pr_commands list in the github_app section](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L66)
|
10
docs/TOOLS_GUIDE.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
## Tools Guide
|
||||||
|
- [DESCRIBE](./DESCRIBE.md)
|
||||||
|
- [REVIEW](./REVIEW.md)
|
||||||
|
- [IMPROVE](./IMPROVE.md)
|
||||||
|
- [ASK](./ASK.md)
|
||||||
|
- [SIMILAR_ISSUE](./SIMILAR_ISSUE.md)
|
||||||
|
- [UPDATE CHANGELOG](./UPDATE_CHANGELOG.md)
|
||||||
|
- [ADD DOCUMENTATION](./ADD_DOCUMENTATION.md)
|
||||||
|
|
||||||
|
See the **[installation guide](/INSTALL.md)** for instructions on how to setup PR-Agent.
|
19
docs/UPDATE_CHANGELOG.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Update Changelog Tool
|
||||||
|
|
||||||
|
The `update_changelog` tool automatically updates the CHANGELOG.md file with the PR changes.
|
||||||
|
It can be invoked manually by commenting on any PR:
|
||||||
|
```
|
||||||
|
/update_changelog
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<kbd><img src=./../pics/update_changelog_comment.png width="768"></kbd>
|
||||||
|
<kbd><img src=./../pics/update_changelog.png width="768"></kbd>
|
||||||
|
|
||||||
|
|
||||||
|
### Configuration options
|
||||||
|
|
||||||
|
Under the section 'pr_update_changelog', the [configuration file](./../pr_agent/settings/configuration.toml#L50) contains options to customize the 'update changelog' tool:
|
||||||
|
|
||||||
|
- `push_changelog_changes`: whether to push the changes to CHANGELOG.md, or just print them. Default is false (print only).
|
||||||
|
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...
|
BIN
pics/add_docs.png
Normal file
After Width: | Height: | Size: 325 KiB |
BIN
pics/add_docs_comment.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
pics/ask.png
Normal file
After Width: | Height: | Size: 308 KiB |
BIN
pics/ask_comment.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
pics/describe.png
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
pics/describe_comment.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
pics/improve.png
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
pics/improve_comment.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
pics/incremental_review.png
Normal file
After Width: | Height: | Size: 286 KiB |
BIN
pics/reflection_answers.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
pics/reflection_insights.png
Normal file
After Width: | Height: | Size: 217 KiB |
BIN
pics/reflection_questions.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
pics/review.png
Normal file
After Width: | Height: | Size: 190 KiB |
BIN
pics/review_comment.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
pics/similar_issue.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
pics/similar_issue_comment.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
pics/similar_issue_original_issue.png
Normal file
After Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 210 KiB |
BIN
pics/update_changelog.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
pics/update_changelog_comment.png
Normal file
After Width: | Height: | Size: 25 KiB |
@ -6,6 +6,8 @@ import tempfile
|
|||||||
from pr_agent.algo.utils import update_settings_from_args
|
from pr_agent.algo.utils import update_settings_from_args
|
||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
from pr_agent.git_providers import get_git_provider
|
from pr_agent.git_providers import get_git_provider
|
||||||
|
from pr_agent.git_providers.utils import apply_repo_settings
|
||||||
|
from pr_agent.tools.pr_add_docs import PRAddDocs
|
||||||
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
|
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
|
||||||
from pr_agent.tools.pr_description import PRDescription
|
from pr_agent.tools.pr_description import PRDescription
|
||||||
from pr_agent.tools.pr_information_from_user import PRInformationFromUser
|
from pr_agent.tools.pr_information_from_user import PRInformationFromUser
|
||||||
@ -32,6 +34,7 @@ command2class = {
|
|||||||
"config": PRConfig,
|
"config": PRConfig,
|
||||||
"settings": PRConfig,
|
"settings": PRConfig,
|
||||||
"similar_issue": PRSimilarIssue,
|
"similar_issue": PRSimilarIssue,
|
||||||
|
"add_docs": PRAddDocs,
|
||||||
}
|
}
|
||||||
|
|
||||||
commands = list(command2class.keys())
|
commands = list(command2class.keys())
|
||||||
@ -42,22 +45,7 @@ class PRAgent:
|
|||||||
|
|
||||||
async def handle_request(self, pr_url, request, notify=None) -> bool:
|
async def handle_request(self, pr_url, request, notify=None) -> bool:
|
||||||
# First, apply repo specific settings if exists
|
# First, apply repo specific settings if exists
|
||||||
if get_settings().config.use_repo_settings_file:
|
apply_repo_settings(pr_url)
|
||||||
repo_settings_file = None
|
|
||||||
try:
|
|
||||||
git_provider = get_git_provider()(pr_url)
|
|
||||||
repo_settings = git_provider.get_repo_settings()
|
|
||||||
if repo_settings:
|
|
||||||
repo_settings_file = None
|
|
||||||
fd, repo_settings_file = tempfile.mkstemp(suffix='.toml')
|
|
||||||
os.write(fd, repo_settings)
|
|
||||||
get_settings().load_file(repo_settings_file)
|
|
||||||
finally:
|
|
||||||
if repo_settings_file:
|
|
||||||
try:
|
|
||||||
os.remove(repo_settings_file)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Failed to remove temporary settings file {repo_settings_file}", e)
|
|
||||||
|
|
||||||
# Then, apply user specific settings if exists
|
# Then, apply user specific settings if exists
|
||||||
request = request.replace("'", "\\'")
|
request = request.replace("'", "\\'")
|
||||||
@ -67,8 +55,8 @@ class PRAgent:
|
|||||||
args = update_settings_from_args(args)
|
args = update_settings_from_args(args)
|
||||||
|
|
||||||
action = action.lstrip("/").lower()
|
action = action.lstrip("/").lower()
|
||||||
if action == "reflect_and_review" and not get_settings().pr_reviewer.ask_and_reflect:
|
if action == "reflect_and_review":
|
||||||
action = "review"
|
get_settings().pr_reviewer.ask_and_reflect = True
|
||||||
if action == "answer":
|
if action == "answer":
|
||||||
if notify:
|
if notify:
|
||||||
notify()
|
notify()
|
||||||
@ -82,3 +70,4 @@ class PRAgent:
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -92,6 +92,8 @@ class AiHandler:
|
|||||||
f"Generating completion with {model}"
|
f"Generating completion with {model}"
|
||||||
f"{(' from deployment ' + deployment_id) if deployment_id else ''}"
|
f"{(' from deployment ' + deployment_id) if deployment_id else ''}"
|
||||||
)
|
)
|
||||||
|
if self.azure:
|
||||||
|
model = 'azure/' + model
|
||||||
response = await acompletion(
|
response = await acompletion(
|
||||||
model=model,
|
model=model,
|
||||||
deployment_id=deployment_id,
|
deployment_id=deployment_id,
|
||||||
@ -100,7 +102,6 @@ class AiHandler:
|
|||||||
{"role": "user", "content": user}
|
{"role": "user", "content": user}
|
||||||
],
|
],
|
||||||
temperature=temperature,
|
temperature=temperature,
|
||||||
azure=self.azure,
|
|
||||||
force_timeout=get_settings().config.ai_timeout
|
force_timeout=get_settings().config.ai_timeout
|
||||||
)
|
)
|
||||||
except (APIError, Timeout, TryAgain) as e:
|
except (APIError, Timeout, TryAgain) as e:
|
||||||
|
31
pr_agent/algo/file_filter.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import fnmatch
|
||||||
|
import re
|
||||||
|
|
||||||
|
from pr_agent.config_loader import get_settings
|
||||||
|
|
||||||
|
def filter_ignored(files):
|
||||||
|
"""
|
||||||
|
Filter out files that match the ignore patterns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# load regex patterns, and translate glob patterns to regex
|
||||||
|
patterns = get_settings().ignore.regex
|
||||||
|
patterns += [fnmatch.translate(glob) for glob in get_settings().ignore.glob]
|
||||||
|
|
||||||
|
# compile all valid patterns
|
||||||
|
compiled_patterns = []
|
||||||
|
for r in patterns:
|
||||||
|
try:
|
||||||
|
compiled_patterns.append(re.compile(r))
|
||||||
|
except re.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# keep filenames that _don't_ match the ignore regex
|
||||||
|
for r in compiled_patterns:
|
||||||
|
files = [f for f in files if not r.match(f.filename)]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Could not filter file list: {e}")
|
||||||
|
|
||||||
|
return files
|
@ -40,12 +40,16 @@ def extend_patch(original_file_str, patch_str, num_lines) -> str:
|
|||||||
extended_patch_lines.extend(
|
extended_patch_lines.extend(
|
||||||
original_lines[start1 + size1 - 1:start1 + size1 - 1 + num_lines])
|
original_lines[start1 + size1 - 1:start1 + size1 - 1 + num_lines])
|
||||||
|
|
||||||
|
res = list(match.groups())
|
||||||
|
for i in range(len(res)):
|
||||||
|
if res[i] is None:
|
||||||
|
res[i] = 0
|
||||||
try:
|
try:
|
||||||
start1, size1, start2, size2 = map(int, match.groups()[:4])
|
start1, size1, start2, size2 = map(int, res[:4])
|
||||||
except: # '@@ -0,0 +1 @@' case
|
except: # '@@ -0,0 +1 @@' case
|
||||||
start1, size1, size2 = map(int, match.groups()[:3])
|
start1, size1, size2 = map(int, res[:3])
|
||||||
start2 = 0
|
start2 = 0
|
||||||
section_header = match.groups()[4]
|
section_header = res[4]
|
||||||
extended_start1 = max(1, start1 - num_lines)
|
extended_start1 = max(1, start1 - num_lines)
|
||||||
extended_size1 = size1 + (start1 - extended_start1) + num_lines
|
extended_size1 = size1 + (start1 - extended_start1) + num_lines
|
||||||
extended_start2 = max(1, start2 - num_lines)
|
extended_start2 = max(1, start2 - num_lines)
|
||||||
@ -207,10 +211,15 @@ __old hunk__
|
|||||||
old_content_lines = []
|
old_content_lines = []
|
||||||
if match:
|
if match:
|
||||||
prev_header_line = header_line
|
prev_header_line = header_line
|
||||||
|
|
||||||
|
res = list(match.groups())
|
||||||
|
for i in range(len(res)):
|
||||||
|
if res[i] is None:
|
||||||
|
res[i] = 0
|
||||||
try:
|
try:
|
||||||
start1, size1, start2, size2 = map(int, match.groups()[:4])
|
start1, size1, start2, size2 = map(int, res[:4])
|
||||||
except: # '@@ -0,0 +1 @@' case
|
except: # '@@ -0,0 +1 @@' case
|
||||||
start1, size1, size2 = map(int, match.groups()[:3])
|
start1, size1, size2 = map(int, res[:3])
|
||||||
start2 = 0
|
start2 = 0
|
||||||
|
|
||||||
elif line.startswith('+'):
|
elif line.startswith('+'):
|
||||||
|
@ -11,6 +11,7 @@ from github import RateLimitExceededException
|
|||||||
from pr_agent.algo import MAX_TOKENS
|
from pr_agent.algo import MAX_TOKENS
|
||||||
from pr_agent.algo.git_patch_processing import convert_to_hunks_with_lines_numbers, extend_patch, handle_patch_deletions
|
from pr_agent.algo.git_patch_processing import convert_to_hunks_with_lines_numbers, extend_patch, handle_patch_deletions
|
||||||
from pr_agent.algo.language_handler import sort_files_by_main_languages
|
from pr_agent.algo.language_handler import sort_files_by_main_languages
|
||||||
|
from pr_agent.algo.file_filter import filter_ignored
|
||||||
from pr_agent.algo.token_handler import TokenHandler, get_token_encoder
|
from pr_agent.algo.token_handler import TokenHandler, get_token_encoder
|
||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
from pr_agent.git_providers.git_provider import FilePatchInfo, GitProvider
|
from pr_agent.git_providers.git_provider import FilePatchInfo, GitProvider
|
||||||
@ -21,7 +22,6 @@ MORE_MODIFIED_FILES_ = "More modified files:\n"
|
|||||||
|
|
||||||
OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD = 1000
|
OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD = 1000
|
||||||
OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD = 600
|
OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD = 600
|
||||||
PATCH_EXTRA_LINES = 3
|
|
||||||
|
|
||||||
def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: str,
|
def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: str,
|
||||||
add_line_numbers_to_hunks: bool = False, disable_extra_lines: bool = False) -> str:
|
add_line_numbers_to_hunks: bool = False, disable_extra_lines: bool = False) -> str:
|
||||||
@ -44,8 +44,9 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if disable_extra_lines:
|
if disable_extra_lines:
|
||||||
global PATCH_EXTRA_LINES
|
|
||||||
PATCH_EXTRA_LINES = 0
|
PATCH_EXTRA_LINES = 0
|
||||||
|
else:
|
||||||
|
PATCH_EXTRA_LINES = get_settings().config.patch_extra_lines
|
||||||
|
|
||||||
try:
|
try:
|
||||||
diff_files = git_provider.get_diff_files()
|
diff_files = git_provider.get_diff_files()
|
||||||
@ -53,12 +54,14 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s
|
|||||||
logging.error(f"Rate limit exceeded for git provider API. original message {e}")
|
logging.error(f"Rate limit exceeded for git provider API. original message {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
diff_files = filter_ignored(diff_files)
|
||||||
|
|
||||||
# get pr languages
|
# get pr languages
|
||||||
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
||||||
|
|
||||||
# generate a standard diff string, with patch extension
|
# generate a standard diff string, with patch extension
|
||||||
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(pr_languages, token_handler,
|
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
|
||||||
add_line_numbers_to_hunks)
|
pr_languages, token_handler, add_line_numbers_to_hunks, patch_extra_lines=PATCH_EXTRA_LINES)
|
||||||
|
|
||||||
# if we are under the limit, return the full diff
|
# if we are under the limit, return the full diff
|
||||||
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < MAX_TOKENS[model]:
|
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < MAX_TOKENS[model]:
|
||||||
@ -80,7 +83,8 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s
|
|||||||
|
|
||||||
def pr_generate_extended_diff(pr_languages: list,
|
def pr_generate_extended_diff(pr_languages: list,
|
||||||
token_handler: TokenHandler,
|
token_handler: TokenHandler,
|
||||||
add_line_numbers_to_hunks: bool) -> Tuple[list, int, list]:
|
add_line_numbers_to_hunks: bool,
|
||||||
|
patch_extra_lines: int = 0) -> Tuple[list, int, list]:
|
||||||
"""
|
"""
|
||||||
Generate a standard diff string with patch extension, while counting the number of tokens used and applying diff
|
Generate a standard diff string with patch extension, while counting the number of tokens used and applying diff
|
||||||
minimization techniques if needed.
|
minimization techniques if needed.
|
||||||
@ -102,7 +106,7 @@ def pr_generate_extended_diff(pr_languages: list,
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# extend each patch with extra lines of context
|
# extend each patch with extra lines of context
|
||||||
extended_patch = extend_patch(original_file_content_str, patch, num_lines=PATCH_EXTRA_LINES)
|
extended_patch = extend_patch(original_file_content_str, patch, num_lines=patch_extra_lines)
|
||||||
full_extended_patch = f"\n\n## {file.filename}\n\n{extended_patch}\n"
|
full_extended_patch = f"\n\n## {file.filename}\n\n{extended_patch}\n"
|
||||||
|
|
||||||
if add_line_numbers_to_hunks:
|
if add_line_numbers_to_hunks:
|
||||||
@ -347,16 +351,16 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
|||||||
"""
|
"""
|
||||||
Retrieves the diff files from a Git provider, sorts them by main language, and generates patches for each file.
|
Retrieves the diff files from a Git provider, sorts them by main language, and generates patches for each file.
|
||||||
The patches are split into multiple groups based on the maximum number of tokens allowed for the given model.
|
The patches are split into multiple groups based on the maximum number of tokens allowed for the given model.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
git_provider (GitProvider): An object that provides access to Git provider APIs.
|
git_provider (GitProvider): An object that provides access to Git provider APIs.
|
||||||
token_handler (TokenHandler): An object that handles tokens in the context of a pull request.
|
token_handler (TokenHandler): An object that handles tokens in the context of a pull request.
|
||||||
model (str): The name of the model.
|
model (str): The name of the model.
|
||||||
max_calls (int, optional): The maximum number of calls to retrieve diff files. Defaults to 5.
|
max_calls (int, optional): The maximum number of calls to retrieve diff files. Defaults to 5.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[str]: A list of final diff strings, split into multiple groups based on the maximum number of tokens allowed for the given model.
|
List[str]: A list of final diff strings, split into multiple groups based on the maximum number of tokens allowed for the given model.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
RateLimitExceededException: If the rate limit for the Git provider API is exceeded.
|
RateLimitExceededException: If the rate limit for the Git provider API is exceeded.
|
||||||
"""
|
"""
|
||||||
@ -366,6 +370,8 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
|||||||
logging.error(f"Rate limit exceeded for git provider API. original message {e}")
|
logging.error(f"Rate limit exceeded for git provider API. original message {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
diff_files = filter_ignored(diff_files)
|
||||||
|
|
||||||
# Sort files by main language
|
# Sort files by main language
|
||||||
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files)
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool=True) -> str:
|
|||||||
markdown_text += f"- {emoji} **{key}:**\n\n"
|
markdown_text += f"- {emoji} **{key}:**\n\n"
|
||||||
for item in value:
|
for item in value:
|
||||||
if isinstance(item, dict) and key.lower() == 'code feedback':
|
if isinstance(item, dict) and key.lower() == 'code feedback':
|
||||||
markdown_text += parse_code_suggestion(item)
|
markdown_text += parse_code_suggestion(item, gfm_supported)
|
||||||
elif item:
|
elif item:
|
||||||
markdown_text += f" - {item}\n"
|
markdown_text += f" - {item}\n"
|
||||||
if key.lower() == 'code feedback':
|
if key.lower() == 'code feedback':
|
||||||
@ -76,7 +76,7 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool=True) -> str:
|
|||||||
return markdown_text
|
return markdown_text
|
||||||
|
|
||||||
|
|
||||||
def parse_code_suggestion(code_suggestions: dict) -> str:
|
def parse_code_suggestion(code_suggestions: dict, gfm_supported: bool=True) -> str:
|
||||||
"""
|
"""
|
||||||
Convert a dictionary of data into markdown format.
|
Convert a dictionary of data into markdown format.
|
||||||
|
|
||||||
@ -99,6 +99,9 @@ def parse_code_suggestion(code_suggestions: dict) -> str:
|
|||||||
markdown_text += f"\n - **{sub_key}:** {sub_value}\n"
|
markdown_text += f"\n - **{sub_key}:** {sub_value}\n"
|
||||||
else:
|
else:
|
||||||
markdown_text += f" **{sub_key}:** {sub_value}\n"
|
markdown_text += f" **{sub_key}:** {sub_value}\n"
|
||||||
|
if not gfm_supported:
|
||||||
|
if "relevant line" not in sub_key.lower(): # nicer presentation
|
||||||
|
markdown_text = markdown_text.rstrip('\n') + "\\\n"
|
||||||
|
|
||||||
markdown_text += "\n"
|
markdown_text += "\n"
|
||||||
return markdown_text
|
return markdown_text
|
||||||
|
@ -14,6 +14,7 @@ global_settings = Dynaconf(
|
|||||||
settings_files=[join(current_dir, f) for f in [
|
settings_files=[join(current_dir, f) for f in [
|
||||||
"settings/.secrets.toml",
|
"settings/.secrets.toml",
|
||||||
"settings/configuration.toml",
|
"settings/configuration.toml",
|
||||||
|
"settings/ignore.toml",
|
||||||
"settings/language_extensions.toml",
|
"settings/language_extensions.toml",
|
||||||
"settings/pr_reviewer_prompts.toml",
|
"settings/pr_reviewer_prompts.toml",
|
||||||
"settings/pr_questions_prompts.toml",
|
"settings/pr_questions_prompts.toml",
|
||||||
@ -22,6 +23,7 @@ global_settings = Dynaconf(
|
|||||||
"settings/pr_sort_code_suggestions_prompts.toml",
|
"settings/pr_sort_code_suggestions_prompts.toml",
|
||||||
"settings/pr_information_from_user_prompts.toml",
|
"settings/pr_information_from_user_prompts.toml",
|
||||||
"settings/pr_update_changelog_prompts.toml",
|
"settings/pr_update_changelog_prompts.toml",
|
||||||
|
"settings/pr_add_docs.toml",
|
||||||
"settings_prod/.secrets.toml"
|
"settings_prod/.secrets.toml"
|
||||||
]]
|
]]
|
||||||
)
|
)
|
||||||
|
@ -88,6 +88,8 @@ class AzureDevopsProvider:
|
|||||||
changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug,
|
changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug,
|
||||||
repository_id=self.repo_slug, commit_id=c.commit_id)
|
repository_id=self.repo_slug, commit_id=c.commit_id)
|
||||||
for i in changes_obj.changes:
|
for i in changes_obj.changes:
|
||||||
|
if(i['item']['gitObjectType'] == 'tree'):
|
||||||
|
continue
|
||||||
diffs.append(i['item']['path'])
|
diffs.append(i['item']['path'])
|
||||||
diff_types[i['item']['path']] = i['changeType']
|
diff_types[i['item']['path']] = i['changeType']
|
||||||
|
|
||||||
@ -98,14 +100,18 @@ class AzureDevopsProvider:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
version = GitVersionDescriptor(version=head_sha.commit_id, version_type='commit')
|
version = GitVersionDescriptor(version=head_sha.commit_id, version_type='commit')
|
||||||
new_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug,
|
try:
|
||||||
path=file,
|
new_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug,
|
||||||
project=self.workspace_slug,
|
path=file,
|
||||||
version_descriptor=version,
|
project=self.workspace_slug,
|
||||||
download=False,
|
version_descriptor=version,
|
||||||
include_content=True)
|
download=False,
|
||||||
|
include_content=True)
|
||||||
|
|
||||||
new_file_content_str = new_file_content_str.content
|
new_file_content_str = new_file_content_str.content
|
||||||
|
except Exception as error:
|
||||||
|
logging.error("Failed to retrieve new file content of %s at version %s. Error: %s", file, version, str(error))
|
||||||
|
new_file_content_str = ""
|
||||||
|
|
||||||
edit_type = EDIT_TYPE.MODIFIED
|
edit_type = EDIT_TYPE.MODIFIED
|
||||||
if diff_types[file] == 'add':
|
if diff_types[file] == 'add':
|
||||||
@ -116,13 +122,17 @@ class AzureDevopsProvider:
|
|||||||
edit_type = EDIT_TYPE.RENAMED
|
edit_type = EDIT_TYPE.RENAMED
|
||||||
|
|
||||||
version = GitVersionDescriptor(version=base_sha.commit_id, version_type='commit')
|
version = GitVersionDescriptor(version=base_sha.commit_id, version_type='commit')
|
||||||
original_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug,
|
try:
|
||||||
|
original_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug,
|
||||||
path=file,
|
path=file,
|
||||||
project=self.workspace_slug,
|
project=self.workspace_slug,
|
||||||
version_descriptor=version,
|
version_descriptor=version,
|
||||||
download=False,
|
download=False,
|
||||||
include_content=True)
|
include_content=True)
|
||||||
original_file_content_str = original_file_content_str.content
|
original_file_content_str = original_file_content_str.content
|
||||||
|
except Exception as error:
|
||||||
|
logging.error("Failed to retrieve original file content of %s at version %s. Error: %s", file, version, str(error))
|
||||||
|
original_file_content_str = ""
|
||||||
|
|
||||||
patch = load_large_diff(file, new_file_content_str, original_file_content_str)
|
patch = load_large_diff(file, new_file_content_str, original_file_content_str)
|
||||||
|
|
||||||
|
@ -233,8 +233,20 @@ class CodeCommitProvider(GitProvider):
|
|||||||
raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet")
|
raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet")
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.pr.get("title", "")
|
return self.pr.title
|
||||||
|
|
||||||
|
def get_pr_id(self):
|
||||||
|
"""
|
||||||
|
Returns the PR ID in the format: "repo_name/pr_number".
|
||||||
|
Note: This is an internal identifier for PR-Agent,
|
||||||
|
and is not the same as the CodeCommit PR identifier.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pr_id = f"{self.repo_name}/{self.pr_num}"
|
||||||
|
return pr_id
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
@ -127,6 +127,9 @@ class GitProvider(ABC):
|
|||||||
def get_commit_messages(self):
|
def get_commit_messages(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_pr_id(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
def get_main_pr_language(languages, files) -> str:
|
def get_main_pr_language(languages, files) -> str:
|
||||||
"""
|
"""
|
||||||
Get the main language of the commit. Return an empty string if cannot determine.
|
Get the main language of the commit. Return an empty string if cannot determine.
|
||||||
|
@ -239,9 +239,10 @@ class GithubProvider(GitProvider):
|
|||||||
def get_user_id(self):
|
def get_user_id(self):
|
||||||
if not self.github_user_id:
|
if not self.github_user_id:
|
||||||
try:
|
try:
|
||||||
self.github_user_id = self.github_client.get_user().login
|
self.github_user_id = self.github_client.get_user().raw_data['login']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(f"Failed to get user id, error: {e}")
|
self.github_user_id = ""
|
||||||
|
# logging.exception(f"Failed to get user id, error: {e}")
|
||||||
return self.github_user_id
|
return self.github_user_id
|
||||||
|
|
||||||
def get_notifications(self, since: datetime):
|
def get_notifications(self, since: datetime):
|
||||||
@ -446,3 +447,10 @@ class GithubProvider(GitProvider):
|
|||||||
logging.info(f"Failed adding line link, error: {e}")
|
logging.info(f"Failed adding line link, error: {e}")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def get_pr_id(self):
|
||||||
|
try:
|
||||||
|
pr_id = f"{self.repo}/{self.pr_num}"
|
||||||
|
return pr_id
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
@ -7,7 +8,7 @@ import gitlab
|
|||||||
from gitlab import GitlabGetError
|
from gitlab import GitlabGetError
|
||||||
|
|
||||||
from ..algo.language_handler import is_valid_file
|
from ..algo.language_handler import is_valid_file
|
||||||
from ..algo.pr_processing import clip_tokens
|
from ..algo.pr_processing import clip_tokens, find_line_number_of_relevant_line_in_file
|
||||||
from ..algo.utils import load_large_diff
|
from ..algo.utils import load_large_diff
|
||||||
from ..config_loader import get_settings
|
from ..config_loader import get_settings
|
||||||
from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider
|
from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider
|
||||||
@ -175,8 +176,7 @@ class GitLabProvider(GitProvider):
|
|||||||
pos_obj['new_line'] = target_line_no - 1
|
pos_obj['new_line'] = target_line_no - 1
|
||||||
pos_obj['old_line'] = source_line_no - 1
|
pos_obj['old_line'] = source_line_no - 1
|
||||||
logging.debug(f"Creating comment in {self.id_mr} with body {body} and position {pos_obj}")
|
logging.debug(f"Creating comment in {self.id_mr} with body {body} and position {pos_obj}")
|
||||||
self.mr.discussions.create({'body': body,
|
self.mr.discussions.create({'body': body, 'position': pos_obj})
|
||||||
'position': pos_obj})
|
|
||||||
|
|
||||||
def get_relevant_diff(self, relevant_file: str, relevant_line_in_file: int) -> Optional[dict]:
|
def get_relevant_diff(self, relevant_file: str, relevant_line_in_file: int) -> Optional[dict]:
|
||||||
changes = self.mr.changes() # Retrieve the changes for the merge request once
|
changes = self.mr.changes() # Retrieve the changes for the merge request once
|
||||||
@ -228,6 +228,9 @@ class GitLabProvider(GitProvider):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(f"Could not publish code suggestion:\nsuggestion: {suggestion}\nerror: {e}")
|
logging.exception(f"Could not publish code suggestion:\nsuggestion: {suggestion}\nerror: {e}")
|
||||||
|
|
||||||
|
# note that we publish suggestions one-by-one. so, if one fails, the rest will still be published
|
||||||
|
return True
|
||||||
|
|
||||||
def search_line(self, relevant_file, relevant_line_in_file):
|
def search_line(self, relevant_file, relevant_line_in_file):
|
||||||
target_file = None
|
target_file = None
|
||||||
|
|
||||||
@ -378,4 +381,35 @@ class GitLabProvider(GitProvider):
|
|||||||
commit_messages_str = ""
|
commit_messages_str = ""
|
||||||
if max_tokens:
|
if max_tokens:
|
||||||
commit_messages_str = clip_tokens(commit_messages_str, max_tokens)
|
commit_messages_str = clip_tokens(commit_messages_str, max_tokens)
|
||||||
return commit_messages_str
|
return commit_messages_str
|
||||||
|
|
||||||
|
def get_pr_id(self):
|
||||||
|
try:
|
||||||
|
pr_id = self.mr.web_url
|
||||||
|
return pr_id
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def generate_link_to_relevant_line_number(self, suggestion) -> str:
|
||||||
|
try:
|
||||||
|
relevant_file = suggestion['relevant file'].strip('`').strip("'")
|
||||||
|
relevant_line_str = suggestion['relevant line']
|
||||||
|
if not relevant_line_str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
position, absolute_position = find_line_number_of_relevant_line_in_file \
|
||||||
|
(self.diff_files, relevant_file, relevant_line_str)
|
||||||
|
|
||||||
|
if absolute_position != -1:
|
||||||
|
# link to right file only
|
||||||
|
link = f"https://gitlab.com/codiumai/pr-agent/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{absolute_position}"
|
||||||
|
|
||||||
|
# # link to diff
|
||||||
|
# sha_file = hashlib.sha1(relevant_file.encode('utf-8')).hexdigest()
|
||||||
|
# link = f"{self.pr.web_url}/diffs#{sha_file}_{absolute_position}_{absolute_position}"
|
||||||
|
return link
|
||||||
|
except Exception as e:
|
||||||
|
if get_settings().config.verbosity_level >= 2:
|
||||||
|
logging.info(f"Failed adding line link, error: {e}")
|
||||||
|
|
||||||
|
return ""
|
||||||
|
35
pr_agent/git_providers/utils.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from dynaconf import Dynaconf
|
||||||
|
|
||||||
|
from pr_agent.config_loader import get_settings
|
||||||
|
from pr_agent.git_providers import get_git_provider
|
||||||
|
|
||||||
|
|
||||||
|
def apply_repo_settings(pr_url):
|
||||||
|
if get_settings().config.use_repo_settings_file:
|
||||||
|
repo_settings_file = None
|
||||||
|
try:
|
||||||
|
git_provider = get_git_provider()(pr_url)
|
||||||
|
repo_settings = git_provider.get_repo_settings()
|
||||||
|
if repo_settings:
|
||||||
|
repo_settings_file = None
|
||||||
|
fd, repo_settings_file = tempfile.mkstemp(suffix='.toml')
|
||||||
|
os.write(fd, repo_settings)
|
||||||
|
new_settings = Dynaconf(settings_files=[repo_settings_file])
|
||||||
|
for section, contents in new_settings.as_dict().items():
|
||||||
|
section_dict = copy.deepcopy(get_settings().as_dict().get(section, {}))
|
||||||
|
for key, value in contents.items():
|
||||||
|
section_dict[key] = value
|
||||||
|
get_settings().unset(section)
|
||||||
|
get_settings().set(section, section_dict, merge=False)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if repo_settings_file:
|
||||||
|
try:
|
||||||
|
os.remove(repo_settings_file)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to remove temporary settings file {repo_settings_file}", e)
|
34
pr_agent/servers/bitbucket_pipeline_runner.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import os
|
||||||
|
from pr_agent.agent.pr_agent import PRAgent
|
||||||
|
from pr_agent.config_loader import get_settings
|
||||||
|
from pr_agent.tools.pr_reviewer import PRReviewer
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def run_action():
|
||||||
|
try:
|
||||||
|
pull_request_id = os.environ.get("BITBUCKET_PR_ID", '')
|
||||||
|
slug = os.environ.get("BITBUCKET_REPO_SLUG", '')
|
||||||
|
workspace = os.environ.get("BITBUCKET_WORKSPACE", '')
|
||||||
|
bearer_token = os.environ.get('BITBUCKET_BEARER_TOKEN', None)
|
||||||
|
OPENAI_KEY = os.environ.get('OPENAI_API_KEY') or os.environ.get('OPENAI.KEY')
|
||||||
|
OPENAI_ORG = os.environ.get('OPENAI_ORG') or os.environ.get('OPENAI.ORG')
|
||||||
|
# Check if required environment variables are set
|
||||||
|
if not bearer_token:
|
||||||
|
print("BITBUCKET_BEARER_TOKEN not set")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not OPENAI_KEY:
|
||||||
|
print("OPENAI_KEY not set")
|
||||||
|
return
|
||||||
|
# Set the environment variables in the settings
|
||||||
|
get_settings().set("BITBUCKET.BEARER_TOKEN", bearer_token)
|
||||||
|
get_settings().set("OPENAI.KEY", OPENAI_KEY)
|
||||||
|
if OPENAI_ORG:
|
||||||
|
get_settings().set("OPENAI.ORG", OPENAI_ORG)
|
||||||
|
if pull_request_id and slug and workspace:
|
||||||
|
pr_url = f"https://bitbucket.org/{workspace}/{slug}/pull-requests/{pull_request_id}"
|
||||||
|
await PRReviewer(pr_url).run()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(run_action())
|
@ -5,6 +5,8 @@ import os
|
|||||||
from pr_agent.agent.pr_agent import PRAgent
|
from pr_agent.agent.pr_agent import PRAgent
|
||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
from pr_agent.git_providers import get_git_provider
|
from pr_agent.git_providers import get_git_provider
|
||||||
|
from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
|
||||||
|
from pr_agent.tools.pr_description import PRDescription
|
||||||
from pr_agent.tools.pr_reviewer import PRReviewer
|
from pr_agent.tools.pr_reviewer import PRReviewer
|
||||||
|
|
||||||
|
|
||||||
@ -53,7 +55,15 @@ async def run_action():
|
|||||||
if action in ["opened", "reopened"]:
|
if action in ["opened", "reopened"]:
|
||||||
pr_url = event_payload.get("pull_request", {}).get("url")
|
pr_url = event_payload.get("pull_request", {}).get("url")
|
||||||
if pr_url:
|
if pr_url:
|
||||||
await PRReviewer(pr_url).run()
|
auto_review = os.environ.get('github_action.auto_review', None)
|
||||||
|
if auto_review is None or (isinstance(auto_review, str) and auto_review.lower() == 'true'):
|
||||||
|
await PRReviewer(pr_url).run()
|
||||||
|
auto_describe = os.environ.get('github_action.auto_describe', None)
|
||||||
|
if isinstance(auto_describe, str) and auto_describe.lower() == 'true':
|
||||||
|
await PRDescription(pr_url).run()
|
||||||
|
auto_improve = os.environ.get('github_action.auto_improve', None)
|
||||||
|
if isinstance(auto_improve, str) and auto_improve.lower() == 'true':
|
||||||
|
await PRCodeSuggestions(pr_url).run()
|
||||||
|
|
||||||
# Handle issue comment event
|
# Handle issue comment event
|
||||||
elif GITHUB_EVENT_NAME == "issue_comment":
|
elif GITHUB_EVENT_NAME == "issue_comment":
|
||||||
|
@ -15,6 +15,7 @@ from pr_agent.agent.pr_agent import PRAgent
|
|||||||
from pr_agent.algo.utils import update_settings_from_args
|
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.git_providers.utils import apply_repo_settings
|
||||||
from pr_agent.servers.utils import verify_signature
|
from pr_agent.servers.utils import verify_signature
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
|
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
|
||||||
@ -124,6 +125,7 @@ async def handle_request(body: Dict[str, Any], event: str):
|
|||||||
# avoid double reviews when opening a PR for the first time
|
# avoid double reviews when opening a PR for the first time
|
||||||
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}")
|
||||||
|
apply_repo_settings(api_url)
|
||||||
for command in get_settings().github_app.pr_commands:
|
for command in get_settings().github_app.pr_commands:
|
||||||
split_command = command.split(" ")
|
split_command = command.split(" ")
|
||||||
command = split_command[0]
|
command = split_command[0]
|
||||||
|
@ -10,6 +10,7 @@ use_repo_settings_file=true
|
|||||||
ai_timeout=180
|
ai_timeout=180
|
||||||
max_description_tokens = 500
|
max_description_tokens = 500
|
||||||
max_commits_tokens = 500
|
max_commits_tokens = 500
|
||||||
|
patch_extra_lines = 3
|
||||||
secret_provider="google_cloud_storage"
|
secret_provider="google_cloud_storage"
|
||||||
cli_mode=false
|
cli_mode=false
|
||||||
|
|
||||||
@ -47,6 +48,10 @@ rank_extended_suggestions = true
|
|||||||
max_number_of_calls = 5
|
max_number_of_calls = 5
|
||||||
final_clip_factor = 0.9
|
final_clip_factor = 0.9
|
||||||
|
|
||||||
|
[pr_add_docs] # /add_docs #
|
||||||
|
extra_instructions = ""
|
||||||
|
docs_style = "Sphinx Style" # "Google Style with Args, Returns, Attributes...etc", "Numpy Style", "Sphinx Style", "PEP257", "reStructuredText"
|
||||||
|
|
||||||
[pr_update_changelog] # /update_changelog #
|
[pr_update_changelog] # /update_changelog #
|
||||||
push_changelog_changes=false
|
push_changelog_changes=false
|
||||||
extra_instructions = ""
|
extra_instructions = ""
|
||||||
@ -58,6 +63,11 @@ extra_instructions = ""
|
|||||||
deployment_type = "user"
|
deployment_type = "user"
|
||||||
ratelimit_retries = 5
|
ratelimit_retries = 5
|
||||||
|
|
||||||
|
[github_action]
|
||||||
|
# auto_review = true # set as env var in .github/workflows/pr-agent.yaml
|
||||||
|
# auto_describe = true # set as env var in .github/workflows/pr-agent.yaml
|
||||||
|
# auto_improve = true # set as env var in .github/workflows/pr-agent.yaml
|
||||||
|
|
||||||
[github_app]
|
[github_app]
|
||||||
# these toggles allows running the github app from custom deployments
|
# these toggles allows running the github app from custom deployments
|
||||||
bot_user = "github-actions[bot]"
|
bot_user = "github-actions[bot]"
|
||||||
|
11
pr_agent/settings/ignore.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[ignore]
|
||||||
|
|
||||||
|
glob = [
|
||||||
|
# Ignore files and directories matching these glob patterns.
|
||||||
|
# See https://docs.python.org/3/library/glob.html
|
||||||
|
'vendor/**',
|
||||||
|
]
|
||||||
|
regex = [
|
||||||
|
# Ignore files and directories matching these regex patterns.
|
||||||
|
# See https://learnbyexample.github.io/python-regex-cheatsheet/
|
||||||
|
]
|
@ -53,7 +53,8 @@ default = [
|
|||||||
'xz',
|
'xz',
|
||||||
'zip',
|
'zip',
|
||||||
'zst',
|
'zst',
|
||||||
'snap'
|
'snap',
|
||||||
|
'lockb'
|
||||||
]
|
]
|
||||||
extra = [
|
extra = [
|
||||||
'md',
|
'md',
|
||||||
|
117
pr_agent/settings/pr_add_docs.toml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
[pr_add_docs_prompt]
|
||||||
|
system="""You are a language model called PR-Code-Documentation Agent, that specializes in generating documentation for code.
|
||||||
|
Your task is to generate meaningfull {{ docs_for_language }} to a PR (the '+' lines).
|
||||||
|
|
||||||
|
Example for a PR Diff input:
|
||||||
|
'
|
||||||
|
## src/file1.py
|
||||||
|
|
||||||
|
@@ -12,3 +12,5 @@ def func1():
|
||||||
|
__new hunk__
|
||||||
|
12 code line that already existed in the file...
|
||||||
|
13 code line that already existed in the file....
|
||||||
|
14 +new code line1 added in the PR
|
||||||
|
15 +new code line2 added in the PR
|
||||||
|
16 code line that already existed in the file...
|
||||||
|
__old hunk__
|
||||||
|
code line that already existed in the file...
|
||||||
|
-code line that was removed in the PR
|
||||||
|
code line that already existed in the file...
|
||||||
|
|
||||||
|
|
||||||
|
@@ ... @@ def func2():
|
||||||
|
__new hunk__
|
||||||
|
...
|
||||||
|
__old hunk__
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
## src/file2.py
|
||||||
|
...
|
||||||
|
'
|
||||||
|
|
||||||
|
Specific instructions:
|
||||||
|
- Try to identify edited/added code components (classes/functions/methods...) that are undocumented. and generate {{ docs_for_language }} for each one.
|
||||||
|
- If there are documented (any type of {{ language }} documentation) code components in the PR, Don't generate {{ docs_for_language }} for them.
|
||||||
|
- Ignore code components that don't appear fully in the '__new hunk__' section. For example. you must see the component header and body,
|
||||||
|
- Make sure the {{ docs_for_language }} starts and ends with standart {{ language }} {{ docs_for_language }} signs.
|
||||||
|
- The {{ docs_for_language }} should be in standard format.
|
||||||
|
- Provide the exact line number (inclusive) where the {{ docs_for_language }} should be added.
|
||||||
|
|
||||||
|
|
||||||
|
{%- if extra_instructions %}
|
||||||
|
|
||||||
|
Extra instructions from the user:
|
||||||
|
'
|
||||||
|
{{ extra_instructions }}
|
||||||
|
'
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
You must use the following YAML schema to format your answer:
|
||||||
|
```yaml
|
||||||
|
Code Documentation:
|
||||||
|
type: array
|
||||||
|
uniqueItems: true
|
||||||
|
items:
|
||||||
|
relevant file:
|
||||||
|
type: string
|
||||||
|
description: the relevant file full path
|
||||||
|
relevant line:
|
||||||
|
type: integer
|
||||||
|
description: |-
|
||||||
|
The relevant line number from a '__new hunk__' section where the {{ docs_for_language }} should be added.
|
||||||
|
doc placement:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- before
|
||||||
|
- after
|
||||||
|
description: |-
|
||||||
|
The {{ docs_for_language }} placement relative to the relevant line (code component).
|
||||||
|
documentation:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
The {{ docs_for_language }} content. It should be complete, correctly formatted and indented, and without line numbers.
|
||||||
|
```
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```yaml
|
||||||
|
Code Documentation:
|
||||||
|
- relevant file: |-
|
||||||
|
src/file1.py
|
||||||
|
relevant lines: 12
|
||||||
|
doc placement: after
|
||||||
|
documentation: |-
|
||||||
|
\"\"\"
|
||||||
|
This is a python docstring for func1.
|
||||||
|
\"\"\"
|
||||||
|
- ...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Each YAML output MUST be after a newline, indented, with block scalar indicator ('|-').
|
||||||
|
Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
user="""PR Info:
|
||||||
|
|
||||||
|
Title: '{{ title }}'
|
||||||
|
|
||||||
|
Branch: '{{ branch }}'
|
||||||
|
|
||||||
|
Description: '{{description}}'
|
||||||
|
|
||||||
|
{%- if language %}
|
||||||
|
|
||||||
|
Main language: {{language}}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
|
||||||
|
The PR Diff:
|
||||||
|
```
|
||||||
|
{{- diff|trim }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response (should be a valid YAML, and nothing else):
|
||||||
|
```yaml
|
||||||
|
"""
|
@ -1,6 +1,6 @@
|
|||||||
[pr_code_suggestions_prompt]
|
[pr_code_suggestions_prompt]
|
||||||
system="""You are a language model called PR-Code-Reviewer, that specializes in suggesting code improvements for Pull Request (PR).
|
system="""You are a language model called PR-Code-Reviewer, that specializes in suggesting code improvements for Pull Request (PR).
|
||||||
Your task is to provide meaningful and actionable code suggestions, to improve the new code presented in a PR.
|
Your task is to provide meaningful and actionable code suggestions, to improve the new code presented in a PR (the '+' lines in the diff).
|
||||||
|
|
||||||
Example for a PR Diff input:
|
Example for a PR Diff input:
|
||||||
'
|
'
|
||||||
@ -31,14 +31,13 @@ __old hunk__
|
|||||||
'
|
'
|
||||||
|
|
||||||
Specific instructions:
|
Specific instructions:
|
||||||
- Provide up to {{ num_code_suggestions }} code suggestions.
|
- Provide up to {{ num_code_suggestions }} code suggestions. Try to provide diverse and insightful suggestions.
|
||||||
- Prioritize suggestions that address major problems, issues and bugs in the code.
|
- Prioritize suggestions that address major problems, issues and bugs in the code.
|
||||||
As a second priority, suggestions should focus on best practices, code readability, maintainability, enhancments, performance, and other aspects.
|
As a second priority, suggestions should focus on best practices, code readability, maintainability, enhancments, performance, and other aspects.
|
||||||
Don't suggest to add docstring or type hints.
|
- Don't suggest to add docstring, type hints, or comments.
|
||||||
Try to provide diverse and insightful suggestions.
|
|
||||||
- Suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+').
|
- Suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+').
|
||||||
Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code.
|
- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code.
|
||||||
For each suggestion, make sure to take into consideration also the context, meaning the lines before and after the relevant code.
|
- For each suggestion, make sure to take into consideration also the context, meaning the lines before and after the relevant code.
|
||||||
- Provide the exact line numbers range (inclusive) for each issue.
|
- Provide the exact line numbers range (inclusive) for each issue.
|
||||||
- Assume there is additional relevant code, that is not included in the diff.
|
- Assume there is additional relevant code, that is not included in the diff.
|
||||||
|
|
||||||
@ -46,7 +45,9 @@ Specific instructions:
|
|||||||
{%- if extra_instructions %}
|
{%- if extra_instructions %}
|
||||||
|
|
||||||
Extra instructions from the user:
|
Extra instructions from the user:
|
||||||
|
'
|
||||||
{{ extra_instructions }}
|
{{ extra_instructions }}
|
||||||
|
'
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
You must use the following YAML schema to format your answer:
|
You must use the following YAML schema to format your answer:
|
||||||
|
@ -7,7 +7,9 @@ Your task is to provide full description of the PR content.
|
|||||||
{%- if extra_instructions %}
|
{%- if extra_instructions %}
|
||||||
|
|
||||||
Extra instructions from the user:
|
Extra instructions from the user:
|
||||||
|
'
|
||||||
{{ extra_instructions }}
|
{{ extra_instructions }}
|
||||||
|
'
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
You must use the following YAML schema to format your answer:
|
You must use the following YAML schema to format your answer:
|
||||||
|
@ -22,20 +22,22 @@ code line that already existed in the file....
|
|||||||
...
|
...
|
||||||
'
|
'
|
||||||
|
|
||||||
Thre review should focus on new code added in the PR (lines starting with '+'), and not on code that already existed in the file (lines starting with '-', or without prefix).
|
The review should focus on new code added in the PR (lines starting with '+'), and not on code that already existed in the file (lines starting with '-', or without prefix).
|
||||||
|
|
||||||
{%- if num_code_suggestions > 0 %}
|
{%- if num_code_suggestions > 0 %}
|
||||||
- Provide up to {{ num_code_suggestions }} code suggestions.
|
- Provide up to {{ num_code_suggestions }} code suggestions.
|
||||||
- Focus on important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices.
|
- Focus on important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices.
|
||||||
- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the PR code.
|
- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the PR code.
|
||||||
- Don't suggest to add docstring or type hints.
|
- Don't suggest to add docstring, type hints, or comments.
|
||||||
- Suggestions should focus on improving the new code added in the PR (lines starting with '+')
|
- Suggestions should focus on improving the new code added in the PR (lines starting with '+')
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if extra_instructions %}
|
{%- if extra_instructions %}
|
||||||
|
|
||||||
Extra instructions from the user:
|
Extra instructions from the user:
|
||||||
|
'
|
||||||
{{ extra_instructions }}
|
{{ extra_instructions }}
|
||||||
|
'
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
You must use the following YAML schema to format your answer:
|
You must use the following YAML schema to format your answer:
|
||||||
@ -129,8 +131,7 @@ PR Feedback:
|
|||||||
Security concerns:
|
Security concerns:
|
||||||
type: string
|
type: string
|
||||||
description: >-
|
description: >-
|
||||||
yes\\no question: does this PR code introduce possible security concerns or
|
yes\\no question: does this PR code introduce possible vulnerabilities such as exposure of sensitive information (e.g., API keys, secrets, passwords), or security concerns like SQL injection, XSS, CSRF, and others ? If answered 'yes', explain your answer briefly.
|
||||||
issues, like SQL injection, XSS, CSRF, and others ? If answered 'yes',explain your answer shortly
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -151,6 +152,9 @@ PR Analysis:
|
|||||||
{%- if require_focused %}
|
{%- if require_focused %}
|
||||||
Focused PR: no, because ...
|
Focused PR: no, because ...
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- if require_estimate_effort_to_review %}
|
||||||
|
Estimated effort to review [1-5]: 3, because ...
|
||||||
|
{%- endif %}
|
||||||
PR Feedback:
|
PR Feedback:
|
||||||
General PR suggestions: |-
|
General PR suggestions: |-
|
||||||
...
|
...
|
||||||
@ -193,7 +197,9 @@ Here are questions to better understand the PR. Use the answers to provide bette
|
|||||||
{{question_str|trim}}
|
{{question_str|trim}}
|
||||||
|
|
||||||
User answers:
|
User answers:
|
||||||
|
'
|
||||||
{{answer_str|trim}}
|
{{answer_str|trim}}
|
||||||
|
'
|
||||||
######
|
######
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ Your task is to update the CHANGELOG.md file of the project, to shortly summariz
|
|||||||
{%- if extra_instructions %}
|
{%- if extra_instructions %}
|
||||||
|
|
||||||
Extra instructions from the user:
|
Extra instructions from the user:
|
||||||
|
'
|
||||||
{{ extra_instructions }}
|
{{ extra_instructions }}
|
||||||
|
'
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
173
pr_agent/tools/pr_add_docs.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
import textwrap
|
||||||
|
from typing import List, Dict
|
||||||
|
from jinja2 import Environment, StrictUndefined
|
||||||
|
|
||||||
|
from pr_agent.algo.ai_handler import AiHandler
|
||||||
|
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models, get_pr_multi_diffs
|
||||||
|
from pr_agent.algo.token_handler import TokenHandler
|
||||||
|
from pr_agent.algo.utils import load_yaml
|
||||||
|
from pr_agent.config_loader import get_settings
|
||||||
|
from pr_agent.git_providers import BitbucketProvider, get_git_provider
|
||||||
|
from pr_agent.git_providers.git_provider import get_main_pr_language
|
||||||
|
|
||||||
|
|
||||||
|
class PRAddDocs:
|
||||||
|
def __init__(self, pr_url: str, cli_mode=False, args: list = None):
|
||||||
|
|
||||||
|
self.git_provider = get_git_provider()(pr_url)
|
||||||
|
self.main_language = get_main_pr_language(
|
||||||
|
self.git_provider.get_languages(), self.git_provider.get_files()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ai_handler = AiHandler()
|
||||||
|
self.patches_diff = None
|
||||||
|
self.prediction = None
|
||||||
|
self.cli_mode = cli_mode
|
||||||
|
self.vars = {
|
||||||
|
"title": self.git_provider.pr.title,
|
||||||
|
"branch": self.git_provider.get_pr_branch(),
|
||||||
|
"description": self.git_provider.get_pr_description(),
|
||||||
|
"language": self.main_language,
|
||||||
|
"diff": "", # empty diff for initial calculation
|
||||||
|
"extra_instructions": get_settings().pr_add_docs.extra_instructions,
|
||||||
|
"commit_messages_str": self.git_provider.get_commit_messages(),
|
||||||
|
'docs_for_language': get_docs_for_language(self.main_language,
|
||||||
|
get_settings().pr_add_docs.docs_style),
|
||||||
|
}
|
||||||
|
self.token_handler = TokenHandler(self.git_provider.pr,
|
||||||
|
self.vars,
|
||||||
|
get_settings().pr_add_docs_prompt.system,
|
||||||
|
get_settings().pr_add_docs_prompt.user)
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
try:
|
||||||
|
logging.info('Generating code Docs for PR...')
|
||||||
|
if get_settings().config.publish_output:
|
||||||
|
self.git_provider.publish_comment("Generating Documentation...", is_temporary=True)
|
||||||
|
|
||||||
|
logging.info('Preparing PR documentation...')
|
||||||
|
await retry_with_fallback_models(self._prepare_prediction)
|
||||||
|
data = self._prepare_pr_code_docs()
|
||||||
|
if (not data) or (not 'Code Documentation' in data):
|
||||||
|
logging.info('No code documentation found for PR.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if get_settings().config.publish_output:
|
||||||
|
logging.info('Pushing PR documentation...')
|
||||||
|
self.git_provider.remove_initial_comment()
|
||||||
|
logging.info('Pushing inline code documentation...')
|
||||||
|
self.push_inline_docs(data)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to generate code documentation for PR, error: {e}")
|
||||||
|
|
||||||
|
async def _prepare_prediction(self, model: str):
|
||||||
|
logging.info('Getting PR diff...')
|
||||||
|
self.patches_diff = get_pr_diff(self.git_provider,
|
||||||
|
self.token_handler,
|
||||||
|
model,
|
||||||
|
add_line_numbers_to_hunks=True,
|
||||||
|
disable_extra_lines=False)
|
||||||
|
|
||||||
|
logging.info('Getting AI prediction...')
|
||||||
|
self.prediction = await self._get_prediction(model)
|
||||||
|
|
||||||
|
async def _get_prediction(self, model: str):
|
||||||
|
variables = copy.deepcopy(self.vars)
|
||||||
|
variables["diff"] = self.patches_diff # update diff
|
||||||
|
environment = Environment(undefined=StrictUndefined)
|
||||||
|
system_prompt = environment.from_string(get_settings().pr_add_docs_prompt.system).render(variables)
|
||||||
|
user_prompt = environment.from_string(get_settings().pr_add_docs_prompt.user).render(variables)
|
||||||
|
if get_settings().config.verbosity_level >= 2:
|
||||||
|
logging.info(f"\nSystem prompt:\n{system_prompt}")
|
||||||
|
logging.info(f"\nUser prompt:\n{user_prompt}")
|
||||||
|
response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2,
|
||||||
|
system=system_prompt, user=user_prompt)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _prepare_pr_code_docs(self) -> Dict:
|
||||||
|
docs = self.prediction.strip()
|
||||||
|
data = load_yaml(docs)
|
||||||
|
if isinstance(data, list):
|
||||||
|
data = {'Code Documentation': data}
|
||||||
|
return data
|
||||||
|
|
||||||
|
def push_inline_docs(self, data):
|
||||||
|
docs = []
|
||||||
|
|
||||||
|
if not data['Code Documentation']:
|
||||||
|
return self.git_provider.publish_comment('No code documentation found to improve this PR.')
|
||||||
|
|
||||||
|
for d in data['Code Documentation']:
|
||||||
|
try:
|
||||||
|
if get_settings().config.verbosity_level >= 2:
|
||||||
|
logging.info(f"add_docs: {d}")
|
||||||
|
relevant_file = d['relevant file'].strip()
|
||||||
|
relevant_line = int(d['relevant line']) # absolute position
|
||||||
|
documentation = d['documentation']
|
||||||
|
doc_placement = d['doc placement'].strip()
|
||||||
|
if documentation:
|
||||||
|
new_code_snippet = self.dedent_code(relevant_file, relevant_line, documentation, doc_placement,
|
||||||
|
add_original_line=True)
|
||||||
|
|
||||||
|
body = f"**Suggestion:** Proposed documentation\n```suggestion\n" + new_code_snippet + "\n```"
|
||||||
|
docs.append({'body': body, 'relevant_file': relevant_file,
|
||||||
|
'relevant_lines_start': relevant_line,
|
||||||
|
'relevant_lines_end': relevant_line})
|
||||||
|
except Exception:
|
||||||
|
if get_settings().config.verbosity_level >= 2:
|
||||||
|
logging.info(f"Could not parse code docs: {d}")
|
||||||
|
|
||||||
|
is_successful = self.git_provider.publish_code_suggestions(docs)
|
||||||
|
if not is_successful:
|
||||||
|
logging.info("Failed to publish code docs, trying to publish each docs separately")
|
||||||
|
for doc_suggestion in docs:
|
||||||
|
self.git_provider.publish_code_suggestions([doc_suggestion])
|
||||||
|
|
||||||
|
def dedent_code(self, relevant_file, relevant_lines_start, new_code_snippet, doc_placement='after',
|
||||||
|
add_original_line=False):
|
||||||
|
try: # dedent code snippet
|
||||||
|
self.diff_files = self.git_provider.diff_files if self.git_provider.diff_files \
|
||||||
|
else self.git_provider.get_diff_files()
|
||||||
|
original_initial_line = None
|
||||||
|
for file in self.diff_files:
|
||||||
|
if file.filename.strip() == relevant_file:
|
||||||
|
original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1]
|
||||||
|
break
|
||||||
|
if original_initial_line:
|
||||||
|
if doc_placement == 'after':
|
||||||
|
line = file.head_file.splitlines()[relevant_lines_start]
|
||||||
|
else:
|
||||||
|
line = original_initial_line
|
||||||
|
suggested_initial_line = new_code_snippet.splitlines()[0]
|
||||||
|
original_initial_spaces = len(line) - len(line.lstrip())
|
||||||
|
suggested_initial_spaces = len(suggested_initial_line) - len(suggested_initial_line.lstrip())
|
||||||
|
delta_spaces = original_initial_spaces - suggested_initial_spaces
|
||||||
|
if delta_spaces > 0:
|
||||||
|
new_code_snippet = textwrap.indent(new_code_snippet, delta_spaces * " ").rstrip('\n')
|
||||||
|
if add_original_line:
|
||||||
|
if doc_placement == 'after':
|
||||||
|
new_code_snippet = original_initial_line + "\n" + new_code_snippet
|
||||||
|
else:
|
||||||
|
new_code_snippet = new_code_snippet.rstrip() + "\n" + original_initial_line
|
||||||
|
except Exception as e:
|
||||||
|
if get_settings().config.verbosity_level >= 2:
|
||||||
|
logging.info(f"Could not dedent code snippet for file {relevant_file}, error: {e}")
|
||||||
|
|
||||||
|
return new_code_snippet
|
||||||
|
|
||||||
|
|
||||||
|
def get_docs_for_language(language, style):
|
||||||
|
language = language.lower()
|
||||||
|
if language == 'java':
|
||||||
|
return "Javadocs"
|
||||||
|
elif language in ['python', 'lisp', 'clojure']:
|
||||||
|
return f"Docstring ({style})"
|
||||||
|
elif language in ['javascript', 'typescript']:
|
||||||
|
return "JSdocs"
|
||||||
|
elif language == 'c++':
|
||||||
|
return "Doxygen"
|
||||||
|
else:
|
||||||
|
return "Docs"
|
@ -22,7 +22,10 @@ class PRCodeSuggestions:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# extended mode
|
# extended mode
|
||||||
self.is_extended = any(["extended" in arg for arg in args])
|
try:
|
||||||
|
self.is_extended = any(["extended" in arg for arg in args])
|
||||||
|
except:
|
||||||
|
self.is_extended = False
|
||||||
if self.is_extended:
|
if self.is_extended:
|
||||||
num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions_per_chunk
|
num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions_per_chunk
|
||||||
else:
|
else:
|
||||||
|
@ -29,7 +29,7 @@ class PRDescription:
|
|||||||
self.main_pr_language = get_main_pr_language(
|
self.main_pr_language = get_main_pr_language(
|
||||||
self.git_provider.get_languages(), self.git_provider.get_files()
|
self.git_provider.get_languages(), self.git_provider.get_files()
|
||||||
)
|
)
|
||||||
self.pr_id = f"{self.git_provider.repo}/{self.git_provider.pr_num}"
|
self.pr_id = self.git_provider.get_pr_id()
|
||||||
|
|
||||||
# Initialize the AI handler
|
# Initialize the AI handler
|
||||||
self.ai_handler = AiHandler()
|
self.ai_handler = AiHandler()
|
||||||
@ -67,7 +67,7 @@ class PRDescription:
|
|||||||
try:
|
try:
|
||||||
logging.info(f"Generating a PR description {self.pr_id}")
|
logging.info(f"Generating a PR description {self.pr_id}")
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
self.git_provider.publish_comment("Preparing pr description...", is_temporary=True)
|
self.git_provider.publish_comment("Preparing PR description...", is_temporary=True)
|
||||||
|
|
||||||
await retry_with_fallback_models(self._prepare_prediction)
|
await retry_with_fallback_models(self._prepare_prediction)
|
||||||
|
|
||||||
|
@ -209,6 +209,22 @@ class PRReviewer:
|
|||||||
link = self.git_provider.generate_link_to_relevant_line_number(suggestion)
|
link = self.git_provider.generate_link_to_relevant_line_number(suggestion)
|
||||||
if link:
|
if link:
|
||||||
suggestion['relevant line'] = f"[{suggestion['relevant line']}]({link})"
|
suggestion['relevant line'] = f"[{suggestion['relevant line']}]({link})"
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
# try:
|
||||||
|
# relevant_file = suggestion['relevant file'].strip('`').strip("'")
|
||||||
|
# relevant_line_str = suggestion['relevant line']
|
||||||
|
# if not relevant_line_str:
|
||||||
|
# return ""
|
||||||
|
#
|
||||||
|
# position, absolute_position = find_line_number_of_relevant_line_in_file(
|
||||||
|
# self.git_provider.diff_files, relevant_file, relevant_line_str)
|
||||||
|
# if absolute_position != -1:
|
||||||
|
# suggestion[
|
||||||
|
# 'relevant line'] = f"{suggestion['relevant line']} (line {absolute_position})"
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
# Add incremental review section
|
# Add incremental review section
|
||||||
if self.incremental.is_incremental:
|
if self.incremental.is_incremental:
|
||||||
@ -225,7 +241,8 @@ class PRReviewer:
|
|||||||
# Add help text if not in CLI mode
|
# Add help text if not in CLI mode
|
||||||
if not get_settings().get("CONFIG.CLI_MODE", False):
|
if not get_settings().get("CONFIG.CLI_MODE", False):
|
||||||
markdown_text += "\n### How to use\n"
|
markdown_text += "\n### How to use\n"
|
||||||
if user and '[bot]' not in user:
|
bot_user = "[bot]" if get_settings().github_app.override_deployment_type else get_settings().github_app.bot_user
|
||||||
|
if user and bot_user not in user:
|
||||||
markdown_text += bot_help_text(user)
|
markdown_text += bot_help_text(user)
|
||||||
else:
|
else:
|
||||||
markdown_text += actions_help_text
|
markdown_text += actions_help_text
|
||||||
|
@ -110,7 +110,7 @@ class TestCodeCommitProvider:
|
|||||||
# Mock the response from the AWS client for get_pull_request method
|
# Mock the response from the AWS client for get_pull_request method
|
||||||
api.boto_client.get_pull_request.return_value = {
|
api.boto_client.get_pull_request.return_value = {
|
||||||
"pullRequest": {
|
"pullRequest": {
|
||||||
"pullRequestId": "3",
|
"pullRequestId": "321",
|
||||||
"title": "My PR",
|
"title": "My PR",
|
||||||
"description": "My PR description",
|
"description": "My PR description",
|
||||||
"pullRequestTargets": [
|
"pullRequestTargets": [
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from unittest.mock import patch
|
||||||
from pr_agent.git_providers.codecommit_provider import CodeCommitFile
|
from pr_agent.git_providers.codecommit_provider import CodeCommitFile
|
||||||
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
|
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
|
||||||
|
from pr_agent.git_providers.codecommit_provider import PullRequestCCMimic
|
||||||
from pr_agent.git_providers.git_provider import EDIT_TYPE
|
from pr_agent.git_providers.git_provider import EDIT_TYPE
|
||||||
|
|
||||||
|
|
||||||
@ -25,6 +27,21 @@ class TestCodeCommitFile:
|
|||||||
|
|
||||||
|
|
||||||
class TestCodeCommitProvider:
|
class TestCodeCommitProvider:
|
||||||
|
def test_get_title(self):
|
||||||
|
# Test that the get_title() function returns the PR title
|
||||||
|
with patch.object(CodeCommitProvider, "__init__", lambda x, y: None):
|
||||||
|
provider = CodeCommitProvider(None)
|
||||||
|
provider.pr = PullRequestCCMimic("My Test PR Title", [])
|
||||||
|
assert provider.get_title() == "My Test PR Title"
|
||||||
|
|
||||||
|
def test_get_pr_id(self):
|
||||||
|
# Test that the get_pr_id() function returns the correct ID
|
||||||
|
with patch.object(CodeCommitProvider, "__init__", lambda x, y: None):
|
||||||
|
provider = CodeCommitProvider(None)
|
||||||
|
provider.repo_name = "my_test_repo"
|
||||||
|
provider.pr_num = 321
|
||||||
|
assert provider.get_pr_id() == "my_test_repo/321"
|
||||||
|
|
||||||
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
|
# 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"
|
||||||
@ -169,4 +186,4 @@ class TestCodeCommitProvider:
|
|||||||
def test_remove_markdown_html(self):
|
def test_remove_markdown_html(self):
|
||||||
input = "## PR Feedback\n<details><summary>Code feedback:</summary>\nfile foo\n</summary>\n"
|
input = "## PR Feedback\n<details><summary>Code feedback:</summary>\nfile foo\n</summary>\n"
|
||||||
expect = "## PR Feedback\nCode feedback:\nfile foo\n\n"
|
expect = "## PR Feedback\nCode feedback:\nfile foo\n\n"
|
||||||
assert CodeCommitProvider._remove_markdown_html(input) == expect
|
assert CodeCommitProvider._remove_markdown_html(input) == expect
|
||||||
|
80
tests/unittest/test_file_filter.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import pytest
|
||||||
|
from pr_agent.algo.file_filter import filter_ignored
|
||||||
|
from pr_agent.config_loader import global_settings
|
||||||
|
|
||||||
|
class TestIgnoreFilter:
|
||||||
|
def test_no_ignores(self):
|
||||||
|
"""
|
||||||
|
Test no files are ignored when no patterns are specified.
|
||||||
|
"""
|
||||||
|
files = [
|
||||||
|
type('', (object,), {'filename': 'file1.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file2.java'})(),
|
||||||
|
type('', (object,), {'filename': 'file3.cpp'})(),
|
||||||
|
type('', (object,), {'filename': 'file4.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file5.py'})()
|
||||||
|
]
|
||||||
|
assert filter_ignored(files) == files, "Expected all files to be returned when no ignore patterns are given."
|
||||||
|
|
||||||
|
def test_glob_ignores(self, monkeypatch):
|
||||||
|
"""
|
||||||
|
Test files are ignored when glob patterns are specified.
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(global_settings.ignore, 'glob', ['*.py'])
|
||||||
|
|
||||||
|
files = [
|
||||||
|
type('', (object,), {'filename': 'file1.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file2.java'})(),
|
||||||
|
type('', (object,), {'filename': 'file3.cpp'})(),
|
||||||
|
type('', (object,), {'filename': 'file4.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file5.py'})()
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
files[1],
|
||||||
|
files[2]
|
||||||
|
]
|
||||||
|
|
||||||
|
filtered_files = filter_ignored(files)
|
||||||
|
assert filtered_files == expected, f"Expected {[file.filename for file in expected]}, but got {[file.filename for file in filtered_files]}."
|
||||||
|
|
||||||
|
def test_regex_ignores(self, monkeypatch):
|
||||||
|
"""
|
||||||
|
Test files are ignored when regex patterns are specified.
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(global_settings.ignore, 'regex', ['^file[2-4]\..*$'])
|
||||||
|
|
||||||
|
files = [
|
||||||
|
type('', (object,), {'filename': 'file1.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file2.java'})(),
|
||||||
|
type('', (object,), {'filename': 'file3.cpp'})(),
|
||||||
|
type('', (object,), {'filename': 'file4.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file5.py'})()
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
files[0],
|
||||||
|
files[4]
|
||||||
|
]
|
||||||
|
|
||||||
|
filtered_files = filter_ignored(files)
|
||||||
|
assert filtered_files == expected, f"Expected {[file.filename for file in expected]}, but got {[file.filename for file in filtered_files]}."
|
||||||
|
|
||||||
|
def test_invalid_regex(self, monkeypatch):
|
||||||
|
"""
|
||||||
|
Test invalid patterns are quietly ignored.
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(global_settings.ignore, 'regex', ['(((||', '^file[2-4]\..*$'])
|
||||||
|
|
||||||
|
files = [
|
||||||
|
type('', (object,), {'filename': 'file1.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file2.java'})(),
|
||||||
|
type('', (object,), {'filename': 'file3.cpp'})(),
|
||||||
|
type('', (object,), {'filename': 'file4.py'})(),
|
||||||
|
type('', (object,), {'filename': 'file5.py'})()
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
files[0],
|
||||||
|
files[4]
|
||||||
|
]
|
||||||
|
|
||||||
|
filtered_files = filter_ignored(files)
|
||||||
|
assert filtered_files == expected, f"Expected {[file.filename for file in expected]}, but got {[file.filename for file in filtered_files]}."
|