Skip to content

Commit 02ce83a

Browse files
authored
Merge pull request #2 from Robbings/Feature/module_optimization
Feature/module optimization
2 parents 200ceda + db4beb1 commit 02ce83a

File tree

21 files changed

+381
-160
lines changed

21 files changed

+381
-160
lines changed

config/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131

3232
# Prompt
3333
gpt_message = """
34-
你是一位资深编程专家,gitlab的分支代码变更将以git diff 字符串的形式提供,请你帮忙review本段代码。然后你review内容的返回内容必须严格遵守下面的格式,包括标题内容。模板中的变量内容解释:变量5是代码中的优点儿 变量1是给review打分,分数区间为0~100分。 变量2 是code review发现的问题点。 变量3是具体的修改建议。变量4是你给出的修改后的代码。 必须要求:1. 以精炼的语言、严厉的语气指出存在的问题。2. 你的反馈内容必须使用严谨的markdown格式 3. 不要携带变量内容解释信息。4. 有清晰的标题结构。有清晰的标题结构。有清晰的标题结构。
34+
你是一位资深编程专家,gitlab的分支代码变更将以git diff 字符串的形式提供,请你帮忙review本段代码。然后你review内容的返回内容必须严格遵守下面的格式,包括标题内容。模板中的变量内容解释:
35+
变量5为: 代码中的优点。变量1:给review打分,分数区间为0~100分。变量2:code review发现的问题点。变量3:具体的修改建议。变量4:是你给出的修改后的代码。
36+
必须要求:1. 以精炼的语言、严厉的语气指出存在的问题。2. 你的反馈内容必须使用严谨的markdown格式 3. 不要携带变量内容解释信息。4. 有清晰的标题结构。有清晰的标题结构。有清晰的标题结构。
3537
返回格式严格如下:
3638
3739

gitlab_integration/gitlab_fetcher.py

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
1+
import os
2+
import re
3+
import shutil
4+
import subprocess
5+
import time
6+
17
import requests
28
from retrying import retry
39
from config.config import *
410
from utils.logger import log
11+
from utils.tools import run_command
12+
513

614
class GitlabMergeRequestFetcher:
715
def __init__(self, project_id, merge_request_iid):
816
self.project_id = project_id
917
self.iid = merge_request_iid
18+
self._changes_cache = None
19+
self._file_content_cache = {}
20+
self._info_cache = None
1021

1122
@retry(stop_max_attempt_number=3, wait_fixed=2000)
12-
def get_changes(self):
23+
def get_changes(self, force=False):
1324
"""
1425
Get the changes of the merge request
1526
:return: changes
1627
"""
28+
if self._changes_cache and not force:
29+
return self._changes_cache
1730
# URL for the GitLab API endpoint
1831
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}/merge_requests/{self.iid}/changes"
1932

@@ -27,12 +40,49 @@ def get_changes(self):
2740

2841
# Check if the request was successful
2942
if response.status_code == 200:
43+
self._changes_cache = response.json()["changes"]
3044
return response.json()["changes"]
3145
else:
3246
return None
3347

3448
@retry(stop_max_attempt_number=3, wait_fixed=2000)
35-
def get_info(self):
49+
# 获取文件内容
50+
def get_file_content(self, file_path, branch_name='main', force=False):
51+
"""
52+
Get the content of the file
53+
:param file_path: The path of the file
54+
:return: The content of the file
55+
"""
56+
# 对file_path中的'/'转换为'%2F'
57+
file_path = file_path.replace('/', '%2F')
58+
if file_path in self._file_content_cache and not force:
59+
return self._file_content_cache[file_path]
60+
# URL for the GitLab API endpoint
61+
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}/repository/files/{file_path}/raw?ref={branch_name}"
62+
63+
# Headers for the request
64+
headers = {
65+
"PRIVATE-TOKEN": gitlab_private_token
66+
}
67+
68+
# Make the GET request
69+
response = requests.get(url, headers=headers)
70+
71+
# Check if the request was successful
72+
if response.status_code == 200:
73+
self._file_content_cache[file_path] = response.text
74+
return response.text
75+
else:
76+
return None
77+
78+
@retry(stop_max_attempt_number=3, wait_fixed=2000)
79+
def get_info(self, force=False):
80+
"""
81+
Get the merge request information
82+
:return: Merge request information
83+
"""
84+
if self._info_cache and not force:
85+
return self._info_cache
3686
# URL for the GitLab API endpoint
3787
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}/merge_requests/{self.iid}"
3888

@@ -44,8 +94,112 @@ def get_info(self):
4494
# Make the GET request
4595
response = requests.get(url, headers=headers)
4696

97+
# Check if the request was successful
98+
if response.status_code == 200:
99+
self._info_cache = response.json()
100+
return response.json()
101+
else:
102+
return None
103+
104+
# gitlab仓库clone和管理
105+
class GitlabRepoManager:
106+
def __init__(self, project_id, branch_name = ""):
107+
self.project_id = project_id
108+
self.timestamp = int(time.time() * 1000)
109+
self.repo_path = f"./repo/{self.project_id}_{self.timestamp}"
110+
self.has_cloned = False
111+
112+
def get_info(self):
113+
"""
114+
Get the project information
115+
:return: Project information
116+
"""
117+
# URL for the GitLab API endpoint
118+
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}"
119+
120+
# Headers for the request
121+
headers = {
122+
"PRIVATE-TOKEN": gitlab_private_token
123+
}
124+
125+
# Make the GET request
126+
response = requests.get(url, headers=headers)
127+
47128
# Check if the request was successful
48129
if response.status_code == 200:
49130
return response.json()
50131
else:
51-
return None
132+
return None
133+
134+
@retry(stop_max_attempt_number=3, wait_fixed=2000)
135+
def shallow_clone(self, branch_name = "main"):
136+
"""
137+
Perform a shallow clone of the repository
138+
param branch_name: The name of the branch to clone
139+
"""
140+
# If the target directory exists, remove it
141+
self.delete_repo()
142+
143+
# Build the authenticated URL
144+
authenticated_url = self._build_authenticated_url(self.get_info()["http_url_to_repo"])
145+
146+
# Build the Git command
147+
command = ["git", "clone", authenticated_url, "--depth", "1"]
148+
if branch_name:
149+
command.extend(["--branch", branch_name])
150+
command.extend([self.repo_path + "/" + str(branch_name)])
151+
else:
152+
command.extend([self.repo_path + "/default"])
153+
# command 添加clone到的位置:
154+
if run_command(command) != 0:
155+
log.error("Failed to clone the repository")
156+
self.has_cloned = True
157+
158+
# 切换分支
159+
def checkout_branch(self, branch_name, force=False):
160+
# Build the Git command
161+
if not self.has_cloned:
162+
self.shallow_clone(branch_name)
163+
else:
164+
# 检查是否已经在目标分支上
165+
if not force and os.path.exists(self.repo_path + "/" + str(branch_name) + "/.git"):
166+
return
167+
else:
168+
self.shallow_clone(branch_name)
169+
170+
# 删除库
171+
def delete_repo(self):
172+
if os.path.exists(self.repo_path):
173+
shutil.rmtree(self.repo_path)
174+
175+
# 查找相关文件列表
176+
def find_files_by_keyword(self, keyword, branch_name="main"):
177+
matching_files = []
178+
regex = re.compile(keyword)
179+
self.checkout_branch(branch_name)
180+
for root, _, files in os.walk(self.repo_path + "/" + str(branch_name)):
181+
for file in files:
182+
file_path = os.path.join(root, file)
183+
try:
184+
with open(file_path, 'r', encoding='utf-8') as f:
185+
content = f.read()
186+
if regex.search(content):
187+
matching_files.append(file_path)
188+
except (UnicodeDecodeError, FileNotFoundError, PermissionError):
189+
# 跳过无法读取的文件
190+
continue
191+
192+
return matching_files
193+
194+
195+
# 构建带有身份验证信息的 URL
196+
def _build_authenticated_url(self, repo_url):
197+
# 如果 URL 使用 https
198+
token = gitlab_private_token
199+
if repo_url.startswith("https://"):
200+
return f"https://oauth2:{token}@{repo_url[8:]}"
201+
# 如果 URL 使用 http
202+
elif repo_url.startswith("http://"):
203+
return f"http://oauth2:{token}@{repo_url[7:]}"
204+
else:
205+
raise ValueError("Unsupported URL scheme")

gitlab_integration/webhook_listener.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from flask import request, jsonify
55

6-
from gitlab_integration.gitlab_fetcher import GitlabMergeRequestFetcher
7-
from reply_module.reply import Reply
6+
from gitlab_integration.gitlab_fetcher import GitlabMergeRequestFetcher, GitlabRepoManager
7+
from response_module.response_controller import ReviewResponse
88
from review_engine.review_engine import ReviewEngine
99
from utils.logger import log
1010

@@ -31,22 +31,22 @@ def call_handle(self, gitlab_payload, event_type):
3131
'project_id': gitlab_payload.get('project')['id'],
3232
'merge_request_iid': gitlab_payload.get('object_attributes')['iid']
3333
}
34-
reply = Reply(config)
34+
reply = ReviewResponse(config)
3535
return self.handle_merge_request(gitlab_payload, reply)
3636
elif event_type == 'push':
3737
config = {
3838
'type': 'push',
3939
'project_id': gitlab_payload.get('project')['id']
4040
}
41-
reply = Reply(config)
41+
reply = ReviewResponse(config)
4242

4343
return self.handle_push(gitlab_payload, reply)
4444
else:
4545
config = {
4646
'type': 'other',
4747
'project_id': gitlab_payload.get('project')['id']
4848
}
49-
reply = Reply(config)
49+
reply = ReviewResponse(config)
5050
return self.handle_other(gitlab_payload, reply)
5151

5252
def handle_merge_request(self, gitlab_payload, reply):
@@ -58,11 +58,9 @@ def handle_merge_request(self, gitlab_payload, reply):
5858
project_id = gitlab_payload.get('project')['id']
5959
merge_request_iid = gitlab_payload.get("object_attributes")["iid"]
6060
review_engine = ReviewEngine(reply)
61-
fetcher = GitlabMergeRequestFetcher(project_id, merge_request_iid)
62-
63-
changes = fetcher.get_changes()
64-
info = fetcher.get_info()
65-
thread = threading.Thread(target=review_engine.handle_merge, args=(changes, info, gitlab_payload))
61+
gitlabMergeRequestFetcher = GitlabMergeRequestFetcher(project_id, merge_request_iid)
62+
gitlabRepoManager = GitlabRepoManager(project_id)
63+
thread = threading.Thread(target=review_engine.handle_merge, args=(gitlabMergeRequestFetcher, gitlabRepoManager, gitlab_payload))
6664
thread.start()
6765

6866
return jsonify({'status': 'success'}), 200

reply_module/abstract_reply.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

reply_module/reply_factory.py

Lines changed: 0 additions & 28 deletions
This file was deleted.
File renamed without changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from abc import ABC, abstractmethod
2+
3+
class AbstractResponse(ABC):
4+
@abstractmethod
5+
def __init__(self, config):
6+
self.config = config
7+
8+
9+
class AbstractResponseMessage(AbstractResponse):
10+
@abstractmethod
11+
def __init__(self, config):
12+
super().__init__(config)
13+
14+
@abstractmethod
15+
def send(self, message):
16+
pass
17+
18+
19+
class AbstractResponseOther(AbstractResponse):
20+
@abstractmethod
21+
def __init__(self, config):
22+
super().__init__(config)
23+
24+
@abstractmethod
25+
def set_state(self, *args, **kwargs):
26+
pass
27+
28+
@abstractmethod
29+
def send(self, *args, **kwargs):
30+
pass

0 commit comments

Comments
 (0)