Compare commits

..

9 Commits

Author SHA1 Message Date
2d5b0fa37f Fix repo settings bug in Gitlab 2023-08-20 14:39:05 +03:00
99f5a2ab0f Merge pull request #216 from idavidov/idavidov/specific_version
Adding Instructions for Specific Docker Image Version and GitHub Action Commit in Installation Guide
2023-08-20 14:12:29 +03:00
d7dcecfe00 Merge pull request #220 from zmeir/zmeir-safe_parse_config_overrides
Safe parse key value in config override
2023-08-20 10:29:53 +03:00
c6f8d985c2 Safe parse key value in config override 2023-08-20 10:11:39 +03:00
532dfd223e Merge pull request #215 from tjwp/auto-review
Addition of Automatic Review Configuration for GitHub App
2023-08-19 16:50:34 +03:00
6e7622822e Merge branch 'main' into idavidov/specific_version 2023-08-18 10:04:23 +03:00
631fb93b28 Implement Automatic Review Configuration for GitHub app 2023-08-16 16:24:30 -04:00
7803d8eec4 + pin to specific commit with gihub actions in Istallation part of readme 2023-08-16 14:22:14 +03:00
9a84b4b184 + digest usage for docker in Istallation part of readme 2023-08-16 12:56:15 +03:00
8 changed files with 60 additions and 38 deletions

View File

