jarvis-ai-assistant 0.1.161__py3-none-any.whl → 0.1.163__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +24 -6
- jarvis/jarvis_agent/jarvis.py +1 -2
- jarvis/jarvis_code_agent/code_agent.py +126 -159
- jarvis/jarvis_dev/main.py +17 -4
- jarvis/jarvis_git_details/main.py +2 -1
- jarvis/jarvis_git_utils/git_commiter.py +2 -3
- jarvis/jarvis_multi_agent/__init__.py +2 -3
- jarvis/jarvis_platform/base.py +0 -2
- jarvis/jarvis_platform/yuanbao.py +1 -0
- jarvis/jarvis_tools/edit_file.py +279 -0
- jarvis/jarvis_tools/file_operation.py +0 -2
- jarvis/jarvis_tools/read_code.py +0 -2
- jarvis/jarvis_tools/rewrite_file.py +183 -0
- jarvis/jarvis_utils/git_utils.py +144 -0
- jarvis/jarvis_utils/globals.py +0 -29
- jarvis/jarvis_utils/methodology.py +15 -9
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/METADATA +3 -1
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/RECORD +23 -22
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/WHEEL +1 -1
- jarvis/jarvis_agent/patch.py +0 -617
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""
|
|
2
|
+
文件搜索替换工具类
|
|
3
|
+
|
|
4
|
+
功能概述:
|
|
5
|
+
1. 提供精确的文件内容搜索和替换功能
|
|
6
|
+
2. 支持单个文件的编辑操作,包括创建新文件
|
|
7
|
+
3. 实现原子操作:所有修改要么全部成功,要么全部回滚
|
|
8
|
+
4. 严格匹配控制:每个搜索文本必须且只能匹配一次
|
|
9
|
+
|
|
10
|
+
核心特性:
|
|
11
|
+
- 支持不存在的文件和空文件处理
|
|
12
|
+
- 自动创建所需目录结构
|
|
13
|
+
- 完善的错误处理和回滚机制
|
|
14
|
+
- 严格的格式保持要求
|
|
15
|
+
"""
|
|
16
|
+
from typing import Any, Dict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FileSearchReplaceTool:
|
|
20
|
+
name = "edit_file"
|
|
21
|
+
description = """代码编辑工具,用于编辑单个文件
|
|
22
|
+
|
|
23
|
+
# 代码编辑规范
|
|
24
|
+
|
|
25
|
+
## 重要提示
|
|
26
|
+
此工具可以查看和修改单个文件的代码,只需提供要修改的代码片段即可。应尽量精简内容,只包含必要的上下文和修改部分。特别注意:不要提供完整文件内容,只提供需要修改的部分!
|
|
27
|
+
|
|
28
|
+
## 基本使用
|
|
29
|
+
1. 指定需要修改的文件路径
|
|
30
|
+
2. 提供一组或多组"搜索文本"和"替换文本"对
|
|
31
|
+
3. 每个搜索文本需在目标文件中有且仅有一次精确匹配
|
|
32
|
+
4. 创建新文件时,使用空字符串("")作为搜索文本,替换文本作为完整文件内容
|
|
33
|
+
5. 所有修改要么全部成功,要么全部失败并回滚
|
|
34
|
+
|
|
35
|
+
## 核心原则
|
|
36
|
+
1. **精准修改**:只提供需要修改的代码部分,不需要展示整个文件内容
|
|
37
|
+
2. **最小补丁原则**:始终生成最小范围的补丁,只包含必要的上下文和实际修改
|
|
38
|
+
3. **格式严格保持**:
|
|
39
|
+
- 严格保持原始代码的缩进方式(空格或制表符)
|
|
40
|
+
- 保持原始代码的空行数量和位置
|
|
41
|
+
- 保持原始代码的行尾空格处理方式
|
|
42
|
+
- 不改变原始代码的换行风格
|
|
43
|
+
4. **新旧区分**:
|
|
44
|
+
- 对于新文件:提供完整的代码内容
|
|
45
|
+
- 对于现有文件:只提供修改部分,不要提供整个文件
|
|
46
|
+
|
|
47
|
+
## 格式兼容性要求
|
|
48
|
+
1. **缩进一致性**:
|
|
49
|
+
- 如果原代码使用4个空格缩进,替换文本也必须使用4个空格缩进
|
|
50
|
+
- 如果原代码使用制表符缩进,替换文本也必须使用制表符
|
|
51
|
+
2. **空行保留**:
|
|
52
|
+
- 如果原代码在函数之间有两个空行,替换文本也必须保留这两个空行
|
|
53
|
+
- 如果原代码在类方法之间有一个空行,替换文本也必须保留这一个空行
|
|
54
|
+
3. **行尾处理**:
|
|
55
|
+
- 不要改变原代码的行尾空格或换行符风格
|
|
56
|
+
- 保持原有的换行符类型(CR、LF或CRLF)
|
|
57
|
+
|
|
58
|
+
## 最佳实践
|
|
59
|
+
1. 每个修改应专注于单一职责,避免包含过多无关代码
|
|
60
|
+
2. 设计唯一的搜索文本,避免多处匹配的风险
|
|
61
|
+
3. 创建新文件时提供完整、格式良好的内容
|
|
62
|
+
4. 不要出现未实现的代码,如:TODO
|
|
63
|
+
"""
|
|
64
|
+
parameters = {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"file": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "需要修改的文件路径"
|
|
70
|
+
},
|
|
71
|
+
"changes": {
|
|
72
|
+
"type": "array",
|
|
73
|
+
"description": "一组或多组搜索替换对",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"search": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "需要被替换的文本,必须在文件中唯一匹配,新文件用空字符串"
|
|
80
|
+
},
|
|
81
|
+
"replace": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "替换的目标文本,需保持与原代码相同的缩进和格式风格"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"required": ["search", "replace"]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"required": ["file", "changes"]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def __init__(self):
|
|
94
|
+
"""初始化文件搜索替换工具"""
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
98
|
+
"""
|
|
99
|
+
执行文件搜索替换操作,每个搜索文本只允许有且只有一次匹配,否则失败
|
|
100
|
+
特殊支持不存在的文件和空文件处理
|
|
101
|
+
|
|
102
|
+
参数:
|
|
103
|
+
file (str): 文件路径
|
|
104
|
+
changes (list): 一组或多组搜索替换对,格式如下:
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
"search": "搜索文本1",
|
|
108
|
+
"replace": "替换文本1"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"search": "搜索文本2",
|
|
112
|
+
"replace": "替换文本2"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
返回:
|
|
117
|
+
dict: 包含执行结果的字典
|
|
118
|
+
{
|
|
119
|
+
"success": bool, # 是否成功完成所有替换
|
|
120
|
+
"stdout": str, # 标准输出信息
|
|
121
|
+
"stderr": str # 错误信息
|
|
122
|
+
}
|
|
123
|
+
"""
|
|
124
|
+
import os
|
|
125
|
+
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
126
|
+
|
|
127
|
+
stdout_messages = []
|
|
128
|
+
stderr_messages = []
|
|
129
|
+
success = True
|
|
130
|
+
|
|
131
|
+
file_path = args["file"]
|
|
132
|
+
changes = args["changes"]
|
|
133
|
+
|
|
134
|
+
# 创建已处理文件变量,用于失败时回滚
|
|
135
|
+
original_content = None
|
|
136
|
+
processed = False
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
file_exists = os.path.exists(file_path)
|
|
140
|
+
content = ""
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# 如果文件存在,则读取内容
|
|
144
|
+
if file_exists:
|
|
145
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
146
|
+
content = f.read()
|
|
147
|
+
original_content = content
|
|
148
|
+
|
|
149
|
+
# 创建一个临时内容副本进行操作
|
|
150
|
+
temp_content = content
|
|
151
|
+
replaced_count = 0
|
|
152
|
+
|
|
153
|
+
# 处理所有搜索替换对
|
|
154
|
+
for change in changes:
|
|
155
|
+
search_text = change["search"]
|
|
156
|
+
replace_text = change["replace"]
|
|
157
|
+
|
|
158
|
+
# 特殊处理不存在的文件或空文件
|
|
159
|
+
if not file_exists or content == "":
|
|
160
|
+
if search_text == "":
|
|
161
|
+
# 对于不存在的文件或空文件,如果搜索文本为空,则直接使用替换文本作为内容
|
|
162
|
+
temp_content = replace_text
|
|
163
|
+
replaced_count += 1
|
|
164
|
+
# 只允许有一个空字符串搜索
|
|
165
|
+
break
|
|
166
|
+
else:
|
|
167
|
+
stderr_message = f"文件 {file_path} {'不存在' if not file_exists else '为空'},但搜索文本非空: '{search_text}'"
|
|
168
|
+
stderr_messages.append(stderr_message)
|
|
169
|
+
PrettyOutput.print(stderr_message, OutputType.ERROR)
|
|
170
|
+
success = False
|
|
171
|
+
break
|
|
172
|
+
else:
|
|
173
|
+
# 正常文件处理 - 检查匹配次数
|
|
174
|
+
match_count = temp_content.count(search_text)
|
|
175
|
+
|
|
176
|
+
if match_count == 0:
|
|
177
|
+
stderr_message = f"文件 {file_path} 中未找到匹配文本: '{search_text}'"
|
|
178
|
+
stderr_messages.append(stderr_message)
|
|
179
|
+
PrettyOutput.print(stderr_message, OutputType.ERROR)
|
|
180
|
+
success = False
|
|
181
|
+
break
|
|
182
|
+
elif match_count > 1:
|
|
183
|
+
stderr_message = f"文件 {file_path} 中匹配到多个 ({match_count}) '{search_text}',搜索文本只允许一次匹配"
|
|
184
|
+
stderr_messages.append(stderr_message)
|
|
185
|
+
PrettyOutput.print(stderr_message, OutputType.ERROR)
|
|
186
|
+
success = False
|
|
187
|
+
break
|
|
188
|
+
else:
|
|
189
|
+
# 只有一个匹配,执行替换
|
|
190
|
+
temp_content = temp_content.replace(search_text, replace_text, 1)
|
|
191
|
+
replaced_count += 1
|
|
192
|
+
|
|
193
|
+
# 只有当所有替换操作都成功时,才写回文件
|
|
194
|
+
if success and (temp_content != original_content or not file_exists):
|
|
195
|
+
# 确保目录存在
|
|
196
|
+
os.makedirs(os.path.dirname(os.path.abspath(file_path)), exist_ok=True)
|
|
197
|
+
|
|
198
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
199
|
+
f.write(temp_content)
|
|
200
|
+
|
|
201
|
+
processed = True
|
|
202
|
+
|
|
203
|
+
action = "创建并写入" if not file_exists else "成功替换"
|
|
204
|
+
stdout_message = f"文件 {file_path} {action} {replaced_count} 处"
|
|
205
|
+
stdout_messages.append(stdout_message)
|
|
206
|
+
PrettyOutput.print(stdout_message, OutputType.SUCCESS)
|
|
207
|
+
elif success:
|
|
208
|
+
stdout_message = f"文件 {file_path} 没有找到需要替换的内容"
|
|
209
|
+
stdout_messages.append(stdout_message)
|
|
210
|
+
PrettyOutput.print(stdout_message, OutputType.INFO)
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
|
|
214
|
+
stderr_messages.append(stderr_message)
|
|
215
|
+
PrettyOutput.print(stderr_message, OutputType.ERROR)
|
|
216
|
+
success = False
|
|
217
|
+
|
|
218
|
+
# 如果操作失败,回滚已修改的文件
|
|
219
|
+
if not success and processed:
|
|
220
|
+
rollback_message = "操作失败,正在回滚修改..."
|
|
221
|
+
stderr_messages.append(rollback_message)
|
|
222
|
+
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
if original_content is None:
|
|
226
|
+
# 如果是新创建的文件,则删除
|
|
227
|
+
if os.path.exists(file_path):
|
|
228
|
+
os.remove(file_path)
|
|
229
|
+
rollback_file_message = f"已删除新创建的文件: {file_path}"
|
|
230
|
+
else:
|
|
231
|
+
# 如果是修改的文件,则恢复原内容
|
|
232
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
233
|
+
f.write(original_content)
|
|
234
|
+
rollback_file_message = f"已回滚文件: {file_path}"
|
|
235
|
+
|
|
236
|
+
stderr_messages.append(rollback_file_message)
|
|
237
|
+
PrettyOutput.print(rollback_file_message, OutputType.INFO)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
rollback_error = f"回滚文件 {file_path} 失败: {str(e)}"
|
|
240
|
+
stderr_messages.append(rollback_error)
|
|
241
|
+
PrettyOutput.print(rollback_error, OutputType.ERROR)
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
"success": success,
|
|
245
|
+
"stdout": "\n".join(stdout_messages),
|
|
246
|
+
"stderr": "\n".join(stderr_messages)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
error_msg = f"文件搜索替换操作失败: {str(e)}"
|
|
251
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
252
|
+
|
|
253
|
+
# 如果有已修改的文件,尝试回滚
|
|
254
|
+
if processed:
|
|
255
|
+
rollback_message = "操作失败,正在回滚修改..."
|
|
256
|
+
stderr_messages.append(rollback_message)
|
|
257
|
+
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
if original_content is None:
|
|
261
|
+
# 如果是新创建的文件,则删除
|
|
262
|
+
if os.path.exists(file_path):
|
|
263
|
+
os.remove(file_path)
|
|
264
|
+
stderr_messages.append(f"已删除新创建的文件: {file_path}")
|
|
265
|
+
else:
|
|
266
|
+
# 如果是修改的文件,则恢复原内容
|
|
267
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
268
|
+
f.write(original_content)
|
|
269
|
+
stderr_messages.append(f"已回滚文件: {file_path}")
|
|
270
|
+
except:
|
|
271
|
+
stderr_messages.append(f"回滚文件失败: {file_path}")
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
"success": False,
|
|
275
|
+
"stdout": "",
|
|
276
|
+
"stderr": error_msg + "\n" + "\n".join(stderr_messages)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
@@ -4,7 +4,6 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
from yaspin import yaspin # type: ignore
|
|
6
6
|
|
|
7
|
-
from jarvis.jarvis_utils.globals import add_read_file_record
|
|
8
7
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
8
|
# 导入文件处理器
|
|
10
9
|
from jarvis.jarvis_utils.file_processors import (
|
|
@@ -57,7 +56,6 @@ class FileOperationTool:
|
|
|
57
56
|
"""Handle operations for a single file"""
|
|
58
57
|
try:
|
|
59
58
|
abs_path = os.path.abspath(filepath)
|
|
60
|
-
add_read_file_record(abs_path)
|
|
61
59
|
|
|
62
60
|
if operation == "read":
|
|
63
61
|
with yaspin(text=f"正在读取文件: {abs_path}...", color="cyan") as spinner:
|
jarvis/jarvis_tools/read_code.py
CHANGED
|
@@ -3,7 +3,6 @@ import os
|
|
|
3
3
|
|
|
4
4
|
from yaspin import yaspin
|
|
5
5
|
|
|
6
|
-
from jarvis.jarvis_utils.globals import add_read_file_record
|
|
7
6
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
8
7
|
|
|
9
8
|
class ReadCodeTool:
|
|
@@ -43,7 +42,6 @@ class ReadCodeTool:
|
|
|
43
42
|
"""
|
|
44
43
|
try:
|
|
45
44
|
abs_path = os.path.abspath(filepath)
|
|
46
|
-
add_read_file_record(abs_path)
|
|
47
45
|
with yaspin(text=f"正在读取文件: {abs_path}...", color="cyan") as spinner:
|
|
48
46
|
# 文件存在性检查
|
|
49
47
|
if not os.path.exists(abs_path):
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
文件重写工具类
|
|
3
|
+
|
|
4
|
+
功能概述:
|
|
5
|
+
1. 提供完整的文件重写功能
|
|
6
|
+
2. 支持创建新文件或完全重写现有文件
|
|
7
|
+
3. 实现原子操作:所有修改要么全部成功,要么全部回滚
|
|
8
|
+
4. 自动创建所需目录结构
|
|
9
|
+
|
|
10
|
+
核心特性:
|
|
11
|
+
- 支持不存在的文件和空文件处理
|
|
12
|
+
- 自动创建所需目录结构
|
|
13
|
+
- 完善的错误处理和回滚机制
|
|
14
|
+
- 保持文件格式和编码
|
|
15
|
+
"""
|
|
16
|
+
from typing import Any, Dict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FileRewriteTool:
|
|
20
|
+
name = "rewrite_file"
|
|
21
|
+
description = """文件重写工具,用于完全重写或创建文件
|
|
22
|
+
|
|
23
|
+
# 文件重写规范
|
|
24
|
+
|
|
25
|
+
## 重要提示
|
|
26
|
+
此工具用于完全重写文件内容或创建新文件。与edit_file不同,此工具会替换文件的全部内容。
|
|
27
|
+
|
|
28
|
+
## 基本使用
|
|
29
|
+
1. 指定需要重写的文件路径
|
|
30
|
+
2. 提供新的文件内容
|
|
31
|
+
3. 所有操作要么全部成功,要么全部失败并回滚
|
|
32
|
+
|
|
33
|
+
## 核心原则
|
|
34
|
+
1. **完整重写**:提供完整的文件内容,将替换原文件的所有内容
|
|
35
|
+
2. **格式保持**:
|
|
36
|
+
- 保持原始代码的缩进方式(空格或制表符)
|
|
37
|
+
- 保持原始代码的空行数量和位置
|
|
38
|
+
- 保持原始代码的行尾空格处理方式
|
|
39
|
+
- 不改变原始代码的换行风格
|
|
40
|
+
|
|
41
|
+
## 最佳实践
|
|
42
|
+
1. 确保提供格式良好的完整文件内容
|
|
43
|
+
2. 创建新文件时提供完整、格式良好的内容
|
|
44
|
+
3. 不要出现未实现的代码,如:TODO
|
|
45
|
+
"""
|
|
46
|
+
parameters = {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"file": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "需要重写的文件路径"
|
|
52
|
+
},
|
|
53
|
+
"content": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "新的文件内容,将完全替换原文件内容"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"required": ["file", "content"]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def __init__(self):
|
|
62
|
+
"""初始化文件重写工具"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
66
|
+
"""
|
|
67
|
+
执行文件重写操作,完全替换文件内容
|
|
68
|
+
|
|
69
|
+
参数:
|
|
70
|
+
file (str): 文件路径
|
|
71
|
+
content (str): 新的文件内容
|
|
72
|
+
|
|
73
|
+
返回:
|
|
74
|
+
dict: 包含执行结果的字典
|
|
75
|
+
{
|
|
76
|
+
"success": bool, # 是否成功完成重写
|
|
77
|
+
"stdout": str, # 标准输出信息
|
|
78
|
+
"stderr": str # 错误信息
|
|
79
|
+
}
|
|
80
|
+
"""
|
|
81
|
+
import os
|
|
82
|
+
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
83
|
+
|
|
84
|
+
stdout_messages = []
|
|
85
|
+
stderr_messages = []
|
|
86
|
+
success = True
|
|
87
|
+
|
|
88
|
+
file_path = args["file"]
|
|
89
|
+
new_content = args["content"]
|
|
90
|
+
|
|
91
|
+
# 创建已处理文件变量,用于失败时回滚
|
|
92
|
+
original_content = None
|
|
93
|
+
processed = False
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
file_exists = os.path.exists(file_path)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
# 如果文件存在,则读取原内容用于回滚
|
|
100
|
+
if file_exists:
|
|
101
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
102
|
+
original_content = f.read()
|
|
103
|
+
|
|
104
|
+
# 确保目录存在
|
|
105
|
+
os.makedirs(os.path.dirname(os.path.abspath(file_path)), exist_ok=True)
|
|
106
|
+
|
|
107
|
+
# 写入新内容
|
|
108
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
109
|
+
f.write(new_content)
|
|
110
|
+
|
|
111
|
+
processed = True
|
|
112
|
+
|
|
113
|
+
action = "创建并写入" if not file_exists else "成功重写"
|
|
114
|
+
stdout_message = f"文件 {file_path} {action}"
|
|
115
|
+
stdout_messages.append(stdout_message)
|
|
116
|
+
PrettyOutput.print(stdout_message, OutputType.SUCCESS)
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
|
|
120
|
+
stderr_messages.append(stderr_message)
|
|
121
|
+
PrettyOutput.print(stderr_message, OutputType.ERROR)
|
|
122
|
+
success = False
|
|
123
|
+
|
|
124
|
+
# 如果操作失败,回滚已修改的文件
|
|
125
|
+
if not success and processed:
|
|
126
|
+
rollback_message = "操作失败,正在回滚修改..."
|
|
127
|
+
stderr_messages.append(rollback_message)
|
|
128
|
+
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
if original_content is None:
|
|
132
|
+
# 如果是新创建的文件,则删除
|
|
133
|
+
if os.path.exists(file_path):
|
|
134
|
+
os.remove(file_path)
|
|
135
|
+
rollback_file_message = f"已删除新创建的文件: {file_path}"
|
|
136
|
+
else:
|
|
137
|
+
# 如果是修改的文件,则恢复原内容
|
|
138
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
139
|
+
f.write(original_content)
|
|
140
|
+
rollback_file_message = f"已回滚文件: {file_path}"
|
|
141
|
+
|
|
142
|
+
stderr_messages.append(rollback_file_message)
|
|
143
|
+
PrettyOutput.print(rollback_file_message, OutputType.INFO)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
rollback_error = f"回滚文件 {file_path} 失败: {str(e)}"
|
|
146
|
+
stderr_messages.append(rollback_error)
|
|
147
|
+
PrettyOutput.print(rollback_error, OutputType.ERROR)
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
"success": success,
|
|
151
|
+
"stdout": "\n".join(stdout_messages),
|
|
152
|
+
"stderr": "\n".join(stderr_messages)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
error_msg = f"文件重写操作失败: {str(e)}"
|
|
157
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
158
|
+
|
|
159
|
+
# 如果有已修改的文件,尝试回滚
|
|
160
|
+
if processed:
|
|
161
|
+
rollback_message = "操作失败,正在回滚修改..."
|
|
162
|
+
stderr_messages.append(rollback_message)
|
|
163
|
+
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
if original_content is None:
|
|
167
|
+
# 如果是新创建的文件,则删除
|
|
168
|
+
if os.path.exists(file_path):
|
|
169
|
+
os.remove(file_path)
|
|
170
|
+
stderr_messages.append(f"已删除新创建的文件: {file_path}")
|
|
171
|
+
else:
|
|
172
|
+
# 如果是修改的文件,则恢复原内容
|
|
173
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
174
|
+
f.write(original_content)
|
|
175
|
+
stderr_messages.append(f"已回滚文件: {file_path}")
|
|
176
|
+
except:
|
|
177
|
+
stderr_messages.append(f"回滚文件失败: {file_path}")
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
"success": False,
|
|
181
|
+
"stdout": "",
|
|
182
|
+
"stderr": error_msg + "\n" + "\n".join(stderr_messages)
|
|
183
|
+
}
|
jarvis/jarvis_utils/git_utils.py
CHANGED
|
@@ -12,7 +12,9 @@ import os
|
|
|
12
12
|
import re
|
|
13
13
|
import subprocess
|
|
14
14
|
from typing import List, Tuple, Dict
|
|
15
|
+
from jarvis.jarvis_utils.config import is_confirm_before_apply_patch
|
|
15
16
|
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
17
|
+
from jarvis.jarvis_utils.utils import user_confirm
|
|
16
18
|
def find_git_root(start_dir: str = ".") -> str:
|
|
17
19
|
"""
|
|
18
20
|
切换到给定路径的Git根目录,如果不是Git仓库则初始化。
|
|
@@ -92,6 +94,130 @@ def get_commits_between(start_hash: str, end_hash: str) -> List[Tuple[str, str]]
|
|
|
92
94
|
except Exception as e:
|
|
93
95
|
PrettyOutput.print(f"获取commit历史异常: {str(e)}", OutputType.ERROR)
|
|
94
96
|
return []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# 修改后的获取差异函数
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_diff() -> str:
|
|
103
|
+
"""使用git获取暂存区差异"""
|
|
104
|
+
import subprocess
|
|
105
|
+
|
|
106
|
+
# 初始化状态
|
|
107
|
+
need_reset = False
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# 暂存所有修改
|
|
111
|
+
subprocess.run(['git', 'add', '.'], check=True)
|
|
112
|
+
need_reset = True
|
|
113
|
+
|
|
114
|
+
# 获取差异
|
|
115
|
+
result = subprocess.run(
|
|
116
|
+
['git', 'diff', '--cached'],
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=False,
|
|
119
|
+
check=True
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# 解码输出
|
|
123
|
+
try:
|
|
124
|
+
ret = result.stdout.decode('utf-8')
|
|
125
|
+
except UnicodeDecodeError:
|
|
126
|
+
ret = result.stdout.decode('utf-8', errors='replace')
|
|
127
|
+
|
|
128
|
+
# 重置暂存区
|
|
129
|
+
subprocess.run(['git', "reset", "--mixed"], check=False)
|
|
130
|
+
return ret
|
|
131
|
+
|
|
132
|
+
except subprocess.CalledProcessError as e:
|
|
133
|
+
if need_reset:
|
|
134
|
+
subprocess.run(['git', "reset", "--mixed"], check=False)
|
|
135
|
+
return f"获取差异失败: {str(e)}"
|
|
136
|
+
except Exception as e:
|
|
137
|
+
if need_reset:
|
|
138
|
+
subprocess.run(['git', "reset", "--mixed"], check=False)
|
|
139
|
+
return f"发生意外错误: {str(e)}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def revert_file(filepath: str):
|
|
146
|
+
"""增强版git恢复,处理新文件"""
|
|
147
|
+
import subprocess
|
|
148
|
+
try:
|
|
149
|
+
# 检查文件是否在版本控制中
|
|
150
|
+
result = subprocess.run(
|
|
151
|
+
['git', 'ls-files', '--error-unmatch', filepath],
|
|
152
|
+
stderr=subprocess.PIPE,
|
|
153
|
+
text=False # 禁用自动文本解码
|
|
154
|
+
)
|
|
155
|
+
if result.returncode == 0:
|
|
156
|
+
subprocess.run(['git', 'checkout', 'HEAD',
|
|
157
|
+
'--', filepath], check=True)
|
|
158
|
+
else:
|
|
159
|
+
if os.path.exists(filepath):
|
|
160
|
+
os.remove(filepath)
|
|
161
|
+
subprocess.run(['git', 'clean', '-f', '--', filepath], check=True)
|
|
162
|
+
except subprocess.CalledProcessError as e:
|
|
163
|
+
error_msg = e.stderr.decode('utf-8', errors='replace') if e.stderr else str(e)
|
|
164
|
+
PrettyOutput.print(f"恢复文件失败: {error_msg}", OutputType.ERROR)
|
|
165
|
+
# 修改后的恢复函数
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def revert_change():
|
|
169
|
+
"""恢复所有未提交的修改到HEAD状态"""
|
|
170
|
+
import subprocess
|
|
171
|
+
try:
|
|
172
|
+
# 检查是否为空仓库
|
|
173
|
+
head_check = subprocess.run(
|
|
174
|
+
['git', 'rev-parse', '--verify', 'HEAD'],
|
|
175
|
+
stderr=subprocess.PIPE,
|
|
176
|
+
stdout=subprocess.PIPE
|
|
177
|
+
)
|
|
178
|
+
if head_check.returncode == 0:
|
|
179
|
+
subprocess.run(['git', 'reset', '--hard', 'HEAD'], check=True)
|
|
180
|
+
subprocess.run(['git', 'clean', '-fd'], check=True)
|
|
181
|
+
except subprocess.CalledProcessError as e:
|
|
182
|
+
return f"恢复更改失败: {str(e)}"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def handle_commit_workflow() -> bool:
|
|
186
|
+
"""Handle the git commit workflow and return the commit details.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
bool: 提交是否成功
|
|
190
|
+
"""
|
|
191
|
+
if is_confirm_before_apply_patch() and not user_confirm("是否要提交代码?", default=True):
|
|
192
|
+
revert_change()
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
import subprocess
|
|
196
|
+
try:
|
|
197
|
+
# 获取当前分支的提交总数
|
|
198
|
+
commit_count = subprocess.run(
|
|
199
|
+
['git', 'rev-list', '--count', 'HEAD'],
|
|
200
|
+
capture_output=True,
|
|
201
|
+
text=True
|
|
202
|
+
)
|
|
203
|
+
if commit_count.returncode != 0:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
commit_count = int(commit_count.stdout.strip())
|
|
207
|
+
|
|
208
|
+
# 暂存所有修改
|
|
209
|
+
subprocess.run(['git', 'add', '.'], check=True)
|
|
210
|
+
|
|
211
|
+
# 提交变更
|
|
212
|
+
subprocess.run(
|
|
213
|
+
['git', 'commit', '-m', f'CheckPoint #{commit_count + 1}'],
|
|
214
|
+
check=True
|
|
215
|
+
)
|
|
216
|
+
return True
|
|
217
|
+
except subprocess.CalledProcessError as e:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
95
221
|
def get_latest_commit_hash() -> str:
|
|
96
222
|
"""获取当前Git仓库的最新提交哈希值
|
|
97
223
|
|
|
@@ -149,3 +275,21 @@ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
|
149
275
|
result[current_file] = (start_line, end_line)
|
|
150
276
|
|
|
151
277
|
return result
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def is_file_in_git_repo(filepath: str) -> bool:
|
|
282
|
+
"""检查文件是否在当前Git仓库中"""
|
|
283
|
+
import subprocess
|
|
284
|
+
try:
|
|
285
|
+
# 获取Git仓库根目录
|
|
286
|
+
repo_root = subprocess.run(
|
|
287
|
+
['git', 'rev-parse', '--show-toplevel'],
|
|
288
|
+
capture_output=True,
|
|
289
|
+
text=True
|
|
290
|
+
).stdout.strip()
|
|
291
|
+
|
|
292
|
+
# 检查文件路径是否在仓库根目录下
|
|
293
|
+
return os.path.abspath(filepath).startswith(os.path.abspath(repo_root))
|
|
294
|
+
except:
|
|
295
|
+
return False
|