Merge commit 'e352c98ce83bfbd99078f62d8705eb938a6ba5b5' into hl/type_vs_labels

This commit is contained in:
Hussam.lawen
2023-11-06 14:24:33 +02:00
12 changed files with 129 additions and 45 deletions

View File

@ -32,8 +32,10 @@ class BitbucketProvider(GitProvider):
self.repo = None
self.pr_num = None
self.pr = None
self.pr_url = pr_url
self.temp_comments = []
self.incremental = incremental
self.diff_files = None
if pr_url:
self.set_pr(pr_url)
self.bitbucket_comment_api_url = self.pr._BitbucketBase__data["links"]["comments"]["href"]
@ -44,6 +46,8 @@ class BitbucketProvider(GitProvider):
url = (f"https://api.bitbucket.org/2.0/repositories/{self.workspace_slug}/{self.repo_slug}/src/"
f"{self.pr.destination_branch}/.pr_agent.toml")
response = requests.request("GET", url, headers=self.headers)
if response.status_code == 404: # not found
return ""
contents = response.text.encode('utf-8')
return contents
except Exception:
@ -114,6 +118,9 @@ class BitbucketProvider(GitProvider):
return [diff.new.path for diff in self.pr.diffstat()]
def get_diff_files(self) -> list[FilePatchInfo]:
if self.diff_files:
return self.diff_files
diffs = self.pr.diffstat()
diff_split = [
"diff --git%s" % x for x in self.pr.diff().split("diff --git") if x.strip()
@ -133,6 +140,7 @@ class BitbucketProvider(GitProvider):
diff.new.path,
)
)
self.diff_files = diff_files
return diff_files
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
@ -181,9 +189,29 @@ class BitbucketProvider(GitProvider):
)
return response
def generate_link_to_relevant_line_number(self, suggestion) -> str:
try:
relevant_file = suggestion['relevant file'].strip('`').strip("'")
relevant_line_str = suggestion['relevant line']
if not relevant_line_str:
return ""
diff_files = self.get_diff_files()
position, absolute_position = find_line_number_of_relevant_line_in_file \
(diff_files, relevant_file, relevant_line_str)
if absolute_position != -1 and self.pr_url:
link = f"{self.pr_url}/#L{relevant_file}T{absolute_position}"
return link
except Exception as e:
if get_settings().config.verbosity_level >= 2:
get_logger().info(f"Failed adding line link, error: {e}")
return ""
def publish_inline_comments(self, comments: list[dict]):
for comment in comments:
self.publish_inline_comment(comment['body'], comment['start_line'], comment['path'])
self.publish_inline_comment(comment['body'], comment['position'], comment['path'])
def get_title(self):
return self.pr.title

View File

@ -190,6 +190,13 @@ class IncrementalPR:
def __init__(self, is_incremental: bool = False):
self.is_incremental = is_incremental
self.commits_range = None
self.first_new_commit_sha = None
self.last_seen_commit_sha = None
self.first_new_commit = None
self.last_seen_commit = None
@property
def first_new_commit_sha(self):
return None if self.first_new_commit is None else self.first_new_commit.sha
@property
def last_seen_commit_sha(self):
return None if self.last_seen_commit is None else self.last_seen_commit.sha

View File

@ -66,10 +66,10 @@ class GithubProvider(GitProvider):
first_new_commit_index = None
for index in range(len(self.commits) - 1, -1, -1):
if self.commits[index].commit.author.date > last_review_time:
self.incremental.first_new_commit_sha = self.commits[index].sha
self.incremental.first_new_commit = self.commits[index]
first_new_commit_index = index
else:
self.incremental.last_seen_commit_sha = self.commits[index].sha
self.incremental.last_seen_commit = self.commits[index]
break
return self.commits[first_new_commit_index:] if first_new_commit_index is not None else []

View File

@ -27,7 +27,8 @@ def apply_repo_settings(pr_url):
get_settings().unset(section)
get_settings().set(section, section_dict, merge=False)
get_logger().info(f"Applying repo settings for section {section}, contents: {contents}")
except Exception as e:
get_logger().exception("Failed to apply repo settings", e)
finally:
if repo_settings_file:
try:

View File

