@andrebuzeli/git-mcp 10.0.7 → 10.0.9
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 +0 -0
- package/README.md +1 -1
- package/dist/providers/providerManager.js +2 -4
- package/dist/scripts/test_e2e.d.ts +1 -0
- package/dist/scripts/test_e2e.js +199 -0
- package/dist/scripts/test_exhaustive.d.ts +1 -0
- package/dist/scripts/test_exhaustive.js +275 -0
- package/dist/scripts/test_gitea_creation.d.ts +1 -0
- package/dist/scripts/test_gitea_creation.js +116 -0
- package/dist/scripts/verify_setup.d.ts +1 -0
- package/dist/scripts/verify_setup.js +61 -0
- package/dist/tools/gitAnalytics.js +3 -3
- package/dist/tools/gitBackup.d.ts +2 -2
- package/dist/tools/gitBackup.js +5 -5
- package/dist/tools/gitBranches.js +40 -29
- package/dist/tools/gitConfig.d.ts +2 -2
- package/dist/tools/gitConfig.js +12 -13
- package/dist/tools/gitFix.d.ts +2 -1
- package/dist/tools/gitFix.js +20 -24
- package/dist/tools/gitFix.tool.d.ts +2 -2
- package/dist/tools/gitFix.tool.js +2 -2
- package/dist/tools/gitHistory.js +4 -5
- package/dist/tools/gitIssues.js +8 -8
- package/dist/tools/gitMonitor.js +28 -33
- package/dist/tools/gitPulls.js +8 -8
- package/dist/tools/gitRelease.js +5 -6
- package/dist/tools/gitRemote.d.ts +1 -11
- package/dist/tools/gitRemote.js +14 -20
- package/dist/tools/gitReset.js +6 -7
- package/dist/tools/gitStash.d.ts +1 -12
- package/dist/tools/gitStash.js +11 -22
- package/dist/tools/gitSync.js +44 -36
- package/dist/tools/gitTags.d.ts +8 -0
- package/dist/tools/gitTags.js +44 -34
- package/dist/tools/gitUpdate.d.ts +27 -0
- package/dist/tools/gitUpdate.js +61 -34
- package/dist/tools/gitUpload.js +29 -44
- package/dist/tools/gitWorkflow.js +27 -46
- package/dist/utils/gitAdapter.js +10 -0
- package/dist/utils/repoHelpers.js +7 -12
- package/package.json +2 -3
- package/dist/server-new.d.ts +0 -2
- package/dist/server-new.js +0 -224
- package/dist/tools/gitFiles-new.d.ts +0 -89
- package/dist/tools/gitFiles-new.js +0 -335
package/dist/tools/gitSync.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import simpleGit from 'simple-git';
|
|
2
1
|
import { MCPError } from '../utils/errors.js';
|
|
3
2
|
export class GitSyncTool {
|
|
4
3
|
constructor() {
|
|
@@ -39,12 +38,14 @@ export class GitSyncTool {
|
|
|
39
38
|
if (!action || !projectPath) {
|
|
40
39
|
throw new MCPError('VALIDATION_ERROR', 'action and projectPath are required');
|
|
41
40
|
}
|
|
42
|
-
|
|
41
|
+
if (!ctx.gitAdapter) {
|
|
42
|
+
throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
|
|
43
|
+
}
|
|
43
44
|
switch (action) {
|
|
44
45
|
case 'fetch': {
|
|
45
46
|
const remote = params.remote || 'origin';
|
|
46
47
|
try {
|
|
47
|
-
await
|
|
48
|
+
await ctx.gitAdapter.fetch(projectPath, remote);
|
|
48
49
|
return { success: true, projectPath, remote, action: 'fetch' };
|
|
49
50
|
}
|
|
50
51
|
catch (err) {
|
|
@@ -53,15 +54,13 @@ export class GitSyncTool {
|
|
|
53
54
|
}
|
|
54
55
|
case 'pull': {
|
|
55
56
|
const remote = params.remote || 'origin';
|
|
56
|
-
|
|
57
|
+
let branch = params.branch;
|
|
57
58
|
try {
|
|
58
|
-
if (branch) {
|
|
59
|
-
await
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
await git.pull(remote);
|
|
59
|
+
if (!branch) {
|
|
60
|
+
branch = await ctx.gitAdapter.getCurrentBranch(projectPath);
|
|
63
61
|
}
|
|
64
|
-
|
|
62
|
+
await ctx.gitAdapter.pull(projectPath, remote, branch);
|
|
63
|
+
return { success: true, projectPath, remote, branch, action: 'pull' };
|
|
65
64
|
}
|
|
66
65
|
catch (err) {
|
|
67
66
|
throw new MCPError('PULL_ERROR', `Failed to pull from ${remote}: ${err.message}`);
|
|
@@ -69,16 +68,20 @@ export class GitSyncTool {
|
|
|
69
68
|
}
|
|
70
69
|
case 'push': {
|
|
71
70
|
const remote = params.remote || 'origin';
|
|
72
|
-
|
|
71
|
+
let branch = params.branch;
|
|
72
|
+
const force = params.force || false;
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
if (!branch) {
|
|
75
|
+
branch = await ctx.gitAdapter.getCurrentBranch(projectPath);
|
|
76
|
+
}
|
|
77
|
+
await ctx.gitAdapter.push(projectPath, remote, branch, force);
|
|
75
78
|
return {
|
|
76
79
|
success: true,
|
|
77
80
|
projectPath,
|
|
78
81
|
remote,
|
|
79
|
-
branch
|
|
82
|
+
branch,
|
|
80
83
|
action: 'push',
|
|
81
|
-
|
|
84
|
+
message: 'Push successful'
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
87
|
catch (err) {
|
|
@@ -87,14 +90,20 @@ export class GitSyncTool {
|
|
|
87
90
|
}
|
|
88
91
|
case 'sync': {
|
|
89
92
|
const remote = params.remote || 'origin';
|
|
90
|
-
|
|
93
|
+
let branch = params.branch;
|
|
91
94
|
try {
|
|
92
|
-
if (branch) {
|
|
93
|
-
await
|
|
95
|
+
if (!branch) {
|
|
96
|
+
branch = await ctx.gitAdapter.getCurrentBranch(projectPath);
|
|
97
|
+
}
|
|
98
|
+
// Checkout if needed (not strictly necessary if we are already on the branch, but good for safety)
|
|
99
|
+
const currentBranch = await ctx.gitAdapter.getCurrentBranch(projectPath);
|
|
100
|
+
if (branch !== currentBranch) {
|
|
101
|
+
await ctx.gitAdapter.checkout(projectPath, branch);
|
|
94
102
|
}
|
|
95
|
-
await
|
|
96
|
-
await
|
|
97
|
-
|
|
103
|
+
await ctx.gitAdapter.fetch(projectPath, remote);
|
|
104
|
+
await ctx.gitAdapter.pull(projectPath, remote, branch);
|
|
105
|
+
await ctx.gitAdapter.push(projectPath, remote, branch);
|
|
106
|
+
return { success: true, projectPath, remote, branch, message: 'Repository synced' };
|
|
98
107
|
}
|
|
99
108
|
catch (err) {
|
|
100
109
|
throw new MCPError('SYNC_ERROR', `Failed to sync with ${remote}: ${err.message}`);
|
|
@@ -102,8 +111,8 @@ export class GitSyncTool {
|
|
|
102
111
|
}
|
|
103
112
|
case 'status': {
|
|
104
113
|
try {
|
|
105
|
-
const status = await
|
|
106
|
-
const remotes = await
|
|
114
|
+
const status = await ctx.gitAdapter.status(projectPath);
|
|
115
|
+
const remotes = await ctx.gitAdapter.listRemotes(projectPath);
|
|
107
116
|
return {
|
|
108
117
|
success: true,
|
|
109
118
|
projectPath,
|
|
@@ -114,7 +123,7 @@ export class GitSyncTool {
|
|
|
114
123
|
modified: status.modified,
|
|
115
124
|
created: status.created,
|
|
116
125
|
deleted: status.deleted,
|
|
117
|
-
clean: status.isClean
|
|
126
|
+
clean: status.isClean,
|
|
118
127
|
},
|
|
119
128
|
remotes,
|
|
120
129
|
};
|
|
@@ -126,7 +135,7 @@ export class GitSyncTool {
|
|
|
126
135
|
case 'one-shot': {
|
|
127
136
|
// One-shot sync: fetch, pull, and push in a single operation
|
|
128
137
|
const remote = params.remote || 'origin';
|
|
129
|
-
|
|
138
|
+
let branch = params.branch;
|
|
130
139
|
const results = {
|
|
131
140
|
success: true,
|
|
132
141
|
projectPath,
|
|
@@ -135,31 +144,30 @@ export class GitSyncTool {
|
|
|
135
144
|
steps: [],
|
|
136
145
|
};
|
|
137
146
|
try {
|
|
147
|
+
if (!branch) {
|
|
148
|
+
branch = await ctx.gitAdapter.getCurrentBranch(projectPath);
|
|
149
|
+
results.branch = branch;
|
|
150
|
+
}
|
|
138
151
|
// Step 1: Fetch
|
|
139
152
|
results.steps.push({ action: 'fetch', timestamp: new Date().toISOString() });
|
|
140
|
-
await
|
|
153
|
+
await ctx.gitAdapter.fetch(projectPath, remote);
|
|
141
154
|
// Step 2: Pull
|
|
142
155
|
results.steps.push({ action: 'pull', timestamp: new Date().toISOString() });
|
|
143
|
-
|
|
144
|
-
await git.pull(remote, branch);
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
await git.pull(remote);
|
|
148
|
-
}
|
|
156
|
+
await ctx.gitAdapter.pull(projectPath, remote, branch);
|
|
149
157
|
// Step 3: Push
|
|
150
158
|
results.steps.push({ action: 'push', timestamp: new Date().toISOString() });
|
|
151
|
-
|
|
159
|
+
await ctx.gitAdapter.push(projectPath, remote, branch, false, true);
|
|
152
160
|
results.pushResult = {
|
|
153
|
-
pushed:
|
|
154
|
-
|
|
161
|
+
pushed: true, // Isomorphic git doesn't return detailed push stats easily
|
|
162
|
+
message: 'Push successful'
|
|
155
163
|
};
|
|
156
164
|
// Step 4: Final status
|
|
157
|
-
const finalStatus = await
|
|
165
|
+
const finalStatus = await ctx.gitAdapter.status(projectPath);
|
|
158
166
|
results.finalStatus = {
|
|
159
167
|
branch: finalStatus.current,
|
|
160
168
|
ahead: finalStatus.ahead,
|
|
161
169
|
behind: finalStatus.behind,
|
|
162
|
-
clean: finalStatus.isClean
|
|
170
|
+
clean: finalStatus.isClean,
|
|
163
171
|
};
|
|
164
172
|
return results;
|
|
165
173
|
}
|
package/dist/tools/gitTags.d.ts
CHANGED
|
@@ -26,10 +26,18 @@ export declare class GitTagsTool implements Tool {
|
|
|
26
26
|
type: string;
|
|
27
27
|
description: string;
|
|
28
28
|
};
|
|
29
|
+
annotated: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
29
33
|
force: {
|
|
30
34
|
type: string;
|
|
31
35
|
description: string;
|
|
32
36
|
};
|
|
37
|
+
remote: {
|
|
38
|
+
type: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
33
41
|
};
|
|
34
42
|
required: string[];
|
|
35
43
|
};
|
package/dist/tools/gitTags.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import simpleGit from 'simple-git';
|
|
2
1
|
import { MCPError } from '../utils/errors.js';
|
|
3
2
|
import axios from 'axios';
|
|
4
3
|
import { getRepoInfo } from '../utils/repoHelpers.js';
|
|
@@ -30,9 +29,17 @@ export class GitTagsTool {
|
|
|
30
29
|
type: "string",
|
|
31
30
|
description: "Tag annotation message (optional for create)"
|
|
32
31
|
},
|
|
32
|
+
annotated: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Create annotated tag (optional for create, default: false)"
|
|
35
|
+
},
|
|
33
36
|
force: {
|
|
34
37
|
type: "boolean",
|
|
35
38
|
description: "Force create/delete/push tag (default: true)"
|
|
39
|
+
},
|
|
40
|
+
remote: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "Remote name (optional for push, default: origin)"
|
|
36
43
|
}
|
|
37
44
|
},
|
|
38
45
|
required: ["projectPath", "action"]
|
|
@@ -44,37 +51,37 @@ export class GitTagsTool {
|
|
|
44
51
|
if (!action || !projectPath) {
|
|
45
52
|
throw new MCPError('VALIDATION_ERROR', 'action and projectPath are required');
|
|
46
53
|
}
|
|
47
|
-
|
|
54
|
+
if (!ctx.gitAdapter) {
|
|
55
|
+
throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
|
|
56
|
+
}
|
|
48
57
|
switch (action) {
|
|
49
58
|
case 'create': {
|
|
50
59
|
const tagName = params.tagName;
|
|
51
60
|
if (!tagName)
|
|
52
61
|
throw new MCPError('VALIDATION_ERROR', 'tagName is required');
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
else {
|
|
57
|
-
await git.addTag(tagName);
|
|
58
|
-
}
|
|
62
|
+
const ref = params.ref || 'HEAD';
|
|
63
|
+
const message = params.annotated ? (params.message || tagName) : undefined;
|
|
64
|
+
await ctx.gitAdapter.createTag(projectPath, tagName, ref, message);
|
|
59
65
|
return { success: true, tag: tagName, local: true };
|
|
60
66
|
}
|
|
61
67
|
case 'list': {
|
|
62
|
-
const localTags = await
|
|
68
|
+
const localTags = await ctx.gitAdapter.listTags(projectPath);
|
|
63
69
|
const results = {
|
|
64
70
|
success: true,
|
|
65
|
-
local: { tags: localTags
|
|
71
|
+
local: { tags: localTags },
|
|
66
72
|
providers: {}
|
|
67
73
|
};
|
|
68
74
|
// Also query remote APIs
|
|
69
75
|
const repoInfo = getRepoInfo(projectPath);
|
|
70
|
-
const
|
|
71
|
-
const
|
|
76
|
+
const repo = params.repo || repoInfo.repoName;
|
|
77
|
+
const githubOwner = params.owner || process.env.GITHUB_USERNAME;
|
|
78
|
+
const giteaOwner = params.owner || process.env.GITEA_USERNAME;
|
|
72
79
|
// GitHub
|
|
73
80
|
if (ctx.providerManager.github && githubOwner) {
|
|
74
81
|
try {
|
|
75
82
|
const tags = await ctx.providerManager.github.rest.repos.listTags({
|
|
76
83
|
owner: githubOwner,
|
|
77
|
-
repo:
|
|
84
|
+
repo: repo,
|
|
78
85
|
});
|
|
79
86
|
results.providers.github = {
|
|
80
87
|
success: true,
|
|
@@ -93,7 +100,7 @@ export class GitTagsTool {
|
|
|
93
100
|
// Gitea
|
|
94
101
|
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
95
102
|
try {
|
|
96
|
-
const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${
|
|
103
|
+
const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
|
|
97
104
|
results.providers.gitea = {
|
|
98
105
|
success: true,
|
|
99
106
|
tags: tags.data.map((t) => ({
|
|
@@ -114,30 +121,24 @@ export class GitTagsTool {
|
|
|
114
121
|
const tagName = params.tagName;
|
|
115
122
|
if (!tagName)
|
|
116
123
|
throw new MCPError('VALIDATION_ERROR', 'tagName is required');
|
|
117
|
-
const localTags = await
|
|
118
|
-
const localTag = localTags.
|
|
124
|
+
const localTags = await ctx.gitAdapter.listTags(projectPath);
|
|
125
|
+
const localTag = localTags.find(t => t === tagName);
|
|
119
126
|
const results = {
|
|
120
127
|
success: true,
|
|
121
128
|
local: localTag ? { found: true, tag: tagName } : { found: false },
|
|
122
129
|
providers: {}
|
|
123
130
|
};
|
|
124
|
-
if (localTag) {
|
|
125
|
-
try {
|
|
126
|
-
const show = await git.show([tagName]);
|
|
127
|
-
results.local.details = show;
|
|
128
|
-
}
|
|
129
|
-
catch { }
|
|
130
|
-
}
|
|
131
131
|
// Also query remote APIs
|
|
132
132
|
const repoInfo = getRepoInfo(projectPath);
|
|
133
|
-
const
|
|
134
|
-
const
|
|
133
|
+
const repo = params.repo || repoInfo.repoName;
|
|
134
|
+
const githubOwner = params.owner || process.env.GITHUB_USERNAME;
|
|
135
|
+
const giteaOwner = params.owner || process.env.GITEA_USERNAME;
|
|
135
136
|
// GitHub
|
|
136
137
|
if (ctx.providerManager.github && githubOwner) {
|
|
137
138
|
try {
|
|
138
139
|
const tag = await ctx.providerManager.github.rest.git.getRef({
|
|
139
140
|
owner: githubOwner,
|
|
140
|
-
repo:
|
|
141
|
+
repo: repo,
|
|
141
142
|
ref: `tags/${tagName}`,
|
|
142
143
|
});
|
|
143
144
|
results.providers.github = {
|
|
@@ -157,7 +158,7 @@ export class GitTagsTool {
|
|
|
157
158
|
// Gitea
|
|
158
159
|
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
159
160
|
try {
|
|
160
|
-
const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${
|
|
161
|
+
const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
|
|
161
162
|
const tag = tags.data.find((t) => t.name === tagName);
|
|
162
163
|
if (tag) {
|
|
163
164
|
results.providers.gitea = {
|
|
@@ -183,15 +184,23 @@ export class GitTagsTool {
|
|
|
183
184
|
const tagName = params.tagName;
|
|
184
185
|
if (!tagName)
|
|
185
186
|
throw new MCPError('VALIDATION_ERROR', 'tagName is required');
|
|
186
|
-
await
|
|
187
|
+
await ctx.gitAdapter.deleteTag(projectPath, tagName);
|
|
187
188
|
return { success: true, deleted: tagName, local: true };
|
|
188
189
|
}
|
|
190
|
+
case 'push': {
|
|
191
|
+
const tagName = params.tagName;
|
|
192
|
+
if (!tagName)
|
|
193
|
+
throw new MCPError('VALIDATION_ERROR', 'tagName is required');
|
|
194
|
+
const remote = params.remote || 'origin';
|
|
195
|
+
await ctx.gitAdapter.pushTag(projectPath, remote, tagName);
|
|
196
|
+
return { success: true, pushed: tagName, remote };
|
|
197
|
+
}
|
|
189
198
|
case 'search': {
|
|
190
199
|
const pattern = params.pattern;
|
|
191
200
|
if (!pattern)
|
|
192
201
|
throw new MCPError('VALIDATION_ERROR', 'pattern is required');
|
|
193
|
-
const localTags = await
|
|
194
|
-
const filtered = localTags.
|
|
202
|
+
const localTags = await ctx.gitAdapter.listTags(projectPath);
|
|
203
|
+
const filtered = localTags.filter(t => t.includes(pattern));
|
|
195
204
|
const results = {
|
|
196
205
|
success: true,
|
|
197
206
|
local: { tags: filtered },
|
|
@@ -199,14 +208,15 @@ export class GitTagsTool {
|
|
|
199
208
|
};
|
|
200
209
|
// Also search remote APIs
|
|
201
210
|
const repoInfo = getRepoInfo(projectPath);
|
|
202
|
-
const
|
|
203
|
-
const
|
|
211
|
+
const repo = params.repo || repoInfo.repoName;
|
|
212
|
+
const githubOwner = params.owner || process.env.GITHUB_USERNAME;
|
|
213
|
+
const giteaOwner = params.owner || process.env.GITEA_USERNAME;
|
|
204
214
|
// GitHub
|
|
205
215
|
if (ctx.providerManager.github && githubOwner) {
|
|
206
216
|
try {
|
|
207
217
|
const tags = await ctx.providerManager.github.rest.repos.listTags({
|
|
208
218
|
owner: githubOwner,
|
|
209
|
-
repo:
|
|
219
|
+
repo: repo,
|
|
210
220
|
});
|
|
211
221
|
const matchedTags = tags.data.filter((t) => t.name.includes(pattern));
|
|
212
222
|
results.providers.github = {
|
|
@@ -221,7 +231,7 @@ export class GitTagsTool {
|
|
|
221
231
|
// Gitea
|
|
222
232
|
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
223
233
|
try {
|
|
224
|
-
const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${
|
|
234
|
+
const tags = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
|
|
225
235
|
const matchedTags = tags.data.filter((t) => t.name.includes(pattern));
|
|
226
236
|
results.providers.gitea = {
|
|
227
237
|
success: true,
|
|
@@ -22,6 +22,33 @@ export declare class GitUpdateTool implements Tool {
|
|
|
22
22
|
type: string;
|
|
23
23
|
description: string;
|
|
24
24
|
};
|
|
25
|
+
message: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
commitMessage: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
branch: {
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
};
|
|
37
|
+
files: {
|
|
38
|
+
type: string;
|
|
39
|
+
items: {
|
|
40
|
+
type: string;
|
|
41
|
+
};
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
repoName: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
description: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
};
|
|
25
52
|
};
|
|
26
53
|
required: string[];
|
|
27
54
|
additionalProperties: boolean;
|
package/dist/tools/gitUpdate.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import simpleGit from 'simple-git';
|
|
2
1
|
import { MCPError } from '../utils/errors.js';
|
|
3
2
|
import axios from 'axios';
|
|
4
3
|
import * as fs from 'fs/promises';
|
|
@@ -27,6 +26,31 @@ export class GitUpdateTool {
|
|
|
27
26
|
owner: {
|
|
28
27
|
type: "string",
|
|
29
28
|
description: "Repository owner for providers (optional, uses env vars)"
|
|
29
|
+
},
|
|
30
|
+
message: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Commit message"
|
|
33
|
+
},
|
|
34
|
+
commitMessage: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Commit message (alias)"
|
|
37
|
+
},
|
|
38
|
+
branch: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Branch name"
|
|
41
|
+
},
|
|
42
|
+
files: {
|
|
43
|
+
type: "array",
|
|
44
|
+
items: { type: "string" },
|
|
45
|
+
description: "Files to add"
|
|
46
|
+
},
|
|
47
|
+
repoName: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Repository name"
|
|
50
|
+
},
|
|
51
|
+
description: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Repository description"
|
|
30
54
|
}
|
|
31
55
|
},
|
|
32
56
|
required: ["projectPath"],
|
|
@@ -38,10 +62,12 @@ export class GitUpdateTool {
|
|
|
38
62
|
if (!projectPath) {
|
|
39
63
|
throw new MCPError('VALIDATION_ERROR', 'projectPath is required');
|
|
40
64
|
}
|
|
65
|
+
if (!ctx.gitAdapter) {
|
|
66
|
+
throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
|
|
67
|
+
}
|
|
41
68
|
const commitMessage = params.message || params.commitMessage || `[git-update] Update at ${new Date().toISOString()}`;
|
|
42
69
|
const branch = params.branch || 'master';
|
|
43
70
|
const files = params.files || ['.'];
|
|
44
|
-
const git = simpleGit({ baseDir: projectPath });
|
|
45
71
|
// Resultado com rastreabilidade completa
|
|
46
72
|
const results = {
|
|
47
73
|
success: true,
|
|
@@ -65,7 +91,7 @@ export class GitUpdateTool {
|
|
|
65
91
|
action: 'check_pre_update_status',
|
|
66
92
|
timestamp: new Date().toISOString(),
|
|
67
93
|
});
|
|
68
|
-
const preStatus = await
|
|
94
|
+
const preStatus = await ctx.gitAdapter.status(projectPath);
|
|
69
95
|
results.traceability.preUpdateStatus = {
|
|
70
96
|
branch: preStatus.current,
|
|
71
97
|
ahead: preStatus.ahead,
|
|
@@ -76,7 +102,7 @@ export class GitUpdateTool {
|
|
|
76
102
|
renamed: preStatus.renamed,
|
|
77
103
|
staged: preStatus.staged,
|
|
78
104
|
conflicted: preStatus.conflicted,
|
|
79
|
-
isClean: preStatus.isClean
|
|
105
|
+
isClean: preStatus.isClean,
|
|
80
106
|
};
|
|
81
107
|
// 2. Detectar arquivos alterados com diff
|
|
82
108
|
results.traceability.updateSteps.push({
|
|
@@ -84,15 +110,11 @@ export class GitUpdateTool {
|
|
|
84
110
|
action: 'detect_changes',
|
|
85
111
|
timestamp: new Date().toISOString(),
|
|
86
112
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
status: status.trim(),
|
|
93
|
-
file: fileParts.join('\t').trim(),
|
|
94
|
-
};
|
|
95
|
-
});
|
|
113
|
+
// Isomorphic git diff is basic, so we rely on status for changed files list
|
|
114
|
+
results.traceability.changedFiles = preStatus.files.map(f => ({
|
|
115
|
+
status: f.working_dir,
|
|
116
|
+
file: f.path
|
|
117
|
+
}));
|
|
96
118
|
// 3. Add files
|
|
97
119
|
results.traceability.updateSteps.push({
|
|
98
120
|
step: 3,
|
|
@@ -100,7 +122,7 @@ export class GitUpdateTool {
|
|
|
100
122
|
timestamp: new Date().toISOString(),
|
|
101
123
|
files,
|
|
102
124
|
});
|
|
103
|
-
await
|
|
125
|
+
await ctx.gitAdapter.add(projectPath, files);
|
|
104
126
|
// 4. Commit
|
|
105
127
|
results.traceability.updateSteps.push({
|
|
106
128
|
step: 4,
|
|
@@ -110,7 +132,13 @@ export class GitUpdateTool {
|
|
|
110
132
|
});
|
|
111
133
|
let commitResult;
|
|
112
134
|
try {
|
|
113
|
-
|
|
135
|
+
const sha = await ctx.gitAdapter.commit(projectPath, commitMessage);
|
|
136
|
+
commitResult = {
|
|
137
|
+
commit: sha,
|
|
138
|
+
summary: { changes: 0, insertions: 0, deletions: 0 }, // Basic adapter doesn't return stats yet
|
|
139
|
+
branch: await ctx.gitAdapter.getCurrentBranch(projectPath),
|
|
140
|
+
author: { name: 'unknown', email: 'unknown' } // We could fetch this if needed
|
|
141
|
+
};
|
|
114
142
|
results.traceability.commit = {
|
|
115
143
|
hash: commitResult.commit,
|
|
116
144
|
summary: commitResult.summary,
|
|
@@ -119,7 +147,7 @@ export class GitUpdateTool {
|
|
|
119
147
|
};
|
|
120
148
|
}
|
|
121
149
|
catch (err) {
|
|
122
|
-
if (err.message.includes('nothing to commit')) {
|
|
150
|
+
if (err.message.includes('nothing to commit') || (preStatus.isClean && files[0] === '.')) {
|
|
123
151
|
results.traceability.commit = { message: 'No changes to commit' };
|
|
124
152
|
}
|
|
125
153
|
else {
|
|
@@ -127,7 +155,7 @@ export class GitUpdateTool {
|
|
|
127
155
|
}
|
|
128
156
|
}
|
|
129
157
|
// 5. Get remotes and ensure repos exist
|
|
130
|
-
const remotes = await
|
|
158
|
+
const remotes = await ctx.gitAdapter.listRemotes(projectPath);
|
|
131
159
|
let githubRemote = remotes.find(r => r.name === 'github' || r.name === 'origin');
|
|
132
160
|
let giteaRemote = remotes.find(r => r.name === 'gitea');
|
|
133
161
|
// Auto-create repos if they don't exist
|
|
@@ -169,8 +197,9 @@ export class GitUpdateTool {
|
|
|
169
197
|
});
|
|
170
198
|
}
|
|
171
199
|
// Add remote
|
|
172
|
-
|
|
173
|
-
|
|
200
|
+
const url = `https://github.com/${githubOwner}/${repoName}.git`;
|
|
201
|
+
await ctx.gitAdapter.addRemote(projectPath, 'github', url);
|
|
202
|
+
githubRemote = { name: 'github', remote: 'github', url, fetch: url };
|
|
174
203
|
}
|
|
175
204
|
catch (err) {
|
|
176
205
|
results.traceability.errors.push({
|
|
@@ -211,8 +240,9 @@ export class GitUpdateTool {
|
|
|
211
240
|
});
|
|
212
241
|
}
|
|
213
242
|
// Add remote
|
|
214
|
-
|
|
215
|
-
|
|
243
|
+
const url = `${ctx.providerManager.giteaBaseUrl}/${giteaOwner}/${repoName}.git`;
|
|
244
|
+
await ctx.gitAdapter.addRemote(projectPath, 'gitea', url);
|
|
245
|
+
giteaRemote = { name: 'gitea', remote: 'gitea', url, fetch: url };
|
|
216
246
|
}
|
|
217
247
|
catch (err) {
|
|
218
248
|
results.traceability.errors.push({
|
|
@@ -233,15 +263,14 @@ export class GitUpdateTool {
|
|
|
233
263
|
branch,
|
|
234
264
|
});
|
|
235
265
|
try {
|
|
236
|
-
|
|
266
|
+
await ctx.gitAdapter.push(projectPath, githubRemote.name, branch);
|
|
237
267
|
results.providers.github = {
|
|
238
268
|
success: true,
|
|
239
269
|
remote: githubRemote.name,
|
|
240
|
-
url: githubRemote.
|
|
270
|
+
url: githubRemote.url,
|
|
241
271
|
pushed: true,
|
|
242
272
|
pushResult: {
|
|
243
|
-
|
|
244
|
-
update: pushResult.update,
|
|
273
|
+
message: 'Push successful'
|
|
245
274
|
}
|
|
246
275
|
};
|
|
247
276
|
// Verificar commit no GitHub via API
|
|
@@ -285,15 +314,14 @@ export class GitUpdateTool {
|
|
|
285
314
|
branch,
|
|
286
315
|
});
|
|
287
316
|
try {
|
|
288
|
-
|
|
317
|
+
await ctx.gitAdapter.push(projectPath, giteaRemote.name, branch);
|
|
289
318
|
results.providers.gitea = {
|
|
290
319
|
success: true,
|
|
291
320
|
remote: giteaRemote.name,
|
|
292
|
-
url: giteaRemote.
|
|
321
|
+
url: giteaRemote.url,
|
|
293
322
|
pushed: true,
|
|
294
323
|
pushResult: {
|
|
295
|
-
|
|
296
|
-
update: pushResult.update,
|
|
324
|
+
message: 'Push successful'
|
|
297
325
|
}
|
|
298
326
|
};
|
|
299
327
|
// Verificar commit no Gitea via API
|
|
@@ -328,12 +356,12 @@ export class GitUpdateTool {
|
|
|
328
356
|
action: 'check_post_update_status',
|
|
329
357
|
timestamp: new Date().toISOString(),
|
|
330
358
|
});
|
|
331
|
-
const postStatus = await
|
|
359
|
+
const postStatus = await ctx.gitAdapter.status(projectPath);
|
|
332
360
|
results.traceability.postUpdateStatus = {
|
|
333
361
|
branch: postStatus.current,
|
|
334
362
|
ahead: postStatus.ahead,
|
|
335
363
|
behind: postStatus.behind,
|
|
336
|
-
isClean: postStatus.isClean
|
|
364
|
+
isClean: postStatus.isClean,
|
|
337
365
|
tracking: postStatus.tracking,
|
|
338
366
|
};
|
|
339
367
|
// 9. Salvar histórico local
|
|
@@ -374,12 +402,11 @@ export class GitUpdateTool {
|
|
|
374
402
|
const githubOwner = params.owner || process.env.GITHUB_USERNAME;
|
|
375
403
|
const giteaOwner = params.owner || process.env.GITEA_USERNAME;
|
|
376
404
|
// Pegar nome do repo dos remotes do Git ao invés do nome da pasta
|
|
377
|
-
const
|
|
378
|
-
const remotes = await git.getRemotes(true);
|
|
405
|
+
const remotes = await ctx.gitAdapter.listRemotes(projectPath);
|
|
379
406
|
let repoName = getRepoNameFromPath(projectPath); // fallback
|
|
380
407
|
if (remotes.length > 0) {
|
|
381
408
|
const githubRemote = remotes.find(r => r.name === 'github') || remotes.find(r => r.name === 'origin') || remotes[0];
|
|
382
|
-
const match = githubRemote.
|
|
409
|
+
const match = githubRemote.fetch?.match(/\/([^\/]+?)(\.git)?$/);
|
|
383
410
|
if (match)
|
|
384
411
|
repoName = match[1];
|
|
385
412
|
}
|