@git-ai/cli 1.0.2 → 1.0.3
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/LICENSE +22 -0
- package/README.md +369 -369
- package/bin/index.cjs +13 -14
- package/bin/index.mjs +40 -41
- package/package.json +72 -46
- package/src/actions/BaseAction.mjs +42 -44
- package/src/actions/BaseUrlAction.mjs +24 -24
- package/src/actions/CommitAction.mjs +432 -484
- package/src/actions/MaxTokenAction.mjs +34 -38
- package/src/actions/ModelAction.mjs +122 -131
- package/src/actions/SelectModelAction.mjs +151 -161
- package/src/actions/TokenAction.mjs +25 -25
- package/src/const.mjs +43 -41
- package/src/index.mjs +95 -69
- package/src/services/AIService.mjs +106 -113
- package/src/services/GitService.mjs +366 -388
- package/src/utils/ConflictUtils.mjs +39 -39
- package/src/utils/Log.mjs +138 -146
- package/src/utils/Logger.mjs +34 -34
- package/src/utils/MessageUtils.mjs +189 -197
- package/src/utils/OpenAI.mjs +64 -68
- package/src/utils/Spinner.mjs +39 -39
- package/src/utils/Storage.mjs +7 -7
- package/src/utils/Utils.mjs +71 -72
|
@@ -1,484 +1,432 @@
|
|
|
1
|
-
import { isAxiosError } from
|
|
2
|
-
import { config } from
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
60
|
-
this.
|
|
61
|
-
await this.
|
|
62
|
-
this.
|
|
63
|
-
this.
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
error.response
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
} catch (error) {
|
|
435
|
-
collectWarning(error);
|
|
436
|
-
Logger.warn(
|
|
437
|
-
`合并失败:${error && error.message ? error.message : error}`
|
|
438
|
-
);
|
|
439
|
-
this.commitMerge();
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* 推送代码
|
|
445
|
-
*/
|
|
446
|
-
async push() {
|
|
447
|
-
const pushCount = this.gitService.getPushCount(
|
|
448
|
-
this.remoteName,
|
|
449
|
-
this.currentBranch
|
|
450
|
-
);
|
|
451
|
-
const originHasBranch = pushCount === -1;
|
|
452
|
-
|
|
453
|
-
if (pushCount !== 0) {
|
|
454
|
-
Logger.info(
|
|
455
|
-
originHasBranch
|
|
456
|
-
? `远程分支${this.currentBranch}不存在,推送新分支...`
|
|
457
|
-
: "正在推送本地分支与远程分支的差异..."
|
|
458
|
-
);
|
|
459
|
-
try {
|
|
460
|
-
await this.gitService.push(this.remoteName, this.currentBranch);
|
|
461
|
-
Logger.success(
|
|
462
|
-
originHasBranch
|
|
463
|
-
? "新分支已推送到远程仓库。"
|
|
464
|
-
: "本地分支与远程分支的差异已推送。"
|
|
465
|
-
);
|
|
466
|
-
} catch (error) {
|
|
467
|
-
collectError(error);
|
|
468
|
-
Logger.error("本地分支与远程分支的差异推送失败");
|
|
469
|
-
this.gitService.reset();
|
|
470
|
-
throw error && error.message ? error.message : error;
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
Logger.warn("本地分支与远程分支没有差异,无需推送");
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* 导出 commit 命令处理函数
|
|
480
|
-
*/
|
|
481
|
-
export default async function (args) {
|
|
482
|
-
const action = new CommitAction(args);
|
|
483
|
-
await action.run();
|
|
484
|
-
}
|
|
1
|
+
import { isAxiosError } from 'axios';
|
|
2
|
+
import { config } from '../utils/Storage.mjs';
|
|
3
|
+
import { BIN, OPENAI_COMMIT_MESSAGE_TYPES, OPENAI_MAX_TOKEN_DEFAULT } from '../const.mjs';
|
|
4
|
+
import { formatMessage } from '../utils/MessageUtils.mjs';
|
|
5
|
+
import { findConflictFiles } from '../utils/ConflictUtils.mjs';
|
|
6
|
+
import { GitService } from '../services/GitService.mjs';
|
|
7
|
+
import { AIService } from '../services/AIService.mjs';
|
|
8
|
+
import Logger from '../utils/Logger.mjs';
|
|
9
|
+
import Spinner from '../utils/Spinner.mjs';
|
|
10
|
+
import BaseAction from './BaseAction.mjs';
|
|
11
|
+
import inquirer from 'inquirer';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import { collectError, collectWarning } from '../utils/Log.mjs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Commit Action 类
|
|
17
|
+
* 负责处理 git commit 的完整流程
|
|
18
|
+
*/
|
|
19
|
+
class CommitAction extends BaseAction {
|
|
20
|
+
constructor({ dryRun, allowEmpty, noVerify, skip }) {
|
|
21
|
+
super({ dryRun, allowEmpty, noVerify, skip });
|
|
22
|
+
|
|
23
|
+
this.dryRun = dryRun;
|
|
24
|
+
this.allowEmpty = allowEmpty;
|
|
25
|
+
this.noVerify = noVerify;
|
|
26
|
+
this.skip = skip;
|
|
27
|
+
// 是否有冲突文件
|
|
28
|
+
this.isConflit = false;
|
|
29
|
+
|
|
30
|
+
// 初始化服务
|
|
31
|
+
this.gitService = new GitService();
|
|
32
|
+
this.aiService = null;
|
|
33
|
+
|
|
34
|
+
// 配置信息
|
|
35
|
+
this.maxToken = 0;
|
|
36
|
+
|
|
37
|
+
// 工作信息
|
|
38
|
+
this.workingPrefix = '';
|
|
39
|
+
this.userName = '';
|
|
40
|
+
this.diffString = '';
|
|
41
|
+
this.commitCommand = '';
|
|
42
|
+
this.commitMessage = '';
|
|
43
|
+
this.OPENAI_COMMIT_MESSAGE_REGEXP = '';
|
|
44
|
+
this.currentBranch = '';
|
|
45
|
+
this.remoteName = '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 初始化并执行完整流程
|
|
50
|
+
*/
|
|
51
|
+
async execute() {
|
|
52
|
+
this.prepare();
|
|
53
|
+
this.gitService.checkInstalled();
|
|
54
|
+
this.gitService.checkRepository();
|
|
55
|
+
this.checkWorkingPrefix();
|
|
56
|
+
this.checkGitConflict();
|
|
57
|
+
await this.addCommand();
|
|
58
|
+
this.commitMerge();
|
|
59
|
+
this.getGitUserInfo();
|
|
60
|
+
this.getDiffString();
|
|
61
|
+
await this.commit();
|
|
62
|
+
this.getBranchInfo();
|
|
63
|
+
await this.getRemoteInfo();
|
|
64
|
+
this.gitFetch();
|
|
65
|
+
this.gitMerge();
|
|
66
|
+
this.checkGitConflict();
|
|
67
|
+
this.push();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 准备配置
|
|
72
|
+
*/
|
|
73
|
+
prepare() {
|
|
74
|
+
this.maxToken = parseInt(config.get('maxToken') || OPENAI_MAX_TOKEN_DEFAULT);
|
|
75
|
+
this.commitCommand = this.dryRun
|
|
76
|
+
? 'git commit --dry-run -m'
|
|
77
|
+
: this.allowEmpty
|
|
78
|
+
? 'git commit --allow-empty -m'
|
|
79
|
+
: this.noVerify
|
|
80
|
+
? 'git commit --no-verify -m'
|
|
81
|
+
: 'git commit -m';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 检查工作目录
|
|
86
|
+
*/
|
|
87
|
+
checkWorkingPrefix() {
|
|
88
|
+
this.workingPrefix = this.gitService.getWorkingPrefix();
|
|
89
|
+
if (this.workingPrefix) {
|
|
90
|
+
Logger.warn(`当前在子目录 ${this.workingPrefix} 下操作,将只处理此目录下的文件`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 检查 Git 冲突
|
|
96
|
+
*/
|
|
97
|
+
checkGitConflict() {
|
|
98
|
+
// 检查状态
|
|
99
|
+
const statusOutput = this.gitService.getStatus();
|
|
100
|
+
const lines = statusOutput.trim().split('\n');
|
|
101
|
+
let modified = [];
|
|
102
|
+
// 冲突文件列表
|
|
103
|
+
const conflict = [];
|
|
104
|
+
|
|
105
|
+
lines.forEach((line) => {
|
|
106
|
+
const item = line.trim();
|
|
107
|
+
const p = item.slice(2).trim();
|
|
108
|
+
if (item.startsWith('UU')) {
|
|
109
|
+
conflict.push(`[冲突]${p}`);
|
|
110
|
+
} else if (item.startsWith('AA')) {
|
|
111
|
+
conflict.push(`[已添加,又被添加]${p}`);
|
|
112
|
+
} else if (item.startsWith('DD')) {
|
|
113
|
+
conflict.push(`[已删除,又被删除]${p}`);
|
|
114
|
+
} else if (item.startsWith('MM') || item.startsWith('M')) {
|
|
115
|
+
modified.push(p);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const { conflictFiles, ignoreFiles } = findConflictFiles(
|
|
120
|
+
Array.from(new Set(modified)),
|
|
121
|
+
this.workingPrefix
|
|
122
|
+
);
|
|
123
|
+
if (this.workingPrefix && ignoreFiles.length && typeof this.isConflit === 'boolean') {
|
|
124
|
+
Logger.info(
|
|
125
|
+
ignoreFiles.length > 6
|
|
126
|
+
? `已忽略${ignoreFiles.length}个文件的冲突标记检查`
|
|
127
|
+
: `冲突标记检查忽略文件:\n - ${ignoreFiles.join('\n - ')}`
|
|
128
|
+
);
|
|
129
|
+
this.isConflit = 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (conflictFiles.length > 0) {
|
|
133
|
+
const str = conflictFiles.join('\n - ');
|
|
134
|
+
Logger.warn(`存在冲突标记,需要手动合并文件:\n - ${str}`);
|
|
135
|
+
throw '请手动解决 git 冲突...';
|
|
136
|
+
}
|
|
137
|
+
if (!conflict.length) return;
|
|
138
|
+
Logger.warn(`Git 冲突文件:\n - ${conflict.join('\n - ')}`);
|
|
139
|
+
this.isConflit = this.isConflit ? this.isConflit + 1 : 1;
|
|
140
|
+
}
|
|
141
|
+
// 使用 inquirer 输入 y 确认是否解决冲突
|
|
142
|
+
async confirmConflict() {
|
|
143
|
+
const answer = await inquirer.prompt([
|
|
144
|
+
{
|
|
145
|
+
type: 'confirm',
|
|
146
|
+
name: 'confirm',
|
|
147
|
+
message: '确认是否已解决冲突?',
|
|
148
|
+
default: false,
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
return answer.confirm;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 执行 git add
|
|
155
|
+
*/
|
|
156
|
+
async addCommand() {
|
|
157
|
+
if (this.isConflit && !(await this.confirmConflict())) {
|
|
158
|
+
throw '请手动解决冲突';
|
|
159
|
+
}
|
|
160
|
+
if (this.skip) return;
|
|
161
|
+
this.gitService.add();
|
|
162
|
+
if (!this.isConflit) return;
|
|
163
|
+
this.checkGitConflict();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 获取 Git 用户信息
|
|
168
|
+
*/
|
|
169
|
+
getGitUserInfo() {
|
|
170
|
+
this.userName = this.gitService.getUserName();
|
|
171
|
+
if (this.userName) {
|
|
172
|
+
this.OPENAI_COMMIT_MESSAGE_REGEXP = new RegExp(
|
|
173
|
+
`^((${OPENAI_COMMIT_MESSAGE_TYPES.join('|')})\\(${this.userName}\\)):[\\s\\S]*`
|
|
174
|
+
);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
throw `获取 git 用户信息时出错,\n 请执行 \`git config user.name <your name>\` 设置 git 用户名`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 获取差异字符串
|
|
182
|
+
*/
|
|
183
|
+
getDiffString() {
|
|
184
|
+
this.diffString = this.gitService.getDiff(this.maxToken);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 生成提交消息
|
|
189
|
+
*/
|
|
190
|
+
async generateMessage() {
|
|
191
|
+
Logger.verbose(`按 Ctrl+C 退出...`);
|
|
192
|
+
|
|
193
|
+
const spinner = new Spinner(`正在生成提交消息...`);
|
|
194
|
+
spinner.start();
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// 初始化 AI 服务
|
|
198
|
+
this.aiService = new AIService(this.userName);
|
|
199
|
+
const result = await this.aiService.generateCommitMessage(this.diffString);
|
|
200
|
+
spinner.stop();
|
|
201
|
+
this.commitMessage = formatMessage(result, this.OPENAI_COMMIT_MESSAGE_REGEXP);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
collectError(error);
|
|
204
|
+
spinner.error('生成 commit message 失败');
|
|
205
|
+
if (isAxiosError(error)) {
|
|
206
|
+
this.gitService.reset();
|
|
207
|
+
if (
|
|
208
|
+
error.response &&
|
|
209
|
+
error.response.status === 400 &&
|
|
210
|
+
error.response.data &&
|
|
211
|
+
error.response.data.error &&
|
|
212
|
+
error.response.data.error.code === 'context_length_exceeded'
|
|
213
|
+
) {
|
|
214
|
+
Logger.error(
|
|
215
|
+
`更新内容超过模型支持的最大 token 数,请选择更小的文件集,使用 \`${BIN} set-max-token < maxToken >\` 设置最大 token 数。`
|
|
216
|
+
);
|
|
217
|
+
} else {
|
|
218
|
+
if (
|
|
219
|
+
error.response &&
|
|
220
|
+
error.response.data &&
|
|
221
|
+
error.response.data.error &&
|
|
222
|
+
error.response.data.error.message
|
|
223
|
+
) {
|
|
224
|
+
Logger.error(`错误原因:${error.response.data.error.message}`);
|
|
225
|
+
} else if (error.response && error.response.data) {
|
|
226
|
+
Logger.error('发生了一个意外的 Axios 错误,响应数据。');
|
|
227
|
+
} else if (error.response) {
|
|
228
|
+
Logger.error(`发生了一个 Axios 错误,状态 ${error.response.status}.`);
|
|
229
|
+
} else {
|
|
230
|
+
Logger.error('发生了一个 Axios 网络错误。');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
throw error && error.message ? error.message : error;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const messageContent = this.commitMessage ? `:\n${chalk.blue(this.commitMessage)}` : '为空。';
|
|
238
|
+
const verify = this.OPENAI_COMMIT_MESSAGE_REGEXP.test(this.commitMessage);
|
|
239
|
+
spinner[verify ? 'success' : 'error'](`AI 生成的内容${messageContent}`);
|
|
240
|
+
if (!verify) {
|
|
241
|
+
throw 'AI 生成的内容不符合规则,请重新生成...';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 判断是否需要通过 commit 去 merge
|
|
247
|
+
*/
|
|
248
|
+
commitMerge() {
|
|
249
|
+
if (!this.gitService.isMerging()) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 确保没有未解决的冲突
|
|
254
|
+
const statusOutput = this.gitService.getStatus();
|
|
255
|
+
const hasUnresolved = statusOutput
|
|
256
|
+
.split('\n')
|
|
257
|
+
.some((line) => line.startsWith('UU') || line.startsWith('AA') || line.startsWith('DD'));
|
|
258
|
+
|
|
259
|
+
if (hasUnresolved) {
|
|
260
|
+
throw '仍有未解决的合并冲突,请手动解决后再继续。';
|
|
261
|
+
}
|
|
262
|
+
Logger.info('检测到处于合并流程,正在结束合并...');
|
|
263
|
+
try {
|
|
264
|
+
this.gitService.finishMerge();
|
|
265
|
+
Logger.success('合并已完成');
|
|
266
|
+
} catch (error) {
|
|
267
|
+
collectError(error);
|
|
268
|
+
Logger.error(`合并失败`);
|
|
269
|
+
throw error && error.message ? error.message : error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Git commit
|
|
275
|
+
*/
|
|
276
|
+
async commit() {
|
|
277
|
+
if (!this.diffString) {
|
|
278
|
+
Logger.warn(this.workingPrefix ? '当前目录下没有要提交的更改' : '没有要提交的更改');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
await this.generateMessage();
|
|
282
|
+
const usageMessage = this.aiService.getUsageMessage();
|
|
283
|
+
if (usageMessage) {
|
|
284
|
+
Logger.info(usageMessage);
|
|
285
|
+
}
|
|
286
|
+
const spinner = new Spinner('正在提交代码...');
|
|
287
|
+
spinner.start();
|
|
288
|
+
await spinner.sleep();
|
|
289
|
+
try {
|
|
290
|
+
await this.gitService.commit(`${this.commitCommand} "${this.commitMessage}"`, this.debug);
|
|
291
|
+
spinner.success('git commit 提交成功...');
|
|
292
|
+
} catch (error) {
|
|
293
|
+
spinner.stop();
|
|
294
|
+
collectError(error);
|
|
295
|
+
let aiDiagnosis = '';
|
|
296
|
+
let usageMessage = '';
|
|
297
|
+
if (this.aiService && typeof this.aiService.analyzeCommitFailure === 'function') {
|
|
298
|
+
const errSpinner = new Spinner('AI 诊断 git commit 失败原因...');
|
|
299
|
+
errSpinner.start();
|
|
300
|
+
try {
|
|
301
|
+
const gitStatus = this.gitService.getStatus();
|
|
302
|
+
const errorMessage = error && error.message ? error.message : error;
|
|
303
|
+
const hookLogs = [error && error.stdout, error && error.stderr]
|
|
304
|
+
.map((log) =>
|
|
305
|
+
typeof log === 'string'
|
|
306
|
+
? log.trim()
|
|
307
|
+
: log && Buffer.isBuffer(log)
|
|
308
|
+
? log.toString('utf8').trim()
|
|
309
|
+
: ''
|
|
310
|
+
)
|
|
311
|
+
.filter(Boolean)
|
|
312
|
+
.join('\n');
|
|
313
|
+
aiDiagnosis = await this.aiService.analyzeCommitFailure({
|
|
314
|
+
errorMessage,
|
|
315
|
+
gitStatus,
|
|
316
|
+
hookLogs,
|
|
317
|
+
});
|
|
318
|
+
errSpinner.stop();
|
|
319
|
+
|
|
320
|
+
usageMessage = this.aiService.getUsageMessage();
|
|
321
|
+
} catch (diagnosisError) {
|
|
322
|
+
collectWarning(diagnosisError);
|
|
323
|
+
errSpinner.warn('AI 诊断 git commit 失败原因时出错');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const baseMessage = 'git commit 提交失败';
|
|
327
|
+
this.gitService.reset();
|
|
328
|
+
throw aiDiagnosis
|
|
329
|
+
? `${baseMessage},AI 诊断:\n${aiDiagnosis}${usageMessage ? '\n\n' : ''}${usageMessage}`
|
|
330
|
+
: baseMessage;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* 获取分支信息
|
|
336
|
+
*/
|
|
337
|
+
getBranchInfo() {
|
|
338
|
+
try {
|
|
339
|
+
this.currentBranch = this.gitService.getCurrentBranch();
|
|
340
|
+
} catch (error) {
|
|
341
|
+
collectError(error);
|
|
342
|
+
Logger.error(`获取分支名称失败,请检查 Git 是否正确配置`);
|
|
343
|
+
throw error && error.message ? error.message : error;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 获取远程仓库信息
|
|
349
|
+
*/
|
|
350
|
+
async getRemoteInfo() {
|
|
351
|
+
Logger.info(`获取 git 远程仓库地址`);
|
|
352
|
+
try {
|
|
353
|
+
this.remoteName = await this.gitService.getRemoteName(this.currentBranch);
|
|
354
|
+
Logger.success(`获取 git 远程仓库地址成功`);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
collectError(error);
|
|
357
|
+
Logger.error(`获取 git 远程仓库地址失败,请检查 Git 是否正确配置`);
|
|
358
|
+
throw error && error.message ? error.message : error;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Git fetch
|
|
364
|
+
*/
|
|
365
|
+
gitFetch() {
|
|
366
|
+
Logger.info(`获取远程仓库最新状态,执行 git fetch...`);
|
|
367
|
+
try {
|
|
368
|
+
this.gitService.fetch(this.remoteName);
|
|
369
|
+
Logger.success(`远程分支 ${this.remoteName} 的最新更改状态获取成功`);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
collectWarning(error);
|
|
372
|
+
Logger.error(`fetch 失败:${error && error.message ? error.message : error}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Git merge
|
|
378
|
+
*/
|
|
379
|
+
gitMerge() {
|
|
380
|
+
Logger.info('正在检测是否需要拉取...');
|
|
381
|
+
try {
|
|
382
|
+
const aheadCount = this.gitService.getAheadCount(this.remoteName, this.currentBranch);
|
|
383
|
+
if (aheadCount > 0) {
|
|
384
|
+
this.gitService.merge(this.remoteName, this.currentBranch);
|
|
385
|
+
Logger.success(`本地分支落后远程分支 \x1b[32m${aheadCount}次\x1b[0m,已合并最新代码`);
|
|
386
|
+
} else {
|
|
387
|
+
Logger.success('本地代码是最新,无需合并');
|
|
388
|
+
}
|
|
389
|
+
} catch (error) {
|
|
390
|
+
collectWarning(error);
|
|
391
|
+
Logger.warn(`合并失败:${error && error.message ? error.message : error}`);
|
|
392
|
+
this.commitMerge();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* 推送代码
|
|
398
|
+
*/
|
|
399
|
+
async push() {
|
|
400
|
+
const pushCount = this.gitService.getPushCount(this.remoteName, this.currentBranch);
|
|
401
|
+
const originHasBranch = pushCount === -1;
|
|
402
|
+
|
|
403
|
+
if (pushCount !== 0) {
|
|
404
|
+
Logger.info(
|
|
405
|
+
originHasBranch
|
|
406
|
+
? `远程分支${this.currentBranch}不存在,推送新分支...`
|
|
407
|
+
: '正在推送本地分支与远程分支的差异...'
|
|
408
|
+
);
|
|
409
|
+
try {
|
|
410
|
+
await this.gitService.push(this.remoteName, this.currentBranch);
|
|
411
|
+
Logger.success(
|
|
412
|
+
originHasBranch ? '新分支已推送到远程仓库。' : '本地分支与远程分支的差异已推送。'
|
|
413
|
+
);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
collectError(error);
|
|
416
|
+
Logger.error('本地分支与远程分支的差异推送失败');
|
|
417
|
+
this.gitService.reset();
|
|
418
|
+
throw error && error.message ? error.message : error;
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
Logger.warn('本地分支与远程分支没有差异,无需推送');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* 导出 commit 命令处理函数
|
|
428
|
+
*/
|
|
429
|
+
export default async function (args) {
|
|
430
|
+
const action = new CommitAction(args);
|
|
431
|
+
await action.run();
|
|
432
|
+
}
|