Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ max-line-length = 256
indent-style = tab
tab-size = 4
exclude = .git,__pycache__,venv,.venv
ignore = W191
ignore = W191,E101,W503
155 changes: 112 additions & 43 deletions .github/scripts/review.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,162 @@
"""
Module for automated PR code review using GitHub API and cloud function.
This script fetches PR diffs, filters out .github folder changes, and sends them for review.
"""

import os
import sys
import json
import requests


def filter_diff(diff_text):
"""
Filter out changes from .github folder in the diff.

Args:
diff_text (str): The raw diff text from GitHub API

Returns:
str: Filtered diff text without .github folder changes
"""
lines = diff_text.split('\n')
filtered_lines = []
skip_file = False

for line in lines:
# Check if this is a new file header
if line.startswith('diff --git'):
# Check if the file is in .github folder
if '/.github/' in line or line.endswith('/.github') or ' b/.github/' in line:
skip_file = True
else:
skip_file = False

# Include the line if we're not skipping this file
if not skip_file:
filtered_lines.append(line)

return '\n'.join(filtered_lines)


def get_pr_diff(owner, repo, pr_number, github_token):
url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
headers = {
"Accept": "application/vnd.github.v3.diff",
"Authorization": f"token {github_token}"
}
response = requests.get(url, headers=headers)
"""
Fetch the diff for a specific pull request from GitHub API.

Args:
owner (str): Repository owner
repo (str): Repository name
pr_number (int): Pull request number
github_token (str): GitHub authentication token

Returns:
str: The diff text from the PR

Raises:
SystemExit: If the API request fails
"""
url = f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}'
headers = {'Accept': 'application/vnd.github.v3.diff', 'Authorization': f'token {github_token}'}
response = requests.get(url, headers=headers, timeout=30)
if response.status_code != 200:
print(f"Error: Failed to fetch PR diff (HTTP {response.status_code}).", file=sys.stderr)
print(f'Error: Failed to fetch PR diff (HTTP {response.status_code}).', file=sys.stderr)
sys.exit(1)
return response.text


def get_code_review(diff_text, cloud_function_url):
function_api_key = os.environ.get("FUNCTION_API_KEY")
"""
Send the diff to a cloud function for AI-powered code review.

Args:
diff_text (str): The filtered diff text to review
cloud_function_url (str): URL of the cloud function endpoint

Returns:
str: The code review response from the cloud function

Raises:
SystemExit: If the cloud function call fails or API key is missing
"""
function_api_key = os.environ.get('FUNCTION_API_KEY')
if not function_api_key:
print("function_api_key not found", file=sys.stderr)
print('Error: FUNCTION_API_KEY not found', file=sys.stderr)
sys.exit(1)
headers = {
"Content-Type": "application/json",
"Authorization": function_api_key
}
payload = json.dumps({"diff": diff_text})

headers = {'Content-Type': 'application/json', 'Authorization': function_api_key}
payload = json.dumps({'diff': diff_text})

try:
response = requests.request("POST", cloud_function_url, headers=headers, data=payload)
response = requests.post(cloud_function_url, headers=headers, data=payload, timeout=60)
if response.status_code != 200:
print(f"Error: Cloud Function returned HTTP {response.status_code}: {response.text}", file=sys.stderr)
print(f'Error: Cloud Function returned HTTP {response.status_code}: {response.text}', file=sys.stderr)
sys.exit(1)
return response.json().get("review", "No review received.")

except Exception as e:
print(f"Error calling Cloud Function: {e}", file=sys.stderr)
return response.json().get('review', 'No review received.')
except requests.exceptions.RequestException as exc:
print(f'Error calling Cloud Function: {exc}', file=sys.stderr)
sys.exit(1)


def main():
github_event_path = os.environ.get("GITHUB_EVENT_PATH")
"""
Main function to orchestrate the PR review process.
Fetches PR data, retrieves diff, filters it, and sends for review.
"""
github_event_path = os.environ.get('GITHUB_EVENT_PATH')
if not github_event_path:
print("Error: GITHUB_EVENT_PATH not set.", file=sys.stderr)
print('Error: GITHUB_EVENT_PATH not set.', file=sys.stderr)
sys.exit(1)

with open(github_event_path, 'r') as f:
event_data = json.load(f)
with open(github_event_path, 'r', encoding='utf-8') as file:
event_data = json.load(file)

pr = event_data.get("pull_request")
if not pr:
print("Error: This event is not a pull_request.", file=sys.stderr)
pull_request = event_data.get('pull_request')
if not pull_request:
print('Error: This event is not a pull_request.', file=sys.stderr)
sys.exit(1)

pr_number = pr.get("number")
pr_number = pull_request.get('number')
if not pr_number:
print("Error: Could not determine pull request number.", file=sys.stderr)
print('Error: Could not determine pull request number.', file=sys.stderr)
sys.exit(1)

repo_full = os.environ.get("GITHUB_REPOSITORY")
if not repo_full or "/" not in repo_full:
print("Error: GITHUB_REPOSITORY not set or invalid.", file=sys.stderr)
repo_full = os.environ.get('GITHUB_REPOSITORY')
if not repo_full or '/' not in repo_full:
print('Error: GITHUB_REPOSITORY not set or invalid.', file=sys.stderr)
sys.exit(1)
owner, repo = repo_full.split('/')

owner, repo = repo_full.split("/")
github_token = os.environ.get("GITHUB_TOKEN")

github_token = os.environ.get('GITHUB_TOKEN')
if not github_token:
print("Error: GITHUB_TOKEN not set.", file=sys.stderr)
print('Error: GITHUB_TOKEN not set.', file=sys.stderr)
sys.exit(1)

cloud_function_url = os.environ.get("CLOUD_FUNCTION_URL")
cloud_function_url = os.environ.get('CLOUD_FUNCTION_URL')
print(cloud_function_url, file=sys.stderr)
if not cloud_function_url:
print("Error: CLOUD_FUNCTION_URL not set.", file=sys.stderr)
print('Error: CLOUD_FUNCTION_URL not set.', file=sys.stderr)
sys.exit(1)

print(f"Fetching diff for PR #{pr_number} in {owner}/{repo}...", file=sys.stderr)
print(f'Fetching diff for PR #{pr_number} in {owner}/{repo}...', file=sys.stderr)
diff_text = get_pr_diff(owner, repo, pr_number, github_token)

if not diff_text:
print("Error: No diff fetched.", file=sys.stderr)
print('Error: No diff fetched.', file=sys.stderr)
sys.exit(1)

print("Sending diff to Cloud Function for code review...", file=sys.stderr)
review = get_code_review(diff_text, cloud_function_url)
# Filter out .github folder changes
print('Filtering out .github folder changes...', file=sys.stderr)
filtered_diff = filter_diff(diff_text)

if not filtered_diff.strip():
print('No changes to review after filtering .github folder.', file=sys.stderr)
print('✅ No code changes to review (only .github folder modifications).')
sys.exit(0)

print('Sending diff to Cloud Function for code review...', file=sys.stderr)
review = get_code_review(filtered_diff, cloud_function_url)
print(review)


if __name__ == "__main__":
if __name__ == '__main__':
main()