@andre.buzeli/git-mcp 16.0.6 → 16.0.8
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/package.json +1 -1
- package/src/providers/providerManager.js +31 -104
- package/src/tools/git-workflow.js +0 -13
- package/src/utils/gitAdapter.js +15 -5
package/package.json
CHANGED
|
@@ -93,7 +93,9 @@ export class ProviderManager {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async getRemoteUrls(repoName, organization) {
|
|
96
|
-
|
|
96
|
+
// When organization is specified, prefix repo name (no actual org needed)
|
|
97
|
+
const effectiveName = organization ? `${organization}---${repoName}` : repoName;
|
|
98
|
+
const cacheKey = `urls_${effectiveName}`;
|
|
97
99
|
const now = Date.now();
|
|
98
100
|
|
|
99
101
|
// Verificar cache
|
|
@@ -106,20 +108,20 @@ export class ProviderManager {
|
|
|
106
108
|
|
|
107
109
|
const urls = {};
|
|
108
110
|
|
|
109
|
-
// GitHub URL
|
|
111
|
+
// GitHub URL — always personal account
|
|
110
112
|
if (this.github) {
|
|
111
|
-
const owner =
|
|
113
|
+
const owner = await this.getGitHubOwner();
|
|
112
114
|
if (owner) {
|
|
113
|
-
urls.github = `https://github.com/${owner}/${
|
|
115
|
+
urls.github = `https://github.com/${owner}/${effectiveName}.git`;
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
// Gitea URL
|
|
119
|
+
// Gitea URL — always personal account
|
|
118
120
|
if (this.giteaUrl && this.giteaToken) {
|
|
119
|
-
const owner =
|
|
121
|
+
const owner = await this.getGiteaOwner();
|
|
120
122
|
if (owner) {
|
|
121
123
|
const base = this.giteaUrl.replace(/\/$/, "");
|
|
122
|
-
urls.gitea = `${base}/${owner}/${
|
|
124
|
+
urls.gitea = `${base}/${owner}/${effectiveName}.git`;
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -130,76 +132,27 @@ export class ProviderManager {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
async ensureRepos({ repoName, createIfMissing = true, description = "Managed by git-mcpv2", isPublic = false, organization }) {
|
|
133
|
-
//
|
|
135
|
+
// When organization is specified, prefix repo name (e.g., "MCP---GIT_MCP")
|
|
136
|
+
const effectiveName = organization ? `${organization}---${repoName}` : repoName;
|
|
134
137
|
const isPrivate = !isPublic;
|
|
135
138
|
const results = { github: null, gitea: null };
|
|
136
|
-
|
|
139
|
+
|
|
140
|
+
// GitHub — always personal account
|
|
137
141
|
if (this.github) {
|
|
138
|
-
const owner =
|
|
142
|
+
const owner = await this.getGitHubOwner();
|
|
139
143
|
if (owner) {
|
|
140
144
|
try {
|
|
141
|
-
const full = `${owner}/${repoName}`;
|
|
142
145
|
try {
|
|
143
|
-
await this.github.rest.repos.get({ owner, repo:
|
|
144
|
-
results.github = { ok: true, repo:
|
|
146
|
+
await this.github.rest.repos.get({ owner, repo: effectiveName });
|
|
147
|
+
results.github = { ok: true, repo: `${owner}/${effectiveName}`, created: false };
|
|
145
148
|
} catch {
|
|
146
149
|
if (createIfMissing) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
name: repoName,
|
|
154
|
-
description,
|
|
155
|
-
private: isPrivate,
|
|
156
|
-
auto_init: false,
|
|
157
|
-
});
|
|
158
|
-
} catch (orgRepoErr) {
|
|
159
|
-
const errMsg = String(orgRepoErr?.message || orgRepoErr).toLowerCase();
|
|
160
|
-
// Se org não existe, criar a org primeiro e depois o repo
|
|
161
|
-
if (errMsg.includes("not found") || errMsg.includes("404")) {
|
|
162
|
-
console.error(`[ProviderManager] GitHub org '${organization}' not found, creating...`);
|
|
163
|
-
try {
|
|
164
|
-
await this.github.rest.orgs.createForAuthenticatedUser
|
|
165
|
-
? await this.github.request("POST /user/orgs", { login: organization, profile_name: organization })
|
|
166
|
-
: null;
|
|
167
|
-
} catch (orgCreateErr) {
|
|
168
|
-
// GitHub free users can't create orgs via API, try alternative
|
|
169
|
-
console.error(`[ProviderManager] GitHub org creation failed: ${orgCreateErr?.message}. Trying as user repo with org topic...`);
|
|
170
|
-
}
|
|
171
|
-
// Retry repo creation in org
|
|
172
|
-
try {
|
|
173
|
-
cr = await this.github.rest.repos.createInOrg({
|
|
174
|
-
org: organization,
|
|
175
|
-
name: repoName,
|
|
176
|
-
description,
|
|
177
|
-
private: isPrivate,
|
|
178
|
-
auto_init: false,
|
|
179
|
-
});
|
|
180
|
-
} catch {
|
|
181
|
-
// Fallback: create as personal repo if org creation not possible
|
|
182
|
-
console.error(`[ProviderManager] Fallback: creating '${repoName}' as personal repo (GitHub org '${organization}' unavailable)`);
|
|
183
|
-
cr = await this.github.rest.repos.createForAuthenticatedUser({
|
|
184
|
-
name: repoName,
|
|
185
|
-
description: `[org:${organization}] ${description}`,
|
|
186
|
-
private: isPrivate,
|
|
187
|
-
auto_init: false,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
} else {
|
|
191
|
-
throw orgRepoErr;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
} else {
|
|
195
|
-
// Criar repo no usuário pessoal
|
|
196
|
-
cr = await this.github.rest.repos.createForAuthenticatedUser({
|
|
197
|
-
name: repoName,
|
|
198
|
-
description,
|
|
199
|
-
private: isPrivate,
|
|
200
|
-
auto_init: false,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
150
|
+
const cr = await this.github.rest.repos.createForAuthenticatedUser({
|
|
151
|
+
name: effectiveName,
|
|
152
|
+
description,
|
|
153
|
+
private: isPrivate,
|
|
154
|
+
auto_init: false,
|
|
155
|
+
});
|
|
203
156
|
results.github = { ok: true, repo: cr.data.full_name, created: true };
|
|
204
157
|
} else {
|
|
205
158
|
results.github = { ok: false, error: "missing" };
|
|
@@ -210,52 +163,26 @@ export class ProviderManager {
|
|
|
210
163
|
}
|
|
211
164
|
}
|
|
212
165
|
}
|
|
213
|
-
|
|
166
|
+
|
|
167
|
+
// Gitea — always personal account
|
|
214
168
|
if (this.giteaUrl && this.giteaToken) {
|
|
215
|
-
const owner =
|
|
169
|
+
const owner = await this.getGiteaOwner();
|
|
216
170
|
if (owner) {
|
|
217
171
|
try {
|
|
218
172
|
const base = this.giteaUrl.replace(/\/$/, "");
|
|
219
|
-
const getRepo = await axios.get(`${base}/api/v1/repos/${owner}/${
|
|
173
|
+
const getRepo = await axios.get(`${base}/api/v1/repos/${owner}/${effectiveName}`,
|
|
220
174
|
{ headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
221
175
|
if (getRepo.status === 200) {
|
|
222
|
-
results.gitea = { ok: true, repo: `${owner}/${
|
|
176
|
+
results.gitea = { ok: true, repo: `${owner}/${effectiveName}`, created: false };
|
|
223
177
|
}
|
|
224
178
|
} catch (e) {
|
|
225
179
|
if (createIfMissing) {
|
|
226
180
|
try {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
await axios.get(`${this.giteaUrl}/api/v1/orgs/${organization}`,
|
|
232
|
-
{ headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
233
|
-
} catch (orgCheckErr) {
|
|
234
|
-
// Org não existe, criar
|
|
235
|
-
console.error(`[ProviderManager] Gitea org '${organization}' not found, creating...`);
|
|
236
|
-
try {
|
|
237
|
-
await axios.post(`${this.giteaUrl}/api/v1/orgs`, {
|
|
238
|
-
username: organization,
|
|
239
|
-
full_name: organization,
|
|
240
|
-
description: `Organization ${organization}`,
|
|
241
|
-
visibility: isPublic ? "public" : "limited",
|
|
242
|
-
}, { headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
243
|
-
console.error(`[ProviderManager] Gitea org '${organization}' created successfully`);
|
|
244
|
-
} catch (orgCreateErr) {
|
|
245
|
-
const msg = String(orgCreateErr?.message || orgCreateErr).toLowerCase();
|
|
246
|
-
if (!msg.includes("already exists") && !msg.includes("409")) {
|
|
247
|
-
console.error(`[ProviderManager] Gitea org creation failed: ${orgCreateErr?.message}`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
createUrl = `${this.giteaUrl}/api/v1/orgs/${organization}/repos`;
|
|
252
|
-
} else {
|
|
253
|
-
createUrl = `${this.giteaUrl}/api/v1/user/repos`;
|
|
254
|
-
}
|
|
255
|
-
const cr = await axios.post(createUrl,
|
|
256
|
-
{ name: repoName, description, private: isPrivate, auto_init: false },
|
|
181
|
+
const base = this.giteaUrl.replace(/\/$/, "");
|
|
182
|
+
const cr = await axios.post(`${base}/api/v1/user/repos`,
|
|
183
|
+
{ name: effectiveName, description, private: isPrivate, auto_init: false },
|
|
257
184
|
{ headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
258
|
-
results.gitea = { ok: true, repo: `${cr.data?.owner?.login || owner}/${
|
|
185
|
+
results.gitea = { ok: true, repo: `${cr.data?.owner?.login || owner}/${effectiveName}`, created: true };
|
|
259
186
|
} catch (err) {
|
|
260
187
|
results.gitea = { ok: false, error: String(err?.message || err) };
|
|
261
188
|
}
|
|
@@ -318,19 +318,6 @@ EXEMPLOS DE USO:
|
|
|
318
318
|
|
|
319
319
|
const organization = args.organization || undefined;
|
|
320
320
|
|
|
321
|
-
// Ensure repos exist and set correct remote URLs (handles GitHub org fallback)
|
|
322
|
-
if (organization) {
|
|
323
|
-
const repo = getRepoNameFromPath(projectPath);
|
|
324
|
-
const isPublic = args.isPublic === true;
|
|
325
|
-
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic, organization });
|
|
326
|
-
const githubUrl = ensured.github?.ok && ensured.github.repo
|
|
327
|
-
? `https://github.com/${ensured.github.repo}.git` : "";
|
|
328
|
-
const giteaBase = pm.giteaUrl?.replace(/\/$/, "");
|
|
329
|
-
const giteaUrl = ensured.gitea?.ok && ensured.gitea.repo && giteaBase
|
|
330
|
-
? `${giteaBase}/${ensured.gitea.repo}.git` : "";
|
|
331
|
-
await git.ensureRemotes(projectPath, { githubUrl, giteaUrl, organization });
|
|
332
|
-
}
|
|
333
|
-
|
|
334
321
|
// Retry logic for push (often fails due to network or concurrent updates)
|
|
335
322
|
const result = await withRetry(
|
|
336
323
|
() => git.pushParallel(projectPath, branch, force, organization),
|
package/src/utils/gitAdapter.js
CHANGED
|
@@ -616,9 +616,16 @@ export class GitAdapter {
|
|
|
616
616
|
}
|
|
617
617
|
|
|
618
618
|
async pushParallel(dir, branch, force = false, organization) {
|
|
619
|
-
|
|
619
|
+
// Check if remotes already configured (caller may have set correct fallback URLs)
|
|
620
620
|
const remotesStr = await this._exec(dir, ["remote"]);
|
|
621
|
-
|
|
621
|
+
let remotes = remotesStr.split("\n").filter(r => ["github", "gitea"].includes(r.trim()));
|
|
622
|
+
|
|
623
|
+
// Only setup remotes if none exist — caller is responsible for correct URLs
|
|
624
|
+
if (remotes.length === 0) {
|
|
625
|
+
await this.ensureRemotes(dir, { organization });
|
|
626
|
+
const newStr = await this._exec(dir, ["remote"]);
|
|
627
|
+
remotes = newStr.split("\n").filter(r => ["github", "gitea"].includes(r.trim()));
|
|
628
|
+
}
|
|
622
629
|
|
|
623
630
|
if (remotes.length === 0) {
|
|
624
631
|
throw createError("REMOTE_NOT_FOUND", { message: "Nenhum remote github/gitea configurado" });
|
|
@@ -643,10 +650,13 @@ export class GitAdapter {
|
|
|
643
650
|
try {
|
|
644
651
|
console.error(`[GitAdapter] Auto-fix: repositório não existe no remote '${remote}', criando automaticamente...`);
|
|
645
652
|
const repoName = getRepoNameFromPath(dir);
|
|
646
|
-
const ensured = await this.pm.ensureRepos({ repoName, createIfMissing: true, isPublic: false });
|
|
653
|
+
const ensured = await this.pm.ensureRepos({ repoName, createIfMissing: true, isPublic: false, organization });
|
|
647
654
|
|
|
648
|
-
// Atualiza remotes após criar repo
|
|
649
|
-
|
|
655
|
+
// Atualiza remotes após criar repo — use actual result URLs
|
|
656
|
+
const ghUrl = ensured.github?.ok && ensured.github.repo ? `https://github.com/${ensured.github.repo}.git` : "";
|
|
657
|
+
const gtBase = this.pm.giteaUrl?.replace(/\/$/, "");
|
|
658
|
+
const gtUrl = ensured.gitea?.ok && ensured.gitea.repo && gtBase ? `${gtBase}/${ensured.gitea.repo}.git` : "";
|
|
659
|
+
await this.ensureRemotes(dir, { githubUrl: ghUrl, giteaUrl: gtUrl, organization });
|
|
650
660
|
|
|
651
661
|
// Tenta push novamente
|
|
652
662
|
await this.pushOne(dir, remote, branch, force, true); // Usa -u para criar branch também
|