@andre.buzeli/git-mcp 16.1.3 → 16.1.6
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/README.md +3 -2
- package/package.json +14 -3
- package/src/index.js +38 -42
- package/src/resources/index.js +79 -41
- package/src/tools/git-branches.js +5 -2
- package/src/tools/git-clone.js +13 -2
- package/src/tools/git-config.js +5 -2
- package/src/tools/git-diff.js +27 -6
- package/src/tools/git-files.js +5 -2
- package/src/tools/git-help.js +8 -3
- package/src/tools/git-history.js +9 -4
- package/src/tools/git-ignore.js +5 -2
- package/src/tools/git-issues.js +5 -2
- package/src/tools/git-merge.js +12 -7
- package/src/tools/git-pulls.js +5 -2
- package/src/tools/git-remote.js +10 -3
- package/src/tools/git-reset.js +5 -2
- package/src/tools/git-stash.js +5 -2
- package/src/tools/git-sync.js +5 -2
- package/src/tools/git-tags.js +5 -2
- package/src/tools/git-workflow.js +97 -42
- package/src/tools/git-worktree.js +66 -38
- package/src/utils/errors.js +21 -0
- package/src/utils/gitAdapter.js +165 -125
- package/src/utils/retry.js +1 -1
- package/src/utils/worktreeResolver.js +189 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
|
|
4
|
+
import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
|
|
5
5
|
import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
|
|
6
|
+
import { resolveWorktreeContext, isProtectedPath } from "../utils/worktreeResolver.js";
|
|
6
7
|
|
|
7
8
|
const ajv = new Ajv({ allErrors: true });
|
|
8
9
|
|
|
@@ -72,65 +73,65 @@ AÇÕES:
|
|
|
72
73
|
- set-channel: Configurar canal de deploy (prod/beta/alpha)`;
|
|
73
74
|
|
|
74
75
|
async function handle(args) {
|
|
76
|
+
const emptyHelp = handleEmptyCall(args, inputSchema, "git-worktree", { projectPath: "/path/to/project", action: "list" });
|
|
77
|
+
if (emptyHelp) return emptyHelp;
|
|
78
|
+
|
|
75
79
|
const validate = ajv.compile(inputSchema);
|
|
76
|
-
if (!validate(args
|
|
80
|
+
if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
|
|
77
81
|
const { projectPath, action } = args;
|
|
82
|
+
let effectivePath = projectPath;
|
|
83
|
+
let worktreeCtx = null;
|
|
78
84
|
|
|
79
85
|
try {
|
|
80
86
|
validateProjectPath(projectPath);
|
|
81
87
|
|
|
88
|
+
worktreeCtx = await resolveWorktreeContext(projectPath, git);
|
|
89
|
+
effectivePath = worktreeCtx.worktreePath;
|
|
90
|
+
|
|
91
|
+
const _worktreeContext = worktreeCtx.isWorktree ? {
|
|
92
|
+
detected: true,
|
|
93
|
+
repoRoot: worktreeCtx.repoRoot,
|
|
94
|
+
branch: worktreeCtx.branch
|
|
95
|
+
} : undefined;
|
|
96
|
+
|
|
82
97
|
if (action === "list") {
|
|
83
|
-
const worktrees = await git.listWorktrees(
|
|
98
|
+
const worktrees = await git.listWorktrees(effectivePath);
|
|
84
99
|
|
|
85
100
|
// Enrich with channel info if available
|
|
86
101
|
const enriched = [];
|
|
87
102
|
for (const wt of worktrees) {
|
|
88
|
-
const channel = await git.getConfig(
|
|
103
|
+
const channel = await git.getConfig(effectivePath, `worktree-branch.${wt.branch}.channel`);
|
|
89
104
|
enriched.push({ ...wt, channel: channel || "production" }); // Default to production
|
|
90
105
|
}
|
|
91
106
|
|
|
92
|
-
return asToolResult({ worktrees: enriched, count: enriched.length });
|
|
107
|
+
return asToolResult({ worktrees: enriched, count: enriched.length, _worktreeContext });
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
if (action === "add") {
|
|
96
111
|
if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para add", { parameter: "branch" });
|
|
97
112
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
// Mas git worktree add path branch.
|
|
101
|
-
|
|
102
|
-
// Se path não fornecido, assumir irmão do diretório atual se estivermos em um repo.
|
|
103
|
-
// Mas projectPath é a raiz do repo.
|
|
104
|
-
// Vamos usar path relativo ou absoluto fornecido, ou default: join(projectPath, "..", branch)
|
|
105
|
-
|
|
106
|
-
const targetPath = args.path
|
|
113
|
+
// Cria relativo ao projectPath original se path não for absoluto
|
|
114
|
+
const wtPath = args.path
|
|
107
115
|
? path.resolve(projectPath, args.path)
|
|
108
|
-
: path.join(projectPath, args.branch);
|
|
109
|
-
|
|
110
|
-
// Melhor prática: criar dentro se for estrutura monorepo-style, ou fora.
|
|
111
|
-
// O git-mcp v16 parece usar estrutura plana ou aninhada.
|
|
112
|
-
// Vamos usar o comportamento padrão do git: path relativo à raiz.
|
|
113
|
-
// Se o usuário passar apenas o nome da branch como path, o git cria ./branch-name.
|
|
114
|
-
|
|
115
|
-
// Se path não informado, usa o nome da branch (cria pasta ./branch-name dentro do repo atual)
|
|
116
|
-
// Isso pode sujar o repo principal se não estiver no gitignore.
|
|
117
|
-
// Vamos sugerir ou usar ../branch-name se estivermos na raiz?
|
|
118
|
-
// Simplicidade: user define path ou usa ./branch-name.
|
|
116
|
+
: path.join(projectPath, args.branch);
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
if (isProtectedPath(wtPath)) {
|
|
119
|
+
return asToolError("PROTECTED_PATH", `Não é permitido criar worktree em ${wtPath}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await withRetry(() => git.addWorktree(effectivePath, args.branch, wtPath, args.force), 3, "worktree-add");
|
|
123
123
|
|
|
124
124
|
// Set channel if provided
|
|
125
125
|
if (args.channel) {
|
|
126
|
-
await git.setWorktreeConfig(
|
|
126
|
+
await git.setWorktreeConfig(effectivePath, args.branch, { channel: args.channel });
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
return asToolResult({
|
|
130
130
|
success: true,
|
|
131
131
|
branch: args.branch,
|
|
132
|
-
path:
|
|
133
|
-
channel: args.channel || "production"
|
|
132
|
+
path: wtPath,
|
|
133
|
+
channel: args.channel || "production",
|
|
134
|
+
_worktreeContext
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
137
|
|
|
@@ -141,27 +142,54 @@ AÇÕES:
|
|
|
141
142
|
// Precisamos encontrar o path se só branch for fornecida
|
|
142
143
|
let targetPath = args.path;
|
|
143
144
|
if (!targetPath && args.branch) {
|
|
144
|
-
const wts = await git.listWorktrees(
|
|
145
|
+
const wts = await git.listWorktrees(effectivePath);
|
|
145
146
|
const found = wts.find(w => w.branch === args.branch);
|
|
146
147
|
if (!found) return asToolError("WORKTREE_NOT_FOUND", `Worktree para branch '${args.branch}' não encontrado`);
|
|
147
148
|
targetPath = found.path;
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
await withRetry(() => git.removeWorktree(
|
|
151
|
-
return asToolResult({ success: true, removed: targetPath });
|
|
151
|
+
await withRetry(() => git.removeWorktree(effectivePath, targetPath, args.force), 3, "worktree-remove");
|
|
152
|
+
return asToolResult({ success: true, removed: targetPath, _worktreeContext });
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
if (action === "prune") {
|
|
155
|
-
await git.pruneWorktrees(
|
|
156
|
-
return asToolResult({ success: true, message: "Worktrees podados" });
|
|
156
|
+
await git.pruneWorktrees(effectivePath);
|
|
157
|
+
return asToolResult({ success: true, message: "Worktrees podados", _worktreeContext });
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
if (action === "set-channel") {
|
|
160
161
|
if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório");
|
|
161
162
|
if (!args.channel) return asToolError("MISSING_PARAMETER", "channel é obrigatório");
|
|
162
163
|
|
|
163
|
-
await git.setWorktreeConfig(
|
|
164
|
-
return asToolResult({ success: true, branch: args.branch, channel: args.channel });
|
|
164
|
+
await git.setWorktreeConfig(effectivePath, args.branch, { channel: args.channel });
|
|
165
|
+
return asToolResult({ success: true, branch: args.branch, channel: args.channel, _worktreeContext });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (action === "setup") {
|
|
169
|
+
// Setup action logic - Estrutura recomendada
|
|
170
|
+
// 1. Verifica se está num repo
|
|
171
|
+
// 2. Se não estiver, init
|
|
172
|
+
// 3. Move master para subpasta 'main' se estiver na raiz?
|
|
173
|
+
// Simplificação: Apenas cria as pastas se não existirem
|
|
174
|
+
|
|
175
|
+
const mainPath = path.join(effectivePath, 'main');
|
|
176
|
+
const worktreesPath = path.join(effectivePath, 'worktrees');
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(mainPath)) {
|
|
179
|
+
// Se estamos na raiz de um repo normal, setup é meio redundante se não formos mover arquivos.
|
|
180
|
+
// Mas se o usuário quer estruturar do zero:
|
|
181
|
+
// git init bare? Não, o spec não diz bare.
|
|
182
|
+
|
|
183
|
+
// Vamos apenas retornar uma mensagem informativa por enquanto, pois a migração automática é arriscada.
|
|
184
|
+
return asToolResult({
|
|
185
|
+
success: true,
|
|
186
|
+
message: "Para configurar worktrees, recomenda-se mover o branch principal para uma pasta 'main' e criar novos worktrees em pastas irmãs.",
|
|
187
|
+
structure: {
|
|
188
|
+
root: projectPath,
|
|
189
|
+
recommended: ["main/", "feature-branch/", "hotfix/"]
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
165
193
|
}
|
|
166
194
|
|
|
167
195
|
return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["add", "list", "remove", "prune", "set-channel"] });
|
package/src/utils/errors.js
CHANGED
|
@@ -432,3 +432,24 @@ export function errorToResponse(error) {
|
|
|
432
432
|
const mapped = mapExternalError(error);
|
|
433
433
|
return asToolError(mapped.code, mapped.message, mapped.data);
|
|
434
434
|
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Detecta call vazia (sem projectPath/action) e retorna help em vez de erro seco.
|
|
438
|
+
* Isso evita que modelos façam "tool probing" — uma call vazia para descobrir o schema.
|
|
439
|
+
* @param {object} args - Argumentos recebidos
|
|
440
|
+
* @param {object} inputSchema - Schema da tool
|
|
441
|
+
* @param {string} toolName - Nome da tool
|
|
442
|
+
* @param {object} example - Exemplo de uso mínimo
|
|
443
|
+
* @returns {object|null} - Resposta de help, ou null se args não estão vazios
|
|
444
|
+
*/
|
|
445
|
+
export function handleEmptyCall(args, inputSchema, toolName, example) {
|
|
446
|
+
if (args && Object.keys(args).length > 0) return null;
|
|
447
|
+
return asToolResult({
|
|
448
|
+
help: true,
|
|
449
|
+
tool: toolName,
|
|
450
|
+
requiredParams: (inputSchema.required || []).join(", "),
|
|
451
|
+
example,
|
|
452
|
+
actions: inputSchema.properties?.action?.enum || [],
|
|
453
|
+
hint: `Sempre inclua ${(inputSchema.required || []).join(" e ")} na chamada.`
|
|
454
|
+
});
|
|
455
|
+
}
|
package/src/utils/gitAdapter.js
CHANGED
|
@@ -315,10 +315,28 @@ export class GitAdapter {
|
|
|
315
315
|
suggestion: "Use git-workflow init para criar um repositório"
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
|
+
// Worktrees têm .git como arquivo, repos normais como diretório.
|
|
319
|
+
// Ambos são válidos e fs.existsSync retorna true para ambos.
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
/**
|
|
321
|
-
* Verifica se o diretório é um
|
|
323
|
+
* Verifica se o diretório é um worktree (possui arquivo .git)
|
|
324
|
+
* @param {string} dir - Diretório para verificar
|
|
325
|
+
* @returns {boolean} - true se é um worktree
|
|
326
|
+
*/
|
|
327
|
+
async isWorktree(dir) {
|
|
328
|
+
const gitPath = path.join(dir, ".git");
|
|
329
|
+
if (!fs.existsSync(gitPath)) return false;
|
|
330
|
+
try {
|
|
331
|
+
const stats = fs.statSync(gitPath);
|
|
332
|
+
return stats.isFile();
|
|
333
|
+
} catch {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Verifica se o diretório é um repositório git (repo principal ou worktree)
|
|
322
340
|
* @param {string} dir - Diretório para verificar
|
|
323
341
|
* @returns {boolean} - true se é um repo git
|
|
324
342
|
*/
|
|
@@ -842,8 +860,12 @@ export class GitAdapter {
|
|
|
842
860
|
try { await this._exec(dir, args); } catch { }
|
|
843
861
|
}
|
|
844
862
|
|
|
845
|
-
async listConfig(dir) {
|
|
846
|
-
const
|
|
863
|
+
async listConfig(dir, scope) {
|
|
864
|
+
const args = ["config", "--list"];
|
|
865
|
+
if (scope === "global") args.push("--global");
|
|
866
|
+
else if (scope === "system") args.push("--system");
|
|
867
|
+
else if (scope === "local") args.push("--local");
|
|
868
|
+
const out = await this._exec(dir, args);
|
|
847
869
|
const items = {};
|
|
848
870
|
out.split("\n").filter(Boolean).forEach(line => {
|
|
849
871
|
const [k, ...v] = line.split("=");
|
|
@@ -959,16 +981,26 @@ export class GitAdapter {
|
|
|
959
981
|
}
|
|
960
982
|
|
|
961
983
|
// ============ DIFF/CLONE ============
|
|
962
|
-
async diff(dir, options = {}) {
|
|
984
|
+
async diff(dir, options = {}) {
|
|
985
|
+
const args = ["diff"];
|
|
986
|
+
if (options.staged) {
|
|
987
|
+
args.push("--cached");
|
|
988
|
+
} else if (options.target) {
|
|
989
|
+
if (options.source) args.push(options.source);
|
|
990
|
+
args.push(options.target);
|
|
991
|
+
}
|
|
992
|
+
return await this._exec(dir, args);
|
|
993
|
+
}
|
|
963
994
|
async diffCommits(dir, from, to) { return await this._exec(dir, ["diff", from, to]); }
|
|
964
995
|
|
|
965
|
-
async clone(
|
|
966
|
-
const { branch, depth, singleBranch } = options;
|
|
996
|
+
async clone(dir, url, options = {}) {
|
|
997
|
+
const { name, branch, depth, singleBranch } = options;
|
|
967
998
|
const args = ["clone"];
|
|
968
999
|
if (branch) args.push("-b", branch);
|
|
969
1000
|
if (depth) args.push("--depth", depth.toString());
|
|
970
1001
|
if (singleBranch) args.push("--single-branch");
|
|
971
|
-
args.push(url
|
|
1002
|
+
args.push(url);
|
|
1003
|
+
if (name) args.push(name);
|
|
972
1004
|
|
|
973
1005
|
const header = this._getAuthHeader(url);
|
|
974
1006
|
const cmdArgs = [];
|
|
@@ -998,9 +1030,10 @@ export class GitAdapter {
|
|
|
998
1030
|
async removeFromGitignore(dir, patterns) {
|
|
999
1031
|
const p = path.join(dir, ".gitignore");
|
|
1000
1032
|
if (!fs.existsSync(p)) return;
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1033
|
+
const lines = fs.readFileSync(p, "utf8").split("\n");
|
|
1034
|
+
const patternSet = new Set(patterns);
|
|
1035
|
+
const filtered = lines.filter(line => !patternSet.has(line.trim()));
|
|
1036
|
+
fs.writeFileSync(p, filtered.join("\n"));
|
|
1004
1037
|
}
|
|
1005
1038
|
|
|
1006
1039
|
async listRemotesRaw(dir) {
|
|
@@ -1029,121 +1062,128 @@ export class GitAdapter {
|
|
|
1029
1062
|
return Array.from(map.values());
|
|
1030
1063
|
}
|
|
1031
1064
|
|
|
1032
|
-
// ============ WORKTREE ============
|
|
1033
|
-
|
|
1034
|
-
async addWorktree(dir, branch, worktreePath) {
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
if (
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
current.
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
await this._exec(dir,
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1065
|
+
// ============ WORKTREE ============
|
|
1066
|
+
|
|
1067
|
+
async addWorktree(dir, branch, worktreePath, force = false) {
|
|
1068
|
+
const branches = await this.listBranches(dir);
|
|
1069
|
+
const branchExists = branches.includes(branch);
|
|
1070
|
+
|
|
1071
|
+
const args = ["worktree", "add"];
|
|
1072
|
+
if (force) args.push("--force");
|
|
1073
|
+
|
|
1074
|
+
if (branchExists) {
|
|
1075
|
+
// Branch já existe: checkout dela no novo worktree
|
|
1076
|
+
args.push(worktreePath, branch);
|
|
1077
|
+
} else {
|
|
1078
|
+
// Branch nova: cria a partir do HEAD atual
|
|
1079
|
+
args.push("-b", branch, worktreePath);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
await this._exec(dir, args);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
async listWorktrees(dir) {
|
|
1086
|
+
try {
|
|
1087
|
+
const out = await this._exec(dir, ["worktree", "list", "--porcelain"]);
|
|
1088
|
+
const worktrees = [];
|
|
1089
|
+
let current = {};
|
|
1090
|
+
|
|
1091
|
+
const lines = out.split("\n");
|
|
1092
|
+
for (const line of lines) {
|
|
1093
|
+
if (!line.trim()) {
|
|
1094
|
+
if (current.worktree) {
|
|
1095
|
+
worktrees.push(current);
|
|
1096
|
+
current = {};
|
|
1097
|
+
}
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const [key, ...rest] = line.split(" ");
|
|
1102
|
+
const value = rest.join(" ");
|
|
1103
|
+
|
|
1104
|
+
if (key === "worktree") {
|
|
1105
|
+
if (current.worktree) worktrees.push(current);
|
|
1106
|
+
current = { worktree: value };
|
|
1107
|
+
} else if (key === "branch") {
|
|
1108
|
+
current.branch = value.replace("refs/heads/", "");
|
|
1109
|
+
} else if (key === "HEAD") {
|
|
1110
|
+
current.head = value;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (current.worktree) worktrees.push(current);
|
|
1114
|
+
|
|
1115
|
+
return worktrees.map(w => ({
|
|
1116
|
+
path: w.worktree,
|
|
1117
|
+
branch: w.branch,
|
|
1118
|
+
head: w.head
|
|
1119
|
+
}));
|
|
1120
|
+
} catch (e) {
|
|
1121
|
+
// Fallback para versão antiga do git se --porcelain falhar
|
|
1122
|
+
const out = await this._exec(dir, ["worktree", "list"]);
|
|
1123
|
+
return out.split("\n").filter(Boolean).map(line => {
|
|
1124
|
+
const parts = line.split(/\s+/);
|
|
1125
|
+
const path = parts.slice(0, -2).join(" "); // Path pode ter espaços
|
|
1126
|
+
const head = parts[parts.length - 2];
|
|
1127
|
+
const branchRaw = parts[parts.length - 1];
|
|
1128
|
+
const branch = branchRaw.replace("[", "").replace("]", "");
|
|
1129
|
+
return { path, head, branch };
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
async removeWorktree(dir, worktreePath) {
|
|
1135
|
+
await this._exec(dir, ["worktree", "remove", worktreePath, "--force"]);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
async pruneWorktrees(dir) {
|
|
1139
|
+
await this._exec(dir, ["worktree", "prune"]);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
async pushRefspec(dir, remote, localBranch, remoteBranch, force = false) {
|
|
1143
|
+
const remoteUrl = await this._exec(dir, ["remote", "get-url", remote]);
|
|
1144
|
+
const header = this._getAuthHeader(remoteUrl);
|
|
1145
|
+
const args = [];
|
|
1146
|
+
if (header) args.push("-c", `http.extraHeader=${header}`);
|
|
1147
|
+
args.push("push");
|
|
1148
|
+
if (force) args.push("--force");
|
|
1149
|
+
args.push(remote, `${localBranch}:${remoteBranch}`);
|
|
1150
|
+
await this._exec(dir, args);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
async getWorktreeConfigs(dir) {
|
|
1154
|
+
try {
|
|
1155
|
+
const configPath = path.join(dir, ".git", "config");
|
|
1156
|
+
if (!fs.existsSync(configPath)) return [];
|
|
1157
|
+
|
|
1158
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
1159
|
+
const results = [];
|
|
1160
|
+
const sectionRegex = /\[worktree-branch "([^"]+)"\]([\s\S]*?)(?=\[|$)/g;
|
|
1161
|
+
|
|
1162
|
+
let match;
|
|
1163
|
+
while ((match = sectionRegex.exec(content)) !== null) {
|
|
1164
|
+
const branchName = match[1];
|
|
1165
|
+
const body = match[2];
|
|
1166
|
+
const wtPath = body.match(/path\s*=\s*(.+)/)?.[1]?.trim();
|
|
1167
|
+
const channel = body.match(/channel\s*=\s*(.+)/)?.[1]?.trim() || "production";
|
|
1168
|
+
if (branchName && wtPath) {
|
|
1169
|
+
results.push({ branch: branchName, path: wtPath, channel });
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return results;
|
|
1173
|
+
} catch (e) {
|
|
1174
|
+
console.error("[GitAdapter] Error reading worktree configs:", e);
|
|
1175
|
+
return [];
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
async setWorktreeConfig(dir, branchName, config) {
|
|
1180
|
+
if (config.path !== undefined) {
|
|
1181
|
+
const normalizedPath = config.path.replace(/\\/g, "/");
|
|
1182
|
+
await this._exec(dir, ["config", `worktree-branch.${branchName}.path`, normalizedPath]);
|
|
1183
|
+
}
|
|
1184
|
+
await this._exec(dir, ["config", `worktree-branch.${branchName}.channel`, config.channel || "production"]);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1147
1187
|
// ============ GIT LFS SUPPORT ============
|
|
1148
1188
|
|
|
1149
1189
|
/**
|
package/src/utils/retry.js
CHANGED
|
@@ -19,7 +19,7 @@ const DEFAULT_OPTIONS = {
|
|
|
19
19
|
|
|
20
20
|
function shouldRetry(error, options) {
|
|
21
21
|
const msg = (error?.message || String(error)).toLowerCase();
|
|
22
|
-
const code = error?.code
|
|
22
|
+
const code = String(error?.code ?? "").toLowerCase();
|
|
23
23
|
|
|
24
24
|
return options.retryableErrors.some(e =>
|
|
25
25
|
msg.includes(e.toLowerCase()) || code.includes(e.toLowerCase())
|