2
2
import os
3
3
from github import Github
4
4
5
- def get_pr_latest_commit_diff (repo_name , pr_number , github_token ):
6
- """Retrieves and cleans the diff from the latest commit of a PR, excluding test files."""
5
+ def get_pr_latest_commit_diff_files (repo_name , pr_number , github_token ):
6
+ """Retrieves diff information for each file in the latest commit of a PR, excluding test files."""
7
7
g = Github (github_token )
8
8
repo = g .get_repo (repo_name )
9
9
pr = repo .get_pull (pr_number )
10
10
11
11
try :
12
- commits = list (pr .get_commits ()) # Get all commits in the PR
12
+ commits = list (pr .get_commits ())
13
13
if commits :
14
- latest_commit = commits [- 1 ] # Get the latest commit
14
+ latest_commit = commits [- 1 ]
15
15
files = latest_commit .files
16
- combined_diff = ""
16
+ diff_files = []
17
17
for file in files :
18
- # Exclude test files (adjust the condition as needed)
19
18
if not file .filename .endswith ("_test.go" ) and not file .filename .endswith ("_test.py" ) and not "/test/" in file .filename :
20
19
if file .patch :
21
- combined_diff += file . patch + " \n "
22
- return combined_diff
20
+ diff_files . append ( file )
21
+ return diff_files
23
22
else :
24
- return None # No commits in the PR
23
+ return None
25
24
except Exception as e :
26
- print (f"Error getting diff from latest commit: { e } " )
25
+ print (f"Error getting diff files from latest commit: { e } " )
27
26
return None
28
27
29
- def generate_gemini_review ( diff , api_key ):
30
- """Generates a code review using the Gemini API."""
28
+ def generate_gemini_review_with_annotations ( diff_file , api_key ):
29
+ """Generates a code review with annotations for a single file using the Gemini API."""
31
30
genai .configure (api_key = api_key )
32
31
model = genai .GenerativeModel ('gemini-pro' )
33
32
34
- max_diff_length = 20000 # Example limit (adjust based on token count)
33
+ diff = diff_file .patch
34
+ max_diff_length = 20000 # Adjust based on token count
35
35
if len (diff ) > max_diff_length :
36
36
diff = diff [:max_diff_length ]
37
37
diff += "\n ... (truncated due to length limit) ..."
38
38
39
39
prompt = f"""
40
- Review the following code diff and provide feedback. Point out potential issues,
41
- suggest improvements, and highlight good practices. Keep the review concise.
40
+ Review the following code diff from file `{ diff_file .filename } ` and provide feedback.
41
+ Point out potential issues, suggest improvements, and highlight good practices.
42
+ For each issue or suggestion, specify the line numbers from the diff where the change occurs.
43
+ Keep the review concise.
42
44
43
45
```diff
44
46
{ diff }
@@ -47,33 +49,50 @@ def generate_gemini_review(diff, api_key):
47
49
response = model .generate_content (prompt )
48
50
return response .text if response .text else None
49
51
50
- def post_github_comment (repo_name , pr_number , comment , github_token ):
51
- """Posts a comment to a GitHub pull request."""
52
+ def post_github_review_comments (repo_name , pr_number , diff_file , review_comment , github_token ):
53
+ """Posts review comments to a GitHub pull request, annotating specific lines ."""
52
54
g = Github (github_token )
53
55
repo = g .get_repo (repo_name )
54
56
pr = repo .get_pull (pr_number )
55
- pr .create_issue_comment (comment )
56
- print ("Review comment posted successfully." )
57
+
58
+ if review_comment :
59
+ # Parse the review comment for line number annotations
60
+ lines_to_comment = []
61
+ for line in review_comment .split ('\n ' ):
62
+ if "line" in line .lower () and ":" in line :
63
+ try :
64
+ line_num = int (line .lower ().split ("line" )[1 ].split (":" )[0 ].strip ())
65
+ lines_to_comment .append (line_num )
66
+ except ValueError :
67
+ continue # Skip lines that don't have a valid line number
68
+
69
+ if lines_to_comment :
70
+ for line_num in lines_to_comment :
71
+ pr .create_review_comment (body = review_comment , commit = pr .get_commits ()[- 1 ], path = diff_file .filename , position = line_num )
72
+ print (f"Review comments for { diff_file .filename } posted successfully." )
73
+ else :
74
+ pr .create_issue_comment (f"Review for { diff_file .filename } :\n { review_comment } " )
75
+ print (f"Review for { diff_file .filename } posted as general comment since no line number was found." )
76
+
77
+ else :
78
+ print (f"Gemini API returned no response for { diff_file .filename } ." )
57
79
58
80
def main ():
59
- """Main function to orchestrate the Gemini PR review."""
81
+ """Main function to orchestrate the Gemini PR review with annotations ."""
60
82
api_key = os .environ .get ('GEMINI_API_KEY' )
61
83
pr_number = int (os .environ .get ('PR_NUMBER' ))
62
84
repo_name = os .environ .get ('GITHUB_REPOSITORY' )
63
85
github_token = os .environ .get ('GITHUB_TOKEN' )
64
86
65
- diff = get_pr_latest_commit_diff (repo_name , pr_number , github_token )
87
+ diff_files = get_pr_latest_commit_diff_files (repo_name , pr_number , github_token )
66
88
67
- if diff is None :
68
- print ("Failed to retrieve PR diff from latest commit. Exiting." )
89
+ if diff_files is None :
90
+ print ("Failed to retrieve PR diff files from latest commit. Exiting." )
69
91
return
70
92
71
- review_comment = generate_gemini_review (diff , api_key )
72
-
73
- if review_comment :
74
- post_github_comment (repo_name , pr_number , review_comment , github_token )
75
- else :
76
- print ("Gemini API returned no response." )
93
+ for diff_file in diff_files :
94
+ review_comment = generate_gemini_review_with_annotations (diff_file , api_key )
95
+ post_github_review_comments (repo_name , pr_number , diff_file , review_comment , github_token )
77
96
78
97
if __name__ == "__main__" :
79
98
main ()
0 commit comments