feedback-mcp 1.0.64__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 feedback-mcp might be problematic. Click here for more details.
- add_command_dialog.py +712 -0
- command.py +636 -0
- components/__init__.py +15 -0
- components/agent_popup.py +187 -0
- components/chat_history.py +281 -0
- components/command_popup.py +399 -0
- components/feedback_text_edit.py +1125 -0
- components/file_popup.py +417 -0
- components/history_popup.py +582 -0
- components/markdown_display.py +262 -0
- context_formatter.py +301 -0
- debug_logger.py +107 -0
- feedback_config.py +144 -0
- feedback_mcp-1.0.64.dist-info/METADATA +327 -0
- feedback_mcp-1.0.64.dist-info/RECORD +41 -0
- feedback_mcp-1.0.64.dist-info/WHEEL +5 -0
- feedback_mcp-1.0.64.dist-info/entry_points.txt +2 -0
- feedback_mcp-1.0.64.dist-info/top_level.txt +20 -0
- feedback_ui.py +1680 -0
- get_session_id.py +53 -0
- git_operations.py +579 -0
- ide_utils.py +313 -0
- path_config.py +89 -0
- post_task_hook.py +78 -0
- record.py +188 -0
- server.py +746 -0
- session_manager.py +368 -0
- stop_hook.py +87 -0
- tabs/__init__.py +87 -0
- tabs/base_tab.py +34 -0
- tabs/chat_history_style.qss +66 -0
- tabs/chat_history_tab.py +1000 -0
- tabs/chat_tab.py +1931 -0
- tabs/workspace_tab.py +502 -0
- ui/__init__.py +20 -0
- ui/__main__.py +16 -0
- ui/compact_feedback_ui.py +376 -0
- ui/session_list_ui.py +793 -0
- ui/styles/session_list.qss +158 -0
- window_position_manager.py +197 -0
- workspace_manager.py +253 -0
get_session_id.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
获取当前Claude会话ID的辅助脚本
|
|
4
|
+
通过多种方式尝试获取session_id
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import glob
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
|
|
12
|
+
def get_claude_session_id():
|
|
13
|
+
"""获取当前Claude会话ID"""
|
|
14
|
+
|
|
15
|
+
# 方法1: 从环境变量获取
|
|
16
|
+
session_id = os.environ.get('CLAUDE_SESSION_ID')
|
|
17
|
+
if session_id:
|
|
18
|
+
return session_id
|
|
19
|
+
|
|
20
|
+
# 方法2: 从最新的transcript文件获取
|
|
21
|
+
try:
|
|
22
|
+
claude_dir = Path.home() / '.claude'
|
|
23
|
+
projects_dir = claude_dir / 'projects'
|
|
24
|
+
|
|
25
|
+
if projects_dir.exists():
|
|
26
|
+
# 查找所有transcript文件
|
|
27
|
+
transcript_files = []
|
|
28
|
+
for project_dir in projects_dir.iterdir():
|
|
29
|
+
if project_dir.is_dir():
|
|
30
|
+
jsonl_files = list(project_dir.glob('*.jsonl'))
|
|
31
|
+
transcript_files.extend(jsonl_files)
|
|
32
|
+
|
|
33
|
+
if transcript_files:
|
|
34
|
+
# 按修改时间排序,获取最新的
|
|
35
|
+
latest_file = max(transcript_files, key=lambda f: f.stat().st_mtime)
|
|
36
|
+
|
|
37
|
+
# 检查是否是最近24小时内的文件
|
|
38
|
+
file_time = datetime.fromtimestamp(latest_file.stat().st_mtime)
|
|
39
|
+
if datetime.now() - file_time < timedelta(hours=24):
|
|
40
|
+
# 使用文件名作为session_id(去除.jsonl后缀)
|
|
41
|
+
session_id = latest_file.stem
|
|
42
|
+
return session_id
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
# 方法3: 从进程ID生成一个临时session_id
|
|
47
|
+
# 这确保同一进程内的所有调用使用相同的session_id
|
|
48
|
+
pid = os.getpid()
|
|
49
|
+
return f"pid-{pid}-session"
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
session_id = get_claude_session_id()
|
|
53
|
+
print(session_id)
|
git_operations.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git操作类 - 用于AI版本控制
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import re
|
|
7
|
+
import time
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import List, Dict, Optional, Tuple
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# 导入日志系统
|
|
13
|
+
try:
|
|
14
|
+
from debug_logger import get_debug_logger
|
|
15
|
+
logger = get_debug_logger()
|
|
16
|
+
except ImportError:
|
|
17
|
+
# 简单的日志备选方案
|
|
18
|
+
class SimpleLogger:
|
|
19
|
+
def log(self, msg, level="INFO"):
|
|
20
|
+
print(f"[{level}] {msg}")
|
|
21
|
+
logger = SimpleLogger()
|
|
22
|
+
|
|
23
|
+
# 导入统计上报功能
|
|
24
|
+
try:
|
|
25
|
+
from record import report_action, get_user_info
|
|
26
|
+
except ImportError:
|
|
27
|
+
report_action = None
|
|
28
|
+
get_user_info = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GitOperations:
|
|
32
|
+
"""Git操作封装类"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, project_path: str):
|
|
35
|
+
self.project_path = project_path
|
|
36
|
+
self.checkpoint_prefix = "Checkpoint"
|
|
37
|
+
|
|
38
|
+
def _report_stats(self, action: str, content: str = "", extra_data: dict = None):
|
|
39
|
+
"""统计上报辅助方法"""
|
|
40
|
+
if not report_action or not get_user_info:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
user_id, user_name = get_user_info()
|
|
45
|
+
if not user_name:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
stats_data = {
|
|
49
|
+
'user_name': user_name,
|
|
50
|
+
'action': action,
|
|
51
|
+
'content': content,
|
|
52
|
+
'workflow_name': 'Git操作',
|
|
53
|
+
'task_name': '版本控制'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# 添加额外数据
|
|
57
|
+
if extra_data:
|
|
58
|
+
stats_data.update(extra_data)
|
|
59
|
+
|
|
60
|
+
report_action(stats_data)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
# 静默处理统计上报错误,避免影响主要功能
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def _run_git_command(self, cmd: List[str]) -> Tuple[bool, str, str]:
|
|
66
|
+
"""执行Git命令"""
|
|
67
|
+
try:
|
|
68
|
+
logger.log(f"执行Git命令: {' '.join(cmd)}", "DEBUG")
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
cmd,
|
|
71
|
+
cwd=self.project_path,
|
|
72
|
+
capture_output=True,
|
|
73
|
+
text=True,
|
|
74
|
+
timeout=30,
|
|
75
|
+
encoding='utf-8' # 明确指定UTF-8编码
|
|
76
|
+
)
|
|
77
|
+
success = result.returncode == 0
|
|
78
|
+
logger.log(f"Git命令执行结果: 成功={success}, 返回码={result.returncode}", "DEBUG")
|
|
79
|
+
if result.stderr:
|
|
80
|
+
logger.log(f"Git命令stderr: {result.stderr}", "DEBUG")
|
|
81
|
+
return success, result.stdout, result.stderr
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.log(f"Git命令执行异常: {e}", "ERROR")
|
|
84
|
+
return False, "", str(e)
|
|
85
|
+
|
|
86
|
+
def get_current_branch(self) -> str:
|
|
87
|
+
"""获取当前分支名"""
|
|
88
|
+
success, stdout, _ = self._run_git_command(['git', 'branch', '--show-current'])
|
|
89
|
+
branch = stdout.strip() if success else "main"
|
|
90
|
+
logger.log(f"当前分支: {branch}", "DEBUG")
|
|
91
|
+
return branch
|
|
92
|
+
|
|
93
|
+
def _is_git_ignored(self, file_path: str) -> bool:
|
|
94
|
+
"""检查文件是否被git忽略"""
|
|
95
|
+
# 使用 git check-ignore 命令检查文件是否被忽略
|
|
96
|
+
cmd = ['git', 'check-ignore', file_path]
|
|
97
|
+
success, _, _ = self._run_git_command(cmd)
|
|
98
|
+
# 如果返回码为0,说明文件被忽略
|
|
99
|
+
return success
|
|
100
|
+
|
|
101
|
+
def commit(self, msg: str, files: Optional[List[str]] = None) -> Tuple[bool, str]:
|
|
102
|
+
"""创建检查点commit"""
|
|
103
|
+
logger.log(f"开始创建检查点: {msg}", "INFO")
|
|
104
|
+
logger.log(f"原始文件列表: {files}", "DEBUG")
|
|
105
|
+
|
|
106
|
+
# 检查files参数
|
|
107
|
+
if not files or len(files) == 0:
|
|
108
|
+
error_msg = "files 参数为必填项,必须指定要提交的文件列表"
|
|
109
|
+
logger.log(error_msg, "ERROR")
|
|
110
|
+
return False, error_msg
|
|
111
|
+
|
|
112
|
+
branch = self.get_current_branch()
|
|
113
|
+
timestamp = datetime.now().strftime("%H%M%S")
|
|
114
|
+
|
|
115
|
+
# 保持完整描述,不截断
|
|
116
|
+
# 注释掉长度限制,允许完整显示检查点标题
|
|
117
|
+
# if len(msg) > 20:
|
|
118
|
+
# msg = msg[:17] + "..."
|
|
119
|
+
|
|
120
|
+
commit_msg = f"{self.checkpoint_prefix}-{branch}-{timestamp}: {msg}"
|
|
121
|
+
logger.log(f"检查点提交消息: {commit_msg}", "DEBUG")
|
|
122
|
+
|
|
123
|
+
# 验证指定文件是否存在
|
|
124
|
+
for file in files:
|
|
125
|
+
if not os.path.exists(os.path.join(self.project_path, file)):
|
|
126
|
+
error_msg = f"文件 {file} 不存在"
|
|
127
|
+
logger.log(error_msg, "ERROR")
|
|
128
|
+
return False, error_msg
|
|
129
|
+
|
|
130
|
+
# 检查并过滤被git忽略的文件
|
|
131
|
+
valid_files = []
|
|
132
|
+
ignored_files = []
|
|
133
|
+
for file in files:
|
|
134
|
+
# 直接使用相对路径检查
|
|
135
|
+
if self._is_git_ignored(file):
|
|
136
|
+
ignored_files.append(file)
|
|
137
|
+
logger.log(f"文件 {file} 被 git 忽略,将跳过", "WARNING")
|
|
138
|
+
else:
|
|
139
|
+
valid_files.append(file)
|
|
140
|
+
|
|
141
|
+
# 如果有被忽略的文件,记录日志
|
|
142
|
+
if ignored_files:
|
|
143
|
+
logger.log(f"以下文件被 git 忽略,已自动剔除: {', '.join(ignored_files)}", "WARNING")
|
|
144
|
+
|
|
145
|
+
# 如果所有文件都被忽略,返回错误
|
|
146
|
+
if not valid_files:
|
|
147
|
+
error_msg = "所有指定的文件都被 git 忽略,无法创建检查点"
|
|
148
|
+
logger.log(error_msg, "ERROR")
|
|
149
|
+
return False, error_msg
|
|
150
|
+
|
|
151
|
+
# 更新files为有效文件列表
|
|
152
|
+
files = valid_files
|
|
153
|
+
logger.log(f"过滤后的文件列表: {files}", "DEBUG")
|
|
154
|
+
|
|
155
|
+
# 第一步:清空暂存区(将现有暂存的文件移出)
|
|
156
|
+
reset_success, _, reset_stderr = self._run_git_command(['git', 'reset', 'HEAD'])
|
|
157
|
+
if not reset_success:
|
|
158
|
+
error_msg = f"清空暂存区失败: {reset_stderr}"
|
|
159
|
+
logger.log(error_msg, "ERROR")
|
|
160
|
+
return False, error_msg
|
|
161
|
+
|
|
162
|
+
# 短暂等待确保git状态稳定
|
|
163
|
+
time.sleep(0.1)
|
|
164
|
+
|
|
165
|
+
# 第二步:添加指定文件到暂存区
|
|
166
|
+
add_cmd = ['git', 'add'] + files
|
|
167
|
+
add_success, add_stdout, add_stderr = self._run_git_command(add_cmd)
|
|
168
|
+
if not add_success:
|
|
169
|
+
error_msg = f"添加文件到暂存区失败: {add_stderr}"
|
|
170
|
+
logger.log(error_msg, "ERROR")
|
|
171
|
+
return False, error_msg
|
|
172
|
+
|
|
173
|
+
logger.log(f"已将 {len(files)} 个文件添加到暂存区", "DEBUG")
|
|
174
|
+
|
|
175
|
+
# 第三步:提交暂存区的文件
|
|
176
|
+
commit_cmd = ['git', 'commit', '-m', commit_msg]
|
|
177
|
+
success, stdout, stderr = self._run_git_command(commit_cmd)
|
|
178
|
+
if success:
|
|
179
|
+
# 统计上报:创建检查点
|
|
180
|
+
self._report_stats("checkpoint_create", f"创建检查点: {msg}", {
|
|
181
|
+
'step_name': '创建检查点',
|
|
182
|
+
'content': msg,
|
|
183
|
+
'task_id': f'{branch}_{timestamp}',
|
|
184
|
+
'files_count': len(files)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
success_msg = f"检查点创建成功: {commit_msg}"
|
|
188
|
+
if ignored_files:
|
|
189
|
+
success_msg += f"\n(已自动剔除被忽略的文件: {', '.join(ignored_files)})"
|
|
190
|
+
logger.log(success_msg, "SUCCESS")
|
|
191
|
+
return True, success_msg
|
|
192
|
+
else:
|
|
193
|
+
error_msg = f"提交失败: {stderr}"
|
|
194
|
+
logger.log(error_msg, "ERROR")
|
|
195
|
+
return False, error_msg
|
|
196
|
+
|
|
197
|
+
def get_checkpoints(self) -> List[Dict[str, str]]:
|
|
198
|
+
"""获取所有检查点记录"""
|
|
199
|
+
logger.log("获取检查点列表", "DEBUG")
|
|
200
|
+
|
|
201
|
+
# 获取详细的commit信息,包含完整日期时间
|
|
202
|
+
cmd = ['git', 'log', '--grep', f'^{self.checkpoint_prefix}-', '-n', '50', '--pretty=format:%H|%ci|%s']
|
|
203
|
+
success, stdout, _ = self._run_git_command(cmd)
|
|
204
|
+
|
|
205
|
+
if not success:
|
|
206
|
+
logger.log("获取检查点列表失败", "ERROR")
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
checkpoints = []
|
|
210
|
+
for line in stdout.strip().split('\n'):
|
|
211
|
+
if line:
|
|
212
|
+
parts = line.split('|', 2)
|
|
213
|
+
if len(parts) >= 3:
|
|
214
|
+
commit_hash = parts[0]
|
|
215
|
+
commit_date = parts[1] # ISO 8601格式:2024-01-15 14:30:25 +0800
|
|
216
|
+
message = parts[2]
|
|
217
|
+
|
|
218
|
+
# 解析检查点信息
|
|
219
|
+
match = re.match(rf'{self.checkpoint_prefix}-(.+?)-(\d{{6}}): (.+)', message)
|
|
220
|
+
if match:
|
|
221
|
+
branch, timestamp, description = match.groups()
|
|
222
|
+
|
|
223
|
+
# 格式化完整日期时间
|
|
224
|
+
try:
|
|
225
|
+
# 解析Git的ISO 8601时间格式
|
|
226
|
+
dt = datetime.fromisoformat(commit_date.replace(' +0800', '').replace(' +0000', ''))
|
|
227
|
+
date_str = dt.strftime("%Y-%m-%d")
|
|
228
|
+
time_str = dt.strftime("%H:%M:%S")
|
|
229
|
+
full_datetime = dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
230
|
+
except:
|
|
231
|
+
# 备选格式化
|
|
232
|
+
date_str = commit_date.split(' ')[0] if ' ' in commit_date else "未知日期"
|
|
233
|
+
time_str = timestamp
|
|
234
|
+
full_datetime = commit_date
|
|
235
|
+
|
|
236
|
+
checkpoints.append({
|
|
237
|
+
'hash': commit_hash,
|
|
238
|
+
'branch': branch,
|
|
239
|
+
'date': date_str,
|
|
240
|
+
'time': time_str,
|
|
241
|
+
'datetime': full_datetime,
|
|
242
|
+
'description': description,
|
|
243
|
+
'message': message
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
logger.log(f"找到 {len(checkpoints)} 个检查点", "DEBUG")
|
|
247
|
+
return checkpoints
|
|
248
|
+
|
|
249
|
+
def get_checkpoint_files(self, commit_hash: str) -> List[str]:
|
|
250
|
+
"""获取检查点涉及的文件变更列表"""
|
|
251
|
+
logger.log(f"获取检查点文件列表: {commit_hash}", "DEBUG")
|
|
252
|
+
|
|
253
|
+
# 设置Git配置以正确显示中文文件名
|
|
254
|
+
config_cmd = ['git', 'config', 'core.quotepath', 'false']
|
|
255
|
+
self._run_git_command(config_cmd)
|
|
256
|
+
|
|
257
|
+
# 获取该commit相对于父commit的文件变更
|
|
258
|
+
cmd = ['git', 'diff', '--name-status', f'{commit_hash}^', commit_hash]
|
|
259
|
+
success, stdout, stderr = self._run_git_command(cmd)
|
|
260
|
+
|
|
261
|
+
if not success:
|
|
262
|
+
logger.log(f"获取文件列表失败: {stderr}", "ERROR")
|
|
263
|
+
# 如果是第一个commit,尝试显示所有文件
|
|
264
|
+
cmd = ['git', 'show', '--name-status', '--pretty=format:', commit_hash]
|
|
265
|
+
success, stdout, _ = self._run_git_command(cmd)
|
|
266
|
+
if not success:
|
|
267
|
+
return []
|
|
268
|
+
|
|
269
|
+
files = []
|
|
270
|
+
for line in stdout.strip().split('\n'):
|
|
271
|
+
if line.strip():
|
|
272
|
+
parts = line.split('\t', 1)
|
|
273
|
+
if len(parts) >= 2:
|
|
274
|
+
status = parts[0]
|
|
275
|
+
filename = parts[1]
|
|
276
|
+
|
|
277
|
+
# 处理Git转义的文件名(如果有引号包围)
|
|
278
|
+
if filename.startswith('"') and filename.endswith('"'):
|
|
279
|
+
try:
|
|
280
|
+
# 移除引号并解码转义字符
|
|
281
|
+
filename = filename[1:-1].encode('utf-8').decode('unicode_escape')
|
|
282
|
+
except:
|
|
283
|
+
# 如果解码失败,保持原样
|
|
284
|
+
filename = filename[1:-1]
|
|
285
|
+
|
|
286
|
+
# 格式化状态标识
|
|
287
|
+
status_map = {
|
|
288
|
+
'A': '+ 新增',
|
|
289
|
+
'M': '• 修改',
|
|
290
|
+
'D': '- 删除',
|
|
291
|
+
'R': '→ 重命名',
|
|
292
|
+
'C': '→ 复制'
|
|
293
|
+
}
|
|
294
|
+
status_text = status_map.get(status[0], f'{status[0]} 其他')
|
|
295
|
+
files.append(f"{status_text} {filename}")
|
|
296
|
+
elif len(parts) == 1:
|
|
297
|
+
# 只有文件名的情况
|
|
298
|
+
filename = parts[0]
|
|
299
|
+
if filename.startswith('"') and filename.endswith('"'):
|
|
300
|
+
try:
|
|
301
|
+
filename = filename[1:-1].encode('utf-8').decode('unicode_escape')
|
|
302
|
+
except:
|
|
303
|
+
filename = filename[1:-1]
|
|
304
|
+
files.append(f"• {filename}")
|
|
305
|
+
|
|
306
|
+
logger.log(f"检查点 {commit_hash} 包含 {len(files)} 个文件变更", "DEBUG")
|
|
307
|
+
return files
|
|
308
|
+
|
|
309
|
+
def get_reverted_info(self, target_commit_hash: str) -> Dict[str, any]:
|
|
310
|
+
"""获取回退操作的详细信息"""
|
|
311
|
+
logger.log(f"获取回退信息: 目标{target_commit_hash}", "DEBUG")
|
|
312
|
+
|
|
313
|
+
# 获取当前HEAD
|
|
314
|
+
success, current_head, _ = self._run_git_command(['git', 'rev-parse', 'HEAD'])
|
|
315
|
+
if not success:
|
|
316
|
+
return {}
|
|
317
|
+
|
|
318
|
+
current_head = current_head.strip()
|
|
319
|
+
|
|
320
|
+
# 获取被撤销的检查点列表(不限制数量)
|
|
321
|
+
cmd = ['git', 'log', '--oneline', '--grep', f'^{self.checkpoint_prefix}-',
|
|
322
|
+
f'{target_commit_hash}..{current_head}']
|
|
323
|
+
success, stdout, _ = self._run_git_command(cmd)
|
|
324
|
+
|
|
325
|
+
reverted_checkpoints = []
|
|
326
|
+
if success and stdout.strip():
|
|
327
|
+
for line in stdout.strip().split('\n'):
|
|
328
|
+
if line:
|
|
329
|
+
parts = line.split(' ', 1)
|
|
330
|
+
if len(parts) >= 2:
|
|
331
|
+
hash_short = parts[0]
|
|
332
|
+
message = parts[1]
|
|
333
|
+
match = re.match(rf'{self.checkpoint_prefix}-(.+?)-(\d{{6}}): (.+)', message)
|
|
334
|
+
if match:
|
|
335
|
+
_, _, description = match.groups()
|
|
336
|
+
reverted_checkpoints.append({
|
|
337
|
+
'hash': hash_short,
|
|
338
|
+
'description': description,
|
|
339
|
+
'message': message
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
# 获取被撤销的文件变更
|
|
343
|
+
cmd = ['git', 'diff', '--name-status', target_commit_hash, current_head]
|
|
344
|
+
success, stdout, _ = self._run_git_command(cmd)
|
|
345
|
+
|
|
346
|
+
reverted_files = []
|
|
347
|
+
if success and stdout.strip():
|
|
348
|
+
for line in stdout.strip().split('\n'):
|
|
349
|
+
if line.strip():
|
|
350
|
+
parts = line.split('\t', 1)
|
|
351
|
+
if len(parts) >= 2:
|
|
352
|
+
status = parts[0]
|
|
353
|
+
filename = parts[1]
|
|
354
|
+
|
|
355
|
+
# 处理文件名转义
|
|
356
|
+
if filename.startswith('"') and filename.endswith('"'):
|
|
357
|
+
try:
|
|
358
|
+
filename = filename[1:-1].encode('utf-8').decode('unicode_escape')
|
|
359
|
+
except:
|
|
360
|
+
filename = filename[1:-1]
|
|
361
|
+
|
|
362
|
+
status_map = {
|
|
363
|
+
'A': '新增的',
|
|
364
|
+
'M': '修改的',
|
|
365
|
+
'D': '删除的',
|
|
366
|
+
'R': '重命名的',
|
|
367
|
+
'C': '复制的'
|
|
368
|
+
}
|
|
369
|
+
status_text = status_map.get(status[0], '变更的')
|
|
370
|
+
reverted_files.append(f"{status_text}{filename}")
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
'reverted_checkpoints': reverted_checkpoints,
|
|
374
|
+
'reverted_files': reverted_files,
|
|
375
|
+
'checkpoint_count': len(reverted_checkpoints),
|
|
376
|
+
'file_count': len(reverted_files)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
def reset_to_checkpoint(self, commit_hash: str) -> Tuple[bool, str, Dict[str, any]]:
|
|
380
|
+
"""回退到指定检查点"""
|
|
381
|
+
logger.log(f"开始回退到检查点: {commit_hash}", "INFO")
|
|
382
|
+
|
|
383
|
+
# 在回退前获取撤销信息
|
|
384
|
+
revert_info = self.get_reverted_info(commit_hash)
|
|
385
|
+
|
|
386
|
+
success, _, stderr = self._run_git_command(['git', 'reset', '--hard', commit_hash])
|
|
387
|
+
if success:
|
|
388
|
+
success_msg = f"已回退到检查点: {commit_hash}"
|
|
389
|
+
logger.log(success_msg, "SUCCESS")
|
|
390
|
+
return True, success_msg, revert_info
|
|
391
|
+
else:
|
|
392
|
+
error_msg = f"回退失败: {stderr}"
|
|
393
|
+
logger.log(error_msg, "ERROR")
|
|
394
|
+
return False, error_msg, {}
|
|
395
|
+
|
|
396
|
+
def soft_reset_one_commit(self) -> Tuple[bool, str]:
|
|
397
|
+
"""软重置一个提交(取消最后一次提交,保留代码变更)"""
|
|
398
|
+
logger.log("开始软重置最后一次提交", "INFO")
|
|
399
|
+
|
|
400
|
+
# 检查是否有提交可以重置
|
|
401
|
+
success, _, _ = self._run_git_command(['git', 'rev-parse', 'HEAD^'])
|
|
402
|
+
if not success:
|
|
403
|
+
error_msg = "没有父提交可以重置到"
|
|
404
|
+
logger.log(error_msg, "ERROR")
|
|
405
|
+
return False, error_msg
|
|
406
|
+
|
|
407
|
+
# 执行软重置到HEAD^(上一个提交)
|
|
408
|
+
success, _, stderr = self._run_git_command(['git', 'reset', '--soft', 'HEAD^'])
|
|
409
|
+
if success:
|
|
410
|
+
success_msg = "已取消最后一次提交,代码变更已保留"
|
|
411
|
+
logger.log(success_msg, "SUCCESS")
|
|
412
|
+
return True, success_msg
|
|
413
|
+
else:
|
|
414
|
+
error_msg = f"软重置失败: {stderr}"
|
|
415
|
+
logger.log(error_msg, "ERROR")
|
|
416
|
+
return False, error_msg
|
|
417
|
+
|
|
418
|
+
def hard_reset_one_commit(self) -> Tuple[bool, str]:
|
|
419
|
+
"""硬重置一个提交(删除最后一次提交和所有文件变更)"""
|
|
420
|
+
logger.log("开始硬重置最后一次提交", "INFO")
|
|
421
|
+
|
|
422
|
+
# 检查是否有提交可以重置
|
|
423
|
+
success, _, _ = self._run_git_command(['git', 'rev-parse', 'HEAD^'])
|
|
424
|
+
if not success:
|
|
425
|
+
error_msg = "没有父提交可以重置到"
|
|
426
|
+
logger.log(error_msg, "ERROR")
|
|
427
|
+
return False, error_msg
|
|
428
|
+
|
|
429
|
+
# 执行硬重置到HEAD^(上一个提交)
|
|
430
|
+
success, _, stderr = self._run_git_command(['git', 'reset', '--hard', 'HEAD^'])
|
|
431
|
+
if success:
|
|
432
|
+
success_msg = "已删除最后一次提交和所有文件变更"
|
|
433
|
+
logger.log(success_msg, "SUCCESS")
|
|
434
|
+
return True, success_msg
|
|
435
|
+
else:
|
|
436
|
+
error_msg = f"硬重置失败: {stderr}"
|
|
437
|
+
logger.log(error_msg, "ERROR")
|
|
438
|
+
return False, error_msg
|
|
439
|
+
|
|
440
|
+
def squash_commit(self, msg: str) -> Tuple[bool, str]:
|
|
441
|
+
"""汇总提交 - 将检查点合并为最终commit"""
|
|
442
|
+
logger.log(f"开始汇总提交: {msg}", "INFO")
|
|
443
|
+
|
|
444
|
+
# 获取所有检查点
|
|
445
|
+
checkpoints = self.get_checkpoints()
|
|
446
|
+
if not checkpoints:
|
|
447
|
+
error_msg = "没有找到检查点记录"
|
|
448
|
+
logger.log(error_msg, "ERROR")
|
|
449
|
+
return False, error_msg
|
|
450
|
+
|
|
451
|
+
# 获取第一个检查点的父commit
|
|
452
|
+
first_checkpoint = checkpoints[-1]['hash']
|
|
453
|
+
cmd = ['git', 'rev-parse', f'{first_checkpoint}^']
|
|
454
|
+
success, parent_hash, stderr = self._run_git_command(cmd)
|
|
455
|
+
|
|
456
|
+
if not success:
|
|
457
|
+
error_msg = f"无法找到父commit: {stderr}"
|
|
458
|
+
logger.log(error_msg, "ERROR")
|
|
459
|
+
return False, error_msg
|
|
460
|
+
|
|
461
|
+
parent_hash = parent_hash.strip()
|
|
462
|
+
|
|
463
|
+
# 软重置到父commit
|
|
464
|
+
success, _, stderr = self._run_git_command(['git', 'reset', '--soft', parent_hash])
|
|
465
|
+
if not success:
|
|
466
|
+
error_msg = f"软重置失败: {stderr}"
|
|
467
|
+
logger.log(error_msg, "ERROR")
|
|
468
|
+
return False, error_msg
|
|
469
|
+
|
|
470
|
+
# 提交汇总
|
|
471
|
+
success, _, stderr = self._run_git_command(['git', 'commit', '-m', msg])
|
|
472
|
+
if success:
|
|
473
|
+
success_msg = f"汇总提交成功: {msg}"
|
|
474
|
+
logger.log(success_msg, "SUCCESS")
|
|
475
|
+
return True, success_msg
|
|
476
|
+
else:
|
|
477
|
+
error_msg = f"汇总提交失败: {stderr}"
|
|
478
|
+
logger.log(error_msg, "ERROR")
|
|
479
|
+
return False, error_msg
|
|
480
|
+
|
|
481
|
+
def get_status(self) -> Dict[str, any]:
|
|
482
|
+
"""获取Git状态"""
|
|
483
|
+
# 获取修改的文件数量
|
|
484
|
+
success, stdout, _ = self._run_git_command(['git', 'status', '--porcelain'])
|
|
485
|
+
modified_files = len(stdout.strip().split('\n')) if stdout.strip() else 0
|
|
486
|
+
|
|
487
|
+
# 获取当前分支
|
|
488
|
+
branch = self.get_current_branch()
|
|
489
|
+
|
|
490
|
+
# 获取检查点数量
|
|
491
|
+
checkpoints = self.get_checkpoints()
|
|
492
|
+
|
|
493
|
+
status = {
|
|
494
|
+
'branch': branch,
|
|
495
|
+
'modified_files': modified_files,
|
|
496
|
+
'checkpoint_count': len(checkpoints)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
logger.log(f"Git状态: {status}", "DEBUG")
|
|
500
|
+
return status
|
|
501
|
+
|
|
502
|
+
def delete_all_checkpoints(self) -> Tuple[bool, str, int]:
|
|
503
|
+
"""删除所有检查点记录
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Tuple[bool, str, int]: (是否成功, 消息, 删除数量)
|
|
507
|
+
"""
|
|
508
|
+
logger.log("开始删除所有检查点", "INFO")
|
|
509
|
+
|
|
510
|
+
# 先获取所有检查点
|
|
511
|
+
checkpoints = self.get_checkpoints()
|
|
512
|
+
if not checkpoints:
|
|
513
|
+
return True, "没有检查点需要删除", 0
|
|
514
|
+
|
|
515
|
+
checkpoint_count = len(checkpoints)
|
|
516
|
+
|
|
517
|
+
# 找到第一个检查点的父commit(检查点之前的提交)
|
|
518
|
+
oldest_checkpoint = checkpoints[-1] # 最后一个是最老的
|
|
519
|
+
oldest_hash = oldest_checkpoint['hash']
|
|
520
|
+
|
|
521
|
+
# 获取第一个检查点的父commit
|
|
522
|
+
cmd = ['git', 'rev-parse', f'{oldest_hash}^']
|
|
523
|
+
success, parent_hash, stderr = self._run_git_command(cmd)
|
|
524
|
+
|
|
525
|
+
if not success:
|
|
526
|
+
# 如果没有父commit,说明检查点是第一个提交,回退到空仓库状态
|
|
527
|
+
logger.log("检查点是第一个提交,无法删除", "ERROR")
|
|
528
|
+
return False, "无法删除:检查点是仓库的第一个提交", 0
|
|
529
|
+
|
|
530
|
+
parent_hash = parent_hash.strip()
|
|
531
|
+
|
|
532
|
+
# 使用hard reset删除所有检查点,回退到第一个检查点之前的状态
|
|
533
|
+
success, _, stderr = self._run_git_command(['git', 'reset', '--hard', parent_hash])
|
|
534
|
+
|
|
535
|
+
if success:
|
|
536
|
+
success_msg = f"成功删除 {checkpoint_count} 个检查点"
|
|
537
|
+
logger.log(success_msg, "SUCCESS")
|
|
538
|
+
return True, success_msg, checkpoint_count
|
|
539
|
+
else:
|
|
540
|
+
error_msg = f"删除检查点失败: {stderr}"
|
|
541
|
+
logger.log(error_msg, "ERROR")
|
|
542
|
+
return False, error_msg, 0
|
|
543
|
+
|
|
544
|
+
def get_current_commit_info(self) -> Dict[str, str]:
|
|
545
|
+
"""获取当前HEAD指向的commit信息"""
|
|
546
|
+
logger.log("获取当前commit信息", "DEBUG")
|
|
547
|
+
|
|
548
|
+
# 获取当前commit的详细信息
|
|
549
|
+
cmd = ['git', 'log', '-1', '--pretty=format:%H|%ci|%s|%an']
|
|
550
|
+
success, stdout, stderr = self._run_git_command(cmd)
|
|
551
|
+
|
|
552
|
+
if not success:
|
|
553
|
+
logger.log(f"获取当前commit信息失败: {stderr}", "ERROR")
|
|
554
|
+
return {}
|
|
555
|
+
|
|
556
|
+
if stdout.strip():
|
|
557
|
+
parts = stdout.strip().split('|', 3)
|
|
558
|
+
if len(parts) >= 3:
|
|
559
|
+
commit_hash = parts[0]
|
|
560
|
+
commit_date = parts[1]
|
|
561
|
+
commit_message = parts[2]
|
|
562
|
+
author = parts[3] if len(parts) > 3 else "未知"
|
|
563
|
+
|
|
564
|
+
# 格式化日期时间
|
|
565
|
+
try:
|
|
566
|
+
dt = datetime.fromisoformat(commit_date.replace(' +0800', '').replace(' +0000', ''))
|
|
567
|
+
formatted_date = dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
568
|
+
except:
|
|
569
|
+
formatted_date = commit_date
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
'hash': commit_hash,
|
|
573
|
+
'hash_short': commit_hash[:8],
|
|
574
|
+
'date': formatted_date,
|
|
575
|
+
'message': commit_message,
|
|
576
|
+
'author': author
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return {}
|