git-ai-shen 1.0.3 → 1.0.4
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.
- package/lib/core/git-operations.js +41 -66
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const simpleGit = require('simple-git');
|
|
2
|
-
const fs = require('fs')
|
|
2
|
+
const fs = require('fs'); // 导入同步版本的 fs
|
|
3
|
+
const fsPromises = require('fs').promises; // 如果需要 promise 版本,单独命名
|
|
3
4
|
const path = require('path');
|
|
4
5
|
const { exec } = require('child_process');
|
|
5
6
|
const util = require('util');
|
|
@@ -10,6 +11,7 @@ class GitOperations {
|
|
|
10
11
|
this.git = simpleGit(repoPath);
|
|
11
12
|
this.repoPath = repoPath;
|
|
12
13
|
}
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* 获取文件的详细变更信息(哪些行被修改)
|
|
15
17
|
* @param {string} filePath 文件路径
|
|
@@ -17,22 +19,22 @@ class GitOperations {
|
|
|
17
19
|
*/
|
|
18
20
|
async getFileDiff(filePath) {
|
|
19
21
|
try {
|
|
20
|
-
//
|
|
22
|
+
// 使用 -- 分隔符明确指定是文件路径
|
|
21
23
|
const diff = await this.git.diff(['--', filePath]);
|
|
22
24
|
|
|
23
|
-
// 如果返回空,可能是因为文件没被 git
|
|
25
|
+
// 如果返回空,可能是因为文件没被 git 跟踪
|
|
24
26
|
if (!diff) {
|
|
25
27
|
const fullPath = path.join(this.repoPath, filePath);
|
|
28
|
+
// 使用同步的 fs.existsSync
|
|
26
29
|
if (fs.existsSync(fullPath)) {
|
|
27
|
-
//
|
|
28
|
-
const noIndexDiff = await this.git.diff(['--no-index', '--', '
|
|
30
|
+
// Windows 上用 NUL 代替 /dev/null
|
|
31
|
+
const noIndexDiff = await this.git.diff(['--no-index', '--', 'NUL', filePath]);
|
|
29
32
|
return this.parseDiff(noIndexDiff, filePath);
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
return this.parseDiff(diff, filePath);
|
|
34
37
|
} catch (error) {
|
|
35
|
-
// 如果出错,返回一个空的 diff 对象而不是 null
|
|
36
38
|
console.log(`获取 diff 失败: ${error.message}`);
|
|
37
39
|
return {
|
|
38
40
|
file: filePath,
|
|
@@ -43,35 +45,43 @@ class GitOperations {
|
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
}
|
|
48
|
+
|
|
46
49
|
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
* 获取已暂存文件的详细变更信息
|
|
51
|
+
* @param {string} filePath 文件路径
|
|
52
|
+
* @returns {Promise<Object>} 变更详情
|
|
53
|
+
*/
|
|
51
54
|
async getStagedDiff(filePath) {
|
|
52
55
|
try {
|
|
53
|
-
//
|
|
54
|
-
const diff = await this.git.diff(['--cached', filePath]);
|
|
55
|
-
|
|
56
|
-
// 解析 diff 信息
|
|
56
|
+
// 使用 -- 分隔符
|
|
57
|
+
const diff = await this.git.diff(['--cached', '--', filePath]);
|
|
57
58
|
return this.parseDiff(diff, filePath);
|
|
58
59
|
} catch (error) {
|
|
59
60
|
console.error(`获取暂存 diff 失败: ${error.message}`);
|
|
60
|
-
return
|
|
61
|
+
return {
|
|
62
|
+
file: filePath,
|
|
63
|
+
changes: [],
|
|
64
|
+
summary: '获取失败',
|
|
65
|
+
addCount: 0,
|
|
66
|
+
deleteCount: 0
|
|
67
|
+
};
|
|
61
68
|
}
|
|
62
69
|
}
|
|
70
|
+
|
|
63
71
|
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
* 解析 git diff 输出
|
|
73
|
+
* @param {string} diff git diff 的输出
|
|
74
|
+
* @param {string} filePath 文件路径
|
|
75
|
+
* @returns {Object} 解析后的变更信息
|
|
76
|
+
*/
|
|
69
77
|
parseDiff(diff, filePath) {
|
|
70
78
|
if (!diff) {
|
|
71
79
|
return {
|
|
72
80
|
file: filePath,
|
|
73
81
|
changes: [],
|
|
74
|
-
summary: '无变更'
|
|
82
|
+
summary: '无变更',
|
|
83
|
+
addCount: 0,
|
|
84
|
+
deleteCount: 0
|
|
75
85
|
};
|
|
76
86
|
}
|
|
77
87
|
|
|
@@ -79,9 +89,7 @@ class GitOperations {
|
|
|
79
89
|
const changes = [];
|
|
80
90
|
let currentHunk = null;
|
|
81
91
|
|
|
82
|
-
// 解析 diff 的每一行
|
|
83
92
|
for (const line of lines) {
|
|
84
|
-
// 匹配 hunk 头信息,例如:@@ -1,4 +1,5 @@
|
|
85
93
|
const hunkMatch = line.match(/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@/);
|
|
86
94
|
if (hunkMatch) {
|
|
87
95
|
currentHunk = {
|
|
@@ -97,21 +105,18 @@ class GitOperations {
|
|
|
97
105
|
|
|
98
106
|
if (currentHunk) {
|
|
99
107
|
if (line.startsWith('+')) {
|
|
100
|
-
// 新增的行
|
|
101
108
|
currentHunk.changes.push({
|
|
102
109
|
type: 'add',
|
|
103
110
|
content: line.substring(1),
|
|
104
111
|
lineNumber: currentHunk.newStart + currentHunk.changes.filter(c => c.type !== 'delete').length
|
|
105
112
|
});
|
|
106
113
|
} else if (line.startsWith('-')) {
|
|
107
|
-
// 删除的行
|
|
108
114
|
currentHunk.changes.push({
|
|
109
115
|
type: 'delete',
|
|
110
116
|
content: line.substring(1),
|
|
111
117
|
lineNumber: currentHunk.oldStart + currentHunk.changes.filter(c => c.type !== 'add').length
|
|
112
118
|
});
|
|
113
119
|
} else if (!line.startsWith('\\')) {
|
|
114
|
-
// 上下文行
|
|
115
120
|
currentHunk.changes.push({
|
|
116
121
|
type: 'context',
|
|
117
122
|
content: line.substring(1)
|
|
@@ -120,7 +125,6 @@ class GitOperations {
|
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
// 生成变更摘要
|
|
124
128
|
const addCount = changes.flatMap(h => h.changes).filter(c => c.type === 'add').length;
|
|
125
129
|
const deleteCount = changes.flatMap(h => h.changes).filter(c => c.type === 'delete').length;
|
|
126
130
|
|
|
@@ -132,6 +136,7 @@ class GitOperations {
|
|
|
132
136
|
deleteCount
|
|
133
137
|
};
|
|
134
138
|
}
|
|
139
|
+
|
|
135
140
|
/**
|
|
136
141
|
* 获取所有变更文件的详细 diff
|
|
137
142
|
* @param {Array<string>} files 文件列表
|
|
@@ -140,7 +145,6 @@ class GitOperations {
|
|
|
140
145
|
*/
|
|
141
146
|
async getDiffs(files, staged = false) {
|
|
142
147
|
const diffs = {};
|
|
143
|
-
|
|
144
148
|
for (const file of files) {
|
|
145
149
|
if (staged) {
|
|
146
150
|
diffs[file] = await this.getStagedDiff(file);
|
|
@@ -148,9 +152,9 @@ class GitOperations {
|
|
|
148
152
|
diffs[file] = await this.getFileDiff(file);
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
|
-
|
|
152
155
|
return diffs;
|
|
153
156
|
}
|
|
157
|
+
|
|
154
158
|
async isGitRepo() {
|
|
155
159
|
try {
|
|
156
160
|
return await this.git.checkIsRepo();
|
|
@@ -190,29 +194,15 @@ class GitOperations {
|
|
|
190
194
|
|
|
191
195
|
async pull(remote = 'origin', branch = null, sshPassword = null) {
|
|
192
196
|
try {
|
|
193
|
-
if (!branch)
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// 如果有 SSH 密码,使用 expect 脚本处理
|
|
198
|
-
if (sshPassword) {
|
|
199
|
-
return await this.pullWithPassword(remote, branch, sshPassword);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 正常拉取
|
|
197
|
+
if (!branch) branch = await this.getCurrentBranch();
|
|
198
|
+
if (sshPassword) return await this.pullWithPassword(remote, branch, sshPassword);
|
|
203
199
|
await this.git.pull(remote, branch);
|
|
204
200
|
return { success: true };
|
|
205
|
-
|
|
206
201
|
} catch (error) {
|
|
207
|
-
// 检查是否是 SSH 密钥密码错误
|
|
208
202
|
if (error.message.includes('Permission denied') ||
|
|
209
203
|
error.message.includes('authenticity') ||
|
|
210
204
|
error.message.includes('password')) {
|
|
211
|
-
return {
|
|
212
|
-
success: false,
|
|
213
|
-
needPassword: true,
|
|
214
|
-
error: error.message
|
|
215
|
-
};
|
|
205
|
+
return { success: false, needPassword: true, error: error.message };
|
|
216
206
|
}
|
|
217
207
|
return { success: false, error: error.message };
|
|
218
208
|
}
|
|
@@ -220,7 +210,6 @@ class GitOperations {
|
|
|
220
210
|
|
|
221
211
|
async pullWithPassword(remote, branch, password) {
|
|
222
212
|
try {
|
|
223
|
-
// 使用 expect 脚本自动输入 SSH 密码
|
|
224
213
|
const command = `
|
|
225
214
|
expect << 'EOF'
|
|
226
215
|
spawn git pull ${remote} ${branch}
|
|
@@ -231,7 +220,6 @@ class GitOperations {
|
|
|
231
220
|
expect eof
|
|
232
221
|
EOF
|
|
233
222
|
`;
|
|
234
|
-
|
|
235
223
|
await execAsync(command);
|
|
236
224
|
return { success: true };
|
|
237
225
|
} catch (error) {
|
|
@@ -241,28 +229,15 @@ class GitOperations {
|
|
|
241
229
|
|
|
242
230
|
async push(remote = 'origin', branch = null, sshPassword = null) {
|
|
243
231
|
try {
|
|
244
|
-
if (!branch)
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// 如果有 SSH 密码,使用 expect 脚本处理
|
|
249
|
-
if (sshPassword) {
|
|
250
|
-
return await this.pushWithPassword(remote, branch, sshPassword);
|
|
251
|
-
}
|
|
252
|
-
|
|
232
|
+
if (!branch) branch = await this.getCurrentBranch();
|
|
233
|
+
if (sshPassword) return await this.pushWithPassword(remote, branch, sshPassword);
|
|
253
234
|
await this.git.push(remote, branch);
|
|
254
235
|
return { success: true };
|
|
255
|
-
|
|
256
236
|
} catch (error) {
|
|
257
|
-
// 检查是否是 SSH 密钥密码错误
|
|
258
237
|
if (error.message.includes('Permission denied') ||
|
|
259
238
|
error.message.includes('authenticity') ||
|
|
260
239
|
error.message.includes('password')) {
|
|
261
|
-
return {
|
|
262
|
-
success: false,
|
|
263
|
-
needPassword: true,
|
|
264
|
-
error: error.message
|
|
265
|
-
};
|
|
240
|
+
return { success: false, needPassword: true, error: error.message };
|
|
266
241
|
}
|
|
267
242
|
return { success: false, error: error.message };
|
|
268
243
|
}
|
|
@@ -280,7 +255,6 @@ class GitOperations {
|
|
|
280
255
|
expect eof
|
|
281
256
|
EOF
|
|
282
257
|
`;
|
|
283
|
-
|
|
284
258
|
await execAsync(command);
|
|
285
259
|
return { success: true };
|
|
286
260
|
} catch (error) {
|
|
@@ -291,7 +265,8 @@ class GitOperations {
|
|
|
291
265
|
async getFileContent(filePath) {
|
|
292
266
|
try {
|
|
293
267
|
const fullPath = path.join(this.repoPath, filePath);
|
|
294
|
-
|
|
268
|
+
// 使用 fsPromises 读取文件内容
|
|
269
|
+
return await fsPromises.readFile(fullPath, 'utf-8');
|
|
295
270
|
} catch {
|
|
296
271
|
return null;
|
|
297
272
|
}
|