@andrebuzeli/git-mcp 15.8.3 → 15.8.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/package.json +1 -1
- package/src/providers/providerManager.js +27 -13
- package/src/utils/gitAdapter.js +75 -56
package/package.json
CHANGED
|
@@ -11,6 +11,8 @@ export class ProviderManager {
|
|
|
11
11
|
this.github = this.githubToken ? new Octokit({ auth: this.githubToken }) : null;
|
|
12
12
|
this._githubOwner = "";
|
|
13
13
|
this._giteaOwner = "";
|
|
14
|
+
this._githubEmail = "";
|
|
15
|
+
this._giteaEmail = "";
|
|
14
16
|
this._ownerFetchedAt = 0;
|
|
15
17
|
this._remoteUrlsCache = new Map(); // Cache para URLs de remotes
|
|
16
18
|
this._cacheExpiry = 10 * 60 * 1000; // 10 minutos
|
|
@@ -23,6 +25,7 @@ export class ProviderManager {
|
|
|
23
25
|
try {
|
|
24
26
|
const me = await this.github.rest.users.getAuthenticated();
|
|
25
27
|
this._githubOwner = me.data.login || "";
|
|
28
|
+
this._githubEmail = me.data.email || ""; // Capture email
|
|
26
29
|
this._ownerFetchedAt = now;
|
|
27
30
|
return this._githubOwner;
|
|
28
31
|
} catch {
|
|
@@ -30,6 +33,11 @@ export class ProviderManager {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
async getGitHubEmail() {
|
|
37
|
+
if (!this._githubEmail) await this.getGitHubOwner();
|
|
38
|
+
return this._githubEmail;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
async getGiteaOwner() {
|
|
34
42
|
if (!this.giteaUrl || !this.giteaToken) return "";
|
|
35
43
|
const now = Date.now();
|
|
@@ -41,6 +49,7 @@ export class ProviderManager {
|
|
|
41
49
|
});
|
|
42
50
|
const d = r.data || {};
|
|
43
51
|
this._giteaOwner = d.login || d.username || "";
|
|
52
|
+
this._giteaEmail = d.email || ""; // Capture email
|
|
44
53
|
this._ownerFetchedAt = now;
|
|
45
54
|
return this._giteaOwner;
|
|
46
55
|
} catch {
|
|
@@ -48,6 +57,11 @@ export class ProviderManager {
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
async getGiteaEmail() {
|
|
61
|
+
if (!this._giteaEmail) await this.getGiteaOwner();
|
|
62
|
+
return this._giteaEmail;
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
async getRemoteUrls(repoName) {
|
|
52
66
|
const cacheKey = `urls_${repoName}`;
|
|
53
67
|
const now = Date.now();
|
|
@@ -156,16 +170,16 @@ export class ProviderManager {
|
|
|
156
170
|
let page = 1;
|
|
157
171
|
let hasMore = true;
|
|
158
172
|
while (hasMore && page <= maxPages) {
|
|
159
|
-
const { data } = await this.github.rest.repos.listForAuthenticatedUser({
|
|
160
|
-
per_page: perPage,
|
|
173
|
+
const { data } = await this.github.rest.repos.listForAuthenticatedUser({
|
|
174
|
+
per_page: perPage,
|
|
161
175
|
page,
|
|
162
|
-
visibility: "all"
|
|
176
|
+
visibility: "all"
|
|
163
177
|
});
|
|
164
|
-
results.github.push(...data.map(r => ({
|
|
165
|
-
name: r.name,
|
|
166
|
-
fullName: r.full_name,
|
|
167
|
-
url: r.html_url,
|
|
168
|
-
isPrivate: r.private
|
|
178
|
+
results.github.push(...data.map(r => ({
|
|
179
|
+
name: r.name,
|
|
180
|
+
fullName: r.full_name,
|
|
181
|
+
url: r.html_url,
|
|
182
|
+
isPrivate: r.private
|
|
169
183
|
})));
|
|
170
184
|
hasMore = data.length === perPage;
|
|
171
185
|
page++;
|
|
@@ -185,11 +199,11 @@ export class ProviderManager {
|
|
|
185
199
|
const { data } = await axios.get(`${base}/api/v1/user/repos?limit=${perPage}&page=${page}`, {
|
|
186
200
|
headers: { Authorization: `token ${this.giteaToken}` }
|
|
187
201
|
});
|
|
188
|
-
results.gitea.push(...data.map(r => ({
|
|
189
|
-
name: r.name,
|
|
190
|
-
fullName: r.full_name,
|
|
191
|
-
url: r.html_url,
|
|
192
|
-
isPrivate: r.private
|
|
202
|
+
results.gitea.push(...data.map(r => ({
|
|
203
|
+
name: r.name,
|
|
204
|
+
fullName: r.full_name,
|
|
205
|
+
url: r.html_url,
|
|
206
|
+
isPrivate: r.private
|
|
193
207
|
})));
|
|
194
208
|
hasMore = data.length === perPage;
|
|
195
209
|
page++;
|
package/src/utils/gitAdapter.js
CHANGED
|
@@ -61,14 +61,19 @@ export class GitAdapter {
|
|
|
61
61
|
return new Promise((resolve, reject) => {
|
|
62
62
|
const cp = spawn(cmd, args, {
|
|
63
63
|
cwd: dir,
|
|
64
|
-
env: {
|
|
64
|
+
env: {
|
|
65
|
+
...this.gitEnv,
|
|
66
|
+
...env,
|
|
67
|
+
GIT_TERMINAL_PROMPT: "0", // Prevent interactive prompts (fixes "No such device or address")
|
|
68
|
+
GCM_INTERACTIVE: "never" // Disable Credential Manager prompts
|
|
69
|
+
},
|
|
65
70
|
stdio: 'pipe'
|
|
66
71
|
});
|
|
67
72
|
|
|
68
73
|
let stdout = [];
|
|
69
74
|
let stderr = [];
|
|
70
75
|
let killed = false;
|
|
71
|
-
|
|
76
|
+
|
|
72
77
|
// Timeout handler
|
|
73
78
|
const timeoutId = setTimeout(() => {
|
|
74
79
|
killed = true;
|
|
@@ -87,7 +92,7 @@ export class GitAdapter {
|
|
|
87
92
|
cp.on("close", code => {
|
|
88
93
|
clearTimeout(timeoutId);
|
|
89
94
|
if (killed) return; // Already rejected by timeout
|
|
90
|
-
|
|
95
|
+
|
|
91
96
|
const outStr = Buffer.concat(stdout).toString("utf8").trim();
|
|
92
97
|
const errStr = Buffer.concat(stderr).toString("utf8").trim();
|
|
93
98
|
|
|
@@ -109,7 +114,7 @@ export class GitAdapter {
|
|
|
109
114
|
return output;
|
|
110
115
|
} catch (e) {
|
|
111
116
|
const msg = e.message || "";
|
|
112
|
-
|
|
117
|
+
|
|
113
118
|
// Auto-fix 1: dubious ownership error (common on network shares)
|
|
114
119
|
if (msg.includes("dubious ownership") && !options._retried) {
|
|
115
120
|
console.error("[GitAdapter] Auto-fix: dubious ownership, adding safe.directory...");
|
|
@@ -120,7 +125,7 @@ export class GitAdapter {
|
|
|
120
125
|
console.error("[GitAdapter] Failed to configure safe.directory:", configError.message);
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
|
-
|
|
128
|
+
|
|
124
129
|
// Auto-fix 2: Lock file presente (outro processo git pode ter crashado)
|
|
125
130
|
if ((msg.includes(".git/index.lock") || msg.includes("Unable to create") && msg.includes("lock")) && !options._lockRetried) {
|
|
126
131
|
const lockPath = path.join(dir, ".git", "index.lock");
|
|
@@ -134,7 +139,7 @@ export class GitAdapter {
|
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
|
-
|
|
142
|
+
|
|
138
143
|
// Auto-fix 3: CRLF warnings (just retry with autocrlf config)
|
|
139
144
|
if (msg.includes("CRLF will be replaced") && !options._crlfRetried) {
|
|
140
145
|
console.error("[GitAdapter] Auto-fix: configuring core.autocrlf...");
|
|
@@ -147,7 +152,7 @@ export class GitAdapter {
|
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
if (options.ignoreErrors) return "";
|
|
150
|
-
|
|
155
|
+
|
|
151
156
|
// Enhance error message with context
|
|
152
157
|
const cmdStr = `git ${args.join(" ")}`;
|
|
153
158
|
const enhancedError = new Error(`Command failed: ${cmdStr}\n${msg}`);
|
|
@@ -182,12 +187,12 @@ export class GitAdapter {
|
|
|
182
187
|
*/
|
|
183
188
|
async checkIntegrity(dir) {
|
|
184
189
|
await this._ensureGitRepo(dir);
|
|
185
|
-
|
|
190
|
+
|
|
186
191
|
try {
|
|
187
192
|
// Verificação rápida de objetos
|
|
188
193
|
const output = await this._exec(dir, ["fsck", "--connectivity-only"], { ignoreErrors: true });
|
|
189
194
|
const hasErrors = output.includes("error") || output.includes("missing") || output.includes("broken");
|
|
190
|
-
|
|
195
|
+
|
|
191
196
|
return {
|
|
192
197
|
ok: !hasErrors,
|
|
193
198
|
output: output || "Repository integrity OK",
|
|
@@ -211,7 +216,7 @@ export class GitAdapter {
|
|
|
211
216
|
if (!url || typeof url !== "string") {
|
|
212
217
|
return { valid: false, error: "URL vazia ou inválida" };
|
|
213
218
|
}
|
|
214
|
-
|
|
219
|
+
|
|
215
220
|
// Padrões válidos de URL git
|
|
216
221
|
const patterns = [
|
|
217
222
|
/^https?:\/\/[^\s]+\.git$/i, // https://github.com/user/repo.git
|
|
@@ -220,9 +225,9 @@ export class GitAdapter {
|
|
|
220
225
|
/^git:\/\/[^\s]+\.git$/i, // git://github.com/user/repo.git
|
|
221
226
|
/^ssh:\/\/[^\s]+\.git$/i, // ssh://git@github.com/user/repo.git
|
|
222
227
|
];
|
|
223
|
-
|
|
228
|
+
|
|
224
229
|
const isValid = patterns.some(p => p.test(url));
|
|
225
|
-
|
|
230
|
+
|
|
226
231
|
if (!isValid) {
|
|
227
232
|
return {
|
|
228
233
|
valid: false,
|
|
@@ -230,7 +235,7 @@ export class GitAdapter {
|
|
|
230
235
|
suggestion: "Use: https://github.com/user/repo.git ou git@github.com:user/repo.git"
|
|
231
236
|
};
|
|
232
237
|
}
|
|
233
|
-
|
|
238
|
+
|
|
234
239
|
return { valid: true, url };
|
|
235
240
|
}
|
|
236
241
|
|
|
@@ -291,18 +296,18 @@ export class GitAdapter {
|
|
|
291
296
|
|
|
292
297
|
const currentBranch = await this.getCurrentBranch(dir);
|
|
293
298
|
return {
|
|
294
|
-
modified,
|
|
295
|
-
created,
|
|
296
|
-
deleted,
|
|
297
|
-
renamed: [],
|
|
299
|
+
modified,
|
|
300
|
+
created,
|
|
301
|
+
deleted,
|
|
302
|
+
renamed: [],
|
|
298
303
|
notAdded,
|
|
299
304
|
// Aliases para compatibilidade
|
|
300
305
|
not_added: notAdded,
|
|
301
|
-
staged,
|
|
306
|
+
staged,
|
|
302
307
|
conflicted: [],
|
|
303
308
|
currentBranch,
|
|
304
309
|
current: currentBranch, // Alias para compatibilidade
|
|
305
|
-
isClean: lines.length === 0,
|
|
310
|
+
isClean: lines.length === 0,
|
|
306
311
|
files
|
|
307
312
|
};
|
|
308
313
|
}
|
|
@@ -330,8 +335,21 @@ export class GitAdapter {
|
|
|
330
335
|
// Fallback logic
|
|
331
336
|
const ownerGH = await this.pm.getGitHubOwner().catch(() => null);
|
|
332
337
|
const ownerGE = await this.pm.getGiteaOwner().catch(() => null);
|
|
338
|
+
|
|
339
|
+
let email = "";
|
|
340
|
+
|
|
341
|
+
// Try to fetch real email
|
|
342
|
+
const emailGH = await this.pm.getGitHubEmail().catch(() => null);
|
|
343
|
+
const emailGE = await this.pm.getGiteaEmail().catch(() => null);
|
|
344
|
+
|
|
345
|
+
if (emailGE) email = emailGE;
|
|
346
|
+
else if (emailGH) email = emailGH;
|
|
347
|
+
|
|
333
348
|
const who = ownerGH || ownerGE || "Git User";
|
|
334
|
-
|
|
349
|
+
|
|
350
|
+
if (!email) {
|
|
351
|
+
email = `${who}@users.noreply`;
|
|
352
|
+
}
|
|
335
353
|
|
|
336
354
|
await this.setConfig(dir, "user.name", who);
|
|
337
355
|
await this.setConfig(dir, "user.email", email);
|
|
@@ -340,6 +358,7 @@ export class GitAdapter {
|
|
|
340
358
|
}
|
|
341
359
|
|
|
342
360
|
async commit(dir, message) {
|
|
361
|
+
// Ensure author is set
|
|
343
362
|
await this.getAuthor(dir);
|
|
344
363
|
await this._exec(dir, ["commit", "-m", message]);
|
|
345
364
|
return await this._exec(dir, ["rev-parse", "HEAD"]);
|
|
@@ -413,19 +432,19 @@ export class GitAdapter {
|
|
|
413
432
|
} catch (e) {
|
|
414
433
|
const msg = e.message || "";
|
|
415
434
|
const msgLower = msg.toLowerCase();
|
|
416
|
-
|
|
435
|
+
|
|
417
436
|
// Auto-correção: Se branch não existe no remote, tenta com -u (set-upstream)
|
|
418
|
-
const branchNotExists = msgLower.includes("has no upstream branch") ||
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
const branchNotExists = msgLower.includes("has no upstream branch") ||
|
|
438
|
+
msgLower.includes("no upstream branch") ||
|
|
439
|
+
(msgLower.includes("remote branch") && msgLower.includes("does not exist")) ||
|
|
440
|
+
msgLower.includes("ref_not_found") ||
|
|
441
|
+
(msgLower.includes("fatal") && msgLower.includes("current branch") && msgLower.includes("has no upstream"));
|
|
442
|
+
|
|
424
443
|
if (branchNotExists && !setUpstream && !force) {
|
|
425
444
|
console.error(`[GitAdapter] Auto-fix: branch '${branch}' não existe no remote '${remote}', tentando com --set-upstream (-u)...`);
|
|
426
445
|
return await this.pushOne(dir, remote, branch, force, true);
|
|
427
446
|
}
|
|
428
|
-
|
|
447
|
+
|
|
429
448
|
if (msg.includes("rejected") || msgLower.includes("non-fast-forward")) {
|
|
430
449
|
throw createError("PUSH_REJECTED", { message: msg, remote, branch });
|
|
431
450
|
}
|
|
@@ -450,22 +469,22 @@ export class GitAdapter {
|
|
|
450
469
|
} catch (error) {
|
|
451
470
|
const errorMsg = error.message || String(error);
|
|
452
471
|
const errorMsgLower = errorMsg.toLowerCase();
|
|
453
|
-
|
|
472
|
+
|
|
454
473
|
// Auto-correção 1: Se repositório não existe, cria automaticamente
|
|
455
|
-
const repoNotFound = errorMsgLower.includes("repository not found") ||
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
474
|
+
const repoNotFound = errorMsgLower.includes("repository not found") ||
|
|
475
|
+
errorMsgLower.includes("repo not found") ||
|
|
476
|
+
(errorMsgLower.includes("fatal") && errorMsgLower.includes("repository") && errorMsgLower.includes("not found")) ||
|
|
477
|
+
(errorMsgLower.includes("not found") && (errorMsgLower.includes("remote") || errorMsgLower.includes("404")));
|
|
478
|
+
|
|
460
479
|
if (repoNotFound && !force) {
|
|
461
480
|
try {
|
|
462
481
|
console.error(`[GitAdapter] Auto-fix: repositório não existe no remote '${remote}', criando automaticamente...`);
|
|
463
482
|
const repoName = getRepoNameFromPath(dir);
|
|
464
483
|
const ensured = await this.pm.ensureRepos({ repoName, createIfMissing: true, isPublic: false });
|
|
465
|
-
|
|
484
|
+
|
|
466
485
|
// Atualiza remotes após criar repo
|
|
467
486
|
await this.ensureRemotes(dir, {});
|
|
468
|
-
|
|
487
|
+
|
|
469
488
|
// Tenta push novamente
|
|
470
489
|
await this.pushOne(dir, remote, branch, force, true); // Usa -u para criar branch também
|
|
471
490
|
return { remote, success: true, repoCreated: true, setUpstream: true };
|
|
@@ -473,13 +492,13 @@ export class GitAdapter {
|
|
|
473
492
|
return { remote, success: false, error: `Falhou ao criar repo: ${repoCreateError.message}`, triedCreateRepo: true };
|
|
474
493
|
}
|
|
475
494
|
}
|
|
476
|
-
|
|
495
|
+
|
|
477
496
|
// Auto-correção 2: Se branch não existe no remote, tenta com --set-upstream
|
|
478
|
-
const branchNotExists = errorMsgLower.includes("has no upstream branch") ||
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
497
|
+
const branchNotExists = errorMsgLower.includes("has no upstream branch") ||
|
|
498
|
+
errorMsgLower.includes("no upstream branch") ||
|
|
499
|
+
(errorMsgLower.includes("remote branch") && errorMsgLower.includes("does not exist")) ||
|
|
500
|
+
errorMsgLower.includes("ref_not_found");
|
|
501
|
+
|
|
483
502
|
if (branchNotExists && !force) {
|
|
484
503
|
try {
|
|
485
504
|
console.error(`[GitAdapter] Auto-fix: branch '${branch}' não existe no remote '${remote}', tentando com --set-upstream...`);
|
|
@@ -489,7 +508,7 @@ export class GitAdapter {
|
|
|
489
508
|
return { remote, success: false, error: retryError.message, triedSetUpstream: true };
|
|
490
509
|
}
|
|
491
510
|
}
|
|
492
|
-
|
|
511
|
+
|
|
493
512
|
return { remote, success: false, error: errorMsg };
|
|
494
513
|
}
|
|
495
514
|
});
|
|
@@ -499,17 +518,17 @@ export class GitAdapter {
|
|
|
499
518
|
const failed = results.filter(r => r.status === "rejected" || !r.value.success);
|
|
500
519
|
|
|
501
520
|
if (successful.length === 0) {
|
|
502
|
-
throw createError("PUSH_REJECTED", {
|
|
503
|
-
message: "Push falhou para todos os remotes",
|
|
504
|
-
errors: failed.map(f => f.value?.error || f.reason?.message || "Erro desconhecido")
|
|
521
|
+
throw createError("PUSH_REJECTED", {
|
|
522
|
+
message: "Push falhou para todos os remotes",
|
|
523
|
+
errors: failed.map(f => f.value?.error || f.reason?.message || "Erro desconhecido")
|
|
505
524
|
});
|
|
506
525
|
}
|
|
507
526
|
|
|
508
527
|
return {
|
|
509
528
|
pushed: successful.map(s => s.value.remote),
|
|
510
|
-
failed: failed.map(f => ({
|
|
511
|
-
remote: f.value?.remote || "unknown",
|
|
512
|
-
error: f.value?.error || f.reason?.message || "Erro desconhecido"
|
|
529
|
+
failed: failed.map(f => ({
|
|
530
|
+
remote: f.value?.remote || "unknown",
|
|
531
|
+
error: f.value?.error || f.reason?.message || "Erro desconhecido"
|
|
513
532
|
}))
|
|
514
533
|
};
|
|
515
534
|
}
|
|
@@ -709,7 +728,7 @@ export class GitAdapter {
|
|
|
709
728
|
async resetSoft(dir, ref) { await this._exec(dir, ["reset", "--soft", ref]); }
|
|
710
729
|
async resetMixed(dir, ref) { await this._exec(dir, ["reset", "--mixed", ref]); }
|
|
711
730
|
async resetHard(dir, ref) { await this._exec(dir, ["reset", "--hard", ref]); }
|
|
712
|
-
|
|
731
|
+
|
|
713
732
|
async resetHardClean(dir, ref) {
|
|
714
733
|
await this._exec(dir, ["reset", "--hard", ref]);
|
|
715
734
|
const cleanResult = await this.cleanUntracked(dir);
|
|
@@ -719,19 +738,19 @@ export class GitAdapter {
|
|
|
719
738
|
async getMergeStatus(dir) {
|
|
720
739
|
const mergeHeadPath = path.join(dir, ".git", "MERGE_HEAD");
|
|
721
740
|
const isMerging = fs.existsSync(mergeHeadPath);
|
|
722
|
-
|
|
741
|
+
|
|
723
742
|
if (!isMerging) {
|
|
724
743
|
return { merging: false, message: "Nenhum merge em andamento" };
|
|
725
744
|
}
|
|
726
|
-
|
|
745
|
+
|
|
727
746
|
// Get conflicted files
|
|
728
747
|
try {
|
|
729
748
|
const diff = await this._exec(dir, ["diff", "--name-only", "--diff-filter=U"]);
|
|
730
749
|
const conflicts = diff.split("\n").filter(Boolean);
|
|
731
|
-
return {
|
|
732
|
-
merging: true,
|
|
750
|
+
return {
|
|
751
|
+
merging: true,
|
|
733
752
|
conflicts,
|
|
734
|
-
message: conflicts.length > 0
|
|
753
|
+
message: conflicts.length > 0
|
|
735
754
|
? `Merge em andamento com ${conflicts.length} conflito(s)`
|
|
736
755
|
: "Merge em andamento sem conflitos"
|
|
737
756
|
};
|
|
@@ -841,7 +860,7 @@ export class GitAdapter {
|
|
|
841
860
|
}
|
|
842
861
|
|
|
843
862
|
// ============ GIT LFS SUPPORT ============
|
|
844
|
-
|
|
863
|
+
|
|
845
864
|
/**
|
|
846
865
|
* Verifica se Git LFS está instalado
|
|
847
866
|
*/
|