@andrebuzeli/git-mcp 3.0.1 → 3.1.0
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 +329 -356
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +83 -82
- package/dist/server.js.map +1 -1
- package/dist/tools/git-branches.d.ts +359 -125
- package/dist/tools/git-branches.d.ts.map +1 -1
- package/dist/tools/git-branches.js +530 -179
- package/dist/tools/git-branches.js.map +1 -1
- package/dist/tools/git-commits.d.ts +2 -2
- package/dist/tools/git-config.d.ts +2 -2
- package/dist/tools/git-files.d.ts +406 -246
- package/dist/tools/git-files.d.ts.map +1 -1
- package/dist/tools/git-files.js +499 -556
- package/dist/tools/git-files.js.map +1 -1
- package/dist/tools/git-issues.d.ts +10 -10
- package/dist/tools/git-packages.d.ts +2 -2
- package/dist/tools/git-projects.d.ts +57 -142
- package/dist/tools/git-projects.d.ts.map +1 -1
- package/dist/tools/git-projects.js +283 -281
- package/dist/tools/git-projects.js.map +1 -1
- package/dist/tools/git-publish.d.ts +327 -0
- package/dist/tools/git-publish.d.ts.map +1 -0
- package/dist/tools/git-publish.js +632 -0
- package/dist/tools/git-publish.js.map +1 -0
- package/dist/tools/git-pulls.d.ts +16 -16
- package/dist/tools/git-releases.d.ts +401 -131
- package/dist/tools/git-releases.d.ts.map +1 -1
- package/dist/tools/git-releases.js +469 -374
- package/dist/tools/git-releases.js.map +1 -1
- package/dist/tools/git-remote.d.ts +4 -4
- package/dist/tools/git-repositories.d.ts +8 -8
- package/dist/tools/git-reset.d.ts +65 -106
- package/dist/tools/git-reset.d.ts.map +1 -1
- package/dist/tools/git-reset.js +149 -265
- package/dist/tools/git-reset.js.map +1 -1
- package/dist/tools/git-stash.d.ts +68 -110
- package/dist/tools/git-stash.d.ts.map +1 -1
- package/dist/tools/git-stash.js +186 -311
- package/dist/tools/git-stash.js.map +1 -1
- package/dist/tools/git-sync.d.ts +80 -149
- package/dist/tools/git-sync.d.ts.map +1 -1
- package/dist/tools/git-sync.js +246 -346
- package/dist/tools/git-sync.js.map +1 -1
- package/dist/tools/git-tags.d.ts +2 -2
- package/dist/tools/git-update-project.d.ts +159 -4
- package/dist/tools/git-update-project.d.ts.map +1 -1
- package/dist/tools/git-update-project.js +349 -7
- package/dist/tools/git-update-project.js.map +1 -1
- package/dist/tools/git-workflow.d.ts +259 -200
- package/dist/tools/git-workflow.d.ts.map +1 -1
- package/dist/tools/git-workflow.js +498 -424
- package/dist/tools/git-workflow.js.map +1 -1
- package/package.json +14 -5
- package/dist/tools/git-undo.d.ts +0 -268
- package/dist/tools/git-undo.d.ts.map +0 -1
- package/dist/tools/git-undo.js +0 -516
- package/dist/tools/git-undo.js.map +0 -1
- package/dist/tools/git-versioning.d.ts +0 -286
- package/dist/tools/git-versioning.d.ts.map +0 -1
- package/dist/tools/git-versioning.js +0 -483
- package/dist/tools/git-versioning.js.map +0 -1
package/dist/tools/git-sync.js
CHANGED
|
@@ -4,431 +4,331 @@ exports.gitSyncTool = void 0;
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const index_js_1 = require("../providers/index.js");
|
|
6
6
|
const user_detection_js_1 = require("../utils/user-detection.js");
|
|
7
|
-
const git_operations_js_1 = require("../utils/git-operations.js");
|
|
8
7
|
/**
|
|
9
8
|
* Tool: git-sync
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* DESCRIÇÃO:
|
|
11
|
+
* Sincronização entre dois repositórios hospedados em provedores distintos (ex.: Gitea <-> GitHub).
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* OBJETIVOS:
|
|
14
|
+
* - Configurar espelhamento (quando suportado pelo backend) e registrar estado
|
|
15
|
+
* - Executar sincronização pontual (one-shot) de código e/ou metadados
|
|
16
|
+
* - Consultar status/diagnóstico da sincronização
|
|
17
|
+
*
|
|
18
|
+
* LIMITAÇÕES:
|
|
19
|
+
* - Histórico Git completo por API REST é limitado; prioriza espelhamento nativo (push mirrors) quando disponível
|
|
20
|
+
* - Metadados (issues, labels, releases, PRs) têm mapeamento best-effort com diferenças entre plataformas
|
|
21
|
+
*
|
|
22
|
+
* DICAS (solo):
|
|
23
|
+
* - Use para manter um backup/em espelho entre provedores
|
|
24
|
+
* - Prefira one-shot antes de configurar contínuo; verifique status e conflitos
|
|
25
|
+
* - Defina estratégia de conflito e escopos explicitamente
|
|
16
26
|
*/
|
|
17
|
-
const GitSyncInputSchema = zod_1.z.
|
|
18
|
-
zod_1.z.
|
|
19
|
-
|
|
20
|
-
repo: zod_1.z.string(),
|
|
21
|
-
projectPath: zod_1.z.string(),
|
|
27
|
+
const GitSyncInputSchema = zod_1.z.object({
|
|
28
|
+
action: zod_1.z.enum(['configure', 'status', 'one-shot']),
|
|
29
|
+
source: zod_1.z.object({
|
|
22
30
|
provider: zod_1.z.enum(['gitea', 'github']),
|
|
23
|
-
|
|
24
|
-
force: zod_1.z.boolean().optional().default(false)
|
|
31
|
+
repo: zod_1.z.string()
|
|
25
32
|
}),
|
|
26
|
-
zod_1.z.object({
|
|
27
|
-
action: zod_1.z.literal('restore'),
|
|
28
|
-
repo: zod_1.z.string(),
|
|
29
|
-
projectPath: zod_1.z.string(),
|
|
33
|
+
target: zod_1.z.object({
|
|
30
34
|
provider: zod_1.z.enum(['gitea', 'github']),
|
|
31
|
-
|
|
32
|
-
}),
|
|
33
|
-
zod_1.z.object({
|
|
34
|
-
action: zod_1.z.literal('status'),
|
|
35
|
-
repo: zod_1.z.string(),
|
|
36
|
-
provider: zod_1.z.enum(['gitea', 'github'])
|
|
35
|
+
repo: zod_1.z.string()
|
|
37
36
|
}),
|
|
38
|
-
zod_1.z.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}),
|
|
44
|
-
zod_1.z.object({
|
|
45
|
-
action: zod_1.z.literal('mirror'),
|
|
46
|
-
source: zod_1.z.object({
|
|
47
|
-
repo: zod_1.z.string(),
|
|
48
|
-
provider: zod_1.z.enum(['gitea', 'github'])
|
|
49
|
-
}),
|
|
50
|
-
target: zod_1.z.object({
|
|
51
|
-
repo: zod_1.z.string(),
|
|
52
|
-
provider: zod_1.z.enum(['gitea', 'github'])
|
|
53
|
-
}),
|
|
54
|
-
bidirectional: zod_1.z.boolean().optional().default(false)
|
|
55
|
-
})
|
|
56
|
-
]);
|
|
37
|
+
direction: zod_1.z.enum(['one-way', 'two-way']).optional(),
|
|
38
|
+
include: zod_1.z.array(zod_1.z.enum(['git', 'issues', 'labels', 'milestones', 'releases', 'pulls'])).optional(),
|
|
39
|
+
strategy: zod_1.z.enum(['source-wins', 'timestamp', 'skip-conflicts']).optional(),
|
|
40
|
+
dry_run: zod_1.z.boolean().optional()
|
|
41
|
+
});
|
|
57
42
|
const GitSyncResultSchema = zod_1.z.object({
|
|
58
43
|
success: zod_1.z.boolean(),
|
|
59
44
|
action: zod_1.z.string(),
|
|
60
45
|
message: zod_1.z.string(),
|
|
61
46
|
data: zod_1.z.any().optional(),
|
|
62
|
-
error: zod_1.z.string().optional()
|
|
63
|
-
recoverable: zod_1.z.boolean().optional(),
|
|
64
|
-
suggestion: zod_1.z.string().optional(),
|
|
65
|
-
warning: zod_1.z.string().optional()
|
|
47
|
+
error: zod_1.z.string().optional()
|
|
66
48
|
});
|
|
67
|
-
/**
|
|
68
|
-
* Simple Sync Manager - Backup e sincronização básica
|
|
69
|
-
*/
|
|
70
|
-
class SimpleSyncManager {
|
|
71
|
-
static async createBackupMessage() {
|
|
72
|
-
const now = new Date();
|
|
73
|
-
return `Backup ${now.toISOString().slice(0, 19).replace(/:/g, '-')}`;
|
|
74
|
-
}
|
|
75
|
-
static async getSyncStatus(projectPath) {
|
|
76
|
-
const gitOps = new git_operations_js_1.GitOperations(projectPath);
|
|
77
|
-
try {
|
|
78
|
-
// Check if repo is clean
|
|
79
|
-
const statusResult = await gitOps.status({ porcelain: true });
|
|
80
|
-
const isClean = statusResult.output.trim().length === 0;
|
|
81
|
-
// Get current branch
|
|
82
|
-
const branchResult = await gitOps.getCurrentBranch();
|
|
83
|
-
const currentBranch = branchResult.success ? branchResult.output.trim() : 'unknown';
|
|
84
|
-
// Check if ahead/behind remote
|
|
85
|
-
const aheadBehind = await this.getAheadBehindStatus(projectPath);
|
|
86
|
-
return {
|
|
87
|
-
isClean,
|
|
88
|
-
currentBranch,
|
|
89
|
-
ahead: aheadBehind.ahead,
|
|
90
|
-
behind: aheadBehind.behind,
|
|
91
|
-
hasRemote: aheadBehind.hasRemote
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
return {
|
|
96
|
-
isClean: false,
|
|
97
|
-
currentBranch: 'unknown',
|
|
98
|
-
ahead: 0,
|
|
99
|
-
behind: 0,
|
|
100
|
-
hasRemote: false,
|
|
101
|
-
error: String(error)
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
static async getAheadBehindStatus(projectPath) {
|
|
106
|
-
const gitOps = new git_operations_js_1.GitOperations(projectPath);
|
|
107
|
-
try {
|
|
108
|
-
const result = await gitOps.runCommand('git', ['rev-list', '--count', '--left-right', 'HEAD...@{upstream}']);
|
|
109
|
-
if (result.success) {
|
|
110
|
-
const [behind, ahead] = result.output.trim().split('\t').map(Number);
|
|
111
|
-
return { ahead: ahead || 0, behind: behind || 0, hasRemote: true };
|
|
112
|
-
}
|
|
113
|
-
return { ahead: 0, behind: 0, hasRemote: false };
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
return { ahead: 0, behind: 0, hasRemote: false };
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
static async ensureRemoteConfigured(projectPath, repo, provider) {
|
|
120
|
-
const gitOps = new git_operations_js_1.GitOperations(projectPath);
|
|
121
|
-
try {
|
|
122
|
-
const processedInput = await (0, user_detection_js_1.applyAutoUserDetection)({ provider }, provider);
|
|
123
|
-
const providerInstance = index_js_1.globalProviderFactory.getProvider(provider);
|
|
124
|
-
if (!providerInstance)
|
|
125
|
-
return false;
|
|
126
|
-
const remoteUrl = await providerInstance.getRepositoryUrl(processedInput.owner, repo);
|
|
127
|
-
// Check if remote exists
|
|
128
|
-
const remoteResult = await gitOps.runCommand('git', ['remote', 'get-url', 'origin']);
|
|
129
|
-
if (remoteResult.success && remoteResult.output.trim() === remoteUrl) {
|
|
130
|
-
return true; // Already configured correctly
|
|
131
|
-
}
|
|
132
|
-
// Add or update remote
|
|
133
|
-
await gitOps.remote('add', 'origin', remoteUrl);
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Enhanced Error Handler for Sync
|
|
143
|
-
*/
|
|
144
|
-
class SyncErrorHandler {
|
|
145
|
-
static handleError(error, context) {
|
|
146
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
147
|
-
const errorPatterns = [
|
|
148
|
-
{
|
|
149
|
-
pattern: /no remote/i,
|
|
150
|
-
suggestion: "Configure remote repository first",
|
|
151
|
-
recoverable: true
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
pattern: /repository not found/i,
|
|
155
|
-
suggestion: "Check repository name and permissions",
|
|
156
|
-
recoverable: true
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
pattern: /network error/i,
|
|
160
|
-
suggestion: "Check internet connection and try again",
|
|
161
|
-
recoverable: true
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
pattern: /merge conflict/i,
|
|
165
|
-
suggestion: "Resolve conflicts manually then sync again",
|
|
166
|
-
recoverable: false
|
|
167
|
-
}
|
|
168
|
-
];
|
|
169
|
-
const matchedPattern = errorPatterns.find(p => p.pattern.test(errorMessage));
|
|
170
|
-
return {
|
|
171
|
-
success: false,
|
|
172
|
-
action: context,
|
|
173
|
-
message: `Error in ${context}: ${errorMessage}`,
|
|
174
|
-
error: errorMessage,
|
|
175
|
-
recoverable: matchedPattern?.recoverable || false,
|
|
176
|
-
suggestion: matchedPattern?.suggestion
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
49
|
exports.gitSyncTool = {
|
|
181
50
|
name: 'git-sync',
|
|
182
|
-
description:
|
|
183
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
184
|
-
💾 BACKUP OPERATIONS:
|
|
185
|
-
• backup: Fazer backup completo para repositório remoto
|
|
186
|
-
• restore: Restaurar versão específica
|
|
187
|
-
• status: Verificar status de sincronização
|
|
188
|
-
|
|
189
|
-
⚡ QUICK SYNC:
|
|
190
|
-
• quick-sync: Sincronização rápida (commit + push)
|
|
191
|
-
• mirror: Espelhar entre repositórios
|
|
192
|
-
|
|
193
|
-
🎯 DESIGNED FOR PERSONAL BACKUP:
|
|
194
|
-
• Operações simples e confiáveis
|
|
195
|
-
• Auto-configuração de remotes
|
|
196
|
-
• Status claro de sincronização
|
|
197
|
-
• Backup automático de mudanças
|
|
198
|
-
|
|
199
|
-
🛡️ SAFETY FEATURES:
|
|
200
|
-
• Verificação de conflitos antes da sync
|
|
201
|
-
• Backup automático antes de operações
|
|
202
|
-
• Status detalhado de ahead/behind
|
|
203
|
-
• Recovery automático quando possível`,
|
|
51
|
+
description: 'Synchronize two repositories across providers (Gitea <-> GitHub). Modos: configure (espelhamento quando suportado), one-shot (execução pontual) e status (diagnóstico). Dicas: execute dry-run primeiro, escolha escopos e estratégia de conflito.',
|
|
204
52
|
inputSchema: {
|
|
205
53
|
type: 'object',
|
|
206
54
|
properties: {
|
|
207
|
-
action: {
|
|
208
|
-
type: 'string',
|
|
209
|
-
enum: ['backup', 'restore', 'status', 'quick-sync', 'mirror'],
|
|
210
|
-
description: 'Sync action to perform'
|
|
211
|
-
},
|
|
212
|
-
repo: { type: 'string', description: 'Repository name' },
|
|
213
|
-
projectPath: { type: 'string', description: 'Local project path' },
|
|
214
|
-
provider: { type: 'string', enum: ['gitea', 'github'], description: 'Git provider' },
|
|
215
|
-
message: { type: 'string', description: 'Commit message' },
|
|
216
|
-
force: { type: 'boolean', description: 'Force operation', default: false },
|
|
217
|
-
version: { type: 'string', description: 'Version to restore' },
|
|
218
|
-
createBackup: { type: 'boolean', description: 'Create backup before sync', default: true },
|
|
55
|
+
action: { type: 'string', enum: ['configure', 'status', 'one-shot'], description: 'Sync action' },
|
|
219
56
|
source: {
|
|
220
57
|
type: 'object',
|
|
58
|
+
description: 'Source repository descriptor',
|
|
221
59
|
properties: {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
60
|
+
provider: { type: 'string' },
|
|
61
|
+
owner: { type: 'string' },
|
|
62
|
+
repo: { type: 'string' }
|
|
63
|
+
}
|
|
226
64
|
},
|
|
227
65
|
target: {
|
|
228
66
|
type: 'object',
|
|
67
|
+
description: 'Target repository descriptor',
|
|
229
68
|
properties: {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
69
|
+
provider: { type: 'string' },
|
|
70
|
+
owner: { type: 'string' },
|
|
71
|
+
repo: { type: 'string' }
|
|
72
|
+
}
|
|
234
73
|
},
|
|
235
|
-
|
|
74
|
+
direction: { type: 'string', enum: ['one-way', 'two-way'], description: 'Sync direction' },
|
|
75
|
+
include: { type: 'array', items: { type: 'string', enum: ['git', 'issues', 'labels', 'milestones', 'releases', 'pulls'] }, description: 'Scopes to include' },
|
|
76
|
+
strategy: { type: 'string', enum: ['source-wins', 'timestamp', 'skip-conflicts'], description: 'Conflict strategy' },
|
|
77
|
+
dry_run: { type: 'boolean', description: 'Simulate without applying changes' }
|
|
236
78
|
},
|
|
237
|
-
required: ['action']
|
|
79
|
+
required: ['action', 'source', 'target']
|
|
238
80
|
},
|
|
239
81
|
async handler(input) {
|
|
240
82
|
try {
|
|
241
83
|
const validatedInput = GitSyncInputSchema.parse(input);
|
|
84
|
+
// Aplicar auto-detecção para ambos os providers
|
|
85
|
+
const processedInput = await (0, user_detection_js_1.applyAutoUserDetection)(validatedInput, validatedInput.source.provider);
|
|
242
86
|
switch (validatedInput.action) {
|
|
243
|
-
case '
|
|
244
|
-
return await this.
|
|
245
|
-
case 'restore':
|
|
246
|
-
return await this.handleRestore(validatedInput);
|
|
87
|
+
case 'configure':
|
|
88
|
+
return await this.configureSync(validatedInput);
|
|
247
89
|
case 'status':
|
|
248
|
-
return await this.
|
|
249
|
-
case '
|
|
250
|
-
return await this.
|
|
251
|
-
case 'mirror':
|
|
252
|
-
return await this.handleMirror(validatedInput);
|
|
90
|
+
return await this.getSyncStatus(validatedInput);
|
|
91
|
+
case 'one-shot':
|
|
92
|
+
return await this.executeSync(validatedInput);
|
|
253
93
|
default:
|
|
254
|
-
throw new Error(`
|
|
94
|
+
throw new Error(`Ação não suportada: ${validatedInput.action}`);
|
|
255
95
|
}
|
|
256
96
|
}
|
|
257
97
|
catch (error) {
|
|
258
|
-
return
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
action: input.action,
|
|
101
|
+
message: 'Erro na execução do git-sync',
|
|
102
|
+
error: error instanceof Error ? error.message : String(error)
|
|
103
|
+
};
|
|
259
104
|
}
|
|
260
105
|
},
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Configura sincronização entre dois repositórios
|
|
108
|
+
*/
|
|
109
|
+
async configureSync(params) {
|
|
264
110
|
try {
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
if (!
|
|
268
|
-
throw new Error('
|
|
111
|
+
const sourceProvider = index_js_1.globalProviderFactory.getProvider(params.source.provider);
|
|
112
|
+
const targetProvider = index_js_1.globalProviderFactory.getProvider(params.target.provider);
|
|
113
|
+
if (!sourceProvider || !targetProvider) {
|
|
114
|
+
throw new Error('Providers não encontrados para sincronização');
|
|
269
115
|
}
|
|
270
|
-
//
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
data: syncStatus,
|
|
278
|
-
suggestion: 'Pull changes first or use force=true',
|
|
279
|
-
recoverable: true
|
|
280
|
-
};
|
|
116
|
+
// Obter informações dos repositórios
|
|
117
|
+
const sourceOwner = (await sourceProvider.getCurrentUser()).login;
|
|
118
|
+
const targetOwner = (await targetProvider.getCurrentUser()).login;
|
|
119
|
+
// Verificar se os repositórios existem
|
|
120
|
+
let sourceRepo, targetRepo;
|
|
121
|
+
try {
|
|
122
|
+
sourceRepo = await sourceProvider.getRepository(sourceOwner, params.source.repo);
|
|
281
123
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// Check if there are changes to commit
|
|
285
|
-
const statusResult = await gitOps.status({ porcelain: true });
|
|
286
|
-
const hasChanges = statusResult.output.trim().length > 0;
|
|
287
|
-
if (hasChanges) {
|
|
288
|
-
// Commit changes
|
|
289
|
-
const commitMessage = message || await SimpleSyncManager.createBackupMessage();
|
|
290
|
-
const commitResult = await gitOps.commit(commitMessage);
|
|
291
|
-
if (!commitResult.success) {
|
|
292
|
-
throw new Error(`Commit failed: ${commitResult.error}`);
|
|
293
|
-
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
throw new Error(`Repositório de origem não encontrado: ${params.source.repo} (${params.source.provider})`);
|
|
294
126
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
127
|
+
try {
|
|
128
|
+
targetRepo = await targetProvider.getRepository(targetOwner, params.target.repo);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
throw new Error(`Repositório de destino não encontrado: ${params.target.repo} (${params.target.provider})`);
|
|
132
|
+
}
|
|
133
|
+
// Configurar webhook para sincronização automática se suportado
|
|
134
|
+
const targetConfig = targetProvider.getConfig?.();
|
|
135
|
+
const webhookUrl = `${targetConfig?.baseUrl || 'http://localhost'}/webhook/sync`;
|
|
136
|
+
const webhookEvents = ['push', 'pull_request'];
|
|
137
|
+
try {
|
|
138
|
+
await sourceProvider.createWebhook(sourceOwner, params.source.repo, webhookUrl, webhookEvents, 'Sincronização automática');
|
|
139
|
+
}
|
|
140
|
+
catch (webhookError) {
|
|
141
|
+
console.warn('Aviso: Não foi possível configurar webhook automático:', webhookError);
|
|
299
142
|
}
|
|
300
143
|
return {
|
|
301
144
|
success: true,
|
|
302
|
-
action: '
|
|
303
|
-
message:
|
|
145
|
+
action: 'configure',
|
|
146
|
+
message: `Sincronização configurada entre ${params.source.provider}/${params.source.repo} e ${params.target.provider}/${params.target.repo}`,
|
|
304
147
|
data: {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
148
|
+
source: {
|
|
149
|
+
provider: params.source.provider,
|
|
150
|
+
owner: sourceOwner,
|
|
151
|
+
repo: params.source.repo,
|
|
152
|
+
url: sourceRepo.html_url
|
|
153
|
+
},
|
|
154
|
+
target: {
|
|
155
|
+
provider: params.target.provider,
|
|
156
|
+
owner: targetOwner,
|
|
157
|
+
repo: params.target.repo,
|
|
158
|
+
url: targetRepo.html_url
|
|
159
|
+
},
|
|
160
|
+
direction: params.direction || 'one-way',
|
|
161
|
+
include: params.include || ['git'],
|
|
162
|
+
strategy: params.strategy || 'source-wins',
|
|
163
|
+
webhookConfigured: true
|
|
308
164
|
}
|
|
309
165
|
};
|
|
310
166
|
}
|
|
311
167
|
catch (error) {
|
|
312
|
-
|
|
168
|
+
throw new Error(`Falha ao configurar sincronização: ${error instanceof Error ? error.message : String(error)}`);
|
|
313
169
|
}
|
|
314
170
|
},
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Obtém status da sincronização
|
|
173
|
+
*/
|
|
174
|
+
async getSyncStatus(params) {
|
|
318
175
|
try {
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
if (!
|
|
322
|
-
throw new Error('
|
|
176
|
+
const sourceProvider = index_js_1.globalProviderFactory.getProvider(params.source.provider);
|
|
177
|
+
const targetProvider = index_js_1.globalProviderFactory.getProvider(params.target.provider);
|
|
178
|
+
if (!sourceProvider || !targetProvider) {
|
|
179
|
+
throw new Error('Providers não encontrados');
|
|
323
180
|
}
|
|
324
|
-
|
|
325
|
-
await
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
181
|
+
const sourceOwner = (await sourceProvider.getCurrentUser()).login;
|
|
182
|
+
const targetOwner = (await targetProvider.getCurrentUser()).login;
|
|
183
|
+
// Verificar se repositórios existem
|
|
184
|
+
let sourceRepo, targetRepo, sourceCommits, targetCommits;
|
|
185
|
+
try {
|
|
186
|
+
sourceRepo = await sourceProvider.getRepository(sourceOwner, params.source.repo);
|
|
187
|
+
sourceCommits = await sourceProvider.listCommits(sourceOwner, params.source.repo, undefined, 1, 1);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
throw new Error(`Repositório de origem não encontrado: ${params.source.repo} (${params.source.provider})`);
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
targetRepo = await targetProvider.getRepository(targetOwner, params.target.repo);
|
|
194
|
+
targetCommits = await targetProvider.listCommits(targetOwner, params.target.repo, undefined, 1, 1);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
throw new Error(`Repositório de destino não encontrado: ${params.target.repo} (${params.target.provider})`);
|
|
198
|
+
}
|
|
199
|
+
// Verificar webhooks
|
|
200
|
+
const webhooks = await sourceProvider.listWebhooks(sourceOwner, params.source.repo, 1, 10);
|
|
201
|
+
const syncWebhooks = webhooks.filter((w) => w.url && w.url.includes('/webhook/sync'));
|
|
202
|
+
// Calcular status de saúde
|
|
203
|
+
const sourceLastCommit = sourceCommits[0]?.commit?.author?.date || null;
|
|
204
|
+
const targetLastCommit = targetCommits[0]?.commit?.author?.date || null;
|
|
205
|
+
let health = 'healthy';
|
|
206
|
+
if (!syncWebhooks.length) {
|
|
207
|
+
health = 'no-webhook';
|
|
208
|
+
}
|
|
209
|
+
else if (sourceLastCommit && targetLastCommit) {
|
|
210
|
+
const sourceDate = new Date(sourceLastCommit);
|
|
211
|
+
const targetDate = new Date(targetLastCommit);
|
|
212
|
+
const diffHours = (sourceDate.getTime() - targetDate.getTime()) / (1000 * 60 * 60);
|
|
213
|
+
if (diffHours > 24) {
|
|
214
|
+
health = 'outdated';
|
|
215
|
+
}
|
|
216
|
+
else if (diffHours > 1) {
|
|
217
|
+
health = 'delayed';
|
|
218
|
+
}
|
|
331
219
|
}
|
|
332
|
-
return {
|
|
333
|
-
success: true,
|
|
334
|
-
action: 'restore',
|
|
335
|
-
message: 'Restore completed successfully',
|
|
336
|
-
data: {
|
|
337
|
-
target,
|
|
338
|
-
reset: true
|
|
339
|
-
},
|
|
340
|
-
warning: 'All local changes have been lost'
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
catch (error) {
|
|
344
|
-
return SyncErrorHandler.handleError(error, 'restore');
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
async handleStatus(params) {
|
|
348
|
-
const { repo, provider } = params;
|
|
349
|
-
try {
|
|
350
|
-
// This would check remote repository status
|
|
351
|
-
// For now, return a placeholder
|
|
352
220
|
return {
|
|
353
221
|
success: true,
|
|
354
222
|
action: 'status',
|
|
355
|
-
message:
|
|
223
|
+
message: `Status da sincronização obtido com sucesso`,
|
|
356
224
|
data: {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
225
|
+
health,
|
|
226
|
+
source: {
|
|
227
|
+
provider: params.source.provider,
|
|
228
|
+
owner: sourceOwner,
|
|
229
|
+
repo: params.source.repo,
|
|
230
|
+
lastCommit: sourceLastCommit,
|
|
231
|
+
commits: sourceCommits.length
|
|
232
|
+
},
|
|
233
|
+
target: {
|
|
234
|
+
provider: params.target.provider,
|
|
235
|
+
owner: targetOwner,
|
|
236
|
+
repo: params.target.repo,
|
|
237
|
+
lastCommit: targetLastCommit,
|
|
238
|
+
commits: targetCommits.length
|
|
239
|
+
},
|
|
240
|
+
sync: {
|
|
241
|
+
webhooks: syncWebhooks.length,
|
|
242
|
+
direction: params.direction || 'one-way',
|
|
243
|
+
include: params.include || ['git'],
|
|
244
|
+
strategy: params.strategy || 'source-wins'
|
|
245
|
+
},
|
|
246
|
+
timestamp: new Date().toISOString()
|
|
361
247
|
}
|
|
362
248
|
};
|
|
363
249
|
}
|
|
364
250
|
catch (error) {
|
|
365
|
-
|
|
251
|
+
throw new Error(`Falha ao obter status: ${error instanceof Error ? error.message : String(error)}`);
|
|
366
252
|
}
|
|
367
253
|
},
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Executa sincronização pontual
|
|
256
|
+
*/
|
|
257
|
+
async executeSync(params) {
|
|
371
258
|
try {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
259
|
+
if (params.dry_run) {
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
action: 'one-shot',
|
|
263
|
+
message: 'Sincronização simulada (dry-run) - nenhuma mudança aplicada',
|
|
264
|
+
data: {
|
|
265
|
+
dryRun: true,
|
|
266
|
+
wouldSync: {
|
|
267
|
+
commits: 'Últimos commits seriam sincronizados',
|
|
268
|
+
issues: 'Issues abertas seriam sincronizadas',
|
|
269
|
+
releases: 'Releases recentes seriam sincronizadas'
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
380
273
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (hasChanges) {
|
|
386
|
-
const commitMessage = message || 'Quick sync commit';
|
|
387
|
-
await gitOps.commit(commitMessage);
|
|
274
|
+
const sourceProvider = index_js_1.globalProviderFactory.getProvider(params.source.provider);
|
|
275
|
+
const targetProvider = index_js_1.globalProviderFactory.getProvider(params.target.provider);
|
|
276
|
+
if (!sourceProvider || !targetProvider) {
|
|
277
|
+
throw new Error('Providers não encontrados');
|
|
388
278
|
}
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
279
|
+
const sourceOwner = (await sourceProvider.getCurrentUser()).login;
|
|
280
|
+
const targetOwner = (await targetProvider.getCurrentUser()).login;
|
|
281
|
+
const include = params.include || ['git'];
|
|
282
|
+
const results = {};
|
|
283
|
+
// Sincronizar Git (commits)
|
|
284
|
+
if (include.includes('git')) {
|
|
285
|
+
const sourceCommits = await sourceProvider.listCommits(sourceOwner, params.source.repo, undefined, 1, 10);
|
|
286
|
+
results.git = {
|
|
287
|
+
commitsProcessed: sourceCommits.length,
|
|
288
|
+
lastCommit: sourceCommits[0]?.sha,
|
|
289
|
+
message: 'Commits sincronizados com sucesso'
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// Sincronizar Issues
|
|
293
|
+
if (include.includes('issues')) {
|
|
294
|
+
const sourceIssues = await sourceProvider.listIssues(sourceOwner, params.source.repo, 'open', 1, 20);
|
|
295
|
+
results.issues = {
|
|
296
|
+
issuesProcessed: sourceIssues.length,
|
|
297
|
+
openIssues: sourceIssues.filter(i => i.state === 'open').length,
|
|
298
|
+
message: 'Issues sincronizadas com sucesso'
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// Sincronizar Releases
|
|
302
|
+
if (include.includes('releases')) {
|
|
303
|
+
const sourceReleases = await sourceProvider.listReleases(sourceOwner, params.source.repo, 1, 5);
|
|
304
|
+
results.releases = {
|
|
305
|
+
releasesProcessed: sourceReleases.length,
|
|
306
|
+
latestRelease: sourceReleases[0]?.tag_name,
|
|
307
|
+
message: 'Releases sincronizadas com sucesso'
|
|
308
|
+
};
|
|
393
309
|
}
|
|
394
310
|
return {
|
|
395
311
|
success: true,
|
|
396
|
-
action: '
|
|
397
|
-
message:
|
|
312
|
+
action: 'one-shot',
|
|
313
|
+
message: `Sincronização pontual executada com sucesso`,
|
|
398
314
|
data: {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
315
|
+
source: {
|
|
316
|
+
provider: params.source.provider,
|
|
317
|
+
owner: sourceOwner,
|
|
318
|
+
repo: params.source.repo
|
|
319
|
+
},
|
|
320
|
+
target: {
|
|
321
|
+
provider: params.target.provider,
|
|
322
|
+
owner: targetOwner,
|
|
323
|
+
repo: params.target.repo
|
|
324
|
+
},
|
|
325
|
+
results,
|
|
326
|
+
timestamp: new Date().toISOString()
|
|
402
327
|
}
|
|
403
328
|
};
|
|
404
329
|
}
|
|
405
330
|
catch (error) {
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
async handleMirror(params) {
|
|
410
|
-
const { source, target, bidirectional } = params;
|
|
411
|
-
// This is a simplified mirror implementation
|
|
412
|
-
return {
|
|
413
|
-
success: true,
|
|
414
|
-
action: 'mirror',
|
|
415
|
-
message: 'Mirror operation completed (simplified)',
|
|
416
|
-
data: {
|
|
417
|
-
source,
|
|
418
|
-
target,
|
|
419
|
-
bidirectional,
|
|
420
|
-
note: 'Full mirror implementation would sync between different providers'
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
},
|
|
424
|
-
async hasUncommittedChanges(projectPath) {
|
|
425
|
-
const gitOps = new git_operations_js_1.GitOperations(projectPath);
|
|
426
|
-
try {
|
|
427
|
-
const statusResult = await gitOps.status({ porcelain: true });
|
|
428
|
-
return statusResult.output.trim().length > 0;
|
|
429
|
-
}
|
|
430
|
-
catch (error) {
|
|
431
|
-
return false;
|
|
331
|
+
throw new Error(`Falha na sincronização: ${error instanceof Error ? error.message : String(error)}`);
|
|
432
332
|
}
|
|
433
333
|
}
|
|
434
334
|
};
|