@andre.buzeli/git-mcp 15.12.6 → 16.0.1
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 +2 -2
- package/src/utils/errors.js +4 -2
- package/src/utils/gitAdapter.js +118 -0
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andre.buzeli/git-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "MCP server para Git com operações locais e sincronização paralela GitHub/Gitea",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "
|
|
7
|
+
"author": "andre.buzeli",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
package/src/utils/errors.js
CHANGED
|
@@ -167,7 +167,7 @@ export const ERROR_CODES = {
|
|
|
167
167
|
// Git Corruption
|
|
168
168
|
GIT_CORRUPTED: {
|
|
169
169
|
code: "GIT_CORRUPTED",
|
|
170
|
-
suggestion: "Repositório Git corrompido.
|
|
170
|
+
suggestion: "Repositório Git corrompido. O sistema tentou auto-corrigir (remove index, gc --prune=now, re-add). Se persistir, re-clone do remote"
|
|
171
171
|
},
|
|
172
172
|
GIT_LOCK_FILE: {
|
|
173
173
|
code: "GIT_LOCK_FILE",
|
|
@@ -312,7 +312,9 @@ export function mapExternalError(error, context = {}) {
|
|
|
312
312
|
|
|
313
313
|
// Git corruption
|
|
314
314
|
if (msg.includes("corrupt") || msg.includes("bad object") || msg.includes("broken") ||
|
|
315
|
-
msg.includes("missing object") || msg.includes("invalid sha") || msg.includes("fatal: packed")
|
|
315
|
+
msg.includes("missing object") || msg.includes("invalid sha") || msg.includes("fatal: packed") ||
|
|
316
|
+
msg.includes("invalid object") || msg.includes("error building trees") ||
|
|
317
|
+
msg.includes("unable to read tree")) {
|
|
316
318
|
return createError("GIT_CORRUPTED", { originalError: msg });
|
|
317
319
|
}
|
|
318
320
|
|
package/src/utils/gitAdapter.js
CHANGED
|
@@ -181,6 +181,124 @@ export class GitAdapter {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// Auto-fix 4: Git object corruption (invalid object, error building trees, bad object, unable to read tree)
|
|
185
|
+
if ((msg.includes("invalid object") || msg.includes("error building trees") ||
|
|
186
|
+
msg.includes("bad object") || msg.includes("corrupt") ||
|
|
187
|
+
msg.includes("missing object") || msg.includes("broken link") ||
|
|
188
|
+
msg.includes("unable to read tree")) &&
|
|
189
|
+
!options._corruptionRetried) {
|
|
190
|
+
console.error("[GitAdapter] Auto-fix: git object corruption detected, attempting multi-step recovery...");
|
|
191
|
+
try {
|
|
192
|
+
// Step 1: Remove corrupt index file
|
|
193
|
+
const indexPath = path.join(dir, ".git", "index");
|
|
194
|
+
if (fs.existsSync(indexPath)) {
|
|
195
|
+
try { fs.unlinkSync(indexPath); } catch { }
|
|
196
|
+
console.error("[GitAdapter] Removed corrupt index file");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Step 2: Try to clean corrupt refs (branches pointing to bad objects)
|
|
200
|
+
try {
|
|
201
|
+
const packedRefsPath = path.join(dir, ".git", "packed-refs");
|
|
202
|
+
if (fs.existsSync(packedRefsPath)) {
|
|
203
|
+
// Read packed refs and remove lines referencing corrupt objects
|
|
204
|
+
const content = fs.readFileSync(packedRefsPath, "utf8");
|
|
205
|
+
const lines = content.split("\n");
|
|
206
|
+
const cleanLines = lines.filter(line => {
|
|
207
|
+
// Keep header and non-ref lines
|
|
208
|
+
if (line.startsWith("#") || !line.trim()) return true;
|
|
209
|
+
// Keep lines that DON'T reference tmp/ or test branches
|
|
210
|
+
return !line.includes("tmp/") && !line.includes("test-");
|
|
211
|
+
});
|
|
212
|
+
if (cleanLines.length !== lines.length) {
|
|
213
|
+
fs.writeFileSync(packedRefsPath, cleanLines.join("\n"), "utf8");
|
|
214
|
+
console.error(`[GitAdapter] Cleaned ${lines.length - cleanLines.length} potentially corrupt packed refs`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch { }
|
|
218
|
+
|
|
219
|
+
// Step 3: Remove loose corrupt objects (they'll be re-created)
|
|
220
|
+
try {
|
|
221
|
+
const objectsDir = path.join(dir, ".git", "objects");
|
|
222
|
+
// Run fsck to identify broken objects (ignore errors)
|
|
223
|
+
const fsckOutput = await this._runSpawn(this.gitPath, ["fsck", "--no-dangling"], dir).catch(e => e.message || "");
|
|
224
|
+
const brokenShas = [];
|
|
225
|
+
const fsckLines = (typeof fsckOutput === "string" ? fsckOutput : "").split("\n");
|
|
226
|
+
for (const line of fsckLines) {
|
|
227
|
+
// Match patterns like "error in tree SHA: invalid object"
|
|
228
|
+
const shaMatch = line.match(/([0-9a-f]{40})/);
|
|
229
|
+
if (shaMatch && (line.includes("invalid") || line.includes("corrupt") || line.includes("broken") || line.includes("missing"))) {
|
|
230
|
+
brokenShas.push(shaMatch[1]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Remove identified corrupt loose objects
|
|
234
|
+
for (const sha of brokenShas) {
|
|
235
|
+
const objPath = path.join(objectsDir, sha.substring(0, 2), sha.substring(2));
|
|
236
|
+
if (fs.existsSync(objPath)) {
|
|
237
|
+
try { fs.unlinkSync(objPath); console.error(`[GitAdapter] Removed corrupt object: ${sha.substring(0, 8)}`); } catch { }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch { }
|
|
241
|
+
|
|
242
|
+
// Step 4: Try gc (may fail with deep corruption, that's OK)
|
|
243
|
+
await this._runSpawn(this.gitPath, ["reflog", "expire", "--expire=now", "--all"], dir).catch(() => { });
|
|
244
|
+
await this._runSpawn(this.gitPath, ["gc", "--prune=now", "--aggressive"], dir).catch(() => { });
|
|
245
|
+
|
|
246
|
+
// Step 5: Try simple reset first
|
|
247
|
+
let resetOk = false;
|
|
248
|
+
try {
|
|
249
|
+
await this._runSpawn(this.gitPath, ["reset"], dir);
|
|
250
|
+
resetOk = true;
|
|
251
|
+
} catch {
|
|
252
|
+
// Reset failed (HEAD tree is corrupt) - try nuclear recovery
|
|
253
|
+
console.error("[GitAdapter] Simple reset failed, attempting nuclear recovery...");
|
|
254
|
+
try {
|
|
255
|
+
// Re-add all working directory files to create fresh objects
|
|
256
|
+
await this._runSpawn(this.gitPath, ["add", "."], dir);
|
|
257
|
+
// Create new tree from working directory
|
|
258
|
+
const newTree = await this._runSpawn(this.gitPath, ["write-tree"], dir);
|
|
259
|
+
if (newTree && newTree.match(/^[0-9a-f]{40}$/)) {
|
|
260
|
+
// Find a valid parent commit (walk back until we find one)
|
|
261
|
+
let parentArg = [];
|
|
262
|
+
for (let i = 1; i <= 10; i++) {
|
|
263
|
+
try {
|
|
264
|
+
const parentSha = await this._runSpawn(this.gitPath, ["rev-parse", `HEAD~${i}`], dir);
|
|
265
|
+
if (parentSha && parentSha.match(/^[0-9a-f]{40}$/)) {
|
|
266
|
+
parentArg = ["-p", parentSha];
|
|
267
|
+
console.error(`[GitAdapter] Found valid parent at HEAD~${i}: ${parentSha.substring(0, 8)}`);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
} catch { continue; }
|
|
271
|
+
}
|
|
272
|
+
// Create recovery commit using low-level git commands
|
|
273
|
+
const newCommit = await this._runSpawn(this.gitPath,
|
|
274
|
+
["commit-tree", newTree, ...parentArg, "-m", "chore: auto-recovery from git object corruption"], dir);
|
|
275
|
+
if (newCommit && newCommit.match(/^[0-9a-f]{40}$/)) {
|
|
276
|
+
// Point HEAD to the new clean commit
|
|
277
|
+
await this._runSpawn(this.gitPath, ["update-ref", "HEAD", newCommit], dir);
|
|
278
|
+
console.error(`[GitAdapter] Nuclear recovery complete: created clean commit ${newCommit.substring(0, 8)}`);
|
|
279
|
+
resetOk = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} catch (nuclearError) {
|
|
283
|
+
console.error("[GitAdapter] Nuclear recovery failed:", nuclearError.message);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Step 6: Final re-add (rebuild index from working directory)
|
|
288
|
+
await this._runSpawn(this.gitPath, ["add", "."], dir).catch(() => { });
|
|
289
|
+
|
|
290
|
+
if (resetOk) {
|
|
291
|
+
console.error("[GitAdapter] Recovery complete, retrying operation...");
|
|
292
|
+
return await this._exec(dir, args, { ...options, _corruptionRetried: true });
|
|
293
|
+
} else {
|
|
294
|
+
console.error("[GitAdapter] Recovery partially failed. Retrying anyway...");
|
|
295
|
+
return await this._exec(dir, args, { ...options, _corruptionRetried: true });
|
|
296
|
+
}
|
|
297
|
+
} catch (recoveryError) {
|
|
298
|
+
console.error("[GitAdapter] Recovery failed:", recoveryError.message);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
184
302
|
if (options.ignoreErrors) return "";
|
|
185
303
|
|
|
186
304
|
// Enhance error message with context
|