@andrebuzeli/git-mcp 15.8.3 → 15.8.5
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 +39 -125
- package/package.json +27 -44
- package/src/index.js +146 -139
- package/src/providers/providerManager.js +203 -203
- package/src/tools/git-diff.js +137 -126
- package/src/tools/git-help.js +285 -285
- package/src/tools/git-remote.js +472 -472
- package/src/tools/git-workflow.js +403 -403
- package/src/utils/env.js +104 -104
- package/src/utils/errors.js +431 -431
- package/src/utils/gitAdapter.js +932 -932
- package/src/utils/hooks.js +255 -255
- package/src/utils/metrics.js +198 -198
- package/src/utils/providerExec.js +58 -58
- package/src/utils/repoHelpers.js +160 -160
- package/src/utils/retry.js +123 -123
- package/install.sh +0 -68
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
import { Octokit } from "@octokit/rest";
|
|
2
|
-
import axios from "axios";
|
|
3
|
-
import { getProvidersEnv } from "../utils/repoHelpers.js";
|
|
4
|
-
|
|
5
|
-
export class ProviderManager {
|
|
6
|
-
constructor() {
|
|
7
|
-
const { githubToken, giteaUrl, giteaToken } = getProvidersEnv();
|
|
8
|
-
this.githubToken = githubToken || "";
|
|
9
|
-
this.giteaUrl = giteaUrl || "";
|
|
10
|
-
this.giteaToken = giteaToken || "";
|
|
11
|
-
this.github = this.githubToken ? new Octokit({ auth: this.githubToken }) : null;
|
|
12
|
-
this._githubOwner = "";
|
|
13
|
-
this._giteaOwner = "";
|
|
14
|
-
this._ownerFetchedAt = 0;
|
|
15
|
-
this._remoteUrlsCache = new Map(); // Cache para URLs de remotes
|
|
16
|
-
this._cacheExpiry = 10 * 60 * 1000; // 10 minutos
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async getGitHubOwner() {
|
|
20
|
-
if (!this.github) return "";
|
|
21
|
-
const now = Date.now();
|
|
22
|
-
if (this._githubOwner && now - this._ownerFetchedAt < 5 * 60 * 1000) return this._githubOwner;
|
|
23
|
-
try {
|
|
24
|
-
const me = await this.github.rest.users.getAuthenticated();
|
|
25
|
-
this._githubOwner = me.data.login || "";
|
|
26
|
-
this._ownerFetchedAt = now;
|
|
27
|
-
return this._githubOwner;
|
|
28
|
-
} catch {
|
|
29
|
-
return "";
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async getGiteaOwner() {
|
|
34
|
-
if (!this.giteaUrl || !this.giteaToken) return "";
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
if (this._giteaOwner && now - this._ownerFetchedAt < 5 * 60 * 1000) return this._giteaOwner;
|
|
37
|
-
try {
|
|
38
|
-
const r = await axios.get(`${this.giteaUrl}/api/v1/user`, {
|
|
39
|
-
headers: { Authorization: `token ${this.giteaToken}` },
|
|
40
|
-
timeout: 8000,
|
|
41
|
-
});
|
|
42
|
-
const d = r.data || {};
|
|
43
|
-
this._giteaOwner = d.login || d.username || "";
|
|
44
|
-
this._ownerFetchedAt = now;
|
|
45
|
-
return this._giteaOwner;
|
|
46
|
-
} catch {
|
|
47
|
-
return "";
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async getRemoteUrls(repoName) {
|
|
52
|
-
const cacheKey = `urls_${repoName}`;
|
|
53
|
-
const now = Date.now();
|
|
54
|
-
|
|
55
|
-
// Verificar cache
|
|
56
|
-
if (this._remoteUrlsCache.has(cacheKey)) {
|
|
57
|
-
const cached = this._remoteUrlsCache.get(cacheKey);
|
|
58
|
-
if (now - cached.timestamp < this._cacheExpiry) {
|
|
59
|
-
return cached.urls;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const urls = {};
|
|
64
|
-
|
|
65
|
-
// GitHub URL
|
|
66
|
-
if (this.github) {
|
|
67
|
-
const owner = await this.getGitHubOwner();
|
|
68
|
-
if (owner) {
|
|
69
|
-
urls.github = `https://github.com/${owner}/${repoName}.git`;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Gitea URL
|
|
74
|
-
if (this.giteaUrl && this.giteaToken) {
|
|
75
|
-
const owner = await this.getGiteaOwner();
|
|
76
|
-
if (owner) {
|
|
77
|
-
const base = this.giteaUrl.replace(/\/$/, "");
|
|
78
|
-
urls.gitea = `${base}/${owner}/${repoName}.git`;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Cachear resultado
|
|
83
|
-
this._remoteUrlsCache.set(cacheKey, { urls, timestamp: now });
|
|
84
|
-
|
|
85
|
-
return urls;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async ensureRepos({ repoName, createIfMissing = true, description = "Managed by git-mcpv2", isPublic = false }) {
|
|
89
|
-
// Por padrão, repositórios são PRIVADOS. Use isPublic=true para público.
|
|
90
|
-
const isPrivate = !isPublic;
|
|
91
|
-
const results = { github: null, gitea: null };
|
|
92
|
-
// GitHub
|
|
93
|
-
if (this.github) {
|
|
94
|
-
const owner = await this.getGitHubOwner();
|
|
95
|
-
if (owner) {
|
|
96
|
-
try {
|
|
97
|
-
const full = `${owner}/${repoName}`;
|
|
98
|
-
try {
|
|
99
|
-
await this.github.rest.repos.get({ owner, repo: repoName });
|
|
100
|
-
results.github = { ok: true, repo: full, created: false };
|
|
101
|
-
} catch {
|
|
102
|
-
if (createIfMissing) {
|
|
103
|
-
const cr = await this.github.rest.repos.createForAuthenticatedUser({
|
|
104
|
-
name: repoName,
|
|
105
|
-
description,
|
|
106
|
-
private: isPrivate,
|
|
107
|
-
auto_init: false,
|
|
108
|
-
});
|
|
109
|
-
results.github = { ok: true, repo: cr.data.full_name, created: true };
|
|
110
|
-
} else {
|
|
111
|
-
results.github = { ok: false, error: "missing" };
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch (e) {
|
|
115
|
-
results.github = { ok: false, error: String(e?.message || e) };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Gitea
|
|
120
|
-
if (this.giteaUrl && this.giteaToken) {
|
|
121
|
-
const owner = await this.getGiteaOwner();
|
|
122
|
-
if (owner) {
|
|
123
|
-
try {
|
|
124
|
-
const base = this.giteaUrl.replace(/\/$/, "");
|
|
125
|
-
const getRepo = await axios.get(`${base}/api/v1/repos/${owner}/${repoName}`,
|
|
126
|
-
{ headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
127
|
-
if (getRepo.status === 200) {
|
|
128
|
-
results.gitea = { ok: true, repo: `${owner}/${repoName}`, created: false };
|
|
129
|
-
}
|
|
130
|
-
} catch (e) {
|
|
131
|
-
if (createIfMissing) {
|
|
132
|
-
try {
|
|
133
|
-
const cr = await axios.post(`${this.giteaUrl}/api/v1/user/repos`,
|
|
134
|
-
{ name: repoName, description, private: isPrivate, auto_init: false },
|
|
135
|
-
{ headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
136
|
-
results.gitea = { ok: true, repo: `${cr.data?.owner?.login || owner}/${repoName}`, created: true };
|
|
137
|
-
} catch (err) {
|
|
138
|
-
results.gitea = { ok: false, error: String(err?.message || err) };
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
results.gitea = { ok: false, error: "missing" };
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return results;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async listAllRepos(options = {}) {
|
|
150
|
-
const { maxPages = 10, perPage = 100 } = options;
|
|
151
|
-
const results = { github: [], gitea: [] };
|
|
152
|
-
|
|
153
|
-
// GitHub - com paginação
|
|
154
|
-
if (this.github) {
|
|
155
|
-
try {
|
|
156
|
-
let page = 1;
|
|
157
|
-
let hasMore = true;
|
|
158
|
-
while (hasMore && page <= maxPages) {
|
|
159
|
-
const { data } = await this.github.rest.repos.listForAuthenticatedUser({
|
|
160
|
-
per_page: perPage,
|
|
161
|
-
page,
|
|
162
|
-
visibility: "all"
|
|
163
|
-
});
|
|
164
|
-
results.github.push(...data.map(r => ({
|
|
165
|
-
name: r.name,
|
|
166
|
-
fullName: r.full_name,
|
|
167
|
-
url: r.html_url,
|
|
168
|
-
isPrivate: r.private
|
|
169
|
-
})));
|
|
170
|
-
hasMore = data.length === perPage;
|
|
171
|
-
page++;
|
|
172
|
-
}
|
|
173
|
-
} catch (e) {
|
|
174
|
-
console.error("GitHub List Error:", e.message);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Gitea - com paginação
|
|
179
|
-
if (this.giteaUrl && this.giteaToken) {
|
|
180
|
-
try {
|
|
181
|
-
let page = 1;
|
|
182
|
-
let hasMore = true;
|
|
183
|
-
const base = this.giteaUrl.replace(/\/$/, "");
|
|
184
|
-
while (hasMore && page <= maxPages) {
|
|
185
|
-
const { data } = await axios.get(`${base}/api/v1/user/repos?limit=${perPage}&page=${page}`, {
|
|
186
|
-
headers: { Authorization: `token ${this.giteaToken}` }
|
|
187
|
-
});
|
|
188
|
-
results.gitea.push(...data.map(r => ({
|
|
189
|
-
name: r.name,
|
|
190
|
-
fullName: r.full_name,
|
|
191
|
-
url: r.html_url,
|
|
192
|
-
isPrivate: r.private
|
|
193
|
-
})));
|
|
194
|
-
hasMore = data.length === perPage;
|
|
195
|
-
page++;
|
|
196
|
-
}
|
|
197
|
-
} catch (e) {
|
|
198
|
-
console.error("Gitea List Error:", e.message);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return results;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { getProvidersEnv } from "../utils/repoHelpers.js";
|
|
4
|
+
|
|
5
|
+
export class ProviderManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
const { githubToken, giteaUrl, giteaToken } = getProvidersEnv();
|
|
8
|
+
this.githubToken = githubToken || "";
|
|
9
|
+
this.giteaUrl = giteaUrl || "";
|
|
10
|
+
this.giteaToken = giteaToken || "";
|
|
11
|
+
this.github = this.githubToken ? new Octokit({ auth: this.githubToken }) : null;
|
|
12
|
+
this._githubOwner = "";
|
|
13
|
+
this._giteaOwner = "";
|
|
14
|
+
this._ownerFetchedAt = 0;
|
|
15
|
+
this._remoteUrlsCache = new Map(); // Cache para URLs de remotes
|
|
16
|
+
this._cacheExpiry = 10 * 60 * 1000; // 10 minutos
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getGitHubOwner() {
|
|
20
|
+
if (!this.github) return "";
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
if (this._githubOwner && now - this._ownerFetchedAt < 5 * 60 * 1000) return this._githubOwner;
|
|
23
|
+
try {
|
|
24
|
+
const me = await this.github.rest.users.getAuthenticated();
|
|
25
|
+
this._githubOwner = me.data.login || "";
|
|
26
|
+
this._ownerFetchedAt = now;
|
|
27
|
+
return this._githubOwner;
|
|
28
|
+
} catch {
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getGiteaOwner() {
|
|
34
|
+
if (!this.giteaUrl || !this.giteaToken) return "";
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
if (this._giteaOwner && now - this._ownerFetchedAt < 5 * 60 * 1000) return this._giteaOwner;
|
|
37
|
+
try {
|
|
38
|
+
const r = await axios.get(`${this.giteaUrl}/api/v1/user`, {
|
|
39
|
+
headers: { Authorization: `token ${this.giteaToken}` },
|
|
40
|
+
timeout: 8000,
|
|
41
|
+
});
|
|
42
|
+
const d = r.data || {};
|
|
43
|
+
this._giteaOwner = d.login || d.username || "";
|
|
44
|
+
this._ownerFetchedAt = now;
|
|
45
|
+
return this._giteaOwner;
|
|
46
|
+
} catch {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getRemoteUrls(repoName) {
|
|
52
|
+
const cacheKey = `urls_${repoName}`;
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
|
|
55
|
+
// Verificar cache
|
|
56
|
+
if (this._remoteUrlsCache.has(cacheKey)) {
|
|
57
|
+
const cached = this._remoteUrlsCache.get(cacheKey);
|
|
58
|
+
if (now - cached.timestamp < this._cacheExpiry) {
|
|
59
|
+
return cached.urls;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const urls = {};
|
|
64
|
+
|
|
65
|
+
// GitHub URL
|
|
66
|
+
if (this.github) {
|
|
67
|
+
const owner = await this.getGitHubOwner();
|
|
68
|
+
if (owner) {
|
|
69
|
+
urls.github = `https://github.com/${owner}/${repoName}.git`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Gitea URL
|
|
74
|
+
if (this.giteaUrl && this.giteaToken) {
|
|
75
|
+
const owner = await this.getGiteaOwner();
|
|
76
|
+
if (owner) {
|
|
77
|
+
const base = this.giteaUrl.replace(/\/$/, "");
|
|
78
|
+
urls.gitea = `${base}/${owner}/${repoName}.git`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Cachear resultado
|
|
83
|
+
this._remoteUrlsCache.set(cacheKey, { urls, timestamp: now });
|
|
84
|
+
|
|
85
|
+
return urls;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async ensureRepos({ repoName, createIfMissing = true, description = "Managed by git-mcpv2", isPublic = false }) {
|
|
89
|
+
// Por padrão, repositórios são PRIVADOS. Use isPublic=true para público.
|
|
90
|
+
const isPrivate = !isPublic;
|
|
91
|
+
const results = { github: null, gitea: null };
|
|
92
|
+
// GitHub
|
|
93
|
+
if (this.github) {
|
|
94
|
+
const owner = await this.getGitHubOwner();
|
|
95
|
+
if (owner) {
|
|
96
|
+
try {
|
|
97
|
+
const full = `${owner}/${repoName}`;
|
|
98
|
+
try {
|
|
99
|
+
await this.github.rest.repos.get({ owner, repo: repoName });
|
|
100
|
+
results.github = { ok: true, repo: full, created: false };
|
|
101
|
+
} catch {
|
|
102
|
+
if (createIfMissing) {
|
|
103
|
+
const cr = await this.github.rest.repos.createForAuthenticatedUser({
|
|
104
|
+
name: repoName,
|
|
105
|
+
description,
|
|
106
|
+
private: isPrivate,
|
|
107
|
+
auto_init: false,
|
|
108
|
+
});
|
|
109
|
+
results.github = { ok: true, repo: cr.data.full_name, created: true };
|
|
110
|
+
} else {
|
|
111
|
+
results.github = { ok: false, error: "missing" };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
results.github = { ok: false, error: String(e?.message || e) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Gitea
|
|
120
|
+
if (this.giteaUrl && this.giteaToken) {
|
|
121
|
+
const owner = await this.getGiteaOwner();
|
|
122
|
+
if (owner) {
|
|
123
|
+
try {
|
|
124
|
+
const base = this.giteaUrl.replace(/\/$/, "");
|
|
125
|
+
const getRepo = await axios.get(`${base}/api/v1/repos/${owner}/${repoName}`,
|
|
126
|
+
{ headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
127
|
+
if (getRepo.status === 200) {
|
|
128
|
+
results.gitea = { ok: true, repo: `${owner}/${repoName}`, created: false };
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
if (createIfMissing) {
|
|
132
|
+
try {
|
|
133
|
+
const cr = await axios.post(`${this.giteaUrl}/api/v1/user/repos`,
|
|
134
|
+
{ name: repoName, description, private: isPrivate, auto_init: false },
|
|
135
|
+
{ headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
|
|
136
|
+
results.gitea = { ok: true, repo: `${cr.data?.owner?.login || owner}/${repoName}`, created: true };
|
|
137
|
+
} catch (err) {
|
|
138
|
+
results.gitea = { ok: false, error: String(err?.message || err) };
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
results.gitea = { ok: false, error: "missing" };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async listAllRepos(options = {}) {
|
|
150
|
+
const { maxPages = 10, perPage = 100 } = options;
|
|
151
|
+
const results = { github: [], gitea: [] };
|
|
152
|
+
|
|
153
|
+
// GitHub - com paginação
|
|
154
|
+
if (this.github) {
|
|
155
|
+
try {
|
|
156
|
+
let page = 1;
|
|
157
|
+
let hasMore = true;
|
|
158
|
+
while (hasMore && page <= maxPages) {
|
|
159
|
+
const { data } = await this.github.rest.repos.listForAuthenticatedUser({
|
|
160
|
+
per_page: perPage,
|
|
161
|
+
page,
|
|
162
|
+
visibility: "all"
|
|
163
|
+
});
|
|
164
|
+
results.github.push(...data.map(r => ({
|
|
165
|
+
name: r.name,
|
|
166
|
+
fullName: r.full_name,
|
|
167
|
+
url: r.html_url,
|
|
168
|
+
isPrivate: r.private
|
|
169
|
+
})));
|
|
170
|
+
hasMore = data.length === perPage;
|
|
171
|
+
page++;
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.error("GitHub List Error:", e.message);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Gitea - com paginação
|
|
179
|
+
if (this.giteaUrl && this.giteaToken) {
|
|
180
|
+
try {
|
|
181
|
+
let page = 1;
|
|
182
|
+
let hasMore = true;
|
|
183
|
+
const base = this.giteaUrl.replace(/\/$/, "");
|
|
184
|
+
while (hasMore && page <= maxPages) {
|
|
185
|
+
const { data } = await axios.get(`${base}/api/v1/user/repos?limit=${perPage}&page=${page}`, {
|
|
186
|
+
headers: { Authorization: `token ${this.giteaToken}` }
|
|
187
|
+
});
|
|
188
|
+
results.gitea.push(...data.map(r => ({
|
|
189
|
+
name: r.name,
|
|
190
|
+
fullName: r.full_name,
|
|
191
|
+
url: r.html_url,
|
|
192
|
+
isPrivate: r.private
|
|
193
|
+
})));
|
|
194
|
+
hasMore = data.length === perPage;
|
|
195
|
+
page++;
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error("Gitea List Error:", e.message);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
}
|