mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-04 21:00:40 +08:00
Compare commits
91 Commits
v0.26
...
test_best_
Author | SHA1 | Date | |
---|---|---|---|
566eaf63e7 | |||
e0b6db9bec | |||
2f2cdd787d | |||
2b25053de8 | |||
054957fcfe | |||
0075084a22 | |||
e62db20c1e | |||
67d4c96166 | |||
b335cacffd | |||
87a5a7e156 | |||
24aa15f074 | |||
ad4a96caf1 | |||
e7f874a4b2 | |||
4ab9392042 | |||
536d97b9da | |||
cea1b58714 | |||
5ed260d885 | |||
e58a535594 | |||
d949f440a8 | |||
4923c8d810 | |||
fe6540275c | |||
1997da3aac | |||
d0c442b74b | |||
1f86cbc942 | |||
633a1c7bd0 | |||
2d9141adad | |||
95d0d4d71d | |||
5daf8e043c | |||
bd611bc1c2 | |||
4bb04e37c2 | |||
b3f116bb35 | |||
d15d08bb3b | |||
445a7fc015 | |||
c1c5c353e5 | |||
a74fca7b7d | |||
7479ae3224 | |||
6bb5ce58f2 | |||
2783f9b1bb | |||
f4b895d870 | |||
07d40e2c05 | |||
6e1178b168 | |||
c889e566e8 | |||
d9540d3b43 | |||
793f76fec5 | |||
5adc246040 | |||
d4d58babd5 | |||
2df4bc8b53 | |||
e431979b8b | |||
bde594c9e8 | |||
b570c758aa | |||
f6b80174b5 | |||
833ae67820 | |||
ee913336ad | |||
79d589e145 | |||
2d4cd3e270 | |||
7f950a3aa9 | |||
379fa957ea | |||
a1a7c8e44c | |||
6d0ed06fc4 | |||
e695af6917 | |||
5318047202 | |||
5bf5f63c78 | |||
5971a06d73 | |||
71e477a993 | |||
efc621a58b | |||
951fa3385a | |||
56e15c0348 | |||
4c82b8a43e | |||
2d5daf4364 | |||
aa95d5fb68 | |||
f6b470bf5e | |||
36df75ce14 | |||
e2be1f1cee | |||
d1caa0f15f | |||
2c2af93eed | |||
04197a9271 | |||
9faf1521b4 | |||
8819293770 | |||
0ec4491542 | |||
4fd121d34a | |||
6e80f5fcce | |||
4a1b042152 | |||
6fbd95e1a9 | |||
fd6e81978f | |||
467136a48a | |||
3b14d3a3c2 | |||
0f973681ea | |||
19f6078ed0 | |||
3994a2ba60 | |||
796b36f0d1 | |||
4688b20284 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@ dist/
|
|||||||
build/
|
build/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
docs/.cache/
|
docs/.cache/
|
||||||
|
.qodo
|
||||||
|
26
README.md
26
README.md
@ -41,6 +41,21 @@ Qode Merge PR-Agent aims to help efficiently review and handle pull requests, by
|
|||||||
|
|
||||||
## News and Updates
|
## News and Updates
|
||||||
|
|
||||||
|
### Jan 2, 2025
|
||||||
|
|
||||||
|
New tool [/Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) (💎), which converts human code review discussions and feedback into ready-to-commit code changes.
|
||||||
|
|
||||||
|
<kbd><img src="https://codium.ai/images/pr_agent/implement1.png" width="512"></kbd>
|
||||||
|
|
||||||
|
|
||||||
|
### Jan 1, 2025
|
||||||
|
|
||||||
|
Update logic and [documentation](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/#ollama) for running local models via Ollama.
|
||||||
|
|
||||||
|
### December 30, 2024
|
||||||
|
|
||||||
|
Following [feedback](https://research.kudelskisecurity.com/2024/08/29/careful-where-you-code-multiple-vulnerabilities-in-ai-powered-pr-agent/) from the community, we have addressed two vulnerabilities identified in the open-source PR-Agent project. The fixes are now included in the newly released version (v0.26), available as of today.
|
||||||
|
|
||||||
### December 25, 2024
|
### December 25, 2024
|
||||||
|
|
||||||
The `review` tool previously included a legacy feature for providing code suggestions (controlled by '--pr_reviewer.num_code_suggestion'). This functionality has been deprecated. Use instead the [`improve`](https://qodo-merge-docs.qodo.ai/tools/improve/) tool, which offers higher quality and more actionable code suggestions.
|
The `review` tool previously included a legacy feature for providing code suggestions (controlled by '--pr_reviewer.num_code_suggestion'). This functionality has been deprecated. Use instead the [`improve`](https://qodo-merge-docs.qodo.ai/tools/improve/) tool, which offers higher quality and more actionable code suggestions.
|
||||||
@ -75,12 +90,6 @@ Focused mode
|
|||||||
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_focused_mode.png" width="512"></kbd>
|
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_focused_mode.png" width="512"></kbd>
|
||||||
|
|
||||||
|
|
||||||
### November 4, 2024
|
|
||||||
|
|
||||||
Qodo Merge PR Agent will now leverage context from Jira or GitHub tickets to enhance the PR Feedback. Read more about this feature
|
|
||||||
[here](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/)
|
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
<div style="text-align:left;">
|
<div style="text-align:left;">
|
||||||
|
|
||||||
@ -105,6 +114,7 @@ Supported commands per platform:
|
|||||||
| | [Similar Code](https://pr-agent-docs.codium.ai/tools/similar_code/) 💎 | ✅ | | | |
|
| | [Similar Code](https://pr-agent-docs.codium.ai/tools/similar_code/) 💎 | ✅ | | | |
|
||||||
| | [Custom Prompt](https://pr-agent-docs.codium.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
| | [Custom Prompt](https://pr-agent-docs.codium.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
||||||
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||||
|
| | [Implement](https://pr-agent-docs.codium.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||||
| | | | | | |
|
| | | | | | |
|
||||||
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
|
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
|
||||||
| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ |
|
| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ |
|
||||||
@ -144,6 +154,8 @@ ___
|
|||||||
\
|
\
|
||||||
‣ **Analyze 💎 ([`/analyze`](https://pr-agent-docs.codium.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component.
|
‣ **Analyze 💎 ([`/analyze`](https://pr-agent-docs.codium.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component.
|
||||||
\
|
\
|
||||||
|
‣ **Test 💎 ([`/test`](https://pr-agent-docs.codium.ai/tools/test/))**: Generate tests for a selected component, based on the PR code changes.
|
||||||
|
\
|
||||||
‣ **Custom Prompt 💎 ([`/custom_prompt`](https://pr-agent-docs.codium.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
|
‣ **Custom Prompt 💎 ([`/custom_prompt`](https://pr-agent-docs.codium.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
|
||||||
\
|
\
|
||||||
‣ **Generate Tests 💎 ([`/test component_name`](https://pr-agent-docs.codium.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes.
|
‣ **Generate Tests 💎 ([`/test component_name`](https://pr-agent-docs.codium.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes.
|
||||||
@ -151,6 +163,8 @@ ___
|
|||||||
‣ **CI Feedback 💎 ([`/checks ci_job`](https://pr-agent-docs.codium.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job.
|
‣ **CI Feedback 💎 ([`/checks ci_job`](https://pr-agent-docs.codium.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job.
|
||||||
\
|
\
|
||||||
‣ **Similar Code 💎 ([`/find_similar_component`](https://pr-agent-docs.codium.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code.
|
‣ **Similar Code 💎 ([`/find_similar_component`](https://pr-agent-docs.codium.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code.
|
||||||
|
\
|
||||||
|
‣ **Implement 💎 ([`/implement`](https://qodo-merge-docs.qodo.ai/tools/implement/))**: Generates implementation code from review suggestions.
|
||||||
___
|
___
|
||||||
|
|
||||||
## Example results
|
## Example results
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
[Qodo Merge Chrome extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl) is a collection of tools that integrates seamlessly with your GitHub environment, aiming to enhance your Git usage experience, and providing AI-powered capabilities to your PRs.
|
[Qodo Merge Chrome extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl){:target="_blank"} is a collection of tools that integrates seamlessly with your GitHub environment, aiming to enhance your Git usage experience, and providing AI-powered capabilities to your PRs.
|
||||||
|
|
||||||
With a single-click installation you will gain access to a context-aware chat on your pull requests code, a toolbar extension with multiple AI feedbacks, Qodo Merge filters, and additional abilities.
|
With a single-click installation you will gain access to a context-aware chat on your pull requests code, a toolbar extension with multiple AI feedbacks, Qodo Merge filters, and additional abilities.
|
||||||
|
|
||||||
The extension is powered by top code models like Claude 3.5 Sonnet and GPT4. All the extension's features are free to use on public repositories.
|
The extension is powered by top code models like Claude 3.5 Sonnet and GPT4. All the extension's features are free to use on public repositories.
|
||||||
|
|
||||||
For private repositories, you will need to install [Qodo Merge Pro](https://github.com/apps/qodo-merge-pro) in addition to the extension (Quick GitHub app setup with a 14-day free trial. No credit card needed).
|
For private repositories, you will need to install [Qodo Merge Pro](https://github.com/apps/qodo-merge-pro){:target="_blank"} in addition to the extension (Quick GitHub app setup with a 14-day free trial. No credit card needed).
|
||||||
For a demonstration of how to install Qodo Merge Pro and use it with the Chrome extension, please refer to the tutorial video at the provided [link](https://codium.ai/images/pr_agent/private_repos.mp4).
|
For a demonstration of how to install Qodo Merge Pro and use it with the Chrome extension, please refer to the tutorial video at the provided [link](https://codium.ai/images/pr_agent/private_repos.mp4){:target="_blank"}.
|
||||||
|
|
||||||
<img src="https://codium.ai/images/pr_agent/PR-AgentChat.gif" width="768">
|
<img src="https://codium.ai/images/pr_agent/PR-AgentChat.gif" width="768">
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Fetching Ticket Context for PRs
|
# Fetching Ticket Context for PRs
|
||||||
`Supported Git Platforms : GitHub, GitLab, Bitbucket`
|
`Supported Git Platforms: GitHub, GitLab, Bitbucket`
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Qodo Merge PR Agent streamlines code review workflows by seamlessly connecting with multiple ticket management systems.
|
Qodo Merge PR Agent streamlines code review workflows by seamlessly connecting with multiple ticket management systems.
|
||||||
@ -120,9 +120,45 @@ jira_api_email = "YOUR_EMAIL"
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Jira Server/Data Center 💎
|
#### Jira Data Center/Server 💎
|
||||||
|
|
||||||
Currently, we only support the Personal Access Token (PAT) Authentication method.
|
##### Local App Authentication (For Qodo Merge On-Premise Customers)
|
||||||
|
|
||||||
|
##### 1. Step 1: Set up an application link in Jira Data Center/Server
|
||||||
|
* Go to Jira Administration > Applications > Application Links > Click on `Create link`
|
||||||
|
|
||||||
|
{width=384}
|
||||||
|
* Choose `External application` and set the direction to `Incoming` and then click `Continue`
|
||||||
|
|
||||||
|
{width=256}
|
||||||
|
* In the following screen, enter the following details:
|
||||||
|
* Name: `Qodo Merge`
|
||||||
|
* Redirect URL: Enter your Qodo Merge URL followed `https://{QODO_MERGE_ENDPOINT}/register_ticket_provider`
|
||||||
|
* Permission: Select `Read`
|
||||||
|
* Click `Save`
|
||||||
|
|
||||||
|
{width=384}
|
||||||
|
* Copy the `Client ID` and `Client secret` and set them in your `.secrets` file:
|
||||||
|
|
||||||
|
{width=256}
|
||||||
|
```toml
|
||||||
|
[jira]
|
||||||
|
jira_app_secret = "..."
|
||||||
|
jira_client_id = "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 2. Step 2: Authenticate with Jira Data Center/Server
|
||||||
|
* Open this URL in your browser: `https://{QODO_MERGE_ENDPOINT}/jira_auth`
|
||||||
|
* Click on link
|
||||||
|
|
||||||
|
{width=384}
|
||||||
|
|
||||||
|
* You will be redirected to Jira Data Center/Server, click `Allow`
|
||||||
|
* You will be redirected back to Qodo Merge PR Agent and you will see a success message.
|
||||||
|
|
||||||
|
|
||||||
|
##### Personal Access Token (PAT) Authentication
|
||||||
|
We also support Personal Access Token (PAT) Authentication method.
|
||||||
|
|
||||||
1. Create a [Personal Access Token (PAT)](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) in your Jira account
|
1. Create a [Personal Access Token (PAT)](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) in your Jira account
|
||||||
2. In your Configuration file/Environment variables/Secrets file, add the following lines:
|
2. In your Configuration file/Environment variables/Secrets file, add the following lines:
|
||||||
|
@ -32,14 +32,14 @@ For example, when generating code suggestions for different files, Qodo Merge ca
|
|||||||
|
|
||||||
@@ ... @@ def func1():
|
@@ ... @@ def func1():
|
||||||
__new hunk__
|
__new hunk__
|
||||||
11 unchanged code line0 in the PR
|
11 unchanged code line0
|
||||||
12 unchanged code line1 in the PR
|
12 unchanged code line1
|
||||||
13 +new code line2 added in the PR
|
13 +new code line2 added
|
||||||
14 unchanged code line3 in the PR
|
14 unchanged code line3
|
||||||
__old hunk__
|
__old hunk__
|
||||||
unchanged code line0
|
unchanged code line0
|
||||||
unchanged code line1
|
unchanged code line1
|
||||||
-old code line2 removed in the PR
|
-old code line2 removed
|
||||||
unchanged code line3
|
unchanged code line3
|
||||||
|
|
||||||
@@ ... @@ def func2():
|
@@ ... @@ def func2():
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
??? note "Question: Can Qodo Merge serve as a substitute for a human reviewer?"
|
??? note "Q: Can Qodo Merge serve as a substitute for a human reviewer?"
|
||||||
#### Answer:<span style="display:none;">1</span>
|
#### Answer:<span style="display:none;">1</span>
|
||||||
|
|
||||||
Qodo Merge is designed to assist, not replace, human reviewers.
|
Qodo Merge is designed to assist, not replace, human reviewers.
|
||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
1. Preserves user's original PR header
|
1. Preserves user's original PR header
|
||||||
2. Places user's description above the AI-generated PR description
|
2. Places user's description above the AI-generated PR description
|
||||||
3. Cannot approve PRs; approval remains reviewer's responsibility
|
3. Won't approve PRs; approval remains reviewer's responsibility
|
||||||
4. The code suggestions are optional, and aim to:
|
4. The code suggestions are optional, and aim to:
|
||||||
- Encourage self-review and self-reflection
|
- Encourage self-review and self-reflection
|
||||||
- Highlight potential bugs or oversights
|
- Highlight potential bugs or oversights
|
||||||
@ -22,15 +22,15 @@
|
|||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
??? note "Question: I received an incorrect or irrelevant suggestion. Why?"
|
??? note "Q: I received an incorrect or irrelevant suggestion. Why?"
|
||||||
|
|
||||||
#### Answer:<span style="display:none;">2</span>
|
#### Answer:<span style="display:none;">2</span>
|
||||||
|
|
||||||
- Modern AI models, like Claude 3.5 Sonnet and GPT-4, are improving rapidly but remain imperfect. Users should critically evaluate all suggestions rather than accepting them automatically.
|
- Modern AI models, like Claude 3.5 Sonnet and GPT-4, are improving rapidly but remain imperfect. Users should critically evaluate all suggestions rather than accepting them automatically.
|
||||||
- AI errors are rare, but possible. A main value from reviewing the code suggestions lies in their high probability of catching **mistakes or bugs made by the PR author**. We believe it's worth spending 30-60 seconds reviewing suggestions, even if some aren't relevant, as this practice can enhances code quality and prevent bugs in production.
|
- AI errors are rare, but possible. A main value from reviewing the code suggestions lies in their high probability of catching **mistakes or bugs made by the PR author**. We believe it's worth spending 30-60 seconds reviewing suggestions, even if some aren't relevant, as this practice can enhance code quality and prevent bugs in production.
|
||||||
|
|
||||||
|
|
||||||
- The hierarchical structure of the suggestions is designed to help the user to _quickly_ understand them, and to decide which ones are relevant and which are not:
|
- The hierarchical structure of the suggestions is designed to help the user _quickly_ understand them, and to decide which ones are relevant and which are not:
|
||||||
|
|
||||||
- Only if the `Category` header is relevant, the user should move to the summarized suggestion description.
|
- Only if the `Category` header is relevant, the user should move to the summarized suggestion description.
|
||||||
- Only if the summarized suggestion description is relevant, the user should click on the collapsible, to read the full suggestion description with a code preview example.
|
- Only if the summarized suggestion description is relevant, the user should click on the collapsible, to read the full suggestion description with a code preview example.
|
||||||
@ -40,14 +40,14 @@ ___
|
|||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
??? note "Question: How can I get more tailored suggestions?"
|
??? note "Q: How can I get more tailored suggestions?"
|
||||||
#### Answer:<span style="display:none;">3</span>
|
#### Answer:<span style="display:none;">3</span>
|
||||||
|
|
||||||
See [here](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices) for more information on how to use the `extra_instructions` and `best_practices` configuration options, to guide the model to more tailored suggestions.
|
See [here](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices) for more information on how to use the `extra_instructions` and `best_practices` configuration options, to guide the model to more tailored suggestions.
|
||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
??? note "Question: Will you store my code ? Are you using my code to train models?"
|
??? note "Q: Will you store my code? Are you using my code to train models?"
|
||||||
#### Answer:<span style="display:none;">4</span>
|
#### Answer:<span style="display:none;">4</span>
|
||||||
|
|
||||||
No. Qodo Merge strict privacy policy ensures that your code is not stored or used for training purposes.
|
No. Qodo Merge strict privacy policy ensures that your code is not stored or used for training purposes.
|
||||||
@ -56,12 +56,35 @@ ___
|
|||||||
|
|
||||||
___
|
___
|
||||||
|
|
||||||
??? note "Question: Can I use my own LLM keys with Qodo Merge?"
|
??? note "Q: Can I use my own LLM keys with Qodo Merge?"
|
||||||
#### Answer:<span style="display:none;">5</span>
|
#### Answer:<span style="display:none;">5</span>
|
||||||
|
|
||||||
When you self-host, you use your own keys.
|
When you self-host the [open-source](https://github.com/Codium-ai/pr-agent) version, you use your own keys.
|
||||||
|
|
||||||
Qodo Merge Pro with SaaS deployment is a hosted version of Qodo Merge, where Qodo manages the infrastructure and the keys.
|
Qodo Merge Pro with SaaS deployment is a hosted version of Qodo Merge, where Qodo manages the infrastructure and the keys.
|
||||||
For enterprise customers, on-prem deployment is also available. [Contact us](https://www.codium.ai/contact/#pricing) for more information.
|
For enterprise customers, on-prem deployment is also available. [Contact us](https://www.codium.ai/contact/#pricing) for more information.
|
||||||
|
___
|
||||||
|
|
||||||
|
??? note "Q: Can Qodo Merge review draft/offline PRs?"
|
||||||
|
#### Answer:<span style="display:none;">5</span>
|
||||||
|
|
||||||
|
Yes. While Qodo Merge won't automatically review draft PRs, you can still get feedback by manually requesting it through [online commenting](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#online-usage).
|
||||||
|
|
||||||
|
For active PRs, you can customize the automatic feedback settings [here](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#qodo-merge-automatic-feedback) to match your team's workflow.
|
||||||
|
___
|
||||||
|
|
||||||
|
??? note "Q: Can the 'Review effort' feedback be calibrated or customized?"
|
||||||
|
#### Answer:<span style="display:none;">5</span>
|
||||||
|
|
||||||
|
Yes, you can customize review effort estimates using the `extra_instructions` configuration option (see [documentation](https://qodo-merge-docs.qodo.ai/tools/review/#configuration-options)).
|
||||||
|
|
||||||
|
Example mapping:
|
||||||
|
|
||||||
|
- Effort 1: < 30 minutes review time
|
||||||
|
- Effort 2: 30-60 minutes review time
|
||||||
|
- Effort 3: 60-90 minutes review time
|
||||||
|
- ...
|
||||||
|
|
||||||
|
Note: The effort levels (1-5) are primarily meant for _comparative_ purposes, helping teams prioritize reviewing smaller PRs first. The actual review duration may vary, as the focus is on providing consistent relative effort estimates.
|
||||||
|
|
||||||
___
|
___
|
||||||
|
@ -44,6 +44,7 @@ Qodo Merge offers extensive pull request functionalities across various git prov
|
|||||||
| | [Similar Code](https://pr-agent-docs.codium.ai/tools/similar_code/) 💎 | ✅ | | | |
|
| | [Similar Code](https://pr-agent-docs.codium.ai/tools/similar_code/) 💎 | ✅ | | | |
|
||||||
| | [Custom Prompt](https://pr-agent-docs.codium.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
| | [Custom Prompt](https://pr-agent-docs.codium.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
||||||
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||||
|
| | [Implement](https://pr-agent-docs.codium.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||||
| | | | | | |
|
| | | | | | |
|
||||||
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
|
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
|
||||||
| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ |
|
| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
## Self-hosted Qodo Merge
|
## Self-hosted Qodo Merge
|
||||||
If you choose to host your own Qodo Merge, you first need to acquire two tokens:
|
If you choose to host your own Qodo Merge, you first need to acquire two tokens:
|
||||||
|
|
||||||
1. An OpenAI key from [here](https://platform.openai.com/api-keys), with access to GPT-4 (or a key for other [language models](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/), if you prefer).
|
1. An OpenAI key from [here](https://platform.openai.com/api-keys){:target="_blank"}, with access to GPT-4 (or a key for other [language models](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/), if you prefer).
|
||||||
2. A GitHub\GitLab\BitBucket personal access token (classic), with the repo scope. [GitHub from [here](https://github.com/settings/tokens)]
|
2. A GitHub\GitLab\BitBucket personal access token (classic), with the repo scope. [GitHub from [here](https://github.com/settings/tokens){:target="_blank"}]
|
||||||
|
|
||||||
There are several ways to use self-hosted Qodo Merge:
|
There are several ways to use self-hosted Qodo Merge:
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ Qodo Merge offers extensive pull request functionalities across various git prov
|
|||||||
| | [Add PR Documentation](./tools/documentation.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
| | [Add PR Documentation](./tools/documentation.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||||
| | [Generate Custom Labels](./tools/describe.md#handle-custom-labels-from-the-repos-labels-page-💎){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
| | [Generate Custom Labels](./tools/describe.md#handle-custom-labels-from-the-repos-labels-page-💎){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||||
| | [Analyze PR Components](./tools/analyze.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
| | [Analyze PR Components](./tools/analyze.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||||
|
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||||
|
| | [Implement](https://pr-agent-docs.codium.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||||
| | | | | | ️ |
|
| | | | | | ️ |
|
||||||
| USAGE | CLI | ✅ | ✅ | ✅ | ✅ |
|
| USAGE | CLI | ✅ | ✅ | ✅ | ✅ |
|
||||||
| | App / webhook | ✅ | ✅ | ✅ | ✅ |
|
| | App / webhook | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
### Overview
|
### Overview
|
||||||
|
|
||||||
[Qodo Merge Pro](https://www.codium.ai/pricing/) is a hosted version of open-source [Qodo Merge (PR-Agent)](https://github.com/Codium-ai/pr-agent). A complimentary two-week trial is offered, followed by a monthly subscription fee.
|
[Qodo Merge Pro](https://www.codium.ai/pricing/){:target="_blank"} is a hosted version of open-source [Qodo Merge (PR-Agent)](https://github.com/Codium-ai/pr-agent){:target="_blank"}. A complimentary two-week trial is offered, followed by a monthly subscription fee.
|
||||||
Qodo Merge Pro is designed for companies and teams that require additional features and capabilities. It provides the following benefits:
|
Qodo Merge Pro is designed for companies and teams that require additional features and capabilities. It provides the following benefits:
|
||||||
|
|
||||||
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the Qodo Merge app to your GitHub\GitLab\BitBucket repo.
|
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the Qodo Merge app to your GitHub\GitLab\BitBucket repo.
|
||||||
@ -34,13 +34,14 @@ Here are some of the additional features and capabilities that Qodo Merge Pro of
|
|||||||
Here are additional tools that are available only for Qodo Merge Pro users:
|
Here are additional tools that are available only for Qodo Merge Pro users:
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
|---------|-------------|
|
|---------------------------------------------------------------------------------------|-------------|
|
||||||
| [**Custom Prompt Suggestions**](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) | Generate code suggestions based on custom prompts from the user |
|
| [**Custom Prompt Suggestions**](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) | Generate code suggestions based on custom prompts from the user |
|
||||||
| [**Analyze PR components**](https://qodo-merge-docs.qodo.ai/tools/analyze/) | Identify the components that changed in the PR, and enable to interactively apply different tools to them |
|
| [**Analyze PR components**](https://qodo-merge-docs.qodo.ai/tools/analyze/) | Identify the components that changed in the PR, and enable to interactively apply different tools to them |
|
||||||
| [**Tests**](https://qodo-merge-docs.qodo.ai/tools/test/) | Generate tests for code components that changed in the PR |
|
| [**Tests**](https://qodo-merge-docs.qodo.ai/tools/test/) | Generate tests for code components that changed in the PR |
|
||||||
| [**PR documentation**](https://qodo-merge-docs.qodo.ai/tools/documentation/) | Generate docstring for code components that changed in the PR |
|
| [**PR documentation**](https://qodo-merge-docs.qodo.ai/tools/documentation/) | Generate docstring for code components that changed in the PR |
|
||||||
| [**Improve Component**](https://qodo-merge-docs.qodo.ai/tools/improve_component/) | Generate code suggestions for code components that changed in the PR |
|
| [**Improve Component**](https://qodo-merge-docs.qodo.ai/tools/improve_component/) | Generate code suggestions for code components that changed in the PR |
|
||||||
| [**Similar code search**](https://qodo-merge-docs.qodo.ai/tools/similar_code/) | Search for similar code in the repository, organization, or entire GitHub |
|
| [**Similar code search**](https://qodo-merge-docs.qodo.ai/tools/similar_code/) | Search for similar code in the repository, organization, or entire GitHub |
|
||||||
|
| [**Code implementation**](https://qodo-merge-docs.qodo.ai/tools/implement/) | Generates implementation code from review suggestions |
|
||||||
|
|
||||||
|
|
||||||
### Supported languages
|
### Supported languages
|
||||||
|
50
docs/docs/tools/implement.md
Normal file
50
docs/docs/tools/implement.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
## Overview
|
||||||
|
|
||||||
|
The `implement` tool converts human code review discussions and feedback into ready-to-commit code changes.
|
||||||
|
It leverages LLM technology to transform PR comments and review suggestions into concrete implementation code, helping developers quickly turn feedback into working solutions.
|
||||||
|
|
||||||
|
## Usage Scenarios
|
||||||
|
|
||||||
|
|
||||||
|
### For Reviewers
|
||||||
|
|
||||||
|
Reviewers can request code changes by: <br>
|
||||||
|
1. Selecting the code block to be modified. <br>
|
||||||
|
2. Adding a comment with the syntax:
|
||||||
|
```
|
||||||
|
/implement <code-change-description>
|
||||||
|
```
|
||||||
|
|
||||||
|
{width=640}
|
||||||
|
|
||||||
|
|
||||||
|
### For PR Authors
|
||||||
|
|
||||||
|
PR authors can implement suggested changes by replying to a review comment using either: <br>
|
||||||
|
1. Add specific implementation details as described above
|
||||||
|
```
|
||||||
|
/implement <code-change-description>
|
||||||
|
```
|
||||||
|
2. Use the original review comment as instructions
|
||||||
|
```
|
||||||
|
/implement
|
||||||
|
```
|
||||||
|
|
||||||
|
{width=640}
|
||||||
|
|
||||||
|
### For Referencing Comments
|
||||||
|
|
||||||
|
You can reference and implement changes from any comment by:
|
||||||
|
```
|
||||||
|
/implement <link-to-review-comment>
|
||||||
|
```
|
||||||
|
|
||||||
|
{width=640}
|
||||||
|
|
||||||
|
Note that the implementation will occur within the review discussion thread.
|
||||||
|
|
||||||
|
|
||||||
|
**Configuration options** <br>
|
||||||
|
- Use `/implement` to implement code change within and based on the review discussion. <br>
|
||||||
|
- Use `/implement <code-change-description>` inside a review discussion to implement specific instructions. <br>
|
||||||
|
- Use `/implement <link-to-review-comment>` to indirectly call the tool from any comment. <br>
|
@ -122,7 +122,7 @@ Use triple quotes to write multi-line instructions. Use bullet points or numbers
|
|||||||
|
|
||||||
>`Platforms supported: GitHub, GitLab, Bitbucket`
|
>`Platforms supported: GitHub, GitLab, Bitbucket`
|
||||||
|
|
||||||
Another option to give additional guidance to the AI model is by creating a dedicated [**wiki page**](https://github.com/Codium-ai/pr-agent/wiki) called `best_practices.md`.
|
Another option to give additional guidance to the AI model is by creating a `best_practices.md` file, either in your repository's root directory or as a [**wiki page**](https://github.com/Codium-ai/pr-agent/wiki) (we recommend the wiki page, as editing and maintaining it over time is easier).
|
||||||
This page can contain a list of best practices, coding standards, and guidelines that are specific to your repo/organization.
|
This page can contain a list of best practices, coding standards, and guidelines that are specific to your repo/organization.
|
||||||
|
|
||||||
The AI model will use this wiki page as a reference, and in case the PR code violates any of the guidelines, it will create additional suggestions, with a dedicated label: `Organization
|
The AI model will use this wiki page as a reference, and in case the PR code violates any of the guidelines, it will create additional suggestions, with a dedicated label: `Organization
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
Here is a list of Qodo Merge tools, each with a dedicated page that explains how to use it:
|
Here is a list of Qodo Merge tools, each with a dedicated page that explains how to use it:
|
||||||
|
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **[PR Description (`/describe`](./describe.md))** | Automatically generating PR description - title, type, summary, code walkthrough and labels |
|
| **[PR Description (`/describe`](./describe.md))** | Automatically generating PR description - title, type, summary, code walkthrough and labels |
|
||||||
| **[PR Review (`/review`](./review.md))** | Adjustable feedback about the PR, possible issues, security concerns, review effort and more |
|
| **[PR Review (`/review`](./review.md))** | Adjustable feedback about the PR, possible issues, security concerns, review effort and more |
|
||||||
| **[Code Suggestions (`/improve`](./improve.md))** | Code suggestions for improving the PR |
|
| **[Code Suggestions (`/improve`](./improve.md))** | Code suggestions for improving the PR |
|
||||||
@ -13,10 +13,11 @@ Here is a list of Qodo Merge tools, each with a dedicated page that explains how
|
|||||||
| **[Help (`/help`](./help.md))** | Provides a list of all the available tools. Also enables to trigger them interactively (💎) |
|
| **[Help (`/help`](./help.md))** | Provides a list of all the available tools. Also enables to trigger them interactively (💎) |
|
||||||
| **💎 [Add Documentation (`/add_docs`](./documentation.md))** | Generates documentation to methods/functions/classes that changed in the PR |
|
| **💎 [Add Documentation (`/add_docs`](./documentation.md))** | Generates documentation to methods/functions/classes that changed in the PR |
|
||||||
| **💎 [Generate Custom Labels (`/generate_labels`](./custom_labels.md))** | Generates custom labels for the PR, based on specific guidelines defined by the user |
|
| **💎 [Generate Custom Labels (`/generate_labels`](./custom_labels.md))** | Generates custom labels for the PR, based on specific guidelines defined by the user |
|
||||||
| **💎 [Analyze (`/analyze`](./analyze.md))** | Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component |
|
| **💎 [Analyze (`/analyze`](./analyze.md))** | Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component|
|
||||||
|
| **💎 [Test (`/test`](./test.md))** | generate tests for a selected component, based on the PR code changes |
|
||||||
| **💎 [Custom Prompt (`/custom_prompt`](./custom_prompt.md))** | Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user |
|
| **💎 [Custom Prompt (`/custom_prompt`](./custom_prompt.md))** | Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user |
|
||||||
| **💎 [Generate Tests (`/test component_name`](./test.md))** | Automatically generates unit tests for a selected component, based on the PR code changes |
|
| **💎 [Generate Tests (`/test component_name`](./test.md))** | Automatically generates unit tests for a selected component, based on the PR code changes |
|
||||||
| **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR |
|
| **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR |
|
||||||
| **💎 [CI Feedback (`/checks ci_job`](./ci_feedback.md))** | Automatically generates feedback and analysis for a failed CI job |
|
| **💎 [CI Feedback (`/checks ci_job`](./ci_feedback.md))** | Automatically generates feedback and analysis for a failed CI job |
|
||||||
|
| **💎 [Implement (`/implement`](./implement.md))** | Generates implementation code from review suggestions |
|
||||||
Note that the tools marked with 💎 are available only for Qodo Merge Pro users.
|
Note that the tools marked with 💎 are available only for Qodo Merge Pro users.
|
||||||
|
@ -56,6 +56,10 @@ extra_instructions = "..."
|
|||||||
<td><b>persistent_comment</b></td>
|
<td><b>persistent_comment</b></td>
|
||||||
<td>If set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.</td>
|
<td>If set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>final_update_message</b></td>
|
||||||
|
<td>When set to true, updating a persistent review comment during online commenting will automatically add a short comment with a link to the updated review in the pull request .Default is true.</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>extra_instructions</b></td>
|
<td><b>extra_instructions</b></td>
|
||||||
<td>Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".</td>
|
<td>Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".</td>
|
||||||
|
@ -17,8 +17,8 @@ The tool will generate tests for the selected component (if no component is stat
|
|||||||
|
|
||||||
(Example taken from [here](https://github.com/Codium-ai/pr-agent/pull/598#issuecomment-1913679429)):
|
(Example taken from [here](https://github.com/Codium-ai/pr-agent/pull/598#issuecomment-1913679429)):
|
||||||
|
|
||||||
**Notes**
|
**Notes** <br>
|
||||||
- Language that are currently supported by the tool: Python, Java, C++, JavaScript, TypeScript, C#.
|
- The following languages are currently supported: Python, Java, C++, JavaScript, TypeScript, C#. <br>
|
||||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
## Show possible configurations
|
## Show possible configurations
|
||||||
The possible configurations of Qodo Merge are stored in [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml).
|
The possible configurations of Qodo Merge are stored in [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml){:target="_blank"}.
|
||||||
In the [tools](https://qodo-merge-docs.qodo.ai/tools/) page you can find explanations on how to use these configurations for each tool.
|
In the [tools](https://qodo-merge-docs.qodo.ai/tools/) page you can find explanations on how to use these configurations for each tool.
|
||||||
|
|
||||||
To print all the available configurations as a comment on your PR, you can use the following command:
|
To print all the available configurations as a comment on your PR, you can use the following command:
|
||||||
@ -138,7 +138,17 @@ LANGSMITH_BASE_URL=<url>
|
|||||||
|
|
||||||
## Ignoring automatic commands in PRs
|
## Ignoring automatic commands in PRs
|
||||||
|
|
||||||
In some cases, you may want to automatically ignore specific PRs . Qodo Merge enables you to ignore PR with a specific title, or from/to specific branches (regex matching).
|
Qodo Merge allows you to automatically ignore certain PRs based on various criteria:
|
||||||
|
|
||||||
|
- PRs with specific titles (using regex matching)
|
||||||
|
- PRs between specific branches (using regex matching)
|
||||||
|
- PRs that don't include changes from specific folders (using regex matching)
|
||||||
|
- PRs containing specific labels
|
||||||
|
- PRs opened by specific users
|
||||||
|
|
||||||
|
### Example usage
|
||||||
|
|
||||||
|
#### Ignoring PRs with specific titles
|
||||||
|
|
||||||
To ignore PRs with a specific title such as "[Bump]: ...", you can add the following to your `configuration.toml` file:
|
To ignore PRs with a specific title such as "[Bump]: ...", you can add the following to your `configuration.toml` file:
|
||||||
|
|
||||||
@ -149,6 +159,7 @@ ignore_pr_title = ["\\[Bump\\]"]
|
|||||||
|
|
||||||
Where the `ignore_pr_title` is a list of regex patterns to match the PR title you want to ignore. Default is `ignore_pr_title = ["^\\[Auto\\]", "^Auto"]`.
|
Where the `ignore_pr_title` is a list of regex patterns to match the PR title you want to ignore. Default is `ignore_pr_title = ["^\\[Auto\\]", "^Auto"]`.
|
||||||
|
|
||||||
|
#### Ignoring PRs between specific branches
|
||||||
|
|
||||||
To ignore PRs from specific source or target branches, you can add the following to your `configuration.toml` file:
|
To ignore PRs from specific source or target branches, you can add the following to your `configuration.toml` file:
|
||||||
|
|
||||||
@ -161,6 +172,7 @@ ignore_pr_target_branches = ["qa"]
|
|||||||
Where the `ignore_pr_source_branches` and `ignore_pr_target_branches` are lists of regex patterns to match the source and target branches you want to ignore.
|
Where the `ignore_pr_source_branches` and `ignore_pr_target_branches` are lists of regex patterns to match the source and target branches you want to ignore.
|
||||||
They are not mutually exclusive, you can use them together or separately.
|
They are not mutually exclusive, you can use them together or separately.
|
||||||
|
|
||||||
|
#### Ignoring PRs that don't include changes from specific folders
|
||||||
|
|
||||||
To allow only specific folders (often needed in large monorepos), set:
|
To allow only specific folders (often needed in large monorepos), set:
|
||||||
|
|
||||||
@ -170,3 +182,35 @@ allow_only_specific_folders=['folder1','folder2']
|
|||||||
```
|
```
|
||||||
|
|
||||||
For the configuration above, automatic feedback will only be triggered when the PR changes include files from 'folder1' or 'folder2'
|
For the configuration above, automatic feedback will only be triggered when the PR changes include files from 'folder1' or 'folder2'
|
||||||
|
|
||||||
|
#### Ignoring PRs containg specific labels
|
||||||
|
|
||||||
|
To ignore PRs containg specific labels, you can add the following to your `configuration.toml` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
[config]
|
||||||
|
ignore_pr_labels = ["do-not-merge"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Where the `ignore_pr_labels` is a list of labels that when present in the PR, the PR will be ignored.
|
||||||
|
|
||||||
|
#### Ignoring PRs from specific users
|
||||||
|
|
||||||
|
Qodo Merge automatically identifies and ignores pull requests created by bots using:
|
||||||
|
|
||||||
|
- GitHub's native bot detection system
|
||||||
|
- Name-based pattern matching
|
||||||
|
|
||||||
|
While this detection is robust, it may not catch all cases, particularly when:
|
||||||
|
|
||||||
|
- Bots are registered as regular user accounts
|
||||||
|
- Bot names don't match common patterns
|
||||||
|
|
||||||
|
To supplement the automatic bot detection, you can manually specify users to ignore. Add the following to your `configuration.toml` file to ignore PRs from specific users:
|
||||||
|
```
|
||||||
|
[config]
|
||||||
|
ignore_pr_authors = ["my-special-bot-user", ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Where the `ignore_pr_authors` is a list of usernames that you want to ignore.
|
||||||
|
|
||||||
|
@ -30,50 +30,39 @@ model="" # the OpenAI model you've deployed on Azure (e.g. gpt-4o)
|
|||||||
fallback_models=["..."]
|
fallback_models=["..."]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Ollama
|
||||||
|
|
||||||
|
You can run 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 model locally via Ollama, set in `.secrets.toml` or in a configuration file:
|
||||||
|
```
|
||||||
|
[config]
|
||||||
|
model = "ollama/qwen2.5-coder:32b"
|
||||||
|
fallback_models=["ollama/qwen2.5-coder:32b"]
|
||||||
|
custom_model_max_tokens=128000 # set the maximal input tokens for the model
|
||||||
|
duplicate_examples=true # will duplicate the examples in the prompt, to help the model to generate structured output
|
||||||
|
|
||||||
|
[ollama]
|
||||||
|
api_base = "http://localhost:11434" # or whatever port you're running Ollama on
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note "Local models vs commercial models"
|
||||||
|
Qodo Merge is compatible with almost any AI model, but analyzing complex code repositories and pull requests requires a model specifically optimized for code analysis.
|
||||||
|
|
||||||
|
Commercial models such as GPT-4, Claude Sonnet, and Gemini have demonstrated robust capabilities in generating structured output for code analysis tasks with large input. In contrast, most open-source models currently available (as of January 2025) face challenges with these complex tasks.
|
||||||
|
|
||||||
|
Based on our testing, local open-source models are suitable for experimentation and learning purposes, but they are not suitable for production-level code analysis tasks.
|
||||||
|
|
||||||
|
Hence, for production workflows and real-world usage, we recommend using commercial models.
|
||||||
|
|
||||||
### Hugging Face
|
### Hugging Face
|
||||||
|
|
||||||
**Local**
|
|
||||||
You can run Hugging Face 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 Hugging Face model locally via Ollama, set:
|
|
||||||
```
|
|
||||||
[__init__.py]
|
|
||||||
MAX_TOKENS = {
|
|
||||||
"model-name-on-ollama": <max_tokens>
|
|
||||||
}
|
|
||||||
e.g.
|
|
||||||
MAX_TOKENS={
|
|
||||||
...,
|
|
||||||
"ollama/llama2": 4096
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[config] # in configuration.toml
|
|
||||||
model = "ollama/llama2"
|
|
||||||
fallback_models=["ollama/llama2"]
|
|
||||||
|
|
||||||
[ollama] # in .secrets.toml
|
|
||||||
api_base = ... # the base url for your Hugging Face inference endpoint
|
|
||||||
# e.g. if running Ollama locally, you may use:
|
|
||||||
api_base = "http://localhost:11434/"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inference Endpoints
|
|
||||||
|
|
||||||
To use a new model with Hugging Face Inference Endpoints, for example, set:
|
To use a new model with Hugging Face Inference Endpoints, for example, set:
|
||||||
```
|
```
|
||||||
[__init__.py]
|
|
||||||
MAX_TOKENS = {
|
|
||||||
"model-name-on-huggingface": <max_tokens>
|
|
||||||
}
|
|
||||||
e.g.
|
|
||||||
MAX_TOKENS={
|
|
||||||
...,
|
|
||||||
"meta-llama/Llama-2-7b-chat-hf": 4096
|
|
||||||
}
|
|
||||||
[config] # in configuration.toml
|
[config] # in configuration.toml
|
||||||
model = "huggingface/meta-llama/Llama-2-7b-chat-hf"
|
model = "huggingface/meta-llama/Llama-2-7b-chat-hf"
|
||||||
fallback_models=["huggingface/meta-llama/Llama-2-7b-chat-hf"]
|
fallback_models=["huggingface/meta-llama/Llama-2-7b-chat-hf"]
|
||||||
|
custom_model_max_tokens=... # set the maximal input tokens for the model
|
||||||
|
|
||||||
[huggingface] # in .secrets.toml
|
[huggingface] # in .secrets.toml
|
||||||
key = ... # your Hugging Face api key
|
key = ... # your Hugging Face api key
|
||||||
@ -177,6 +166,25 @@ drop_params = true
|
|||||||
|
|
||||||
AWS session is automatically authenticated from your environment, but you can also explicitly set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION_NAME` environment variables. Please refer to [this document](https://litellm.vercel.app/docs/providers/bedrock) for more details.
|
AWS session is automatically authenticated from your environment, but you can also explicitly set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION_NAME` environment variables. Please refer to [this document](https://litellm.vercel.app/docs/providers/bedrock) for more details.
|
||||||
|
|
||||||
|
### DeepSeek
|
||||||
|
|
||||||
|
To use deepseek-chat model with DeepSeek, for example, set:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[config] # in configuration.toml
|
||||||
|
model = "deepseek/deepseek-chat"
|
||||||
|
fallback_models=["deepseek/deepseek-chat"]
|
||||||
|
```
|
||||||
|
|
||||||
|
and fill up your key
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[deepseek] # in .secrets.toml
|
||||||
|
key = ...
|
||||||
|
```
|
||||||
|
|
||||||
|
(you can obtain a deepseek-chat key from [here](https://platform.deepseek.com))
|
||||||
|
|
||||||
### Custom models
|
### Custom models
|
||||||
|
|
||||||
If the relevant model doesn't appear [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/__init__.py), you can still use it as a custom model:
|
If the relevant model doesn't appear [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/__init__.py), you can still use it as a custom model:
|
||||||
|
24
docs/docs/usage-guide/enabling_a_wiki.md
Normal file
24
docs/docs/usage-guide/enabling_a_wiki.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
`Supported Git Platforms: GitHub, GitLab, Bitbucket`
|
||||||
|
|
||||||
|
|
||||||
|
For optimal functionality of Qodo Merge, we recommend enabling a wiki for each repository where Qodo Merge is installed. The wiki serves several important purposes:
|
||||||
|
|
||||||
|
**Key Wiki Features:**
|
||||||
|
|
||||||
|
- Storing a [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#wiki-configuration-file)
|
||||||
|
- Defining a [`best_practices.md`](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) file
|
||||||
|
- Track [accepted suggestions](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking)
|
||||||
|
- Facilitates learning over time by creating an [auto_best_practices.md]() file
|
||||||
|
|
||||||
|
|
||||||
|
**Setup Instructions (GitHub):**
|
||||||
|
|
||||||
|
To enable a wiki for your repository:
|
||||||
|
|
||||||
|
1. Navigate to your repository's main page on GitHub
|
||||||
|
2. Select "Settings" from the top navigation bar
|
||||||
|
3. Locate the "Features" section
|
||||||
|
4. Enable the "Wikis" option by checking the corresponding box
|
||||||
|
5. Return to your repository's main page
|
||||||
|
6. Look for the newly added "Wiki" tab in the top navigation
|
||||||
|
7. Initialize your wiki by clicking "Create the first page" (this step is important - without creating an initial page, the wiki will not be fully functional)
|
@ -5,6 +5,7 @@ It includes information on how to adjust Qodo Merge configurations, define which
|
|||||||
|
|
||||||
|
|
||||||
- [Introduction](./introduction.md)
|
- [Introduction](./introduction.md)
|
||||||
|
- [Enabling a Wiki](./enabling_a_wiki)
|
||||||
- [Configuration File](./configuration_options.md)
|
- [Configuration File](./configuration_options.md)
|
||||||
- [Usage and Automation](./automations_and_usage.md)
|
- [Usage and Automation](./automations_and_usage.md)
|
||||||
- [Local Repo (CLI)](./automations_and_usage.md#local-repo-cli)
|
- [Local Repo (CLI)](./automations_and_usage.md#local-repo-cli)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
After [installation](https://qodo-merge-docs.qodo.ai/installation/), there are three basic ways to invoke Qodo Merge:
|
After [installation](https://qodo-merge-docs.qodo.ai/installation/), there are three basic ways to invoke Qodo Merge:
|
||||||
|
|
||||||
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){:target="_blank"} on a PR
|
||||||
3. Enabling Qodo Merge tools to run automatically when a new PR is opened
|
3. Enabling Qodo Merge tools to run automatically when a new PR is opened
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ nav:
|
|||||||
- Usage Guide:
|
- Usage Guide:
|
||||||
- 'usage-guide/index.md'
|
- 'usage-guide/index.md'
|
||||||
- Introduction: 'usage-guide/introduction.md'
|
- Introduction: 'usage-guide/introduction.md'
|
||||||
|
- Enabling a Wiki: 'usage-guide/enabling_a_wiki.md'
|
||||||
- Configuration File: 'usage-guide/configuration_options.md'
|
- Configuration File: 'usage-guide/configuration_options.md'
|
||||||
- Usage and Automation: 'usage-guide/automations_and_usage.md'
|
- Usage and Automation: 'usage-guide/automations_and_usage.md'
|
||||||
- Managing Mail Notifications: 'usage-guide/mail_notifications.md'
|
- Managing Mail Notifications: 'usage-guide/mail_notifications.md'
|
||||||
@ -41,6 +42,7 @@ nav:
|
|||||||
- 💎 Custom Prompt: 'tools/custom_prompt.md'
|
- 💎 Custom Prompt: 'tools/custom_prompt.md'
|
||||||
- 💎 CI Feedback: 'tools/ci_feedback.md'
|
- 💎 CI Feedback: 'tools/ci_feedback.md'
|
||||||
- 💎 Similar Code: 'tools/similar_code.md'
|
- 💎 Similar Code: 'tools/similar_code.md'
|
||||||
|
- 💎 Implement: 'tools/implement.md'
|
||||||
- Core Abilities:
|
- Core Abilities:
|
||||||
- 'core-abilities/index.md'
|
- 'core-abilities/index.md'
|
||||||
- Fetching ticket context: 'core-abilities/fetching_ticket_context.md'
|
- Fetching ticket context: 'core-abilities/fetching_ticket_context.md'
|
||||||
|
@ -60,13 +60,21 @@ class PRAgent:
|
|||||||
else:
|
else:
|
||||||
action, *args = request
|
action, *args = request
|
||||||
|
|
||||||
forbidden_cli_args = ['enable_auto_approval', 'base_url', 'url', 'app_name', 'secret_provider',
|
forbidden_cli_args = ['enable_auto_approval', 'approve_pr_on_self_review', 'base_url', 'url', 'app_name', 'secret_provider',
|
||||||
'git_provider', 'skip_keys', 'key', 'ANALYTICS_FOLDER', 'uri', 'app_id', 'webhook_secret',
|
'git_provider', 'skip_keys', 'openai.key', 'ANALYTICS_FOLDER', 'uri', 'app_id', 'webhook_secret',
|
||||||
'bearer_token', 'PERSONAL_ACCESS_TOKEN', 'override_deployment_type', 'private_key', 'api_base', 'api_type', 'api_version']
|
'bearer_token', 'PERSONAL_ACCESS_TOKEN', 'override_deployment_type', 'private_key',
|
||||||
|
'local_cache_path', 'enable_local_cache', 'jira_base_url', 'api_base', 'api_type', 'api_version',
|
||||||
|
'skip_keys']
|
||||||
if args:
|
if args:
|
||||||
for forbidden_arg in forbidden_cli_args:
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if forbidden_arg.lower() in arg.lower():
|
if arg.startswith('--'):
|
||||||
|
arg_word = arg.lower()
|
||||||
|
arg_word = arg_word.replace('__', '.') # replace double underscore with dot, e.g. --openai__key -> --openai.key
|
||||||
|
for forbidden_arg in forbidden_cli_args:
|
||||||
|
forbidden_arg_word = forbidden_arg.lower()
|
||||||
|
if '.' not in forbidden_arg_word:
|
||||||
|
forbidden_arg_word = '.' + forbidden_arg_word
|
||||||
|
if forbidden_arg_word in arg_word:
|
||||||
get_logger().error(
|
get_logger().error(
|
||||||
f"CLI argument for param '{forbidden_arg}' is forbidden. Use instead a configuration file."
|
f"CLI argument for param '{forbidden_arg}' is forbidden. Use instead a configuration file."
|
||||||
)
|
)
|
||||||
|
@ -29,6 +29,7 @@ MAX_TOKENS = {
|
|||||||
'claude-instant-1': 100000,
|
'claude-instant-1': 100000,
|
||||||
'claude-2': 100000,
|
'claude-2': 100000,
|
||||||
'command-nightly': 4096,
|
'command-nightly': 4096,
|
||||||
|
'deepseek/deepseek-chat': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||||
'replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1': 4096,
|
'replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1': 4096,
|
||||||
'meta-llama/Llama-2-7b-chat-hf': 4096,
|
'meta-llama/Llama-2-7b-chat-hf': 4096,
|
||||||
'vertex_ai/codechat-bison': 6144,
|
'vertex_ai/codechat-bison': 6144,
|
||||||
@ -41,6 +42,7 @@ MAX_TOKENS = {
|
|||||||
'vertex_ai/claude-3-5-sonnet-v2@20241022': 100000,
|
'vertex_ai/claude-3-5-sonnet-v2@20241022': 100000,
|
||||||
'vertex_ai/gemini-1.5-pro': 1048576,
|
'vertex_ai/gemini-1.5-pro': 1048576,
|
||||||
'vertex_ai/gemini-1.5-flash': 1048576,
|
'vertex_ai/gemini-1.5-flash': 1048576,
|
||||||
|
'vertex_ai/gemini-2.0-flash-exp': 1048576,
|
||||||
'vertex_ai/gemma2': 8200,
|
'vertex_ai/gemma2': 8200,
|
||||||
'gemini/gemini-1.5-pro': 1048576,
|
'gemini/gemini-1.5-pro': 1048576,
|
||||||
'gemini/gemini-1.5-flash': 1048576,
|
'gemini/gemini-1.5-flash': 1048576,
|
||||||
|
@ -90,6 +90,10 @@ class LiteLLMAIHandler(BaseAiHandler):
|
|||||||
if get_settings().get("GOOGLE_AI_STUDIO.GEMINI_API_KEY", None):
|
if get_settings().get("GOOGLE_AI_STUDIO.GEMINI_API_KEY", None):
|
||||||
os.environ["GEMINI_API_KEY"] = get_settings().google_ai_studio.gemini_api_key
|
os.environ["GEMINI_API_KEY"] = get_settings().google_ai_studio.gemini_api_key
|
||||||
|
|
||||||
|
# Support deepseek models
|
||||||
|
if get_settings().get("DEEPSEEK.KEY", None):
|
||||||
|
os.environ['DEEPSEEK_API_KEY'] = get_settings().get("DEEPSEEK.KEY")
|
||||||
|
|
||||||
def prepare_logs(self, response, system, user, resp, finish_reason):
|
def prepare_logs(self, response, system, user, resp, finish_reason):
|
||||||
response_log = response.dict().copy()
|
response_log = response.dict().copy()
|
||||||
response_log['system'] = system
|
response_log['system'] = system
|
||||||
|
@ -205,10 +205,11 @@ def pr_generate_extended_diff(pr_languages: list,
|
|||||||
if not extended_patch:
|
if not extended_patch:
|
||||||
get_logger().warning(f"Failed to extend patch for file: {file.filename}")
|
get_logger().warning(f"Failed to extend patch for file: {file.filename}")
|
||||||
continue
|
continue
|
||||||
full_extended_patch = f"\n\n## {file.filename}\n{extended_patch.rstrip()}\n"
|
|
||||||
|
|
||||||
if add_line_numbers_to_hunks:
|
if add_line_numbers_to_hunks:
|
||||||
full_extended_patch = convert_to_hunks_with_lines_numbers(extended_patch, file)
|
full_extended_patch = convert_to_hunks_with_lines_numbers(extended_patch, file)
|
||||||
|
else:
|
||||||
|
full_extended_patch = f"\n\n## File: '{file.filename.strip()}'\n{extended_patch.rstrip()}\n"
|
||||||
|
|
||||||
# add AI-summary metadata to the patch
|
# add AI-summary metadata to the patch
|
||||||
if file.ai_file_summary and get_settings().get("config.enable_ai_metadata", False):
|
if file.ai_file_summary and get_settings().get("config.enable_ai_metadata", False):
|
||||||
|
@ -588,6 +588,8 @@ def load_large_diff(filename, new_file_content_str: str, original_file_content_s
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
original_file_content_str = (original_file_content_str or "").rstrip() + "\n"
|
||||||
|
new_file_content_str = (new_file_content_str or "").rstrip() + "\n"
|
||||||
diff = difflib.unified_diff(original_file_content_str.splitlines(keepends=True),
|
diff = difflib.unified_diff(original_file_content_str.splitlines(keepends=True),
|
||||||
new_file_content_str.splitlines(keepends=True))
|
new_file_content_str.splitlines(keepends=True))
|
||||||
if get_settings().config.verbosity_level >= 2 and show_warning:
|
if get_settings().config.verbosity_level >= 2 and show_warning:
|
||||||
|
@ -77,6 +77,8 @@ def run(inargs=None, args=None):
|
|||||||
async def inner():
|
async def inner():
|
||||||
if args.issue_url:
|
if args.issue_url:
|
||||||
result = await asyncio.create_task(PRAgent().handle_request(args.issue_url, [command] + args.rest))
|
result = await asyncio.create_task(PRAgent().handle_request(args.issue_url, [command] + args.rest))
|
||||||
|
elif args.repo_url:
|
||||||
|
result = await asyncio.create_task(PRAgent().handle_request(args.repo_url, [command] + args.rest))
|
||||||
else:
|
else:
|
||||||
result = await asyncio.create_task(PRAgent().handle_request(args.pr_url, [command] + args.rest))
|
result = await asyncio.create_task(PRAgent().handle_request(args.pr_url, [command] + args.rest))
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ global_settings = Dynaconf(
|
|||||||
envvar_prefix=False,
|
envvar_prefix=False,
|
||||||
merge_enabled=True,
|
merge_enabled=True,
|
||||||
settings_files=[join(current_dir, f) for f in [
|
settings_files=[join(current_dir, f) for f in [
|
||||||
"settings/.secrets.toml",
|
|
||||||
"settings/configuration.toml",
|
"settings/configuration.toml",
|
||||||
"settings/ignore.toml",
|
"settings/ignore.toml",
|
||||||
"settings/language_extensions.toml",
|
"settings/language_extensions.toml",
|
||||||
@ -29,6 +28,7 @@ global_settings = Dynaconf(
|
|||||||
"settings/pr_add_docs.toml",
|
"settings/pr_add_docs.toml",
|
||||||
"settings/custom_labels.toml",
|
"settings/custom_labels.toml",
|
||||||
"settings/pr_help_prompts.toml",
|
"settings/pr_help_prompts.toml",
|
||||||
|
"settings/.secrets.toml",
|
||||||
"settings_prod/.secrets.toml",
|
"settings_prod/.secrets.toml",
|
||||||
]]
|
]]
|
||||||
)
|
)
|
||||||
|
@ -326,13 +326,13 @@ class AzureDevopsProvider(GitProvider):
|
|||||||
edit_type = EDIT_TYPE.ADDED
|
edit_type = EDIT_TYPE.ADDED
|
||||||
elif diff_types[file] == "delete":
|
elif diff_types[file] == "delete":
|
||||||
edit_type = EDIT_TYPE.DELETED
|
edit_type = EDIT_TYPE.DELETED
|
||||||
elif diff_types[file] == "rename":
|
elif "rename" in diff_types[file]: # diff_type can be `rename` | `edit, rename`
|
||||||
edit_type = EDIT_TYPE.RENAMED
|
edit_type = EDIT_TYPE.RENAMED
|
||||||
|
|
||||||
version = GitVersionDescriptor(
|
version = GitVersionDescriptor(
|
||||||
version=base_sha.commit_id, version_type="commit"
|
version=base_sha.commit_id, version_type="commit"
|
||||||
)
|
)
|
||||||
if edit_type == EDIT_TYPE.ADDED:
|
if edit_type == EDIT_TYPE.ADDED or edit_type == EDIT_TYPE.RENAMED:
|
||||||
original_file_content_str = ""
|
original_file_content_str = ""
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -402,10 +402,21 @@ class BitbucketServerProvider(GitProvider):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
projects_index = path_parts.index("projects")
|
projects_index = path_parts.index("projects")
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
|
projects_index = -1
|
||||||
|
|
||||||
|
try:
|
||||||
|
users_index = path_parts.index("users")
|
||||||
|
except ValueError:
|
||||||
|
users_index = -1
|
||||||
|
|
||||||
|
if projects_index == -1 and users_index == -1:
|
||||||
raise ValueError(f"The provided URL '{pr_url}' does not appear to be a Bitbucket PR URL")
|
raise ValueError(f"The provided URL '{pr_url}' does not appear to be a Bitbucket PR URL")
|
||||||
|
|
||||||
|
if projects_index != -1:
|
||||||
path_parts = path_parts[projects_index:]
|
path_parts = path_parts[projects_index:]
|
||||||
|
else:
|
||||||
|
path_parts = path_parts[users_index:]
|
||||||
|
|
||||||
if len(path_parts) < 6 or path_parts[2] != "repos" or path_parts[4] != "pull-requests":
|
if len(path_parts) < 6 or path_parts[2] != "repos" or path_parts[4] != "pull-requests":
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -413,6 +424,8 @@ class BitbucketServerProvider(GitProvider):
|
|||||||
)
|
)
|
||||||
|
|
||||||
workspace_slug = path_parts[1]
|
workspace_slug = path_parts[1]
|
||||||
|
if users_index != -1:
|
||||||
|
workspace_slug = f"~{workspace_slug}"
|
||||||
repo_slug = path_parts[3]
|
repo_slug = path_parts[3]
|
||||||
try:
|
try:
|
||||||
pr_number = int(path_parts[5])
|
pr_number = int(path_parts[5])
|
||||||
|
@ -141,6 +141,18 @@ class LocalGitProvider(GitProvider):
|
|||||||
def remove_comment(self, comment):
|
def remove_comment(self, comment):
|
||||||
pass # Not applicable to the local git provider, but required by the interface
|
pass # Not applicable to the local git provider, but required by the interface
|
||||||
|
|
||||||
|
def add_eyes_reaction(self, comment):
|
||||||
|
pass # Not applicable to the local git provider, but required by the interface
|
||||||
|
|
||||||
|
def get_commit_messages(self):
|
||||||
|
pass # Not applicable to the local git provider, but required by the interface
|
||||||
|
|
||||||
|
def get_repo_settings(self):
|
||||||
|
pass # Not applicable to the local git provider, but required by the interface
|
||||||
|
|
||||||
|
def remove_reaction(self, comment):
|
||||||
|
pass # Not applicable to the local git provider, but required by the interface
|
||||||
|
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
"""
|
"""
|
||||||
Calculate percentage of languages in repository. Used for hunk prioritisation.
|
Calculate percentage of languages in repository. Used for hunk prioritisation.
|
||||||
|
@ -24,10 +24,6 @@ from pr_agent.identity_providers import get_identity_provider
|
|||||||
from pr_agent.identity_providers.identity_provider import Eligibility
|
from pr_agent.identity_providers.identity_provider import Eligibility
|
||||||
from pr_agent.log import LoggingFormat, get_logger, setup_logger
|
from pr_agent.log import LoggingFormat, get_logger, setup_logger
|
||||||
from pr_agent.secret_providers import get_secret_provider
|
from pr_agent.secret_providers import get_secret_provider
|
||||||
from pr_agent.servers.github_action_runner import get_setting_or_env, is_true
|
|
||||||
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
|
|
||||||
|
|
||||||
setup_logger(fmt=LoggingFormat.JSON, level="DEBUG")
|
setup_logger(fmt=LoggingFormat.JSON, level="DEBUG")
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@ -75,6 +71,18 @@ async def handle_manifest(request: Request, response: Response):
|
|||||||
return JSONResponse(manifest_obj)
|
return JSONResponse(manifest_obj)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_username(data):
|
||||||
|
actor = data.get("data", {}).get("actor", {})
|
||||||
|
if actor:
|
||||||
|
if "username" in actor:
|
||||||
|
return actor["username"]
|
||||||
|
elif "display_name" in actor:
|
||||||
|
return actor["display_name"]
|
||||||
|
elif "nickname" in actor:
|
||||||
|
return actor["nickname"]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
async def _perform_commands_bitbucket(commands_conf: str, agent: PRAgent, api_url: str, log_context: dict, data: dict):
|
async def _perform_commands_bitbucket(commands_conf: str, agent: PRAgent, api_url: str, log_context: dict, data: dict):
|
||||||
apply_repo_settings(api_url)
|
apply_repo_settings(api_url)
|
||||||
if commands_conf == "pr_commands" and get_settings().config.disable_auto_feedback: # auto commands for PR, and auto feedback is disabled
|
if commands_conf == "pr_commands" and get_settings().config.disable_auto_feedback: # auto commands for PR, and auto feedback is disabled
|
||||||
@ -118,6 +126,14 @@ def should_process_pr_logic(data) -> bool:
|
|||||||
title = pr_data.get("title", "")
|
title = pr_data.get("title", "")
|
||||||
source_branch = pr_data.get("source", {}).get("branch", {}).get("name", "")
|
source_branch = pr_data.get("source", {}).get("branch", {}).get("name", "")
|
||||||
target_branch = pr_data.get("destination", {}).get("branch", {}).get("name", "")
|
target_branch = pr_data.get("destination", {}).get("branch", {}).get("name", "")
|
||||||
|
sender = _get_username(data)
|
||||||
|
|
||||||
|
# logic to ignore PRs from specific users
|
||||||
|
ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", [])
|
||||||
|
if ignore_pr_users and sender:
|
||||||
|
if sender in ignore_pr_users:
|
||||||
|
get_logger().info(f"Ignoring PR from user '{sender}' due to 'config.ignore_pr_authors' setting")
|
||||||
|
return False
|
||||||
|
|
||||||
# logic to ignore PRs with specific titles
|
# logic to ignore PRs with specific titles
|
||||||
if title:
|
if title:
|
||||||
@ -167,16 +183,7 @@ async def handle_github_webhooks(background_tasks: BackgroundTasks, request: Req
|
|||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
# Get the username of the sender
|
# Get the username of the sender
|
||||||
actor = data.get("data", {}).get("actor", {})
|
log_context["sender"] = _get_username(data)
|
||||||
if actor:
|
|
||||||
try:
|
|
||||||
username = actor["username"]
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
username = actor["display_name"]
|
|
||||||
except KeyError:
|
|
||||||
username = actor["nickname"]
|
|
||||||
log_context["sender"] = username
|
|
||||||
|
|
||||||
sender_id = data.get("data", {}).get("actor", {}).get("account_id", "")
|
sender_id = data.get("data", {}).get("actor", {}).get("account_id", "")
|
||||||
log_context["sender_id"] = sender_id
|
log_context["sender_id"] = sender_id
|
||||||
|
@ -257,6 +257,14 @@ def should_process_pr_logic(body) -> bool:
|
|||||||
pr_labels = pull_request.get("labels", [])
|
pr_labels = pull_request.get("labels", [])
|
||||||
source_branch = pull_request.get("head", {}).get("ref", "")
|
source_branch = pull_request.get("head", {}).get("ref", "")
|
||||||
target_branch = pull_request.get("base", {}).get("ref", "")
|
target_branch = pull_request.get("base", {}).get("ref", "")
|
||||||
|
sender = body.get("sender", {}).get("login")
|
||||||
|
|
||||||
|
# logic to ignore PRs from specific users
|
||||||
|
ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", [])
|
||||||
|
if ignore_pr_users and sender:
|
||||||
|
if sender in ignore_pr_users:
|
||||||
|
get_logger().info(f"Ignoring PR from user '{sender}' due to 'config.ignore_pr_authors' setting")
|
||||||
|
return False
|
||||||
|
|
||||||
# logic to ignore PRs with specific titles
|
# logic to ignore PRs with specific titles
|
||||||
if title:
|
if title:
|
||||||
@ -276,6 +284,7 @@ def should_process_pr_logic(body) -> bool:
|
|||||||
get_logger().info(f"Ignoring PR with labels '{labels_str}' due to config.ignore_pr_labels settings")
|
get_logger().info(f"Ignoring PR with labels '{labels_str}' due to config.ignore_pr_labels settings")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# logic to ignore PRs with specific source or target branches
|
||||||
ignore_pr_source_branches = get_settings().get("CONFIG.IGNORE_PR_SOURCE_BRANCHES", [])
|
ignore_pr_source_branches = get_settings().get("CONFIG.IGNORE_PR_SOURCE_BRANCHES", [])
|
||||||
ignore_pr_target_branches = get_settings().get("CONFIG.IGNORE_PR_TARGET_BRANCHES", [])
|
ignore_pr_target_branches = get_settings().get("CONFIG.IGNORE_PR_TARGET_BRANCHES", [])
|
||||||
if pull_request and (ignore_pr_source_branches or ignore_pr_target_branches):
|
if pull_request and (ignore_pr_source_branches or ignore_pr_target_branches):
|
||||||
|
@ -100,6 +100,14 @@ def should_process_pr_logic(data) -> bool:
|
|||||||
if not data.get('object_attributes', {}):
|
if not data.get('object_attributes', {}):
|
||||||
return False
|
return False
|
||||||
title = data['object_attributes'].get('title')
|
title = data['object_attributes'].get('title')
|
||||||
|
sender = data.get("user", {}).get("username", "")
|
||||||
|
|
||||||
|
# logic to ignore PRs from specific users
|
||||||
|
ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", [])
|
||||||
|
if ignore_pr_users and sender:
|
||||||
|
if sender in ignore_pr_users:
|
||||||
|
get_logger().info(f"Ignoring PR from user '{sender}' due to 'config.ignore_pr_authors' settings")
|
||||||
|
return False
|
||||||
|
|
||||||
# logic to ignore MRs for titles, labels and source, target branches.
|
# logic to ignore MRs for titles, labels and source, target branches.
|
||||||
ignore_mr_title = get_settings().get("CONFIG.IGNORE_PR_TITLE", [])
|
ignore_mr_title = get_settings().get("CONFIG.IGNORE_PR_TITLE", [])
|
||||||
|
@ -91,3 +91,6 @@ pat = ""
|
|||||||
# Optional, uncomment if you want to use Azure devops webhooks. Value assinged when you create the webhook
|
# Optional, uncomment if you want to use Azure devops webhooks. Value assinged when you create the webhook
|
||||||
# webhook_username = "<basic auth user>"
|
# webhook_username = "<basic auth user>"
|
||||||
# webhook_password = "<basic auth password>"
|
# webhook_password = "<basic auth password>"
|
||||||
|
|
||||||
|
[deepseek]
|
||||||
|
key = ""
|
||||||
|
@ -34,6 +34,7 @@ ai_disclaimer_title="" # Pro feature, title for a collapsible disclaimer to AI
|
|||||||
ai_disclaimer="" # Pro feature, full text for the AI disclaimer
|
ai_disclaimer="" # Pro feature, full text for the AI disclaimer
|
||||||
output_relevant_configurations=false
|
output_relevant_configurations=false
|
||||||
large_patch_policy = "clip" # "clip", "skip"
|
large_patch_policy = "clip" # "clip", "skip"
|
||||||
|
duplicate_prompt_examples = false
|
||||||
# seed
|
# seed
|
||||||
seed=-1 # set positive value to fix the seed (and ensure temperature=0)
|
seed=-1 # set positive value to fix the seed (and ensure temperature=0)
|
||||||
temperature=0.2
|
temperature=0.2
|
||||||
@ -42,6 +43,7 @@ ignore_pr_title = ["^\\[Auto\\]", "^Auto"] # a list of regular expressions to ma
|
|||||||
ignore_pr_target_branches = [] # a list of regular expressions of target branches to ignore from PR agent when an PR is created
|
ignore_pr_target_branches = [] # a list of regular expressions of target branches to ignore from PR agent when an PR is created
|
||||||
ignore_pr_source_branches = [] # a list of regular expressions of source branches to ignore from PR agent when an PR is created
|
ignore_pr_source_branches = [] # a list of regular expressions of source branches to ignore from PR agent when an PR is created
|
||||||
ignore_pr_labels = [] # labels to ignore from PR agent when an PR is created
|
ignore_pr_labels = [] # labels to ignore from PR agent when an PR is created
|
||||||
|
ignore_pr_authors = [] # authors to ignore from PR agent when an PR is created
|
||||||
#
|
#
|
||||||
is_auto_command = false # will be auto-set to true if the command is triggered by an automation
|
is_auto_command = false # will be auto-set to true if the command is triggered by an automation
|
||||||
enable_ai_metadata = false # will enable adding ai metadata
|
enable_ai_metadata = false # will enable adding ai metadata
|
||||||
|
@ -17,30 +17,36 @@ The PR code diff will be in the following structured format:
|
|||||||
|
|
||||||
@@ ... @@ def func1():
|
@@ ... @@ def func1():
|
||||||
__new hunk__
|
__new hunk__
|
||||||
unchanged code line0 in the PR
|
unchanged code line0
|
||||||
unchanged code line1 in the PR
|
unchanged code line1
|
||||||
+new code line2 added in the PR
|
+new code line2 added
|
||||||
unchanged code line3 in the PR
|
unchanged code line3
|
||||||
__old hunk__
|
__old hunk__
|
||||||
unchanged code line0
|
unchanged code line0
|
||||||
unchanged code line1
|
unchanged code line1
|
||||||
-old code line2 removed in the PR
|
-old code line2 removed
|
||||||
unchanged code line3
|
unchanged code line3
|
||||||
|
|
||||||
@@ ... @@ def func2():
|
@@ ... @@ def func2():
|
||||||
__new hunk__
|
__new hunk__
|
||||||
unchanged code line4
|
unchanged code line4
|
||||||
+new code line5 removed in the PR
|
+new code line5 removed
|
||||||
unchanged code line6
|
unchanged code line6
|
||||||
|
|
||||||
## File: 'src/file2.py'
|
## File: 'src/file2.py'
|
||||||
...
|
...
|
||||||
======
|
======
|
||||||
|
|
||||||
- In the format above, the diff is organized into separate '__new hunk__' and '__old hunk__' sections for each code chunk. '__new hunk__' contains the updated code, while '__old hunk__' shows the removed code. If no code was removed in a specific chunk, the __old hunk__ section will be omitted.
|
Important notes about the structured diff format above:
|
||||||
- Code lines are prefixed with symbols: '+' for new code added in the PR, '-' for code removed, and ' ' for unchanged code.
|
1. Each PR code chunk is decoupled into separate '__new hunk__' and '__old hunk__' sections:
|
||||||
|
- The '__new hunk__' section shows the code chunk AFTER the PR changes.
|
||||||
|
- The '__old hunk__' section shows the code chunk BEFORE the PR changes. If no code was removed from the chunk, the '__old hunk__' section will be omitted.
|
||||||
|
2. The diff uses line prefixes to show changes:
|
||||||
|
'+' → new line code added (will appear only in '__new hunk__')
|
||||||
|
'-' → line code removed (will appear only in '__old hunk__')
|
||||||
|
' ' → unchanged context lines (will appear in both sections)
|
||||||
{%- if is_ai_metadata %}
|
{%- if is_ai_metadata %}
|
||||||
- When available, an AI-generated summary will precede each file's diff, with a high-level overview of the changes. Note that this summary may not be fully accurate or complete.
|
3. When available, an AI-generated summary will precede each file's diff, with a high-level overview of the changes. Note that this summary may not be fully accurate or complete.
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
|
||||||
@ -50,16 +56,17 @@ Specific guidelines for generating code suggestions:
|
|||||||
{%- else %}
|
{%- else %}
|
||||||
- Provide up to {{ num_code_suggestions }} distinct and insightful code suggestions. Return less suggestions if no pertinent ones are applicable.
|
- Provide up to {{ num_code_suggestions }} distinct and insightful code suggestions. Return less suggestions if no pertinent ones are applicable.
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
- Focus solely on enhancing new code introduced in the PR, identified by '+' prefixes in '__new hunk__' sections.
|
- DO NOT suggest implementing changes that are already present in the '+' lines compared to the '-' lines.
|
||||||
|
- Focus your suggestions ONLY on new code introduced in the PR ('+' lines in '__new hunk__' sections).
|
||||||
{%- if not focus_only_on_problems %}
|
{%- if not focus_only_on_problems %}
|
||||||
- Prioritize suggestions that address potential issues, critical problems, and bugs in the PR code. Avoid repeating changes already implemented in the PR. If no pertinent suggestions are applicable, return an empty list.
|
- Prioritize suggestions that address potential issues, critical problems, and bugs in the PR code. Avoid repeating changes already implemented in the PR. If no pertinent suggestions are applicable, return an empty list.
|
||||||
|
- Don't suggest to add docstring, type hints, or comments, to remove unused imports, or to use more specific exception types.
|
||||||
{%- else %}
|
{%- else %}
|
||||||
- Only give suggestions that address critical problems and bugs in the PR code. If no relevant suggestions are applicable, return an empty list.
|
- Only give suggestions that address critical problems and bugs in the PR code. If no relevant suggestions are applicable, return an empty list.
|
||||||
|
- Do not suggest to change packages version, add missing import statement, or declare undefined variable.
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
- Don't suggest to add docstring, type hints, or comments, to remove unused imports, or to use more specific exception types.
|
- When mentioning code elements (variables, names, or files) in your response, surround them with backticks (`). For example: "verify that `user_id` is..."
|
||||||
- When referencing variables or names from the code, enclose them in backticks (`). Example: "ensure that `variable_name` is..."
|
- Note that you only see changed code segments (diff hunks in a PR), not the entire codebase. Avoid suggestions that might duplicate existing functionality or questioning code elements (like variables declerations or import statements) that may be defined elsewhere in the codebase.
|
||||||
- Be mindful you are viewing a partial PR code diff, not the full codebase. Avoid suggestions that might conflict with unseen code or alerting variables not declared in the visible scope, as the context is incomplete.
|
|
||||||
|
|
||||||
|
|
||||||
{%- if extra_instructions %}
|
{%- if extra_instructions %}
|
||||||
|
|
||||||
@ -79,7 +86,7 @@ class CodeSuggestion(BaseModel):
|
|||||||
suggestion_content: str = Field(description="An actionable suggestion to enhance, improve or fix the new code introduced in the PR. Don't present here actual code snippets, just the suggestion. Be short and concise")
|
suggestion_content: str = Field(description="An actionable suggestion to enhance, improve or fix the new code introduced in the PR. Don't present here actual code snippets, just the suggestion. Be short and concise")
|
||||||
existing_code: str = Field(description="A short code snippet from a '__new hunk__' section that the suggestion aims to enhance or fix. Include only complete code lines. Use ellipsis (...) for brevity if needed. This snippet should represent the specific PR code targeted for improvement.")
|
existing_code: str = Field(description="A short code snippet from a '__new hunk__' section that the suggestion aims to enhance or fix. Include only complete code lines. Use ellipsis (...) for brevity if needed. This snippet should represent the specific PR code targeted for improvement.")
|
||||||
improved_code: str = Field(description="A refined code snippet that replaces the 'existing_code' snippet after implementing the suggestion.")
|
improved_code: str = Field(description="A refined code snippet that replaces the 'existing_code' snippet after implementing the suggestion.")
|
||||||
one_sentence_summary: str = Field(description="A concise, single-sentence overview of the suggested improvement. Focus on the 'what'. Be general, and avoid method or variable names.")
|
one_sentence_summary: str = Field(description="A concise, single-sentence overview (up to 6 words) of the suggested improvement. Focus on the 'what'. Be general, and avoid method or variable names.")
|
||||||
{%- if not focus_only_on_problems %}
|
{%- if not focus_only_on_problems %}
|
||||||
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'possible bug', 'possible issue', 'performance', 'enhancement', 'best practice', 'maintainability', 'typo'. Other relevant labels are also acceptable.")
|
label: str = Field(description="A single, descriptive label that best characterizes the suggestion type. Possible labels include 'security', 'possible bug', 'possible issue', 'performance', 'enhancement', 'best practice', 'maintainability', 'typo'. Other relevant labels are also acceptable.")
|
||||||
{%- else %}
|
{%- else %}
|
||||||
@ -111,7 +118,6 @@ code_suggestions:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Each YAML output MUST be after a newline, indented, with block scalar indicator ('|').
|
Each YAML output MUST be after a newline, indented, with block scalar indicator ('|').
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -119,12 +125,40 @@ user="""--PR Info--
|
|||||||
|
|
||||||
Title: '{{title}}'
|
Title: '{{title}}'
|
||||||
|
|
||||||
|
{%- if date %}
|
||||||
|
|
||||||
|
Today's Date: {{date}}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
The PR Diff:
|
The PR Diff:
|
||||||
======
|
======
|
||||||
{{ diff_no_line_numbers|trim }}
|
{{ diff_no_line_numbers|trim }}
|
||||||
======
|
======
|
||||||
|
|
||||||
|
{%- if duplicate_prompt_examples %}
|
||||||
|
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```yaml
|
||||||
|
code_suggestions:
|
||||||
|
- relevant_file: |
|
||||||
|
src/file1.py
|
||||||
|
language: |
|
||||||
|
python
|
||||||
|
suggestion_content: |
|
||||||
|
...
|
||||||
|
existing_code: |
|
||||||
|
...
|
||||||
|
improved_code: |
|
||||||
|
...
|
||||||
|
one_sentence_summary: |
|
||||||
|
...
|
||||||
|
label: |
|
||||||
|
...
|
||||||
|
```
|
||||||
|
(replace '...' with actual content)
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
|
||||||
Response (should be a valid YAML, and nothing else):
|
Response (should be a valid YAML, and nothing else):
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -49,14 +49,14 @@ The PR code diff will be presented in the following structured format:
|
|||||||
|
|
||||||
@@ ... @@ def func1():
|
@@ ... @@ def func1():
|
||||||
__new hunk__
|
__new hunk__
|
||||||
11 unchanged code line0 in the PR
|
11 unchanged code line0
|
||||||
12 unchanged code line1 in the PR
|
12 unchanged code line1
|
||||||
13 +new code line2 added in the PR
|
13 +new code line2 added
|
||||||
14 unchanged code line3 in the PR
|
14 unchanged code line3
|
||||||
__old hunk__
|
__old hunk__
|
||||||
unchanged code line0
|
unchanged code line0
|
||||||
unchanged code line1
|
unchanged code line1
|
||||||
-old code line2 removed in the PR
|
-old code line2 removed
|
||||||
unchanged code line3
|
unchanged code line3
|
||||||
|
|
||||||
@@ ... @@ def func2():
|
@@ ... @@ def func2():
|
||||||
@ -122,6 +122,25 @@ Below are {{ num_code_suggestions }} AI-generated code suggestions for enhancing
|
|||||||
======
|
======
|
||||||
|
|
||||||
|
|
||||||
|
{%- if duplicate_prompt_examples %}
|
||||||
|
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```yaml
|
||||||
|
code_suggestions:
|
||||||
|
- suggestion_summary: |
|
||||||
|
...
|
||||||
|
relevant_file: "..."
|
||||||
|
relevant_lines_start: ...
|
||||||
|
relevant_lines_end: ...
|
||||||
|
suggestion_score: ...
|
||||||
|
why: |
|
||||||
|
...
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
(replace '...' with actual content)
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
Response (should be a valid YAML, and nothing else):
|
Response (should be a valid YAML, and nothing else):
|
||||||
```yaml
|
```yaml
|
||||||
"""
|
"""
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
[pr_description_prompt]
|
[pr_description_prompt]
|
||||||
system="""You are PR-Reviewer, a language model designed to review a Git Pull Request (PR).
|
system="""You are PR-Reviewer, a language model designed to review a Git Pull Request (PR).
|
||||||
{%- if enable_custom_labels %}
|
Your task is to provide a full description for the PR content - type, description, title and files walkthrough.
|
||||||
Your task is to provide a full description for the PR content - files walkthrough, title, type, description and labels.
|
- Focus on the new PR code (lines starting with '+' in the 'PR Git Diff' section).
|
||||||
{%- else %}
|
|
||||||
Your task is to provide a full description for the PR content - files walkthrough, title, type, and description.
|
|
||||||
{%- endif %}
|
|
||||||
- Focus on the new PR code (lines starting with '+').
|
|
||||||
- Keep in mind that the 'Previous title', 'Previous description' and 'Commit messages' sections may be partial, simplistic, non-informative or out of date. Hence, compare them to the PR diff code, and use them only as a reference.
|
- Keep in mind that the 'Previous title', 'Previous description' and 'Commit messages' sections may be partial, simplistic, non-informative or out of date. Hence, compare them to the PR diff code, and use them only as a reference.
|
||||||
- The generated title and description should prioritize the most significant changes.
|
- The generated title and description should prioritize the most significant changes.
|
||||||
- If needed, each YAML output should be in block scalar indicator ('|-')
|
- If needed, each YAML output should be in block scalar indicator ('|')
|
||||||
- When quoting variables, names or file paths from the code, use backticks (`) instead of single quote (').
|
- When quoting variables, names or file paths from the code, use backticks (`) instead of single quote (').
|
||||||
|
|
||||||
{%- if extra_instructions %}
|
{%- if extra_instructions %}
|
||||||
@ -39,7 +35,6 @@ class PRType(str, Enum):
|
|||||||
|
|
||||||
class FileDescription(BaseModel):
|
class FileDescription(BaseModel):
|
||||||
filename: str = Field(description="The full file path of the relevant file")
|
filename: str = Field(description="The full file path of the relevant file")
|
||||||
language: str = Field(description="The programming language of the relevant file")
|
|
||||||
{%- if include_file_summary_changes %}
|
{%- if include_file_summary_changes %}
|
||||||
changes_summary: str = Field(description="concise summary of the changes in the relevant file, in bullet points (1-4 bullet points).")
|
changes_summary: str = Field(description="concise summary of the changes in the relevant file, in bullet points (1-4 bullet points).")
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
@ -49,11 +44,11 @@ class FileDescription(BaseModel):
|
|||||||
|
|
||||||
class PRDescription(BaseModel):
|
class PRDescription(BaseModel):
|
||||||
type: List[PRType] = Field(description="one or more types that describe the PR content. Return the label member value (e.g. 'Bug fix', not 'bug_fix')")
|
type: List[PRType] = Field(description="one or more types that describe the PR content. Return the label member value (e.g. 'Bug fix', not 'bug_fix')")
|
||||||
|
description: str = Field(description="summarize the PR changes in up to four bullet points, each up to 8 words. For large PRs, add sub-bullets if needed. Order bullets by importance, with each bullet highlighting a key change group.")
|
||||||
|
title: str = Field(description="a concise and descriptive title that captures the PR's main theme")
|
||||||
{%- if enable_semantic_files_types %}
|
{%- if enable_semantic_files_types %}
|
||||||
pr_files: List[FileDescription] = Field(max_items=20, description="a list of all the files that were changed in the PR, and summary of their changes. Each file must be analyzed regardless of change size.")
|
pr_files: List[FileDescription] = Field(max_items=20, description="a list of all the files that were changed in the PR, and summary of their changes. Each file must be analyzed regardless of change size.")
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
description: str = Field(description="an informative and concise description of the PR. Use bullet points. Display first the most significant changes.")
|
|
||||||
title: str = Field(description="an informative title for the PR, describing its main theme")
|
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
|
||||||
@ -63,12 +58,14 @@ Example output:
|
|||||||
type:
|
type:
|
||||||
- ...
|
- ...
|
||||||
- ...
|
- ...
|
||||||
|
description: |
|
||||||
|
...
|
||||||
|
title: |
|
||||||
|
...
|
||||||
{%- if enable_semantic_files_types %}
|
{%- if enable_semantic_files_types %}
|
||||||
pr_files:
|
pr_files:
|
||||||
- filename: |
|
- filename: |
|
||||||
...
|
...
|
||||||
language: |
|
|
||||||
...
|
|
||||||
{%- if include_file_summary_changes %}
|
{%- if include_file_summary_changes %}
|
||||||
changes_summary: |
|
changes_summary: |
|
||||||
...
|
...
|
||||||
@ -79,10 +76,6 @@ pr_files:
|
|||||||
label_key_1
|
label_key_1
|
||||||
...
|
...
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
description: |
|
|
||||||
...
|
|
||||||
title: |
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Answer should be a valid YAML, and nothing else. Each YAML output MUST be after a newline, with proper indent, and block scalar indicator ('|')
|
Answer should be a valid YAML, and nothing else. Each YAML output MUST be after a newline, with proper indent, and block scalar indicator ('|')
|
||||||
@ -130,13 +123,44 @@ Commit messages:
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
|
||||||
The PR Diff:
|
The PR Git Diff:
|
||||||
=====
|
=====
|
||||||
{{ diff|trim }}
|
{{ diff|trim }}
|
||||||
=====
|
=====
|
||||||
|
|
||||||
Note that lines in the diff body are prefixed with a symbol that represents the type of change: '-' for deletions, '+' for additions, and ' ' (a space) for unchanged lines.
|
Note that lines in the diff body are prefixed with a symbol that represents the type of change: '-' for deletions, '+' for additions, and ' ' (a space) for unchanged lines.
|
||||||
|
|
||||||
|
{%- if duplicate_prompt_examples %}
|
||||||
|
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```yaml
|
||||||
|
type:
|
||||||
|
- Bug fix
|
||||||
|
- Refactoring
|
||||||
|
- ...
|
||||||
|
description: |
|
||||||
|
...
|
||||||
|
title: |
|
||||||
|
...
|
||||||
|
{%- if enable_semantic_files_types %}
|
||||||
|
pr_files:
|
||||||
|
- filename: |
|
||||||
|
...
|
||||||
|
{%- if include_file_summary_changes %}
|
||||||
|
changes_summary: |
|
||||||
|
...
|
||||||
|
{%- endif %}
|
||||||
|
changes_title: |
|
||||||
|
...
|
||||||
|
label: |
|
||||||
|
label_key_1
|
||||||
|
...
|
||||||
|
{%- endif %}
|
||||||
|
```
|
||||||
|
(replace '...' with the actual values)
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
|
||||||
Response (should be a valid YAML, and nothing else):
|
Response (should be a valid YAML, and nothing else):
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -16,20 +16,20 @@ The format we will use to present the PR code diff:
|
|||||||
|
|
||||||
@@ ... @@ def func1():
|
@@ ... @@ def func1():
|
||||||
__new hunk__
|
__new hunk__
|
||||||
11 unchanged code line0 in the PR
|
11 unchanged code line0
|
||||||
12 unchanged code line1 in the PR
|
12 unchanged code line1
|
||||||
13 +new code line2 added in the PR
|
13 +new code line2 added
|
||||||
14 unchanged code line3 in the PR
|
14 unchanged code line3
|
||||||
__old hunk__
|
__old hunk__
|
||||||
unchanged code line0
|
unchanged code line0
|
||||||
unchanged code line1
|
unchanged code line1
|
||||||
-old code line2 removed in the PR
|
-old code line2 removed
|
||||||
unchanged code line3
|
unchanged code line3
|
||||||
|
|
||||||
@@ ... @@ def func2():
|
@@ ... @@ def func2():
|
||||||
__new hunk__
|
__new hunk__
|
||||||
unchanged code line4
|
unchanged code line4
|
||||||
+new code line5 removed in the PR
|
+new code line5 removed
|
||||||
unchanged code line6
|
unchanged code line6
|
||||||
|
|
||||||
## File: 'src/file2.py'
|
## File: 'src/file2.py'
|
||||||
@ -43,7 +43,7 @@ __new hunk__
|
|||||||
{%- if is_ai_metadata %}
|
{%- if is_ai_metadata %}
|
||||||
- If available, an AI-generated summary will appear and provide a high-level overview of the file changes. Note that this summary may not be fully accurate or complete.
|
- If available, an AI-generated summary will appear and provide a high-level overview of the file changes. Note that this summary may not be fully accurate or complete.
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
- When quoting variables or names from the code, use backticks (`) instead of single quote (').
|
- When quoting variables, names or file paths from the code, use backticks (`) instead of single quote (').
|
||||||
|
|
||||||
|
|
||||||
{%- if extra_instructions %}
|
{%- if extra_instructions %}
|
||||||
@ -221,6 +221,59 @@ The PR code diff:
|
|||||||
======
|
======
|
||||||
|
|
||||||
|
|
||||||
|
{%- if duplicate_prompt_examples %}
|
||||||
|
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```yaml
|
||||||
|
review:
|
||||||
|
{%- if related_tickets %}
|
||||||
|
ticket_compliance_check:
|
||||||
|
- ticket_url: |
|
||||||
|
...
|
||||||
|
ticket_requirements: |
|
||||||
|
...
|
||||||
|
fully_compliant_requirements: |
|
||||||
|
...
|
||||||
|
not_compliant_requirements: |
|
||||||
|
...
|
||||||
|
overall_compliance_level: |
|
||||||
|
...
|
||||||
|
{%- endif %}
|
||||||
|
{%- if require_estimate_effort_to_review %}
|
||||||
|
estimated_effort_to_review_[1-5]: |
|
||||||
|
3
|
||||||
|
{%- endif %}
|
||||||
|
{%- if require_score %}
|
||||||
|
score: 89
|
||||||
|
{%- endif %}
|
||||||
|
relevant_tests: |
|
||||||
|
No
|
||||||
|
key_issues_to_review:
|
||||||
|
- relevant_file: |
|
||||||
|
...
|
||||||
|
issue_header: |
|
||||||
|
...
|
||||||
|
issue_content: |
|
||||||
|
...
|
||||||
|
start_line: ...
|
||||||
|
end_line: ...
|
||||||
|
- ...
|
||||||
|
security_concerns: |
|
||||||
|
No
|
||||||
|
{%- if require_can_be_split_review %}
|
||||||
|
can_be_split:
|
||||||
|
- relevant_files:
|
||||||
|
- ...
|
||||||
|
- ...
|
||||||
|
title: ...
|
||||||
|
- ...
|
||||||
|
{%- endif %}
|
||||||
|
```
|
||||||
|
(replace '...' with the actual values)
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
|
||||||
Response (should be a valid YAML, and nothing else):
|
Response (should be a valid YAML, and nothing else):
|
||||||
```yaml
|
```yaml
|
||||||
"""
|
"""
|
||||||
|
@ -4,6 +4,7 @@ import difflib
|
|||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
|
from datetime import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
@ -81,6 +82,8 @@ class PRCodeSuggestions:
|
|||||||
"relevant_best_practices": "",
|
"relevant_best_practices": "",
|
||||||
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False),
|
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False),
|
||||||
"focus_only_on_problems": get_settings().get("pr_code_suggestions.focus_only_on_problems", False),
|
"focus_only_on_problems": get_settings().get("pr_code_suggestions.focus_only_on_problems", False),
|
||||||
|
"date": datetime.now().strftime('%Y-%m-%d'),
|
||||||
|
'duplicate_prompt_examples': get_settings().config.get('duplicate_prompt_examples', False),
|
||||||
}
|
}
|
||||||
self.pr_code_suggestions_prompt_system = get_settings().pr_code_suggestions_prompt.system
|
self.pr_code_suggestions_prompt_system = get_settings().pr_code_suggestions_prompt.system
|
||||||
|
|
||||||
@ -830,7 +833,8 @@ class PRCodeSuggestions:
|
|||||||
"diff": patches_diff,
|
"diff": patches_diff,
|
||||||
'num_code_suggestions': len(suggestion_list),
|
'num_code_suggestions': len(suggestion_list),
|
||||||
'prev_suggestions_str': prev_suggestions_str,
|
'prev_suggestions_str': prev_suggestions_str,
|
||||||
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False)}
|
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False),
|
||||||
|
'duplicate_prompt_examples': get_settings().config.get('duplicate_prompt_examples', False)}
|
||||||
environment = Environment(undefined=StrictUndefined)
|
environment = Environment(undefined=StrictUndefined)
|
||||||
|
|
||||||
if dedicated_prompt:
|
if dedicated_prompt:
|
||||||
|
@ -38,12 +38,15 @@ class PRConfig:
|
|||||||
if (header.lower().startswith("pr_") or header.lower().startswith("config")) and header.lower() in configuration_headers
|
if (header.lower().startswith("pr_") or header.lower().startswith("config")) and header.lower() in configuration_headers
|
||||||
}
|
}
|
||||||
|
|
||||||
skip_keys = ['ai_disclaimer', 'ai_disclaimer_title', 'ANALYTICS_FOLDER', 'secret_provider', "skip_keys",
|
skip_keys = ['ai_disclaimer', 'ai_disclaimer_title', 'ANALYTICS_FOLDER', 'secret_provider', "skip_keys", "app_id", "redirect",
|
||||||
'trial_prefix_message', 'no_eligible_message', 'identity_provider', 'ALLOWED_REPOS',
|
'trial_prefix_message', 'no_eligible_message', 'identity_provider', 'ALLOWED_REPOS',
|
||||||
'APP_NAME']
|
'APP_NAME', 'PERSONAL_ACCESS_TOKEN', 'shared_secret', 'key', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'user_token',
|
||||||
|
'private_key', 'private_key_id', 'client_id', 'client_secret', 'token', 'bearer_token']
|
||||||
extra_skip_keys = get_settings().config.get('config.skip_keys', [])
|
extra_skip_keys = get_settings().config.get('config.skip_keys', [])
|
||||||
if extra_skip_keys:
|
if extra_skip_keys:
|
||||||
skip_keys.extend(extra_skip_keys)
|
skip_keys.extend(extra_skip_keys)
|
||||||
|
skip_keys_lower = [key.lower() for key in skip_keys]
|
||||||
|
|
||||||
|
|
||||||
markdown_text = "<details> <summary><strong>🛠️ PR-Agent Configurations:</strong></summary> \n\n"
|
markdown_text = "<details> <summary><strong>🛠️ PR-Agent Configurations:</strong></summary> \n\n"
|
||||||
markdown_text += f"\n\n```yaml\n\n"
|
markdown_text += f"\n\n```yaml\n\n"
|
||||||
@ -52,7 +55,7 @@ class PRConfig:
|
|||||||
markdown_text += "\n\n"
|
markdown_text += "\n\n"
|
||||||
markdown_text += f"==================== {header} ===================="
|
markdown_text += f"==================== {header} ===================="
|
||||||
for key, value in configs.items():
|
for key, value in configs.items():
|
||||||
if key in skip_keys:
|
if key.lower() in skip_keys_lower:
|
||||||
continue
|
continue
|
||||||
markdown_text += f"\n{header.lower()}.{key.lower()} = {repr(value) if isinstance(value, str) else value}"
|
markdown_text += f"\n{header.lower()}.{key.lower()} = {repr(value) if isinstance(value, str) else value}"
|
||||||
markdown_text += " "
|
markdown_text += " "
|
||||||
|
@ -71,7 +71,8 @@ class PRDescription:
|
|||||||
"custom_labels_class": "", # will be filled if necessary in 'set_custom_labels' function
|
"custom_labels_class": "", # will be filled if necessary in 'set_custom_labels' function
|
||||||
"enable_semantic_files_types": get_settings().pr_description.enable_semantic_files_types,
|
"enable_semantic_files_types": get_settings().pr_description.enable_semantic_files_types,
|
||||||
"related_tickets": "",
|
"related_tickets": "",
|
||||||
"include_file_summary_changes": len(self.git_provider.get_diff_files()) <= self.COLLAPSIBLE_FILE_LIST_THRESHOLD
|
"include_file_summary_changes": len(self.git_provider.get_diff_files()) <= self.COLLAPSIBLE_FILE_LIST_THRESHOLD,
|
||||||
|
'duplicate_prompt_examples': get_settings().config.get('duplicate_prompt_examples', False),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.user_description = self.git_provider.get_user_description()
|
self.user_description = self.git_provider.get_user_description()
|
||||||
@ -470,7 +471,16 @@ class PRDescription:
|
|||||||
|
|
||||||
def _prepare_pr_answer_with_markers(self) -> Tuple[str, str, str, List[dict]]:
|
def _prepare_pr_answer_with_markers(self) -> Tuple[str, str, str, List[dict]]:
|
||||||
get_logger().info(f"Using description marker replacements {self.pr_id}")
|
get_logger().info(f"Using description marker replacements {self.pr_id}")
|
||||||
|
|
||||||
|
# Remove the 'PR Title' key from the dictionary
|
||||||
|
ai_title = self.data.pop('title', self.vars["title"])
|
||||||
|
if (not get_settings().pr_description.generate_ai_title):
|
||||||
|
# Assign the original PR title to the 'title' variable
|
||||||
title = self.vars["title"]
|
title = self.vars["title"]
|
||||||
|
else:
|
||||||
|
# Assign the value of the 'PR Title' key to 'title' variable
|
||||||
|
title = ai_title
|
||||||
|
|
||||||
body = self.user_description
|
body = self.user_description
|
||||||
if get_settings().pr_description.include_generated_by_header:
|
if get_settings().pr_description.include_generated_by_header:
|
||||||
ai_header = f"### 🤖 Generated by PR Agent at {self.git_provider.last_commit_id.sha}\n\n"
|
ai_header = f"### 🤖 Generated by PR Agent at {self.git_provider.last_commit_id.sha}\n\n"
|
||||||
@ -479,6 +489,10 @@ class PRDescription:
|
|||||||
|
|
||||||
ai_type = self.data.get('type')
|
ai_type = self.data.get('type')
|
||||||
if ai_type and not re.search(r'<!--\s*pr_agent:type\s*-->', body):
|
if ai_type and not re.search(r'<!--\s*pr_agent:type\s*-->', body):
|
||||||
|
if isinstance(ai_type, list):
|
||||||
|
pr_types = [f"{ai_header}{t}" for t in ai_type]
|
||||||
|
pr_type = ','.join(pr_types)
|
||||||
|
else:
|
||||||
pr_type = f"{ai_header}{ai_type}"
|
pr_type = f"{ai_header}{ai_type}"
|
||||||
body = body.replace('pr_agent:type', pr_type)
|
body = body.replace('pr_agent:type', pr_type)
|
||||||
|
|
||||||
@ -556,6 +570,11 @@ class PRDescription:
|
|||||||
elif 'pr_files' in key.lower() and get_settings().pr_description.enable_semantic_files_types:
|
elif 'pr_files' in key.lower() and get_settings().pr_description.enable_semantic_files_types:
|
||||||
changes_walkthrough, pr_file_changes = self.process_pr_files_prediction(changes_walkthrough, value)
|
changes_walkthrough, pr_file_changes = self.process_pr_files_prediction(changes_walkthrough, value)
|
||||||
changes_walkthrough = f"{PRDescriptionHeader.CHANGES_WALKTHROUGH.value}\n{changes_walkthrough}"
|
changes_walkthrough = f"{PRDescriptionHeader.CHANGES_WALKTHROUGH.value}\n{changes_walkthrough}"
|
||||||
|
elif key.lower().strip() == 'description':
|
||||||
|
if isinstance(value, list):
|
||||||
|
value = ', '.join(v.rstrip() for v in value)
|
||||||
|
value = value.replace('\n-', '\n\n-').strip() # makes the bullet points more readable by adding double space
|
||||||
|
pr_body += f"{value}\n"
|
||||||
else:
|
else:
|
||||||
# if the value is a list, join its items by comma
|
# if the value is a list, join its items by comma
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
|
@ -232,7 +232,7 @@ class PRHelpMessage:
|
|||||||
for i in range(len(tool_names)):
|
for i in range(len(tool_names)):
|
||||||
pr_comment += f"\n<tr><td align='left'>\n\n<strong>{tool_names[i]}</strong></td>\n<td>{descriptions[i]}</td>\n<td>\n\n{checkbox_list[i]}\n</td></tr>"
|
pr_comment += f"\n<tr><td align='left'>\n\n<strong>{tool_names[i]}</strong></td>\n<td>{descriptions[i]}</td>\n<td>\n\n{checkbox_list[i]}\n</td></tr>"
|
||||||
pr_comment += "</table>\n\n"
|
pr_comment += "</table>\n\n"
|
||||||
pr_comment += f"""\n\n(1) Note that each tool be [triggered automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
|
pr_comment += f"""\n\n(1) Note that each tool can be [triggered automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
|
||||||
pr_comment += f"""\n\n(2) Tools marked with [*] require additional parameters to be passed. For example, to invoke the `/ask` tool, you need to comment on a PR: `/ask "<question content>"`. See the relevant documentation for each tool for more details."""
|
pr_comment += f"""\n\n(2) Tools marked with [*] require additional parameters to be passed. For example, to invoke the `/ask` tool, you need to comment on a PR: `/ask "<question content>"`. See the relevant documentation for each tool for more details."""
|
||||||
elif isinstance(self.git_provider, BitbucketServerProvider):
|
elif isinstance(self.git_provider, BitbucketServerProvider):
|
||||||
# only support basic commands in BBDC
|
# only support basic commands in BBDC
|
||||||
@ -242,7 +242,7 @@ class PRHelpMessage:
|
|||||||
for i in range(len(tool_names)):
|
for i in range(len(tool_names)):
|
||||||
pr_comment += f"\n<tr><td align='left'>\n\n<strong>{tool_names[i]}</strong></td><td>{commands[i]}</td><td>{descriptions[i]}</td></tr>"
|
pr_comment += f"\n<tr><td align='left'>\n\n<strong>{tool_names[i]}</strong></td><td>{commands[i]}</td><td>{descriptions[i]}</td></tr>"
|
||||||
pr_comment += "</table>\n\n"
|
pr_comment += "</table>\n\n"
|
||||||
pr_comment += f"""\n\nNote that each tool be [invoked automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
|
pr_comment += f"""\n\nNote that each tool can be [invoked automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
|
||||||
|
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
self.git_provider.publish_comment(pr_comment)
|
self.git_provider.publish_comment(pr_comment)
|
||||||
|
@ -94,6 +94,7 @@ class PRReviewer:
|
|||||||
"enable_custom_labels": get_settings().config.enable_custom_labels,
|
"enable_custom_labels": get_settings().config.enable_custom_labels,
|
||||||
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False),
|
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False),
|
||||||
"related_tickets": get_settings().get('related_tickets', []),
|
"related_tickets": get_settings().get('related_tickets', []),
|
||||||
|
'duplicate_prompt_examples': get_settings().config.get('duplicate_prompt_examples', False),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.token_handler = TokenHandler(
|
self.token_handler = TokenHandler(
|
||||||
|
@ -24,6 +24,13 @@ class TestBitbucketServerProvider:
|
|||||||
assert repo_slug == "my-repo"
|
assert repo_slug == "my-repo"
|
||||||
assert pr_number == 1
|
assert pr_number == 1
|
||||||
|
|
||||||
|
def test_parse_pr_url_with_users(self):
|
||||||
|
url = "https://bitbucket.company-server.url/users/username/repos/my-repo/pull-requests/1"
|
||||||
|
workspace_slug, repo_slug, pr_number = BitbucketServerProvider._parse_pr_url(url)
|
||||||
|
assert workspace_slug == "~username"
|
||||||
|
assert repo_slug == "my-repo"
|
||||||
|
assert pr_number == 1
|
||||||
|
|
||||||
def mock_get_content_of_file(self, project_key, repository_slug, filename, at=None, markup=None):
|
def mock_get_content_of_file(self, project_key, repository_slug, filename, at=None, markup=None):
|
||||||
content_map = {
|
content_map = {
|
||||||
'9c1cffdd9f276074bfb6fb3b70fbee62d298b058': 'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile\n',
|
'9c1cffdd9f276074bfb6fb3b70fbee62d298b058': 'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile\n',
|
||||||
@ -244,7 +251,7 @@ class TestBitbucketServerProvider:
|
|||||||
FilePatchInfo(
|
FilePatchInfo(
|
||||||
'file\nwith\nmultiple\nlines\nto\nemulate\na\nreal\nfile',
|
'file\nwith\nmultiple\nlines\nto\nemulate\na\nreal\nfile',
|
||||||
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
|
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
|
||||||
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n-multiple\n+readme\n+without\n+some\n lines\n to\n-emulate\n+simulate\n a\n real\n file',
|
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n-multiple\n+readme\n+without\n+some\n lines\n to\n-emulate\n+simulate\n a\n real\n file\n',
|
||||||
'Readme.md',
|
'Readme.md',
|
||||||
edit_type=EDIT_TYPE.MODIFIED,
|
edit_type=EDIT_TYPE.MODIFIED,
|
||||||
)
|
)
|
||||||
@ -266,7 +273,7 @@ class TestBitbucketServerProvider:
|
|||||||
FilePatchInfo(
|
FilePatchInfo(
|
||||||
'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile',
|
'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile',
|
||||||
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
|
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
|
||||||
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n+readme\n+without\n some\n lines\n to\n-emulate\n+simulate\n a\n real\n file',
|
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n+readme\n+without\n some\n lines\n to\n-emulate\n+simulate\n a\n real\n file\n',
|
||||||
'Readme.md',
|
'Readme.md',
|
||||||
edit_type=EDIT_TYPE.MODIFIED,
|
edit_type=EDIT_TYPE.MODIFIED,
|
||||||
)
|
)
|
||||||
@ -288,7 +295,7 @@ class TestBitbucketServerProvider:
|
|||||||
FilePatchInfo(
|
FilePatchInfo(
|
||||||
'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile',
|
'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile',
|
||||||
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
|
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
|
||||||
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n+readme\n+without\n some\n lines\n to\n-emulate\n+simulate\n a\n real\n file',
|
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n+readme\n+without\n some\n lines\n to\n-emulate\n+simulate\n a\n real\n file\n',
|
||||||
'Readme.md',
|
'Readme.md',
|
||||||
edit_type=EDIT_TYPE.MODIFIED,
|
edit_type=EDIT_TYPE.MODIFIED,
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@ import pytest
|
|||||||
from pr_agent.algo.git_patch_processing import extend_patch
|
from pr_agent.algo.git_patch_processing import extend_patch
|
||||||
from pr_agent.algo.pr_processing import pr_generate_extended_diff
|
from pr_agent.algo.pr_processing import pr_generate_extended_diff
|
||||||
from pr_agent.algo.token_handler import TokenHandler
|
from pr_agent.algo.token_handler import TokenHandler
|
||||||
|
from pr_agent.algo.utils import load_large_diff
|
||||||
from pr_agent.config_loader import get_settings
|
from pr_agent.config_loader import get_settings
|
||||||
|
|
||||||
|
|
||||||
@ -145,8 +146,8 @@ class TestExtendedPatchMoreLines:
|
|||||||
# Check that with no extra lines, the patches are the same as the original patches
|
# Check that with no extra lines, the patches are the same as the original patches
|
||||||
p0 = patches_extended_no_extra_lines[0].strip()
|
p0 = patches_extended_no_extra_lines[0].strip()
|
||||||
p1 = patches_extended_no_extra_lines[1].strip()
|
p1 = patches_extended_no_extra_lines[1].strip()
|
||||||
assert p0 == '## file1\n' + pr_languages[0]['files'][0].patch.strip()
|
assert p0 == "## File: 'file1'\n" + pr_languages[0]['files'][0].patch.strip()
|
||||||
assert p1 == '## file2\n' + pr_languages[0]['files'][1].patch.strip()
|
assert p1 == "## File: 'file2'\n" + pr_languages[0]['files'][1].patch.strip()
|
||||||
|
|
||||||
patches_extended_with_extra_lines, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
|
patches_extended_with_extra_lines, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
|
||||||
pr_languages, token_handler, add_line_numbers_to_hunks=False,
|
pr_languages, token_handler, add_line_numbers_to_hunks=False,
|
||||||
@ -154,5 +155,37 @@ class TestExtendedPatchMoreLines:
|
|||||||
patch_extra_lines_after=1
|
patch_extra_lines_after=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
p0_extended = patches_extended_with_extra_lines[0].strip()
|
p0_extended = patches_extended_with_extra_lines[0].strip()
|
||||||
assert p0_extended == '## file1\n\n@@ -3,8 +3,8 @@ \n line0\n line1\n-original content\n+modified content\n line2\n line3\n line4\n line5\n line6'
|
assert p0_extended == "## File: 'file1'\n\n@@ -3,8 +3,8 @@ \n line0\n line1\n-original content\n+modified content\n line2\n line3\n line4\n line5\n line6"
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoadLargeDiff:
|
||||||
|
def test_no_newline(self):
|
||||||
|
patch = load_large_diff("test.py",
|
||||||
|
"""\
|
||||||
|
old content 1
|
||||||
|
some new content
|
||||||
|
another line
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
old content 1
|
||||||
|
old content 2""")
|
||||||
|
|
||||||
|
patch_expected="""\
|
||||||
|
---
|
||||||
|
+++
|
||||||
|
@@ -1,3 +1,3 @@
|
||||||
|
-
|
||||||
|
old content 1
|
||||||
|
- old content 2
|
||||||
|
+ some new content
|
||||||
|
+ another line
|
||||||
|
"""
|
||||||
|
assert patch == patch_expected
|
||||||
|
|
||||||
|
def test_empty_inputs(self):
|
||||||
|
assert load_large_diff("test.py", "", "") == ""
|
||||||
|
assert load_large_diff("test.py", None, None) == ""
|
||||||
|
assert (load_large_diff("test.py", "content\n", "") ==
|
||||||
|
'--- \n+++ \n@@ -1 +1 @@\n-\n+content\n')
|
Reference in New Issue
Block a user