Compare commits

..

20 Commits

Author SHA1 Message Date
412ae2e16c docs: simplify repo statistics metrics description 2025-05-19 08:20:35 +03:00
809efee3fe docs: add Repo Statistics tool documentation and update feature tables 2025-05-18 15:31:07 +03:00
Tal
65d9269bf2 Merge pull request #1790 from qodo-ai/tr/v_029
Tr/v029
2025-05-17 21:08:41 +03:00
411245155f docs: update README and Dockerfile with v0.29 release information 2025-05-17 21:05:22 +03:00
14fb98aa77 docs: update README and recent updates page with v0.29 release information 2025-05-17 21:03:29 +03:00
b4ae07bf82 Remove test case for YAML parsing that removes last line 2025-05-17 20:58:32 +03:00
db5138dc42 Improve YAML parsing with additional fallback strategies for AI predictions 2025-05-17 20:38:05 +03:00
Tal
9a9feb47a6 Merge pull request #1786 from qodo-ai/pr-1736
Pr 1736
2025-05-17 15:29:23 +03:00
52ce74a31a Remove debug print statements from repository filtering tests 2025-05-16 17:25:10 +03:00
f47da75e6f Remove debug print statement from should_process_pr_logic function 2025-05-16 17:23:27 +03:00
42557feb97 Enhance repository filtering with regex pattern matching for ignore_repositories 2025-05-16 17:20:54 +03:00
Tal
c15fb16528 Merge pull request #1779 from dnnspaul/main
Enable usage of OpenAI like APIs
2025-05-16 16:59:18 +03:00
Tal
d268db5f0d Merge pull request #1778 from smartandhandsome/main
Cleanup: Remove Unused import and Fix Parameter Typo
2025-05-16 16:54:55 +03:00
Tal
ec626f0193 Merge pull request #1785 from qodo-ai/tr/gemini-2.5-pro-preview-05-06
Add Gemini-2.5-pro-preview-05-06 model and update litellm dependency
2025-05-16 16:53:50 +03:00
9974015682 Add Gemini-2.5-pro-preview-05-06 model and update litellm dependency 2025-05-16 16:32:45 +03:00
250870a3da enable usage of openai like apis 2025-05-15 16:05:05 +02:00
a3c9fbbf2c revert try except 2025-05-15 19:40:40 +09:00
c79b655864 Fix typo in method parameter name 2025-05-15 18:42:08 +09:00
e55fd64bda Remove unnecessary nested try-except block for cleaner code. Streamlined the import statement to remove an unused reference to get_git_provider. 2025-05-15 18:41:39 +09:00
d606672801 Add ignore_repositories config for PR filtering
What Changed?
* Added support to ignore PRs/MRs from specific repositories in GitHub, Bitbucket, and GitLab webhook logic
* Updated configuration.toml to include ignore_repositories option
* Added unit tests for ignore_repositories across all supported providers
2025-04-30 14:09:40 -07:00
21 changed files with 261 additions and 255 deletions

View File

