git-push-mcp 1.0.0
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/.env.example +25 -0
- package/README.md +201 -0
- package/index.js +163 -0
- package/lib/git-operator.js +351 -0
- package/lib/mcp-handler.js +603 -0
- package/lib/nlp-processor.js +279 -0
- package/mcp-server.js +199 -0
- package/mcp.json +119 -0
- package/package.json +51 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
const simpleGit = require('simple-git');
|
|
2
|
+
|
|
3
|
+
class GitOperator {
|
|
4
|
+
constructor(logger) {
|
|
5
|
+
this.logger = logger;
|
|
6
|
+
this.git = simpleGit();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 检查是否为Git仓库
|
|
11
|
+
*/
|
|
12
|
+
async checkRepository() {
|
|
13
|
+
try {
|
|
14
|
+
await this.git.status();
|
|
15
|
+
return { isRepository: true };
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return { isRepository: false, error: error.message };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 获取Git状态
|
|
23
|
+
*/
|
|
24
|
+
async getStatus() {
|
|
25
|
+
try {
|
|
26
|
+
this.logger.info('获取Git状态', { service: 'git-push-mcp' });
|
|
27
|
+
const status = await this.git.status();
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
modified: status.modified,
|
|
32
|
+
created: status.created,
|
|
33
|
+
deleted: status.deleted,
|
|
34
|
+
staged: status.staged,
|
|
35
|
+
untracked: status.not_added,
|
|
36
|
+
conflicted: status.conflicted,
|
|
37
|
+
isClean: status.isClean(),
|
|
38
|
+
currentBranch: status.current,
|
|
39
|
+
tracking: status.tracking
|
|
40
|
+
};
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this.logger.error('获取状态失败', {
|
|
43
|
+
error: error?.message || '未知错误',
|
|
44
|
+
stack: error?.stack,
|
|
45
|
+
service: 'git-push-mcp'
|
|
46
|
+
});
|
|
47
|
+
throw new Error(error?.message || '获取Git状态失败');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 添加文件到暂存区
|
|
53
|
+
*/
|
|
54
|
+
async addFiles(files = '.') {
|
|
55
|
+
try {
|
|
56
|
+
this.logger.info(`添加文件到暂存区: ${files}`, { service: 'git-push-mcp' });
|
|
57
|
+
|
|
58
|
+
if (files === '.' || files === 'all') {
|
|
59
|
+
await this.git.add('.');
|
|
60
|
+
} else if (Array.isArray(files)) {
|
|
61
|
+
await this.git.add(files);
|
|
62
|
+
} else {
|
|
63
|
+
await this.git.add(files);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { success: true };
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.logger.error('添加文件失败', {
|
|
69
|
+
error: error?.message || '未知错误',
|
|
70
|
+
files,
|
|
71
|
+
service: 'git-push-mcp'
|
|
72
|
+
});
|
|
73
|
+
throw new Error(error?.message || '添加文件失败');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 提交更改
|
|
79
|
+
*/
|
|
80
|
+
async commit(message, options = {}) {
|
|
81
|
+
try {
|
|
82
|
+
this.logger.info(`提交更改: ${message}`);
|
|
83
|
+
|
|
84
|
+
const commitOptions = {
|
|
85
|
+
'--no-verify': options.noVerify || false
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (options.allowEmpty) {
|
|
89
|
+
commitOptions['--allow-empty'] = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result = await this.git.commit(message, undefined, commitOptions);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
data: {
|
|
97
|
+
commit: result.commit,
|
|
98
|
+
summary: result.summary
|
|
99
|
+
},
|
|
100
|
+
message: `提交成功: ${message}`
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
this.logger.error('提交失败:', error);
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: error.message
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 推送到远程仓库
|
|
113
|
+
*/
|
|
114
|
+
async push(options = {}) {
|
|
115
|
+
try {
|
|
116
|
+
this.logger.info('推送更改到远程仓库');
|
|
117
|
+
|
|
118
|
+
const pushOptions = [];
|
|
119
|
+
if (options.force) {
|
|
120
|
+
pushOptions.push('--force');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = await this.git.push(pushOptions);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
data: result,
|
|
128
|
+
message: '推送成功'
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.logger.error('推送失败:', error);
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: error.message
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 获取提交日志
|
|
141
|
+
*/
|
|
142
|
+
async getLog(limit = 10) {
|
|
143
|
+
try {
|
|
144
|
+
this.logger.info(`获取最近${limit}条提交日志`);
|
|
145
|
+
|
|
146
|
+
const logs = await this.git.log({ maxCount: limit });
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
data: logs.all.map(commit => ({
|
|
151
|
+
hash: commit.hash,
|
|
152
|
+
date: commit.date,
|
|
153
|
+
message: commit.message,
|
|
154
|
+
author: commit.author_name
|
|
155
|
+
}))
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.logger.error('获取日志失败:', error);
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
error: error.message
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 获取差异统计
|
|
168
|
+
*/
|
|
169
|
+
async getDiff() {
|
|
170
|
+
try {
|
|
171
|
+
this.logger.info('获取差异统计');
|
|
172
|
+
|
|
173
|
+
const diff = await this.git.diff(['--stat']);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
data: diff
|
|
178
|
+
};
|
|
179
|
+
} catch (error) {
|
|
180
|
+
this.logger.error('获取差异失败:', error);
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: error.message
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 创建新分支
|
|
190
|
+
*/
|
|
191
|
+
async createBranch(branchName) {
|
|
192
|
+
try {
|
|
193
|
+
this.logger.info(`创建分支: ${branchName}`);
|
|
194
|
+
|
|
195
|
+
await this.git.checkoutLocalBranch(branchName);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
message: `分支创建成功: ${branchName}`
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this.logger.error('创建分支失败:', error);
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: error.message
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 切换分支
|
|
212
|
+
*/
|
|
213
|
+
async checkoutBranch(branchName) {
|
|
214
|
+
try {
|
|
215
|
+
this.logger.info(`切换到分支: ${branchName}`);
|
|
216
|
+
|
|
217
|
+
await this.git.checkout(branchName);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
success: true,
|
|
221
|
+
message: `已切换到分支: ${branchName}`
|
|
222
|
+
};
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this.logger.error('切换分支失败:', error);
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
error: error.message
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 获取所有分支
|
|
234
|
+
*/
|
|
235
|
+
async getBranches() {
|
|
236
|
+
try {
|
|
237
|
+
this.logger.info('获取分支列表');
|
|
238
|
+
|
|
239
|
+
const branches = await this.git.branch();
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
success: true,
|
|
243
|
+
data: {
|
|
244
|
+
current: branches.current,
|
|
245
|
+
local: branches.all.filter(b => !b.startsWith('remotes/')),
|
|
246
|
+
remote: branches.all.filter(b => b.startsWith('remotes/'))
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
} catch (error) {
|
|
250
|
+
this.logger.error('获取分支失败:', error);
|
|
251
|
+
return {
|
|
252
|
+
success: false,
|
|
253
|
+
error: error.message
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 撤销工作区更改
|
|
260
|
+
*/
|
|
261
|
+
async resetWorkingDirectory(files = '.') {
|
|
262
|
+
try {
|
|
263
|
+
this.logger.info(`撤销工作区更改: ${files}`);
|
|
264
|
+
|
|
265
|
+
if (files === '.') {
|
|
266
|
+
await this.git.reset('hard');
|
|
267
|
+
} else {
|
|
268
|
+
await this.git.checkout(files);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
message: '已撤销更改'
|
|
274
|
+
};
|
|
275
|
+
} catch (error) {
|
|
276
|
+
this.logger.error('撤销更改失败:', error);
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
error: error.message
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 撤销暂存区更改
|
|
286
|
+
*/
|
|
287
|
+
async unstageFiles(files = '.') {
|
|
288
|
+
try {
|
|
289
|
+
this.logger.info(`撤销暂存区更改: ${files}`);
|
|
290
|
+
|
|
291
|
+
if (files === '.') {
|
|
292
|
+
await this.git.reset();
|
|
293
|
+
} else {
|
|
294
|
+
await this.git.reset('HEAD', files);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
message: '已撤销暂存'
|
|
300
|
+
};
|
|
301
|
+
} catch (error) {
|
|
302
|
+
this.logger.error('撤销暂存失败:', error);
|
|
303
|
+
return {
|
|
304
|
+
success: false,
|
|
305
|
+
error: error.message
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* 获取仓库根目录
|
|
312
|
+
*/
|
|
313
|
+
async getRepoRoot() {
|
|
314
|
+
try {
|
|
315
|
+
const root = await this.git.revparse(['--show-toplevel']);
|
|
316
|
+
return {
|
|
317
|
+
success: true,
|
|
318
|
+
root: root.trim()
|
|
319
|
+
};
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return {
|
|
322
|
+
success: false,
|
|
323
|
+
error: error.message
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 执行自定义Git命令
|
|
330
|
+
*/
|
|
331
|
+
async executeCustomCommand(command, args = []) {
|
|
332
|
+
try {
|
|
333
|
+
this.logger.info(`执行自定义命令: git ${command} ${args.join(' ')}`);
|
|
334
|
+
|
|
335
|
+
const result = await this.git.raw([command, ...args]);
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
success: true,
|
|
339
|
+
data: result
|
|
340
|
+
};
|
|
341
|
+
} catch (error) {
|
|
342
|
+
this.logger.error('执行自定义命令失败:', error);
|
|
343
|
+
return {
|
|
344
|
+
success: false,
|
|
345
|
+
error: error.message
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
module.exports = { GitOperator };
|