@ -18,6 +18,18 @@ docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> c
``` ```
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> ask "<your question>" docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> ask "<your question>"
``` ```
Note: If you want to ensure you're running a specific version of the Docker image, consider using the image's digest.
The digest is a unique identifier for a specific version of an image. You can pull and run an image using its digest by referencing it like so: repository@sha256:digest. Always ensure you're using the correct and trusted digest for your operations.
1. To request a review for a PR using a specific digest, run the following command:
```bash
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url <pr_url> review
```
2. To ask a question about a PR using the same digest, run the following command:
```bash
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url <pr_url> ask "<your question>"
```
Possible questions you can ask include: Possible questions you can ask include:
@ -51,7 +63,24 @@ jobs:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }} OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
``` ```
** if you want to pin your action to a specific commit for stability reasons
```yaml
on:
pull_request:
issue_comment:
jobs:
pr_agent_job:
runs-on: ubuntu-latest
name: Run pr agent on every pull request, respond to user comments
steps:
- name: PR Agent action step
id: pragent
uses: Codium-ai/pr-agent@<commit_sha>
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
2. Add the following secret to your repository under `Settings > Secrets`: 2. Add the following secret to your repository under `Settings > Secrets`:
``` ```

View File

@ -15,6 +15,7 @@ from pr_agent.tools.pr_update_changelog import PRUpdateChangelog
from pr_agent.tools.pr_config import PRConfig from pr_agent.tools.pr_config import PRConfig
command2class = { command2class = {
"auto_review": PRReviewer,
"answer": PRReviewer, "answer": PRReviewer,
"review": PRReviewer, "review": PRReviewer,
"review_pr": PRReviewer, "review_pr": PRReviewer,
@ -70,6 +71,8 @@ class PRAgent:
if notify: if notify:
notify() notify()
await PRReviewer(pr_url, is_answer=True, args=args).run() await PRReviewer(pr_url, is_answer=True, args=args).run()
elif action == "auto_review":
await PRReviewer(pr_url, is_auto=True, args=args).run()
elif action in command2class: elif action in command2class:
if notify: if notify:
notify() notify()

View File

@ -245,14 +245,12 @@ def update_settings_from_args(args: List[str]) -> List[str]:
arg = arg.strip() arg = arg.strip()
if arg.startswith('--'): if arg.startswith('--'):
arg = arg.strip('-').strip() arg = arg.strip('-').strip()
vals = arg.split('=') vals = arg.split('=', 1)
if len(vals) != 2: if len(vals) != 2:
logging.error(f'Invalid argument format: {arg}') logging.error(f'Invalid argument format: {arg}')
other_args.append(arg) other_args.append(arg)
continue continue
key, value = vals key, value = _fix_key_value(*vals)
key = key.strip().upper()
value = value.strip()
get_settings().set(key, value) get_settings().set(key, value)
logging.info(f'Updated setting {key} to: "{value}"') logging.info(f'Updated setting {key} to: "{value}"')
else: else:
@ -260,6 +258,16 @@ def update_settings_from_args(args: List[str]) -> List[str]:
return other_args return other_args
def _fix_key_value(key: str, value: str):
key = key.strip().upper()
value = value.strip()
try:
value = yaml.safe_load(value)
except Exception as e:
logging.error(f"Failed to parse YAML for config override {key}={value}", exc_info=e)
return key, value
def load_yaml(review_text: str) -> dict: def load_yaml(review_text: str) -> dict:
review_text = review_text.removeprefix('```yaml').rstrip('`') review_text = review_text.removeprefix('```yaml').rstrip('`')
try: try:

View File

@ -14,9 +14,6 @@ from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider
logger = logging.getLogger() logger = logging.getLogger()
class DiffNotFoundError(Exception):
"""Raised when the diff for a merge request cannot be found."""
pass
class GitLabProvider(GitProvider): class GitLabProvider(GitProvider):
@ -59,7 +56,7 @@ class GitLabProvider(GitProvider):
self.last_diff = self.mr.diffs.list(get_all=True)[-1] self.last_diff = self.mr.diffs.list(get_all=True)[-1]
except IndexError as e: except IndexError as e:
logger.error(f"Could not get diff for merge request {self.id_mr}") logger.error(f"Could not get diff for merge request {self.id_mr}")
raise DiffNotFoundError(f"Could not get diff for merge request {self.id_mr}") from e raise ValueError(f"Could not get diff for merge request {self.id_mr}") from e
def _get_pr_file_content(self, file_path: str, branch: str) -> str: def _get_pr_file_content(self, file_path: str, branch: str) -> str:
@ -153,20 +150,16 @@ class GitLabProvider(GitProvider):
def create_inline_comments(self, comments: list[dict]): def create_inline_comments(self, comments: list[dict]):
raise NotImplementedError("Gitlab provider does not support publishing inline comments yet") raise NotImplementedError("Gitlab provider does not support publishing inline comments yet")
def send_inline_comment(self,body: str,edit_type: str,found: bool,relevant_file: str,relevant_line_in_file: int, def send_inline_comment(self, body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
source_line_no: int, target_file: str,target_line_no: int) -> None: target_file, target_line_no):
if not found: if not found:
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}") logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
else: else:
# in order to have exact sha's we have to find correct diff for this change d = self.last_diff
diff = self.get_relevant_diff(relevant_file, relevant_line_in_file)
if diff is None:
logger.error(f"Could not get diff for merge request {self.id_mr}")
raise DiffNotFoundError(f"Could not get diff for merge request {self.id_mr}")
pos_obj = {'position_type': 'text', pos_obj = {'position_type': 'text',
'new_path': target_file.filename, 'new_path': target_file.filename,
'old_path': target_file.old_filename if target_file.old_filename else target_file.filename, 'old_path': target_file.old_filename if target_file.old_filename else target_file.filename,
'base_sha': diff.base_commit_sha, 'start_sha': diff.start_commit_sha, 'head_sha': diff.head_commit_sha} 'base_sha': d.base_commit_sha, 'start_sha': d.start_commit_sha, 'head_sha': d.head_commit_sha}
if edit_type == 'deletion': if edit_type == 'deletion':
pos_obj['old_line'] = source_line_no - 1 pos_obj['old_line'] = source_line_no - 1
elif edit_type == 'addition': elif edit_type == 'addition':
@ -178,23 +171,6 @@ class GitLabProvider(GitProvider):
self.mr.discussions.create({'body': body, self.mr.discussions.create({'body': body,
'position': pos_obj}) 'position': pos_obj})
def get_relevant_diff(self, relevant_file: str, relevant_line_in_file: int) -> Optional[dict]:
changes = self.mr.changes() # Retrieve the changes for the merge request once
if not changes:
logging.error('No changes found for the merge request.')
return None
all_diffs = self.mr.diffs.list(get_all=True)
if not all_diffs:
logging.error('No diffs found for the merge request.')
return None
for diff in all_diffs:
for change in changes['changes']:
if change['new_path'] == relevant_file and relevant_line_in_file in change['diff']:
return diff
logging.debug(
f'No relevant diff found for {relevant_file} {relevant_line_in_file}. Falling back to last diff.')
return self.last_diff # fallback to last_diff if no relevant diff is found
def publish_code_suggestions(self, code_suggestions: list): def publish_code_suggestions(self, code_suggestions: list):
for suggestion in code_suggestions: for suggestion in code_suggestions:
try: try:

View File

@ -93,7 +93,7 @@ async def handle_request(body: Dict[str, Any]):
api_url = pull_request.get("url") api_url = pull_request.get("url")
if not api_url: if not api_url:
return {} return {}
await agent.handle_request(api_url, "/review") await agent.handle_request(api_url, "/auto_review")
return {} return {}

View File

@ -19,6 +19,7 @@ require_security_review=true
num_code_suggestions=3 num_code_suggestions=3
inline_code_comments = false inline_code_comments = false
ask_and_reflect=false ask_and_reflect=false
automatic_review=true
extra_instructions = "" extra_instructions = ""
[pr_description] # /describe # [pr_description] # /describe #

View File

@ -23,7 +23,7 @@ class PRReviewer:
""" """
The PRReviewer class is responsible for reviewing a pull request and generating feedback using an AI model. The PRReviewer class is responsible for reviewing a pull request and generating feedback using an AI model.
""" """
def __init__(self, pr_url: str, is_answer: bool = False, args: list = None): def __init__(self, pr_url: str, is_answer: bool = False, is_auto: bool = False, args: list = None):
""" """
Initialize the PRReviewer object with the necessary attributes and objects to review a pull request. Initialize the PRReviewer object with the necessary attributes and objects to review a pull request.
@ -40,6 +40,7 @@ class PRReviewer:
) )
self.pr_url = pr_url self.pr_url = pr_url
self.is_answer = is_answer self.is_answer = is_answer
self.is_auto = is_auto
if self.is_answer and not self.git_provider.is_supported("get_issue_comments"): if self.is_answer and not self.git_provider.is_supported("get_issue_comments"):
raise Exception(f"Answer mode is not supported for {get_settings().config.git_provider} for now") raise Exception(f"Answer mode is not supported for {get_settings().config.git_provider} for now")
@ -93,7 +94,11 @@ class PRReviewer:
""" """
Review the pull request and generate feedback. Review the pull request and generate feedback.
""" """
logging.info('Reviewing PR...') if self.is_auto and not get_settings().pr_reviewer.automatic_review:
logging.info(f'Automatic review is disabled {self.pr_url}')
return None
logging.info(f'Reviewing PR: {self.pr_url} ...')
if get_settings().config.publish_output: if get_settings().config.publish_output:
self.git_provider.publish_comment("Preparing review...", is_temporary=True) self.git_provider.publish_comment("Preparing review...", is_temporary=True)