@ -27,17 +27,6 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing
</a>
</div>
[//]: # (### [Documentation]&#40;https://qodo-merge-docs.qodo.ai/&#41;)
[//]: # ()
[//]: # (- See the [Installation Guide]&#40;https://qodo-merge-docs.qodo.ai/installation/&#41; for instructions on installing PR-Agent on different platforms.)
[//]: # ()
[//]: # (- See the [Usage Guide]&#40;https://qodo-merge-docs.qodo.ai/usage-guide/&#41; for instructions on running PR-Agent tools via different interfaces, such as CLI, PR Comments, or by automatically triggering them when a new PR is opened.)
[//]: # ()
[//]: # (- See the [Tools Guide]&#40;https://qodo-merge-docs.qodo.ai/tools/&#41; for a detailed description of the different tools, and the available configurations for each tool.)
## Table of Contents
- [News and Updates](#news-and-updates)
@ -53,6 +42,12 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing
## News and Updates
## May 17, 2025
- v0.29 was [released](https://github.com/qodo-ai/pr-agent/releases)
- `Qodo Merge Pull Request Benchmark` was [released](https://qodo-merge-docs.qodo.ai/pr_benchmark/). This benchmark evaluates and compares the performance of LLMs in analyzing pull request code.
- `Recent Updates and Future Roadmap` page was added to the [Qodo Merge Docs](https://qodo-merge-docs.qodo.ai/recent_updates/)
## Apr 30, 2025
A new feature is now available in the `/improve` tool for Qodo Merge 💎 - Chat on code suggestions.
@ -69,22 +64,6 @@ New tool for Qodo Merge 💎 - `/scan_repo_discussions`.
Read more about it [here](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/).
## Apr 14, 2025
GPT-4.1 is out. And it's quite good on coding tasks...
https://openai.com/index/gpt-4-1/
<img width="512" alt="image" src="https://github.com/user-attachments/assets/a8f4c648-a058-4bdc-9825-2a4bb71a23e5" />
## March 28, 2025
A new version, v0.28, was released. See release notes [here](https://github.com/qodo-ai/pr-agent/releases/tag/v0.28).
This version includes a new tool, [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/), which can answer free-text questions based on a documentation folder.
`/help_docs` is now being used to provide immediate automatic feedback to any user who [opens an issue](https://github.com/qodo-ai/pr-agent/issues/1608#issue-2897328825) on PR-Agent's open-source project
## Overview
<div style="text-align:left;">
@ -113,6 +92,7 @@ Supported commands per platform:
| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | |
| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | |
| | [Repo Statistics](https://qodo-merge-docs.qodo.ai/tools/repo_statistics/) 💎 | ✅ | | | |
| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | |
| | | | | | |
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
@ -131,7 +111,7 @@ Supported commands per platform:
| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | |
| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | |
| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ |
| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | |
| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | |
- 💎 means this feature is available only in [Qodo Merge](https://www.qodo.ai/pricing/)
[//]: # (- Support for additional git providers is described in [here]&#40;./docs/Full_environments.md&#41;)

View File

@ -1,206 +0,0 @@
<b>Pattern 1: Always wrap API calls and potentially error-prone operations with try-except blocks to ensure proper error handling and prevent application crashes.</b>
Example code before:
```
response = client.messages.count_tokens(
model="claude-3-7-sonnet-20250219",
system="system",
messages=[{
"role": "user",
"content": patch
}],
)
return response.input_tokens
```
Example code after:
```
try:
response = client.messages.count_tokens(
model="claude-3-7-sonnet-20250219",
system="system",
messages=[{
"role": "user",
"content": patch
}],
)
return response.input_tokens
except Exception as api_error:
get_logger().error(f"Error in API call: {api_error}")
return fallback_value
```
<details><summary>Examples for relevant past discussions:</summary>
- https://github.com/qodo-ai/pr-agent/pull/1644#discussion_r2068039198
- https://github.com/qodo-ai/pr-agent/pull/1644#discussion_r2013912636
- https://github.com/qodo-ai/pr-agent/pull/1263#discussion_r1782129216
</details>
___
<b>Pattern 2: Replace print statements with proper logging using get_logger() to ensure consistent error tracking and monitoring across the application.</b>
Example code before:
```
print(f"Failed to fetch sub-issues. Error: {e}")
```
Example code after:
```
get_logger().error(f"Failed to fetch sub-issues", artifact={"error": e})
```
<details><summary>Examples for relevant past discussions:</summary>
- https://github.com/qodo-ai/pr-agent/pull/1529#discussion_r1958684550
- https://github.com/qodo-ai/pr-agent/pull/1529#discussion_r1958686068
- https://github.com/qodo-ai/pr-agent/pull/1529#discussion_r1964107962
- https://github.com/qodo-ai/pr-agent/pull/1634#discussion_r2007976915
</details>
___
<b>Pattern 3: Validate input parameters before processing to prevent runtime errors, especially when working with optional or user-provided data.</b>
Example code before:
```
if not (comment_id or (file_path and line_number)):
return
```
Example code after:
```
# Return if any required parameter is missing
if not all([comment_id, file_path, line_number]):
get_logger().info("Missing required parameters for conversation history")
return
```
<details><summary>Examples for relevant past discussions:</summary>
- https://github.com/qodo-ai/pr-agent/pull/1687#discussion_r2036939244
- https://github.com/qodo-ai/pr-agent/pull/1290#discussion_r1798939921
- https://github.com/qodo-ai/pr-agent/pull/1529#discussion_r1958698147
</details>
___
<b>Pattern 4: Use defensive programming when handling external API responses or data structures to account for unexpected formats or missing fields.</b>
Example code before:
```
model = get_settings().config.model.lower()
if force_accurate and 'claude' in model and get_settings(use_context=False).get('anthropic.key'):
return self.calc_claude_tokens(patch)
```
Example code after:
```
# Ensure model is a string and not None before operations
if model is None:
get_logger().warning("Model is None, cannot determine model type accurately")
return encoder_estimate
if not isinstance(model, str):
get_logger().warning(f"Model is not a string type: {type(model)}")
return encoder_estimate
if force_accurate and 'claude' in model.lower() and get_settings(use_context=False).get('anthropic.key'):
return self.calc_claude_tokens(patch)
```
<details><summary>Examples for relevant past discussions:</summary>
- https://github.com/qodo-ai/pr-agent/pull/1644#discussion_r2032621065
- https://github.com/qodo-ai/pr-agent/pull/1391#discussion_r1879875489
- https://github.com/qodo-ai/pr-agent/pull/1391#discussion_r1879875496
</details>
___
<b>Pattern 5: Optimize code to reduce unnecessary API calls and improve performance, especially when working with external services or databases.</b>
Example code before:
```
# Get the original comment to find its location
comment = self.pr.get_comment(comment_id)
if not comment:
return []
# Get all comments
all_comments = list(self.pr.get_comments())
```
Example code after:
```
# Fetch all comments with a single API call
all_comments = list(self.pr.get_comments())
# Find the target comment by ID
target_comment = next((c for c in all_comments if c.id == comment_id), None)
if not target_comment:
return []
```
<details><summary>Examples for relevant past discussions:</summary>
- https://github.com/qodo-ai/pr-agent/pull/1687#discussion_r2051644060
- https://github.com/qodo-ai/pr-agent/pull/1555#discussion_r1963228113
- https://github.com/qodo-ai/pr-agent/pull/1687#discussion_r2051640437
</details>
___
<b>Pattern 6: Ensure proper error logging with context by using structured logging with artifacts parameter rather than string concatenation.</b>
Example code before:
```
get_logger().error(f"Failed to get log context: {e}")
```
Example code after:
```
get_logger().error("Failed to get log context", artifact={"error": e})
```
<details><summary>Examples for relevant past discussions:</summary>
- https://github.com/qodo-ai/pr-agent/pull/1634#discussion_r2007976915
- https://github.com/qodo-ai/pr-agent/pull/1529#discussion_r1964110734
- https://github.com/qodo-ai/pr-agent/pull/1529#discussion_r2055606721
</details>
___
<b>Pattern 7: Add clear, descriptive comments for complex logic or non-obvious code to improve maintainability and help future developers understand the code's purpose.</b>
Example code before:
```
if not isinstance(issue, dict):
continue
```
Example code after:
```
# Skip empty issues or non-dictionary items to ensure valid data structure
if not isinstance(issue, dict):
continue
```
<details><summary>Examples for relevant past discussions:</summary>
- https://github.com/qodo-ai/pr-agent/pull/1262#discussion_r1782097204
- https://github.com/qodo-ai/pr-agent/pull/1583#discussion_r1971790979
- https://github.com/qodo-ai/pr-agent/pull/1687#discussion_r2051641090
</details>
___

View File

@ -48,6 +48,7 @@ PR-Agent and Qodo Merge offers extensive pull request functionalities across var
| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | |
| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | |
| | [Repo Statistics](https://qodo-merge-docs.qodo.ai/tools/repo_statistics/) 💎 | ✅ | | | |
| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | |
| | | | | | |
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |

View File

@ -7,11 +7,12 @@ This page summarizes recent enhancements to Qodo Merge (last three months).
It also outlines our development roadmap for the upcoming three months. Please note that the roadmap is subject to change, and features may be adjusted, added, or reprioritized.
=== "Recent Updates"
- **Qodo Merge Pull Request Benchmark** - evaluating the performance of LLMs in analyzing pull request code ([Learn more](https://qodo-merge-docs.qodo.ai/pr_benchmark/))
- **Chat on Suggestions**: Users can now chat with Qodo Merge code suggestions ([Learn more](https://qodo-merge-docs.qodo.ai/tools/improve/#chat-on-code-suggestions))
- **Scan Repo Discussions Tool**: A new tool that analyzes past code discussions to generate a `best_practices.md` file, distilling key insights and recommendations. ([Learn more](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/))
- **Enhanced Models**: Qodo Merge now defaults to a combination of top models (Claude Sonnet 3.7 and Gemini 2.5 Pro) and incorporates dedicated code validation logic for improved results. ([Details 1](https://qodo-merge-docs.qodo.ai/usage-guide/qodo_merge_models/), [Details 2](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/))
- **Chrome Extension Update**: Qodo Merge Chrome extension now supports single-tenant users. ([Learn more](https://qodo-merge-docs.qodo.ai/chrome-extension/options/#configuration-options/))
- **Help Docs Tool**: The help_docs tool can answer free-text questions based on any git documentation folder. ([Learn more](https://qodo-merge-docs.qodo.ai/tools/help_docs/))
- **Repo Statistics Tool**: A new tool that provides repository statistics on time to merge and time to first comment. ([Learn more](https://qodo-merge-docs.qodo.ai/tools/repo_statistics/))
=== "Future Roadmap"
- **Smart Update**: Upon PR updates, Qodo Merge will offer tailored code suggestions, addressing both the entire PR and the specific incremental changes since the last feedback.

View File

@ -3,7 +3,7 @@
Here is a list of Qodo Merge tools, each with a dedicated page that explains how to use it:
| Tool | Description |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| **[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 |
| **[Code Suggestions (`/improve`](./improve.md))** | Code suggestions for improving the PR |
@ -20,5 +20,6 @@ Here is a list of Qodo Merge tools, each with a dedicated page that explains how
| **💎 [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 |
| **💎 [Scan Repo Discussions (`/scan_repo_discussions`](./scan_repo_discussions.md))** | Generates `best_practices.md` file based on previous discussions in the repository |
| **💎 [Repo Statistics (`/repo_statistics`](./repo_statistics.md))** | Provides repository statistics on time to merge and time to first comment |
Note that the tools marked with 💎 are available only for Qodo Merge users.

View File

@ -0,0 +1,44 @@
`Platforms supported: GitHub`
## Overview
The `repo_statistics` tool analyzes statistics from merged pull requests over the past 12 months prior to Qodo Merge installation.
It calculates key metrics that help teams establish a baseline of their PR workflow efficiency.
!!! note "Active repositories are needed"
The tool is designed to work with real-life repositories, as it relies on actual discussions to generate meaningful insights.
At least 30 merged PRs are required to generate meaningful statistical data.
### Metrics Analyzed
- **Time to merge:** The median and average time it takes for PRs to be merged after opening
- **Time to first comment:** The median and average time it takes to get the first comment on a PR
### Usage
The tool can be invoked manually by commenting on any PR:
```
/repo_statistics
```
In response, the bot will comment with the statistical data.
Note that the scan can take several minutes to complete, since up to 100 PRs are scanned.
!!! info "Automatic trigger"
Upon adding the Qodo Merge bot to a repository, the tool will automatically scan the last 365 days of PRs and send them to MixPanel, if enabled.
## Example usage
![repo statistics comment](https://codium.ai/images/pr_agent/repo_statistics_comment.png){width=640}
MixPanel optional presentation:
![repo statistics mixpanel](https://codium.ai/images/pr_agent/repo_statistics_mixpanel.png){width=640}
### Configuration options
- Use `/repo_statistics --repo_statistics.days_back=X` to specify the number of days back to scan for discussions. The default is 365 days.
- Use `/repo_statistics --repo_statistics.minimal_number_of_prs=X` to specify the minimum number of merged PRs needed to generate the statistics. The default is 30 PRs.

View File

@ -164,6 +164,7 @@ Qodo Merge allows you to automatically ignore certain PRs based on various crite
- PRs with specific titles (using regex matching)
- PRs between specific branches (using regex matching)
- PRs from specific repositories (using regex matching)
- PRs not from specific folders
- PRs containing specific labels
- PRs opened by specific users
@ -172,7 +173,7 @@ Qodo Merge allows you to automatically ignore certain PRs based on various crite
To ignore PRs with a specific title such as "[Bump]: ...", you can add the following to your `configuration.toml` file:
```
```toml
[config]
ignore_pr_title = ["\\[Bump\\]"]
```
@ -183,7 +184,7 @@ Where the `ignore_pr_title` is a list of regex patterns to match the PR title yo
To ignore PRs from specific source or target branches, you can add the following to your `configuration.toml` file:
```
```toml
[config]
ignore_pr_source_branches = ['develop', 'main', 'master', 'stage']
ignore_pr_target_branches = ["qa"]
@ -192,6 +193,18 @@ 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.
They are not mutually exclusive, you can use them together or separately.
### Ignoring PRs from specific repositories
To ignore PRs from specific repositories, you can add the following to your `configuration.toml` file:
```toml
[config]
ignore_repositories = ["my-org/my-repo1", "my-org/my-repo2"]
```
Where the `ignore_repositories` is a list of regex patterns to match the repositories you want to ignore. This is useful when you have multiple repositories and want to exclude certain ones from analysis.
### Ignoring PRs not from specific folders
To allow only specific folders (often needed in large monorepos), set:

View File

@ -16,6 +16,23 @@ You can give parameters via a configuration file, or from environment variables.
See [litellm documentation](https://litellm.vercel.app/docs/proxy/quick_start#supported-llms) for the environment variables needed per model, as they may vary and change over time. Our documentation per-model may not always be up-to-date with the latest changes.
Failing to set the needed keys of a specific model will usually result in litellm not identifying the model type, and failing to utilize it.
### OpenAI like API
To use an OpenAI like API, set the following in your `.secrets.toml` file:
```toml
[openai]
api_base = "https://api.openai.com/v1"
api_key = "sk-..."
```
or use the environment variables (make sure to use double underscores `__`):
```bash
OPENAI__API_BASE=https://api.openai.com/v1
OPENAI__KEY=sk-...
```
### Azure
To use Azure, set in your `.secrets.toml` (working from CLI), or in the GitHub `Settings > Secrets and variables` (working from GitHub App or GitHub Action):

View File

@ -41,6 +41,7 @@ nav:
- 💎 Similar Code: 'tools/similar_code.md'
- 💎 Implement: 'tools/implement.md'
- 💎 Scan Repo Discussions: 'tools/scan_repo_discussions.md'
- 💎 Repo Statistics: 'tools/repo_statistics.md'
- Core Abilities:
- 'core-abilities/index.md'
- Auto best practices: 'core-abilities/auto_best_practices.md'

View File

@ -58,6 +58,7 @@ MAX_TOKENS = {
'vertex_ai/claude-3-7-sonnet@20250219': 200000,
'vertex_ai/gemini-1.5-pro': 1048576,
'vertex_ai/gemini-2.5-pro-preview-03-25': 1048576,
'vertex_ai/gemini-2.5-pro-preview-05-06': 1048576,
'vertex_ai/gemini-1.5-flash': 1048576,
'vertex_ai/gemini-2.0-flash': 1048576,
'vertex_ai/gemini-2.5-flash-preview-04-17': 1048576,
@ -66,6 +67,7 @@ MAX_TOKENS = {
'gemini/gemini-1.5-flash': 1048576,
'gemini/gemini-2.0-flash': 1048576,
'gemini/gemini-2.5-pro-preview-03-25': 1048576,
'gemini/gemini-2.5-pro-preview-05-06': 1048576,
'codechat-bison': 6144,
'codechat-bison-32k': 32000,
'anthropic.claude-instant-v1': 100000,

View File

@ -59,6 +59,7 @@ class LiteLLMAIHandler(BaseAiHandler):
litellm.api_version = get_settings().openai.api_version
if get_settings().get("OPENAI.API_BASE", None):
litellm.api_base = get_settings().openai.api_base
self.api_base = get_settings().openai.api_base
if get_settings().get("ANTHROPIC.KEY", None):
litellm.anthropic_key = get_settings().anthropic.key
if get_settings().get("COHERE.KEY", None):

View File

@ -731,8 +731,9 @@ def try_fix_yaml(response_text: str,
response_text_original="") -> dict:
response_text_lines = response_text.split('\n')
keys_yaml = ['relevant line:', 'suggestion content:', 'relevant file:', 'existing code:', 'improved code:']
keys_yaml = ['relevant line:', 'suggestion content:', 'relevant file:', 'existing code:', 'improved code:', 'label:']
keys_yaml = keys_yaml + keys_fix_yaml
# first fallback - try to convert 'relevant line: ...' to relevant line: |-\n ...'
response_text_lines_copy = response_text_lines.copy()
for i in range(0, len(response_text_lines_copy)):
@ -747,8 +748,29 @@ def try_fix_yaml(response_text: str,
except:
pass
# second fallback - try to extract only range from first ```yaml to ````
snippet_pattern = r'```(yaml)?[\s\S]*?```'
# 1.5 fallback - try to convert '|' to '|2'. Will solve cases of indent decreasing during the code
response_text_copy = copy.deepcopy(response_text)
response_text_copy = response_text_copy.replace('|\n', '|2\n')
try:
data = yaml.safe_load(response_text_copy)
get_logger().info(f"Successfully parsed AI prediction after replacing | with |2")
return data
except:
# if it fails, we can try to add spaces to the lines that are not indented properly, and contain '}'.
response_text_lines_copy = response_text_copy.split('\n')
for i in range(0, len(response_text_lines_copy)):
initial_space = len(response_text_lines_copy[i]) - len(response_text_lines_copy[i].lstrip())
if initial_space == 2 and '|2' not in response_text_lines_copy[i] and '}' in response_text_lines_copy[i]:
response_text_lines_copy[i] = ' ' + response_text_lines_copy[i].lstrip()
try:
data = yaml.safe_load('\n'.join(response_text_lines_copy))
get_logger().info(f"Successfully parsed AI prediction after replacing | with |2 and adding spaces")
return data
except:
pass
# second fallback - try to extract only range from first ```yaml to the last ```
snippet_pattern = r'```yaml([\s\S]*?)```(?=\s*$|")'
snippet = re.search(snippet_pattern, '\n'.join(response_text_lines_copy))
if not snippet:
snippet = re.search(snippet_pattern, response_text_original) # before we removed the "```"
@ -803,16 +825,47 @@ def try_fix_yaml(response_text: str,
except:
pass
# sixth fallback - try to remove last lines
for i in range(1, len(response_text_lines)):
response_text_lines_tmp = '\n'.join(response_text_lines[:-i])
# sixth fallback - replace tabs with spaces
if '\t' in response_text:
response_text_copy = copy.deepcopy(response_text)
response_text_copy = response_text_copy.replace('\t', ' ')
try:
data = yaml.safe_load(response_text_lines_tmp)
get_logger().info(f"Successfully parsed AI prediction after removing {i} lines")
data = yaml.safe_load(response_text_copy)
get_logger().info(f"Successfully parsed AI prediction after replacing tabs with spaces")
return data
except:
pass
# seventh fallback - add indent for sections of code blocks
response_text_copy = copy.deepcopy(response_text)
response_text_copy_lines = response_text_copy.split('\n')
start_line = -1
for i, line in enumerate(response_text_copy_lines):
if 'existing_code:' in line or 'improved_code:' in line:
start_line = i
elif line.endswith(': |') or line.endswith(': |-') or line.endswith(': |2') or line.endswith(':'):
start_line = -1
elif start_line != -1:
response_text_copy_lines[i] = ' ' + line
response_text_copy = '\n'.join(response_text_copy_lines)
try:
data = yaml.safe_load(response_text_copy)
get_logger().info(f"Successfully parsed AI prediction after adding indent for sections of code blocks")
return data
except:
pass
# # sixth fallback - try to remove last lines
# for i in range(1, len(response_text_lines)):
# response_text_lines_tmp = '\n'.join(response_text_lines[:-i])
# try:
# data = yaml.safe_load(response_text_lines_tmp)
# get_logger().info(f"Successfully parsed AI prediction after removing {i} lines")
# return data
# except:
# pass
def set_custom_labels(variables, git_provider=None):
if not get_settings().config.enable_custom_labels:

View File

@ -6,8 +6,7 @@ from dynaconf import Dynaconf
from starlette_context import context
from pr_agent.config_loader import get_settings
from pr_agent.git_providers import (get_git_provider,
get_git_provider_with_context)
from pr_agent.git_providers import get_git_provider_with_context
from pr_agent.log import get_logger

View File

@ -10,7 +10,7 @@ class Eligibility(Enum):
class IdentityProvider(ABC):
@abstractmethod
def verify_eligibility(self, git_provider, git_provier_id, pr_url):
def verify_eligibility(self, git_provider, git_provider_id, pr_url):
pass
@abstractmethod

View File

@ -127,6 +127,14 @@ def should_process_pr_logic(data) -> bool:
source_branch = pr_data.get("source", {}).get("branch", {}).get("name", "")
target_branch = pr_data.get("destination", {}).get("branch", {}).get("name", "")
sender = _get_username(data)
repo_full_name = pr_data.get("destination", {}).get("repository", {}).get("full_name", "")
# logic to ignore PRs from specific repositories
ignore_repos = get_settings().get("CONFIG.IGNORE_REPOSITORIES", [])
if repo_full_name and ignore_repos:
if any(re.search(regex, repo_full_name) for regex in ignore_repos):
get_logger().info(f"Ignoring PR from repository '{repo_full_name}' due to 'config.ignore_repositories' setting")
return False
# logic to ignore PRs from specific users
ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", [])

View File

@ -258,6 +258,14 @@ def should_process_pr_logic(body) -> bool:
source_branch = pull_request.get("head", {}).get("ref", "")
target_branch = pull_request.get("base", {}).get("ref", "")
sender = body.get("sender", {}).get("login")
repo_full_name = body.get("repository", {}).get("full_name", "")
# logic to ignore PRs from specific repositories
ignore_repos = get_settings().get("CONFIG.IGNORE_REPOSITORIES", [])
if ignore_repos and repo_full_name:
if any(re.search(regex, repo_full_name) for regex in ignore_repos):
get_logger().info(f"Ignoring PR from repository '{repo_full_name}' due to 'config.ignore_repositories' setting")
return False
# logic to ignore PRs from specific users
ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", [])

View File

@ -113,6 +113,14 @@ def should_process_pr_logic(data) -> bool:
return False
title = data['object_attributes'].get('title')
sender = data.get("user", {}).get("username", "")
repo_full_name = data.get('project', {}).get('path_with_namespace', "")
# logic to ignore PRs from specific repositories
ignore_repos = get_settings().get("CONFIG.IGNORE_REPOSITORIES", [])
if ignore_repos and repo_full_name:
if any(re.search(regex, repo_full_name) for regex in ignore_repos):
get_logger().info(f"Ignoring MR from repository '{repo_full_name}' due to 'config.ignore_repositories' setting")
return False
# logic to ignore PRs from specific users
ignore_pr_users = get_settings().get("CONFIG.IGNORE_PR_AUTHORS", [])

View File

@ -55,6 +55,7 @@ ignore_pr_target_branches = [] # a list of regular expressions of target branche
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_authors = [] # authors to ignore from PR agent when an PR is created
ignore_repositories = [] # a list of regular expressions of repository full names (e.g. "org/repo") to ignore from PR agent processing
#
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

View File

@ -13,7 +13,7 @@ google-cloud-aiplatform==1.38.0
google-generativeai==0.8.3
google-cloud-storage==2.10.0
Jinja2==3.1.2
litellm==1.66.3
litellm==1.69.3
loguru==0.7.2
msrest==0.7.1
openai>=1.55.3

View File

@ -0,0 +1,79 @@
import pytest
from pr_agent.servers.github_app import should_process_pr_logic as github_should_process_pr_logic
from pr_agent.servers.bitbucket_app import should_process_pr_logic as bitbucket_should_process_pr_logic
from pr_agent.servers.gitlab_webhook import should_process_pr_logic as gitlab_should_process_pr_logic
from pr_agent.config_loader import get_settings
def make_bitbucket_payload(full_name):
return {
"data": {
"pullrequest": {
"title": "Test PR",
"source": {"branch": {"name": "feature/test"}},
"destination": {
"branch": {"name": "main"},
"repository": {"full_name": full_name}
}
},
"actor": {"username": "user", "type": "user"}
}
}
def make_github_body(full_name):
return {
"pull_request": {},
"repository": {"full_name": full_name},
"sender": {"login": "user"}
}
def make_gitlab_body(full_name):
return {
"object_attributes": {"title": "Test MR"},
"project": {"path_with_namespace": full_name}
}
PROVIDERS = [
("github", github_should_process_pr_logic, make_github_body),
("bitbucket", bitbucket_should_process_pr_logic, make_bitbucket_payload),
("gitlab", gitlab_should_process_pr_logic, make_gitlab_body),
]
class TestIgnoreRepositories:
def setup_method(self):
get_settings().set("CONFIG.IGNORE_REPOSITORIES", [])
@pytest.mark.parametrize("provider_name, provider_func, body_func", PROVIDERS)
def test_should_ignore_matching_repository(self, provider_name, provider_func, body_func):
get_settings().set("CONFIG.IGNORE_REPOSITORIES", ["org/repo-to-ignore"])
body = {
"pull_request": {},
"repository": {"full_name": "org/repo-to-ignore"},
"sender": {"login": "user"}
}
result = provider_func(body_func(body["repository"]["full_name"]))
# print(f"DEBUG: Provider={provider_name}, test_should_ignore_matching_repository, result={result}")
assert result is False, f"{provider_name}: PR from ignored repository should be ignored (return False)"
@pytest.mark.parametrize("provider_name, provider_func, body_func", PROVIDERS)
def test_should_not_ignore_non_matching_repository(self, provider_name, provider_func, body_func):
get_settings().set("CONFIG.IGNORE_REPOSITORIES", ["org/repo-to-ignore"])
body = {
"pull_request": {},
"repository": {"full_name": "org/other-repo"},
"sender": {"login": "user"}
}
result = provider_func(body_func(body["repository"]["full_name"]))
# print(f"DEBUG: Provider={provider_name}, test_should_not_ignore_non_matching_repository, result={result}")
assert result is True, f"{provider_name}: PR from non-ignored repository should not be ignored (return True)"
@pytest.mark.parametrize("provider_name, provider_func, body_func", PROVIDERS)
def test_should_not_ignore_when_config_empty(self, provider_name, provider_func, body_func):
get_settings().set("CONFIG.IGNORE_REPOSITORIES", [])
body = {
"pull_request": {},
"repository": {"full_name": "org/repo-to-ignore"},
"sender": {"login": "user"}
}
result = provider_func(body_func(body["repository"]["full_name"]))
# print(f"DEBUG: Provider={provider_name}, test_should_not_ignore_when_config_empty, result={result}")
assert result is True, f"{provider_name}: PR should not be ignored if ignore_repositories config is empty"

View File

@ -32,11 +32,6 @@ age: 35
expected_output = {'name': 'John Smith', 'age': 35}
assert try_fix_yaml(review_text) == expected_output
# The function removes the last line(s) of the YAML string and successfully parses the YAML string.
def test_remove_last_line(self):
review_text = "key: value\nextra invalid line\n"
expected_output = {"key": "value"}
assert try_fix_yaml(review_text) == expected_output
# The YAML string is empty.
def test_empty_yaml_fixed(self):