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