@andrebuzeli/git-mcp 15.9.0 → 15.9.2
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/index.js +1 -1
- package/src/tools/git-workflow.js +57 -157
- package/src/utils/errors.js +60 -60
- package/src/utils/gitAdapter.js +20 -7
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -47,7 +47,7 @@ if (!hasGitHub && !hasGitea) {
|
|
|
47
47
|
|
|
48
48
|
const transport = new StdioServerTransport();
|
|
49
49
|
const server = new Server(
|
|
50
|
-
{ name: "git-mcpv2", version: "15.
|
|
50
|
+
{ name: "git-mcpv2", version: "15.9.2" },
|
|
51
51
|
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
|
|
52
52
|
);
|
|
53
53
|
server.connect(transport);
|
|
@@ -8,13 +8,13 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
8
8
|
const inputSchema = {
|
|
9
9
|
type: "object",
|
|
10
10
|
properties: {
|
|
11
|
-
projectPath: {
|
|
12
|
-
type: "string",
|
|
13
|
-
description: "Caminho absoluto do diretório do projeto (ex: 'C:/Users/user/projeto' ou '/home/user/projeto')"
|
|
11
|
+
projectPath: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Caminho absoluto do diretório do projeto (ex: 'C:/Users/user/projeto' ou '/home/user/projeto')"
|
|
14
14
|
},
|
|
15
|
-
action: {
|
|
16
|
-
type: "string",
|
|
17
|
-
enum: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes", "clean"
|
|
15
|
+
action: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes", "clean"],
|
|
18
18
|
description: `Ação a executar:
|
|
19
19
|
- init: Inicializa repositório git local E cria repos no GitHub/Gitea automaticamente
|
|
20
20
|
- status: Retorna arquivos modificados, staged, untracked (use ANTES de commit para ver o que mudou)
|
|
@@ -23,20 +23,19 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
23
23
|
- commit: Cria commit com os arquivos staged (use DEPOIS de add)
|
|
24
24
|
- push: Envia commits para GitHub E Gitea em paralelo (use DEPOIS de commit)
|
|
25
25
|
- ensure-remotes: Configura remotes GitHub e Gitea (use se push falhar por falta de remote)
|
|
26
|
-
- clean: Remove arquivos não rastreados do working directory
|
|
27
|
-
- update: Atualiza projeto completo: init (se necessário), add, commit, ensure-remotes e push`
|
|
26
|
+
- clean: Remove arquivos não rastreados do working directory`
|
|
28
27
|
},
|
|
29
|
-
files: {
|
|
30
|
-
type: "array",
|
|
28
|
+
files: {
|
|
29
|
+
type: "array",
|
|
31
30
|
items: { type: "string" },
|
|
32
31
|
description: "Lista de arquivos para add/remove. Use ['.'] para todos os arquivos. Ex: ['src/index.js', 'package.json']"
|
|
33
32
|
},
|
|
34
|
-
message: {
|
|
33
|
+
message: {
|
|
35
34
|
type: "string",
|
|
36
|
-
description: "Mensagem do commit. Obrigatório para action='commit'.
|
|
35
|
+
description: "Mensagem do commit. Obrigatório para action='commit'. Ex: 'feat: adiciona nova funcionalidade'"
|
|
37
36
|
},
|
|
38
|
-
force: {
|
|
39
|
-
type: "boolean",
|
|
37
|
+
force: {
|
|
38
|
+
type: "boolean",
|
|
40
39
|
description: "Force push (use apenas se push normal falhar com erro de histórico divergente). Default: false"
|
|
41
40
|
},
|
|
42
41
|
createGitignore: {
|
|
@@ -48,7 +47,7 @@ export function createGitWorkflowTool(pm, git) {
|
|
|
48
47
|
description: "Se true, repositório será PÚBLICO. Default: false (privado). Aplica-se a action='init' e 'ensure-remotes'"
|
|
49
48
|
},
|
|
50
49
|
dryRun: {
|
|
51
|
-
type: "boolean",
|
|
50
|
+
type: "boolean",
|
|
52
51
|
description: "Se true, simula a operação sem executar (útil para testes). Default: false"
|
|
53
52
|
}
|
|
54
53
|
},
|
|
@@ -72,7 +71,6 @@ QUANDO USAR CADA ACTION:
|
|
|
72
71
|
- init: Apenas uma vez, para novos projetos (cria .gitignore automaticamente)
|
|
73
72
|
- ensure-remotes: Se push falhar por falta de configuração
|
|
74
73
|
- clean: Limpar arquivos não rastreados
|
|
75
|
-
- update: Para atualizar projeto completo (init + add + commit + push) - mais rápido que fazer cada ação separadamente
|
|
76
74
|
|
|
77
75
|
EXEMPLOS DE USO:
|
|
78
76
|
• Iniciar projeto: { "projectPath": "/path/to/project", "action": "init" }
|
|
@@ -90,29 +88,29 @@ EXEMPLOS DE USO:
|
|
|
90
88
|
try {
|
|
91
89
|
// #region agent log
|
|
92
90
|
if (process.env.DEBUG_AGENT_LOG) {
|
|
93
|
-
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sessionId:'debug-session',runId:'run1',hypothesisId:'H1',location:'git-workflow.js:handle-entry',message:'handle start',data:{action,projectPath},timestamp:Date.now()})}).catch(()=>{});
|
|
91
|
+
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: 'debug-session', runId: 'run1', hypothesisId: 'H1', location: 'git-workflow.js:handle-entry', message: 'handle start', data: { action, projectPath }, timestamp: Date.now() }) }).catch(() => { });
|
|
94
92
|
}
|
|
95
93
|
// #endregion
|
|
96
94
|
|
|
97
95
|
validateProjectPath(projectPath);
|
|
98
96
|
// #region agent log
|
|
99
97
|
if (process.env.DEBUG_AGENT_LOG) {
|
|
100
|
-
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sessionId:'debug-session',runId:'run1',hypothesisId:'H1',location:'git-workflow.js:handle-validated',message:'projectPath validated',data:{action},timestamp:Date.now()})}).catch(()=>{});
|
|
98
|
+
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: 'debug-session', runId: 'run1', hypothesisId: 'H1', location: 'git-workflow.js:handle-validated', message: 'projectPath validated', data: { action }, timestamp: Date.now() }) }).catch(() => { });
|
|
101
99
|
}
|
|
102
100
|
// #endregion
|
|
103
101
|
if (action === "init") {
|
|
104
102
|
if (args.dryRun) {
|
|
105
|
-
return asToolResult({
|
|
106
|
-
success: true,
|
|
103
|
+
return asToolResult({
|
|
104
|
+
success: true,
|
|
107
105
|
dryRun: true,
|
|
108
106
|
message: "DRY RUN: Repositório seria inicializado localmente e nos providers",
|
|
109
107
|
repoName: getRepoNameFromPath(projectPath),
|
|
110
108
|
gitignoreCreated: shouldCreateGitignore
|
|
111
109
|
});
|
|
112
110
|
}
|
|
113
|
-
|
|
111
|
+
|
|
114
112
|
await git.init(projectPath);
|
|
115
|
-
|
|
113
|
+
|
|
116
114
|
// Criar .gitignore baseado no tipo de projeto
|
|
117
115
|
const shouldCreateGitignore = args.createGitignore !== false;
|
|
118
116
|
let gitignoreCreated = false;
|
|
@@ -122,12 +120,12 @@ EXEMPLOS DE USO:
|
|
|
122
120
|
await git.createGitignore(projectPath, patterns);
|
|
123
121
|
gitignoreCreated = true;
|
|
124
122
|
}
|
|
125
|
-
|
|
123
|
+
|
|
126
124
|
const repo = getRepoNameFromPath(projectPath);
|
|
127
125
|
const isPublic = args.isPublic === true; // Default: privado
|
|
128
126
|
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic });
|
|
129
|
-
return asToolResult({
|
|
130
|
-
success: true,
|
|
127
|
+
return asToolResult({
|
|
128
|
+
success: true,
|
|
131
129
|
ensured,
|
|
132
130
|
isPrivate: !isPublic,
|
|
133
131
|
gitignoreCreated,
|
|
@@ -138,24 +136,24 @@ EXEMPLOS DE USO:
|
|
|
138
136
|
const st = await git.status(projectPath);
|
|
139
137
|
// #region agent log
|
|
140
138
|
if (process.env.DEBUG_AGENT_LOG) {
|
|
141
|
-
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sessionId:'debug-session',runId:'run1',hypothesisId:'H2',location:'git-workflow.js:status',message:'status result',data:{modified:st.modified.length,created:st.created.length,deleted:st.deleted.length,notAdded:st.not_added.length,isClean:st.isClean},timestamp:Date.now()})}).catch(()=>{});
|
|
139
|
+
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: 'debug-session', runId: 'run1', hypothesisId: 'H2', location: 'git-workflow.js:status', message: 'status result', data: { modified: st.modified.length, created: st.created.length, deleted: st.deleted.length, notAdded: st.not_added.length, isClean: st.isClean }, timestamp: Date.now() }) }).catch(() => { });
|
|
142
140
|
}
|
|
143
141
|
// #endregion
|
|
144
|
-
|
|
142
|
+
|
|
145
143
|
if (args.dryRun) {
|
|
146
|
-
return asToolResult({
|
|
147
|
-
success: true,
|
|
144
|
+
return asToolResult({
|
|
145
|
+
success: true,
|
|
148
146
|
dryRun: true,
|
|
149
147
|
message: "DRY RUN: Status seria verificado",
|
|
150
148
|
...st
|
|
151
149
|
});
|
|
152
150
|
}
|
|
153
|
-
|
|
151
|
+
|
|
154
152
|
// Adicionar contexto para AI Agent decidir próximo passo
|
|
155
153
|
const hasUnstaged = st.not_added?.length > 0 || st.modified?.length > 0 || st.created?.length > 0;
|
|
156
154
|
const hasStaged = st.staged?.length > 0;
|
|
157
155
|
const hasConflicts = st.conflicted?.length > 0;
|
|
158
|
-
|
|
156
|
+
|
|
159
157
|
let _aiContext = {
|
|
160
158
|
needsAdd: hasUnstaged && !hasStaged,
|
|
161
159
|
readyToCommit: hasStaged && !hasConflicts,
|
|
@@ -163,7 +161,7 @@ EXEMPLOS DE USO:
|
|
|
163
161
|
isClean: st.isClean,
|
|
164
162
|
suggestedAction: null
|
|
165
163
|
};
|
|
166
|
-
|
|
164
|
+
|
|
167
165
|
if (hasConflicts) {
|
|
168
166
|
_aiContext.suggestedAction = "Resolva os conflitos manualmente antes de continuar";
|
|
169
167
|
} else if (hasStaged) {
|
|
@@ -173,21 +171,21 @@ EXEMPLOS DE USO:
|
|
|
173
171
|
} else {
|
|
174
172
|
_aiContext.suggestedAction = "Working tree limpa. Modifique arquivos ou use action='push' se há commits pendentes";
|
|
175
173
|
}
|
|
176
|
-
|
|
174
|
+
|
|
177
175
|
return asToolResult({ ...st, _aiContext }, { tool: 'workflow', action: 'status' });
|
|
178
176
|
}
|
|
179
177
|
if (action === "add") {
|
|
180
178
|
const files = Array.isArray(args.files) && args.files.length ? args.files : ["."];
|
|
181
|
-
|
|
179
|
+
|
|
182
180
|
if (args.dryRun) {
|
|
183
|
-
return asToolResult({
|
|
184
|
-
success: true,
|
|
181
|
+
return asToolResult({
|
|
182
|
+
success: true,
|
|
185
183
|
dryRun: true,
|
|
186
184
|
message: `DRY RUN: Arquivos seriam adicionados: ${files.join(", ")}`,
|
|
187
185
|
files
|
|
188
186
|
});
|
|
189
187
|
}
|
|
190
|
-
|
|
188
|
+
|
|
191
189
|
await git.add(projectPath, files);
|
|
192
190
|
return asToolResult({ success: true, files }, { tool: 'workflow', action: 'add' });
|
|
193
191
|
}
|
|
@@ -200,19 +198,19 @@ EXEMPLOS DE USO:
|
|
|
200
198
|
if (!args.message) {
|
|
201
199
|
return asToolError("MISSING_PARAMETER", "message é obrigatório para commit", { parameter: "message" });
|
|
202
200
|
}
|
|
203
|
-
|
|
201
|
+
|
|
204
202
|
if (args.dryRun) {
|
|
205
|
-
return asToolResult({
|
|
206
|
-
success: true,
|
|
203
|
+
return asToolResult({
|
|
204
|
+
success: true,
|
|
207
205
|
dryRun: true,
|
|
208
206
|
message: `DRY RUN: Commit seria criado com mensagem: "${args.message}"`
|
|
209
207
|
});
|
|
210
208
|
}
|
|
211
|
-
|
|
209
|
+
|
|
212
210
|
const sha = await git.commit(projectPath, args.message);
|
|
213
211
|
// #region agent log
|
|
214
212
|
if (process.env.DEBUG_AGENT_LOG) {
|
|
215
|
-
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sessionId:'debug-session',runId:'run1',hypothesisId:'H3',location:'git-workflow.js:commit',message:'commit created',data:{sha,message:args.message},timestamp:Date.now()})}).catch(()=>{});
|
|
213
|
+
fetch('http://127.0.0.1:8243/ingest/a7114eec-653b-43b0-9f09-7073baee17bf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: 'debug-session', runId: 'run1', hypothesisId: 'H3', location: 'git-workflow.js:commit', message: 'commit created', data: { sha, message: args.message }, timestamp: Date.now() }) }).catch(() => { });
|
|
216
214
|
}
|
|
217
215
|
// #endregion
|
|
218
216
|
return asToolResult({ success: true, sha, message: args.message }, { tool: 'workflow', action: 'commit' });
|
|
@@ -220,19 +218,19 @@ EXEMPLOS DE USO:
|
|
|
220
218
|
if (action === "clean") {
|
|
221
219
|
if (args.dryRun) {
|
|
222
220
|
const result = await git.cleanUntracked(projectPath);
|
|
223
|
-
return asToolResult({
|
|
224
|
-
success: true,
|
|
221
|
+
return asToolResult({
|
|
222
|
+
success: true,
|
|
225
223
|
dryRun: true,
|
|
226
224
|
message: `DRY RUN: ${result.cleaned.length} arquivo(s) seriam removidos`,
|
|
227
225
|
wouldClean: result.cleaned
|
|
228
226
|
});
|
|
229
227
|
}
|
|
230
|
-
|
|
228
|
+
|
|
231
229
|
const result = await git.cleanUntracked(projectPath);
|
|
232
|
-
return asToolResult({
|
|
233
|
-
success: true,
|
|
230
|
+
return asToolResult({
|
|
231
|
+
success: true,
|
|
234
232
|
...result,
|
|
235
|
-
message: result.cleaned.length > 0
|
|
233
|
+
message: result.cleaned.length > 0
|
|
236
234
|
? `${result.cleaned.length} arquivo(s) não rastreados removidos`
|
|
237
235
|
: "Nenhum arquivo para limpar"
|
|
238
236
|
});
|
|
@@ -240,7 +238,7 @@ EXEMPLOS DE USO:
|
|
|
240
238
|
if (action === "ensure-remotes") {
|
|
241
239
|
const repo = getRepoNameFromPath(projectPath);
|
|
242
240
|
const isPublic = args.isPublic === true; // Default: privado
|
|
243
|
-
|
|
241
|
+
|
|
244
242
|
if (args.dryRun) {
|
|
245
243
|
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: false, isPublic }); // Don't create for dry run
|
|
246
244
|
const ghOwner = await pm.getGitHubOwner();
|
|
@@ -248,9 +246,9 @@ EXEMPLOS DE USO:
|
|
|
248
246
|
const githubUrl = ghOwner ? `https://github.com/${ghOwner}/${repo}.git` : "";
|
|
249
247
|
const base = pm.giteaUrl?.replace(/\/$/, "") || "";
|
|
250
248
|
const giteaUrl = geOwner && base ? `${base}/${geOwner}/${repo}.git` : "";
|
|
251
|
-
|
|
252
|
-
return asToolResult({
|
|
253
|
-
success: true,
|
|
249
|
+
|
|
250
|
+
return asToolResult({
|
|
251
|
+
success: true,
|
|
254
252
|
dryRun: true,
|
|
255
253
|
message: "DRY RUN: Remotes seriam configurados",
|
|
256
254
|
repo,
|
|
@@ -259,7 +257,7 @@ EXEMPLOS DE USO:
|
|
|
259
257
|
ensured
|
|
260
258
|
});
|
|
261
259
|
}
|
|
262
|
-
|
|
260
|
+
|
|
263
261
|
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic });
|
|
264
262
|
const ghOwner = await pm.getGitHubOwner();
|
|
265
263
|
const geOwner = await pm.getGiteaOwner();
|
|
@@ -273,120 +271,22 @@ EXEMPLOS DE USO:
|
|
|
273
271
|
if (action === "push") {
|
|
274
272
|
const branch = await git.getCurrentBranch(projectPath);
|
|
275
273
|
const force = !!args.force;
|
|
276
|
-
|
|
274
|
+
|
|
277
275
|
if (args.dryRun) {
|
|
278
|
-
return asToolResult({
|
|
279
|
-
success: true,
|
|
276
|
+
return asToolResult({
|
|
277
|
+
success: true,
|
|
280
278
|
dryRun: true,
|
|
281
279
|
message: `DRY RUN: Push seria executado na branch '${branch}'${force ? ' (force)' : ''}`,
|
|
282
280
|
branch,
|
|
283
281
|
force
|
|
284
282
|
});
|
|
285
283
|
}
|
|
286
|
-
|
|
284
|
+
|
|
287
285
|
const result = await git.pushParallel(projectPath, branch, force);
|
|
288
286
|
return asToolResult({ success: true, branch, ...result });
|
|
289
287
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return asToolResult({
|
|
293
|
-
success: true,
|
|
294
|
-
dryRun: true,
|
|
295
|
-
message: "DRY RUN: Update completo seria executado (init se necessário, add, commit, push)"
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const results = {
|
|
300
|
-
init: null,
|
|
301
|
-
ensureRemotes: null,
|
|
302
|
-
add: null,
|
|
303
|
-
commit: null,
|
|
304
|
-
push: null
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const repo = getRepoNameFromPath(projectPath);
|
|
308
|
-
const isPublic = args.isPublic === true;
|
|
309
|
-
|
|
310
|
-
// 1. Verificar se é repo Git, se não for, fazer init
|
|
311
|
-
const isRepo = await git.isRepo(projectPath).catch(() => false);
|
|
312
|
-
if (!isRepo) {
|
|
313
|
-
await git.init(projectPath);
|
|
314
|
-
|
|
315
|
-
// Criar .gitignore baseado no tipo de projeto
|
|
316
|
-
const shouldCreateGitignore = args.createGitignore !== false;
|
|
317
|
-
if (shouldCreateGitignore) {
|
|
318
|
-
const projectType = detectProjectType(projectPath);
|
|
319
|
-
const patterns = GITIGNORE_TEMPLATES[projectType] || GITIGNORE_TEMPLATES.general;
|
|
320
|
-
await git.createGitignore(projectPath, patterns);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic });
|
|
324
|
-
results.init = { success: true, ensured, isPrivate: !isPublic, gitignoreCreated: shouldCreateGitignore };
|
|
325
|
-
} else {
|
|
326
|
-
results.init = { success: true, skipped: true, message: "Repositório já existe" };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// 2. Garantir remotes configurados (sempre verifica/configura)
|
|
330
|
-
const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic });
|
|
331
|
-
const ghOwner = await pm.getGitHubOwner();
|
|
332
|
-
const geOwner = await pm.getGiteaOwner();
|
|
333
|
-
const githubUrl = ghOwner ? `https://github.com/${ghOwner}/${repo}.git` : "";
|
|
334
|
-
const base = pm.giteaUrl?.replace(/\/$/, "") || "";
|
|
335
|
-
const giteaUrl = geOwner && base ? `${base}/${geOwner}/${repo}.git` : "";
|
|
336
|
-
await git.ensureRemotes(projectPath, { githubUrl, giteaUrl });
|
|
337
|
-
const remotes = await git.listRemotes(projectPath);
|
|
338
|
-
results.ensureRemotes = { success: true, ensured, remotes };
|
|
339
|
-
|
|
340
|
-
// 3. Verificar status e fazer add se necessário
|
|
341
|
-
const status = await git.status(projectPath);
|
|
342
|
-
if (!status.isClean || status.modified?.length > 0 || status.created?.length > 0 || status.notAdded?.length > 0) {
|
|
343
|
-
await git.add(projectPath, ["."]);
|
|
344
|
-
results.add = { success: true, files: ["."] };
|
|
345
|
-
} else {
|
|
346
|
-
results.add = { success: true, skipped: true, message: "Nenhum arquivo para adicionar" };
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// 4. Verificar se há algo staged e fazer commit
|
|
350
|
-
const statusAfterAdd = await git.status(projectPath);
|
|
351
|
-
if (statusAfterAdd.staged?.length > 0) {
|
|
352
|
-
// Gerar mensagem padrão se não fornecida
|
|
353
|
-
const commitMessage = args.message || `Update: ${new Date().toISOString().split('T')[0]} - ${statusAfterAdd.staged.length} arquivo(s) modificado(s)`;
|
|
354
|
-
const sha = await git.commit(projectPath, commitMessage);
|
|
355
|
-
results.commit = { success: true, sha, message: commitMessage };
|
|
356
|
-
} else {
|
|
357
|
-
results.commit = { success: true, skipped: true, message: "Nenhum arquivo staged para commit" };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// 5. Fazer push (só se houver commits para enviar)
|
|
361
|
-
const branch = await git.getCurrentBranch(projectPath);
|
|
362
|
-
const force = !!args.force;
|
|
363
|
-
try {
|
|
364
|
-
const pushResult = await git.pushParallel(projectPath, branch, force);
|
|
365
|
-
results.push = { success: true, branch, ...pushResult };
|
|
366
|
-
} catch (pushError) {
|
|
367
|
-
// Se push falhar mas não houver commits, não é erro crítico
|
|
368
|
-
if (results.commit?.skipped) {
|
|
369
|
-
results.push = { success: true, skipped: true, message: "Nenhum commit para enviar" };
|
|
370
|
-
} else {
|
|
371
|
-
throw pushError;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Resumo final
|
|
376
|
-
const allSuccess = Object.values(results).every(r => r?.success !== false);
|
|
377
|
-
const stepsExecuted = Object.entries(results)
|
|
378
|
-
.filter(([_, r]) => r && !r.skipped)
|
|
379
|
-
.map(([step, _]) => step);
|
|
380
|
-
|
|
381
|
-
return asToolResult({
|
|
382
|
-
success: allSuccess,
|
|
383
|
-
message: `Update completo executado: ${stepsExecuted.join(" → ")}`,
|
|
384
|
-
results,
|
|
385
|
-
stepsExecuted
|
|
386
|
-
}, { tool: 'workflow', action: 'update' });
|
|
387
|
-
}
|
|
388
|
-
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
|
|
389
|
-
availableActions: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes", "clean", "update"],
|
|
288
|
+
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
|
|
289
|
+
availableActions: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes", "clean"],
|
|
390
290
|
suggestion: "Use uma das actions disponíveis"
|
|
391
291
|
});
|
|
392
292
|
} catch (e) {
|
package/src/utils/errors.js
CHANGED
|
@@ -16,14 +16,14 @@ export const ERROR_CODES = {
|
|
|
16
16
|
suggestion: "Verifique se GITHUB_TOKEN está configurado e é válido. Gere um novo token em github.com/settings/tokens"
|
|
17
17
|
},
|
|
18
18
|
AUTH_GITEA_INVALID: {
|
|
19
|
-
code: "AUTH_GITEA_INVALID",
|
|
19
|
+
code: "AUTH_GITEA_INVALID",
|
|
20
20
|
suggestion: "Verifique se GITEA_TOKEN e GITEA_URL estão configurados corretamente"
|
|
21
21
|
},
|
|
22
22
|
AUTH_NO_PERMISSION: {
|
|
23
23
|
code: "AUTH_NO_PERMISSION",
|
|
24
24
|
suggestion: "Token não tem permissão para esta operação. Verifique os scopes do token"
|
|
25
25
|
},
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
// Repo
|
|
28
28
|
REPO_NOT_FOUND: {
|
|
29
29
|
code: "REPO_NOT_FOUND",
|
|
@@ -37,7 +37,7 @@ export const ERROR_CODES = {
|
|
|
37
37
|
code: "REPO_ALREADY_EXISTS",
|
|
38
38
|
suggestion: "Repositório já existe. Use action='ensure' para verificar ou escolha outro nome"
|
|
39
39
|
},
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
// Git Local
|
|
42
42
|
NOT_A_GIT_REPO: {
|
|
43
43
|
code: "NOT_A_GIT_REPO",
|
|
@@ -55,7 +55,7 @@ export const ERROR_CODES = {
|
|
|
55
55
|
code: "NOTHING_TO_PUSH",
|
|
56
56
|
suggestion: "Nenhuma mudança para push. Faça commits primeiro"
|
|
57
57
|
},
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
// Branches
|
|
60
60
|
BRANCH_NOT_FOUND: {
|
|
61
61
|
code: "BRANCH_NOT_FOUND",
|
|
@@ -69,7 +69,7 @@ export const ERROR_CODES = {
|
|
|
69
69
|
code: "CANNOT_DELETE_CURRENT",
|
|
70
70
|
suggestion: "Não pode deletar branch atual. Faça checkout para outra branch primeiro"
|
|
71
71
|
},
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
// Tags
|
|
74
74
|
TAG_NOT_FOUND: {
|
|
75
75
|
code: "TAG_NOT_FOUND",
|
|
@@ -79,7 +79,7 @@ export const ERROR_CODES = {
|
|
|
79
79
|
code: "TAG_ALREADY_EXISTS",
|
|
80
80
|
suggestion: "Tag já existe. Use outro nome ou delete a existente primeiro"
|
|
81
81
|
},
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
// Refs
|
|
84
84
|
REF_NOT_FOUND: {
|
|
85
85
|
code: "REF_NOT_FOUND",
|
|
@@ -89,7 +89,7 @@ export const ERROR_CODES = {
|
|
|
89
89
|
code: "INSUFFICIENT_HISTORY",
|
|
90
90
|
suggestion: "Histórico insuficiente para HEAD~N. Use action='log' para verificar quantos commits existem"
|
|
91
91
|
},
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
// Stash
|
|
94
94
|
NOTHING_TO_STASH: {
|
|
95
95
|
code: "NOTHING_TO_STASH",
|
|
@@ -99,7 +99,7 @@ export const ERROR_CODES = {
|
|
|
99
99
|
code: "STASH_NOT_FOUND",
|
|
100
100
|
suggestion: "Stash não encontrado. Use action='list' para ver stashes disponíveis"
|
|
101
101
|
},
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
// Remote
|
|
104
104
|
REMOTE_NOT_FOUND: {
|
|
105
105
|
code: "REMOTE_NOT_FOUND",
|
|
@@ -113,7 +113,7 @@ export const ERROR_CODES = {
|
|
|
113
113
|
code: "MERGE_CONFLICT",
|
|
114
114
|
suggestion: "Conflito de merge. Resolva conflitos manualmente e faça novo commit"
|
|
115
115
|
},
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
// Network
|
|
118
118
|
NETWORK_TIMEOUT: {
|
|
119
119
|
code: "NETWORK_TIMEOUT",
|
|
@@ -123,7 +123,7 @@ export const ERROR_CODES = {
|
|
|
123
123
|
code: "RATE_LIMIT",
|
|
124
124
|
suggestion: "Rate limit excedido. Aguarde alguns minutos antes de tentar novamente"
|
|
125
125
|
},
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
// Validation
|
|
128
128
|
VALIDATION_ERROR: {
|
|
129
129
|
code: "VALIDATION_ERROR",
|
|
@@ -133,13 +133,13 @@ export const ERROR_CODES = {
|
|
|
133
133
|
code: "MISSING_PARAMETER",
|
|
134
134
|
suggestion: "Parâmetro obrigatório faltando"
|
|
135
135
|
},
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
// Files
|
|
138
138
|
FILE_NOT_FOUND: {
|
|
139
139
|
code: "FILE_NOT_FOUND",
|
|
140
140
|
suggestion: "Arquivo não encontrado no repositório. Use action='list' para ver arquivos disponíveis"
|
|
141
141
|
},
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
// Issues/PRs
|
|
144
144
|
ISSUE_NOT_FOUND: {
|
|
145
145
|
code: "ISSUE_NOT_FOUND",
|
|
@@ -149,7 +149,7 @@ export const ERROR_CODES = {
|
|
|
149
149
|
code: "PR_NOT_FOUND",
|
|
150
150
|
suggestion: "Pull Request não encontrado. Use action='list' para ver PRs disponíveis"
|
|
151
151
|
},
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
// Filesystem
|
|
154
154
|
DISK_FULL: {
|
|
155
155
|
code: "DISK_FULL",
|
|
@@ -163,7 +163,7 @@ export const ERROR_CODES = {
|
|
|
163
163
|
code: "PERMISSION_DENIED_FS",
|
|
164
164
|
suggestion: "Sem permissão para acessar arquivo/diretório. Verifique permissões do sistema de arquivos"
|
|
165
165
|
},
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
// Git Corruption
|
|
168
168
|
GIT_CORRUPTED: {
|
|
169
169
|
code: "GIT_CORRUPTED",
|
|
@@ -171,9 +171,9 @@ export const ERROR_CODES = {
|
|
|
171
171
|
},
|
|
172
172
|
GIT_LOCK_FILE: {
|
|
173
173
|
code: "GIT_LOCK_FILE",
|
|
174
|
-
suggestion: "Arquivo de lock presente (.git/index.lock). Outro processo git pode estar rodando.
|
|
174
|
+
suggestion: "Arquivo de lock presente (.git/index.lock). Outro processo git pode estar rodando (comum em drives de rede). O sistema tentou auto-corrigir, mas se persistir, delete o arquivo de lock manualmente."
|
|
175
175
|
},
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
// Remote/URL
|
|
178
178
|
INVALID_REMOTE_URL: {
|
|
179
179
|
code: "INVALID_REMOTE_URL",
|
|
@@ -187,7 +187,7 @@ export const ERROR_CODES = {
|
|
|
187
187
|
code: "SSL_ERROR",
|
|
188
188
|
suggestion: "Erro de SSL/TLS. Verifique certificados ou use http.sslVerify=false (não recomendado)"
|
|
189
189
|
},
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
// Generic
|
|
192
192
|
UNKNOWN_ERROR: {
|
|
193
193
|
code: "UNKNOWN_ERROR",
|
|
@@ -207,14 +207,14 @@ export function createError(errorCode, details = {}) {
|
|
|
207
207
|
// Função para mapear erros externos para códigos internos
|
|
208
208
|
export function mapExternalError(error, context = {}) {
|
|
209
209
|
const msg = error?.message?.toLowerCase() || String(error).toLowerCase();
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
// GitHub/Gitea API errors
|
|
212
212
|
if (msg.includes("bad credentials") || msg.includes("401")) {
|
|
213
|
-
return context.provider === "gitea"
|
|
213
|
+
return context.provider === "gitea"
|
|
214
214
|
? createError("AUTH_GITEA_INVALID", { originalError: msg })
|
|
215
215
|
: createError("AUTH_GITHUB_INVALID", { originalError: msg });
|
|
216
216
|
}
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
if (msg.includes("not found") || msg.includes("404")) {
|
|
219
219
|
if (context.type === "repo") return createError("REPO_NOT_FOUND", { repo: context.repo, originalError: msg });
|
|
220
220
|
if (context.type === "branch") return createError("BRANCH_NOT_FOUND", { branch: context.branch, originalError: msg });
|
|
@@ -224,116 +224,116 @@ export function mapExternalError(error, context = {}) {
|
|
|
224
224
|
if (context.type === "pr") return createError("PR_NOT_FOUND", { number: context.number, originalError: msg });
|
|
225
225
|
return createError("REF_NOT_FOUND", { ref: context.ref, originalError: msg });
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
if (msg.includes("permission") || msg.includes("403") || msg.includes("forbidden")) {
|
|
229
229
|
return createError("AUTH_NO_PERMISSION", { originalError: msg });
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
if (msg.includes("rate limit") || msg.includes("429")) {
|
|
233
233
|
return createError("RATE_LIMIT", { originalError: msg });
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
|
|
236
236
|
if (msg.includes("timeout") || msg.includes("etimedout") || msg.includes("econnrefused")) {
|
|
237
237
|
return createError("NETWORK_TIMEOUT", { originalError: msg });
|
|
238
238
|
}
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
if (msg.includes("push rejected") || msg.includes("non-fast-forward")) {
|
|
241
241
|
return createError("PUSH_REJECTED", { originalError: msg });
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
if (msg.includes("conflict")) {
|
|
245
245
|
return createError("MERGE_CONFLICT", { originalError: msg });
|
|
246
246
|
}
|
|
247
|
-
|
|
247
|
+
|
|
248
248
|
if (msg.includes("already exists")) {
|
|
249
249
|
if (context.type === "repo") return createError("REPO_ALREADY_EXISTS", { repo: context.repo, originalError: msg });
|
|
250
250
|
if (context.type === "branch") return createError("BRANCH_ALREADY_EXISTS", { branch: context.branch, originalError: msg });
|
|
251
251
|
if (context.type === "tag") return createError("TAG_ALREADY_EXISTS", { tag: context.tag, originalError: msg });
|
|
252
252
|
return createError("BRANCH_ALREADY_EXISTS", { originalError: msg });
|
|
253
253
|
}
|
|
254
|
-
|
|
254
|
+
|
|
255
255
|
// isomorphic-git specific errors
|
|
256
256
|
if (msg.includes("could not find") && msg.includes("head~")) {
|
|
257
257
|
return createError("INSUFFICIENT_HISTORY", { originalError: msg });
|
|
258
258
|
}
|
|
259
|
-
|
|
259
|
+
|
|
260
260
|
if (msg.includes("is not a git repository") || msg.includes("could not find .git")) {
|
|
261
261
|
return createError("NOT_A_GIT_REPO", { path: context.path, originalError: msg });
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
if (msg.includes("nothing to commit") || msg.includes("working tree clean")) {
|
|
265
265
|
return createError("NOTHING_TO_COMMIT", { originalError: msg });
|
|
266
266
|
}
|
|
267
|
-
|
|
267
|
+
|
|
268
268
|
if (msg.includes("could not resolve ref") || msg.includes("invalid reference")) {
|
|
269
269
|
return createError("REF_NOT_FOUND", { originalError: msg });
|
|
270
270
|
}
|
|
271
|
-
|
|
271
|
+
|
|
272
272
|
if (msg.includes("branch") && (msg.includes("does not exist") || msg.includes("not found"))) {
|
|
273
273
|
return createError("BRANCH_NOT_FOUND", { originalError: msg });
|
|
274
274
|
}
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
if (msg.includes("tag") && (msg.includes("does not exist") || msg.includes("not found"))) {
|
|
277
277
|
return createError("TAG_NOT_FOUND", { originalError: msg });
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
|
|
280
280
|
if (msg.includes("no commits") || msg.includes("unknown revision")) {
|
|
281
281
|
return createError("NO_COMMITS", { originalError: msg });
|
|
282
282
|
}
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
if (msg.includes("nada para stash") || msg.includes("nothing to stash") || msg.includes("no local changes")) {
|
|
285
285
|
return createError("NOTHING_TO_STASH", { originalError: msg });
|
|
286
286
|
}
|
|
287
|
-
|
|
287
|
+
|
|
288
288
|
if (msg.includes("stash") && (msg.includes("not found") || msg.includes("não encontrado"))) {
|
|
289
289
|
return createError("STASH_NOT_FOUND", { originalError: msg });
|
|
290
290
|
}
|
|
291
|
-
|
|
291
|
+
|
|
292
292
|
if (msg.includes("author") || msg.includes("committer")) {
|
|
293
|
-
return createError("VALIDATION_ERROR", {
|
|
294
|
-
message: "Configure user.name e user.email",
|
|
293
|
+
return createError("VALIDATION_ERROR", {
|
|
294
|
+
message: "Configure user.name e user.email",
|
|
295
295
|
suggestion: "Use git-config para configurar ou forneça tokens de provider",
|
|
296
|
-
originalError: msg
|
|
296
|
+
originalError: msg
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
|
-
|
|
299
|
+
|
|
300
300
|
// Filesystem errors
|
|
301
301
|
if (msg.includes("enospc") || msg.includes("no space left") || msg.includes("disk full")) {
|
|
302
302
|
return createError("DISK_FULL", { originalError: msg });
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
if (msg.includes("file too large") || msg.includes("exceeds") || msg.includes("size limit")) {
|
|
306
306
|
return createError("FILE_TOO_LARGE", { originalError: msg });
|
|
307
307
|
}
|
|
308
|
-
|
|
308
|
+
|
|
309
309
|
if (msg.includes("eacces") || msg.includes("eperm") || (msg.includes("permission") && msg.includes("denied"))) {
|
|
310
310
|
return createError("PERMISSION_DENIED_FS", { originalError: msg });
|
|
311
311
|
}
|
|
312
|
-
|
|
312
|
+
|
|
313
313
|
// Git corruption
|
|
314
|
-
if (msg.includes("corrupt") || msg.includes("bad object") || msg.includes("broken") ||
|
|
315
|
-
|
|
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")) {
|
|
316
316
|
return createError("GIT_CORRUPTED", { originalError: msg });
|
|
317
317
|
}
|
|
318
|
-
|
|
318
|
+
|
|
319
319
|
if (msg.includes(".git/index.lock") || msg.includes("unable to create") && msg.includes("lock")) {
|
|
320
320
|
return createError("GIT_LOCK_FILE", { originalError: msg });
|
|
321
321
|
}
|
|
322
|
-
|
|
322
|
+
|
|
323
323
|
// Remote/URL errors
|
|
324
324
|
if (msg.includes("invalid url") || msg.includes("malformed") || msg.includes("not a valid")) {
|
|
325
325
|
return createError("INVALID_REMOTE_URL", { originalError: msg });
|
|
326
326
|
}
|
|
327
|
-
|
|
328
|
-
if (msg.includes("could not resolve host") || msg.includes("name resolution") ||
|
|
329
|
-
|
|
327
|
+
|
|
328
|
+
if (msg.includes("could not resolve host") || msg.includes("name resolution") ||
|
|
329
|
+
msg.includes("no address") || msg.includes("unreachable")) {
|
|
330
330
|
return createError("REMOTE_UNREACHABLE", { originalError: msg });
|
|
331
331
|
}
|
|
332
|
-
|
|
332
|
+
|
|
333
333
|
if (msg.includes("ssl") || msg.includes("certificate") || msg.includes("tls")) {
|
|
334
334
|
return createError("SSL_ERROR", { originalError: msg });
|
|
335
335
|
}
|
|
336
|
-
|
|
336
|
+
|
|
337
337
|
// Generic fallback
|
|
338
338
|
return createError("UNKNOWN_ERROR", { originalError: msg, context });
|
|
339
339
|
}
|
|
@@ -347,27 +347,27 @@ export const NEXT_ACTIONS = {
|
|
|
347
347
|
"workflow:add": { tool: "git-workflow", action: "commit", hint: "Crie um commit com uma mensagem descritiva" },
|
|
348
348
|
"workflow:commit": { tool: "git-workflow", action: "push", hint: "Envie as mudanças para GitHub/Gitea" },
|
|
349
349
|
"workflow:push": null, // Fluxo completo
|
|
350
|
-
|
|
350
|
+
|
|
351
351
|
// git-branches
|
|
352
352
|
"branches:create": { tool: "git-branches", action: "checkout", hint: "Mude para a nova branch criada" },
|
|
353
353
|
"branches:checkout": { tool: "git-workflow", action: "status", hint: "Verifique o estado da branch" },
|
|
354
354
|
"branches:delete": null,
|
|
355
|
-
|
|
355
|
+
|
|
356
356
|
// git-stash
|
|
357
357
|
"stash:save": { tool: "git-branches", action: "checkout", hint: "Agora pode trocar de branch com segurança" },
|
|
358
358
|
"stash:pop": { tool: "git-workflow", action: "status", hint: "Verifique as mudanças restauradas" },
|
|
359
|
-
|
|
359
|
+
|
|
360
360
|
// git-merge
|
|
361
361
|
"merge:merge": { tool: "git-workflow", action: "push", hint: "Envie o merge para os remotes" },
|
|
362
|
-
|
|
362
|
+
|
|
363
363
|
// git-tags
|
|
364
364
|
"tags:create": { tool: "git-tags", action: "push", hint: "Envie a tag para os remotes" },
|
|
365
365
|
"tags:push": null,
|
|
366
|
-
|
|
366
|
+
|
|
367
367
|
// git-remote
|
|
368
368
|
"remote:ensure": { tool: "git-workflow", action: "push", hint: "Agora pode fazer push" },
|
|
369
369
|
"remote:release-create": null,
|
|
370
|
-
|
|
370
|
+
|
|
371
371
|
// git-sync
|
|
372
372
|
"sync:pull": { tool: "git-workflow", action: "status", hint: "Verifique mudanças após pull" },
|
|
373
373
|
"sync:fetch": { tool: "git-sync", action: "pull", hint: "Aplique as mudanças baixadas" },
|
|
@@ -388,7 +388,7 @@ export function getNextAction(tool, action) {
|
|
|
388
388
|
export function asToolResult(result, options = {}) {
|
|
389
389
|
const { tool, action } = options;
|
|
390
390
|
const response = { ...result };
|
|
391
|
-
|
|
391
|
+
|
|
392
392
|
// Adicionar próxima ação sugerida se disponível
|
|
393
393
|
if (tool && action) {
|
|
394
394
|
const next = getNextAction(tool, action);
|
|
@@ -398,7 +398,7 @@ export function asToolResult(result, options = {}) {
|
|
|
398
398
|
response._flowComplete = true;
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
|
-
|
|
401
|
+
|
|
402
402
|
return {
|
|
403
403
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
404
404
|
isError: false,
|
package/src/utils/gitAdapter.js
CHANGED
|
@@ -28,7 +28,11 @@ export class GitAdapter {
|
|
|
28
28
|
constructor(providerManager) {
|
|
29
29
|
this.pm = providerManager;
|
|
30
30
|
this.gitPath = "git"; // Initial default
|
|
31
|
-
this.gitEnv = {
|
|
31
|
+
this.gitEnv = {
|
|
32
|
+
...process.env,
|
|
33
|
+
LANG: "en_US.UTF-8",
|
|
34
|
+
GIT_OPTIONAL_LOCKS: "0", // Evita operações de background que seguram lock (importante para drives de rede)
|
|
35
|
+
};
|
|
32
36
|
this.timeout = GIT_TIMEOUT;
|
|
33
37
|
this.resolvePromise = this._resolveGit();
|
|
34
38
|
}
|
|
@@ -145,13 +149,22 @@ export class GitAdapter {
|
|
|
145
149
|
|
|
146
150
|
if (shouldDelete) {
|
|
147
151
|
console.error("[GitAdapter] Auto-fix: removing stale .git/index.lock...");
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
try {
|
|
153
|
+
fs.unlinkSync(lockPath);
|
|
154
|
+
} catch (unlinkErr) {
|
|
155
|
+
console.error("[GitAdapter] Failed to delete lock file:", unlinkErr.message);
|
|
156
|
+
// Set shouldDelete to false so we don't try to retry if deletion failed
|
|
157
|
+
shouldDelete = false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (shouldDelete) {
|
|
161
|
+
// Espera um pouco para o sistema de arquivos (especialmente SMB) registrar
|
|
162
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
163
|
+
return await this._exec(dir, args, { ...options, _lockRetried: true });
|
|
164
|
+
}
|
|
152
165
|
}
|
|
153
|
-
} catch (
|
|
154
|
-
console.error("[GitAdapter]
|
|
166
|
+
} catch (fsError) {
|
|
167
|
+
console.error("[GitAdapter] Error handling lock file:", fsError.message);
|
|
155
168
|
// Continua para lançar o erro original
|
|
156
169
|
}
|
|
157
170
|
}
|