@ -122,7 +122,7 @@ async def handle_request(body: Dict[str, Any], event: str):
if body.get("requested_reviewer", {}).get("login", "") != bot_user:
return {}
get_logger().info(f"Performing review for {api_url=} because of {event=} and {action=}")
await _perform_commands(agent, body, api_url, log_context)
await _perform_commands("pr_commands", agent, body, api_url, log_context)
# handle pull_request event with synchronize action - "push trigger" for new commits
elif event == 'pull_request' and action == 'synchronize' and get_settings().github_app.handle_push_trigger:
@ -174,7 +174,7 @@ async def handle_request(body: Dict[str, Any], event: str):
get_logger().info(f"Skipping incremental review because there was no initial review for {api_url=} yet")
return {}
get_logger().info(f"Performing incremental review for {api_url=} because of {event=} and {action=}")
await _perform_commands(agent, body, api_url, log_context)
await _perform_commands("push_commands", agent, body, api_url, log_context)
finally:
# release the waiting task block
@ -203,9 +203,9 @@ def _check_pull_request_event(action: str, body: dict, log_context: dict, bot_us
return pull_request, api_url
async def _perform_commands(agent: PRAgent, body: dict, api_url: str, log_context: dict):
async def _perform_commands(commands_conf: str, agent: PRAgent, body: dict, api_url: str, log_context: dict):
apply_repo_settings(api_url)
commands = get_settings().github_app.pr_commands
commands = get_settings().get(f"github_app.{commands_conf}")
for command in commands:
split_command = command.split(" ")
command = split_command[0]

View File

@ -1,12 +1,15 @@
from fastapi import FastAPI
from mangum import Mangum
from starlette.middleware import Middleware
from starlette_context.middleware import RawContextMiddleware
from pr_agent.log import setup_logger
from pr_agent.servers.github_app import router
setup_logger()
app = FastAPI()
middleware = [Middleware(RawContextMiddleware)]
app = FastAPI(middleware=middleware)
app.include_router(router)
handler = Mangum(app, lifespan="off")

View File

@ -26,6 +26,10 @@ ask_and_reflect=false
automatic_review=true
remove_previous_review_comment=false
extra_instructions = ""
# specific configurations for incremental review (/review -i)
require_all_thresholds_for_incremental_review=false
minimal_commits_for_incremental_review=0
minimal_minutes_for_incremental_review=0
[pr_description] # /describe #
publish_labels=true
@ -106,6 +110,9 @@ push_commands = [
--pr_reviewer.num_code_suggestions=0 \
--pr_reviewer.inline_code_comments=false \
--pr_reviewer.remove_previous_review_comment=true \
--pr_reviewer.require_all_thresholds_for_incremental_review=false \
--pr_reviewer.minimal_commits_for_incremental_review=5 \
--pr_reviewer.minimal_minutes_for_incremental_review=30 \
--pr_reviewer.extra_instructions='' \
"""
]

View File

@ -1,9 +1,10 @@
[pr_description_prompt]
system="""You are CodiumAI-PR-Reviewer, a language model designed to review git pull requests.
Your task is to provide full description of the PR content.
- Make sure not to focus the new PR code (the '+' lines).
Your task is to provide full description of a Pull Request (PR) content.
- Make sure to focus on the new PR code (the '+' lines).
- Notice that the 'Previous title', 'Previous description' and 'Commit messages' sections may be partial, simplistic, non-informative or not up-to-date. Hence, compare them to the PR diff code, and use them only as a reference.
- If needed, each YAML output should be in block scalar format ('|-')
- Emphasize first the most important changes, and then the less important ones.
- If needed, each YAML output should be in block scalar format ('|-')
{%- if extra_instructions %}
Extra instructions from the user:
@ -51,6 +52,7 @@ PR Main Files Walkthrough:
changes in file:
type: string
description: minimal and concise description of the changes in the relevant file
```
Example output:

View File

@ -257,7 +257,7 @@ class PRDescription:
for file in value:
filename = file['filename'].replace("'", "`")
description = file['changes in file']
pr_body += f'`{filename}`: {description}\n'
pr_body += f'- `{filename}`: {description}\n'
if self.git_provider.is_supported("gfm_markdown"):
pr_body +="</details>\n"
else:

View File

@ -1,4 +1,5 @@
import copy
import datetime
from collections import OrderedDict
from typing import List, Tuple
@ -100,8 +101,7 @@ class PRReviewer:
if self.is_auto and not get_settings().pr_reviewer.automatic_review:
get_logger().info(f'Automatic review is disabled {self.pr_url}')
return None
if self.is_auto and self.incremental.is_incremental and not self.incremental.first_new_commit_sha:
get_logger().info(f"Incremental review is enabled for {self.pr_url} but there are no new commits")
if self.incremental.is_incremental and not self._can_run_incremental_review():
return None
get_logger().info(f'Reviewing PR: {self.pr_url} ...')
@ -217,19 +217,6 @@ class PRReviewer:
suggestion['relevant line'] = f"[{suggestion['relevant line']}]({link})"
else:
pass
# try:
# relevant_file = suggestion['relevant file'].strip('`').strip("'")
# relevant_line_str = suggestion['relevant line']
# if not relevant_line_str:
# return ""
#
# position, absolute_position = find_line_number_of_relevant_line_in_file(
# self.git_provider.diff_files, relevant_file, relevant_line_str)
# if absolute_position != -1:
# suggestion[
# 'relevant line'] = f"{suggestion['relevant line']} (line {absolute_position})"
# except:
# pass
# Add incremental review section
@ -347,3 +334,34 @@ class PRReviewer:
self.git_provider.remove_comment(comment)
except Exception as e:
get_logger().exception(f"Failed to remove previous review comment, error: {e}")
def _can_run_incremental_review(self) -> bool:
"""Checks if we can run incremental review according the various configurations and previous review"""
# checking if running is auto mode but there are no new commits
if self.is_auto and not self.incremental.first_new_commit_sha:
get_logger().info(f"Incremental review is enabled for {self.pr_url} but there are no new commits")
return False
# checking if there are enough commits to start the review
num_new_commits = len(self.incremental.commits_range)
num_commits_threshold = get_settings().pr_reviewer.minimal_commits_for_incremental_review
not_enough_commits = num_new_commits < num_commits_threshold
# checking if the commits are not too recent to start the review
recent_commits_threshold = datetime.datetime.now() - datetime.timedelta(
minutes=get_settings().pr_reviewer.minimal_minutes_for_incremental_review
)
last_seen_commit_date = (
self.incremental.last_seen_commit.commit.author.date if self.incremental.last_seen_commit else None
)
all_commits_too_recent = (
last_seen_commit_date > recent_commits_threshold if self.incremental.last_seen_commit else False
)
# check all the thresholds or just one to start the review
condition = any if get_settings().pr_reviewer.require_all_thresholds_for_incremental_review else all
if condition((not_enough_commits, all_commits_too_recent)):
get_logger().info(
f"Incremental review is enabled for {self.pr_url} but didn't pass the threshold check to run:"
f"\n* Number of new commits = {num_new_commits} (threshold is {num_commits_threshold})"
f"\n* Last seen commit date = {last_seen_commit_date} (threshold is {recent_commits_threshold})"
)
return False
return True