gitxelectron 1.3.5 → 1.3.7
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 +97 -96
- package/bin/postinstall.js +6 -6
- package/bin/preuninstall.js +12 -12
- package/out/main/index.js +429 -49
- package/out/renderer/assets/{index-O0ybH0MV.js → index-DyLS9VOZ.js} +1332 -646
- package/out/renderer/assets/index-Vre34D_W.css +2913 -0
- package/out/renderer/index.html +2 -2
- package/package.json +70 -71
- package/out/renderer/assets/index-CmxKIe3Q.css +0 -1365
package/out/main/index.js
CHANGED
|
@@ -330,14 +330,16 @@ async function getCollaboratedRepos(sessionId) {
|
|
|
330
330
|
ownedRepos.map(async (repo) => {
|
|
331
331
|
try {
|
|
332
332
|
const [collabRes, invRes] = await Promise.all([
|
|
333
|
-
fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}/collaborators`, {
|
|
334
|
-
|
|
333
|
+
fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}/collaborators`, {
|
|
334
|
+
headers
|
|
335
|
+
}),
|
|
336
|
+
fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}/invitations`, {
|
|
337
|
+
headers
|
|
338
|
+
})
|
|
335
339
|
]);
|
|
336
340
|
const collabs = collabRes.ok ? await collabRes.json() : [];
|
|
337
341
|
const invs = invRes.ok ? await invRes.json() : [];
|
|
338
|
-
const hasOtherCollabs = Array.isArray(collabs) && collabs.some(
|
|
339
|
-
(c) => c.login && c.login.toLowerCase() !== currentUsername
|
|
340
|
-
);
|
|
342
|
+
const hasOtherCollabs = Array.isArray(collabs) && collabs.some((c) => c.login && c.login.toLowerCase() !== currentUsername);
|
|
341
343
|
const hasPendingInvs = Array.isArray(invs) && invs.length > 0;
|
|
342
344
|
if (hasOtherCollabs || hasPendingInvs) {
|
|
343
345
|
return {
|
|
@@ -382,12 +384,13 @@ async function createGitHubRepository(sessionId, name, description, isPrivate, a
|
|
|
382
384
|
return await res.json();
|
|
383
385
|
}
|
|
384
386
|
function parseGithubUrl(remoteUrl) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
const url = remoteUrl.trim();
|
|
388
|
+
if (!url.includes("github.com")) throw new Error("Not a GitHub repository");
|
|
389
|
+
let parts = url.split("github.com");
|
|
387
390
|
let pathPart = parts[1].replace(/^[:\/]/, "").replace(/\.git$/, "");
|
|
388
391
|
const pathSplits = pathPart.split("/");
|
|
389
392
|
if (pathSplits.length >= 2) {
|
|
390
|
-
return { owner: pathSplits[0], repo: pathSplits[1] };
|
|
393
|
+
return { owner: pathSplits[0].trim(), repo: pathSplits[1].trim() };
|
|
391
394
|
}
|
|
392
395
|
throw new Error("Invalid GitHub repository URL");
|
|
393
396
|
}
|
|
@@ -400,10 +403,18 @@ async function getCollaborators(sessionId, remoteUrl) {
|
|
|
400
403
|
Accept: "application/vnd.github.v3+json",
|
|
401
404
|
"User-Agent": "gitelectron-App"
|
|
402
405
|
};
|
|
406
|
+
const safeFetch = async (url) => {
|
|
407
|
+
try {
|
|
408
|
+
return await fetch(url, { headers });
|
|
409
|
+
} catch (e) {
|
|
410
|
+
console.error(`Fetch failed for ${url}:`, e.message);
|
|
411
|
+
return { ok: false, json: async () => ({ message: e.message || "Network error" }) };
|
|
412
|
+
}
|
|
413
|
+
};
|
|
403
414
|
const [collabRes, invRes, contribRes] = await Promise.all([
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
415
|
+
safeFetch(`https://api.github.com/repos/${owner}/${repo}/collaborators`),
|
|
416
|
+
safeFetch(`https://api.github.com/repos/${owner}/${repo}/invitations`),
|
|
417
|
+
safeFetch(`https://api.github.com/repos/${owner}/${repo}/contributors`)
|
|
407
418
|
]);
|
|
408
419
|
if (!collabRes.ok) {
|
|
409
420
|
const errorData = await collabRes.json().catch(() => ({}));
|
|
@@ -636,6 +647,58 @@ function getGitSession(sessionId) {
|
|
|
636
647
|
if (!session) throw new Error("Git not initialized for this window");
|
|
637
648
|
return session;
|
|
638
649
|
}
|
|
650
|
+
const DEFAULT_LFS_EXTENSIONS = [
|
|
651
|
+
"*.pt",
|
|
652
|
+
"*.ckpt",
|
|
653
|
+
"*.safetensors",
|
|
654
|
+
"*.bin",
|
|
655
|
+
"*.onnx",
|
|
656
|
+
"*.pb",
|
|
657
|
+
"*.h5",
|
|
658
|
+
"*.pkl",
|
|
659
|
+
"*.model",
|
|
660
|
+
"*.mp4",
|
|
661
|
+
"*.mov",
|
|
662
|
+
"*.avi",
|
|
663
|
+
"*.wav",
|
|
664
|
+
"*.mp3",
|
|
665
|
+
"*.uasset",
|
|
666
|
+
"*.umap",
|
|
667
|
+
"*.pak",
|
|
668
|
+
"*.dll",
|
|
669
|
+
"*.so",
|
|
670
|
+
"*.dylib",
|
|
671
|
+
"*.exe",
|
|
672
|
+
"*.zip",
|
|
673
|
+
"*.tar",
|
|
674
|
+
"*.gz",
|
|
675
|
+
"*.rar",
|
|
676
|
+
"*.7z",
|
|
677
|
+
"*.blend",
|
|
678
|
+
"*.fbx",
|
|
679
|
+
"*.obj"
|
|
680
|
+
];
|
|
681
|
+
async function setupDefaultGitAttributes(repoPath) {
|
|
682
|
+
try {
|
|
683
|
+
const attrsPath = path__namespace.join(repoPath, ".gitattributes");
|
|
684
|
+
let existing = "";
|
|
685
|
+
try {
|
|
686
|
+
existing = await fs__namespace.readFile(attrsPath, "utf8");
|
|
687
|
+
} catch (e) {
|
|
688
|
+
}
|
|
689
|
+
let newLines = [];
|
|
690
|
+
for (const ext of DEFAULT_LFS_EXTENSIONS) {
|
|
691
|
+
if (!existing.includes(ext)) {
|
|
692
|
+
newLines.push(`${ext} filter=lfs diff=lfs merge=lfs -text`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (newLines.length > 0) {
|
|
696
|
+
const appendContent = (existing && !existing.endsWith("\n") ? "\n" : "") + newLines.join("\n") + "\n";
|
|
697
|
+
await fs__namespace.appendFile(attrsPath, appendContent);
|
|
698
|
+
}
|
|
699
|
+
} catch (e) {
|
|
700
|
+
}
|
|
701
|
+
}
|
|
639
702
|
async function isGitRepo(dir) {
|
|
640
703
|
let hasGitFolder = false;
|
|
641
704
|
try {
|
|
@@ -678,7 +741,14 @@ async function isGitRepo(dir) {
|
|
|
678
741
|
}
|
|
679
742
|
async function initializeRepo(sessionId, dir) {
|
|
680
743
|
try {
|
|
681
|
-
const tempGit = simpleGit.simpleGit({
|
|
744
|
+
const tempGit = simpleGit.simpleGit({
|
|
745
|
+
baseDir: dir,
|
|
746
|
+
unsafe: {
|
|
747
|
+
allowUnsafeEditor: true,
|
|
748
|
+
allowUnsafeAskPass: true,
|
|
749
|
+
allowUnsafeCredentialHelper: true
|
|
750
|
+
}
|
|
751
|
+
});
|
|
682
752
|
await tempGit.init();
|
|
683
753
|
const branches = await tempGit.branchLocal();
|
|
684
754
|
if (branches.all.length === 0) {
|
|
@@ -693,6 +763,7 @@ async function initializeRepo(sessionId, dir) {
|
|
|
693
763
|
"Initial repository setup"
|
|
694
764
|
]);
|
|
695
765
|
}
|
|
766
|
+
await setupDefaultGitAttributes(dir);
|
|
696
767
|
initGit(sessionId, dir);
|
|
697
768
|
return true;
|
|
698
769
|
} catch (e) {
|
|
@@ -772,7 +843,9 @@ async function stageFiles(sessionId, files) {
|
|
|
772
843
|
try {
|
|
773
844
|
await git.raw(["lfs", "install"]);
|
|
774
845
|
for (const file of largeFiles) {
|
|
775
|
-
|
|
846
|
+
const ext = file.includes(".") ? "." + file.split(".").pop() : "";
|
|
847
|
+
const trackPattern = ext ? `*${ext}` : file;
|
|
848
|
+
await git.raw(["lfs", "track", trackPattern]);
|
|
776
849
|
}
|
|
777
850
|
if (!fileArray.includes(".gitattributes")) {
|
|
778
851
|
fileArray.push(".gitattributes");
|
|
@@ -786,7 +859,15 @@ async function stageFiles(sessionId, files) {
|
|
|
786
859
|
for (let i = 0; i < fileArray.length; i += chunkSize) {
|
|
787
860
|
const chunk = fileArray.slice(i, i + chunkSize);
|
|
788
861
|
try {
|
|
789
|
-
await git.raw([
|
|
862
|
+
await git.raw([
|
|
863
|
+
"-c",
|
|
864
|
+
"core.safecrlf=false",
|
|
865
|
+
"-c",
|
|
866
|
+
"core.autocrlf=false",
|
|
867
|
+
"add",
|
|
868
|
+
"--force",
|
|
869
|
+
...chunk
|
|
870
|
+
]);
|
|
790
871
|
} catch (e) {
|
|
791
872
|
if (e.message && e.message.includes("LF will be replaced by CRLF") && !e.message.includes("fatal:")) {
|
|
792
873
|
console.warn("Ignored CRLF warning during stage");
|
|
@@ -840,7 +921,10 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
840
921
|
}
|
|
841
922
|
} catch (e) {
|
|
842
923
|
if (remoteUrl.includes("github.com")) {
|
|
843
|
-
return remoteUrl.replace(
|
|
924
|
+
return remoteUrl.replace(
|
|
925
|
+
/https:\/\/(?:[^@]+@)?github\.com\//,
|
|
926
|
+
`https://x-access-token:${token}@github.com/`
|
|
927
|
+
);
|
|
844
928
|
}
|
|
845
929
|
}
|
|
846
930
|
return remoteUrl;
|
|
@@ -898,7 +982,8 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
898
982
|
return `${sec}s left`;
|
|
899
983
|
};
|
|
900
984
|
const etaStr = formatEta(etaMs);
|
|
901
|
-
if (onProgress)
|
|
985
|
+
if (onProgress)
|
|
986
|
+
onProgress(`Processing chunk ${chunkCount} (${chunk.length} files)... ETA: ${etaStr}`);
|
|
902
987
|
if (onDetailedProgress) {
|
|
903
988
|
onDetailedProgress({
|
|
904
989
|
processed: processedFiles,
|
|
@@ -909,20 +994,24 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
909
994
|
}
|
|
910
995
|
const LARGE_FILE_LIMIT = 50 * 1024 * 1024;
|
|
911
996
|
const largeFiles = [];
|
|
912
|
-
await Promise.all(
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
997
|
+
await Promise.all(
|
|
998
|
+
chunk.map(async (file) => {
|
|
999
|
+
try {
|
|
1000
|
+
const stats = await fs__namespace.stat(path__namespace.join(repoPath, file));
|
|
1001
|
+
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
1002
|
+
largeFiles.push(file);
|
|
1003
|
+
}
|
|
1004
|
+
} catch (e) {
|
|
917
1005
|
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
}));
|
|
1006
|
+
})
|
|
1007
|
+
);
|
|
921
1008
|
if (largeFiles.length > 0) {
|
|
922
1009
|
try {
|
|
923
1010
|
await git.raw(["lfs", "install"]);
|
|
924
1011
|
for (const file of largeFiles) {
|
|
925
|
-
|
|
1012
|
+
const ext = file.includes(".") ? "." + file.split(".").pop() : "";
|
|
1013
|
+
const trackPattern = ext ? `*${ext}` : file;
|
|
1014
|
+
await git.raw(["lfs", "track", trackPattern]);
|
|
926
1015
|
}
|
|
927
1016
|
if (!chunk.includes(".gitattributes")) {
|
|
928
1017
|
chunk.push(".gitattributes");
|
|
@@ -934,7 +1023,14 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
934
1023
|
const tempFile = path__namespace.join(repoPath, ".git", "massive_chunk.txt");
|
|
935
1024
|
await fs__namespace.writeFile(tempFile, chunk.join("\0") + "\0", "utf-8");
|
|
936
1025
|
try {
|
|
937
|
-
await git.raw([
|
|
1026
|
+
await git.raw([
|
|
1027
|
+
"-c",
|
|
1028
|
+
"core.safecrlf=false",
|
|
1029
|
+
"add",
|
|
1030
|
+
"--force",
|
|
1031
|
+
"--pathspec-from-file=" + tempFile,
|
|
1032
|
+
"--pathspec-file-nul"
|
|
1033
|
+
]);
|
|
938
1034
|
} catch (e) {
|
|
939
1035
|
if (e.message && e.message.includes("fatal:")) {
|
|
940
1036
|
throw e;
|
|
@@ -973,7 +1069,23 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
973
1069
|
} catch (e) {
|
|
974
1070
|
}
|
|
975
1071
|
if (onProgress) onProgress("Pushing to remote...");
|
|
976
|
-
const pushArgs = [
|
|
1072
|
+
const pushArgs = [
|
|
1073
|
+
"-c",
|
|
1074
|
+
"credential.helper=",
|
|
1075
|
+
"-c",
|
|
1076
|
+
"lfs.allowincompletepush=true",
|
|
1077
|
+
"-c",
|
|
1078
|
+
"pack.windowMemory=256m",
|
|
1079
|
+
"-c",
|
|
1080
|
+
"pack.packSizeLimit=500m",
|
|
1081
|
+
"-c",
|
|
1082
|
+
"http.postBuffer=524288000",
|
|
1083
|
+
"-c",
|
|
1084
|
+
"lfs.concurrenttransfers=8",
|
|
1085
|
+
"push",
|
|
1086
|
+
"--progress",
|
|
1087
|
+
"--no-verify"
|
|
1088
|
+
];
|
|
977
1089
|
try {
|
|
978
1090
|
await git.raw([...pushArgs, authUrl, branch]);
|
|
979
1091
|
} catch (err) {
|
|
@@ -994,14 +1106,33 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
994
1106
|
async function pushToRemote(sessionId, remote, branch) {
|
|
995
1107
|
const remotes = await getRemotes(sessionId);
|
|
996
1108
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
997
|
-
const tempGit = simpleGit.simpleGit({
|
|
1109
|
+
const tempGit = simpleGit.simpleGit({
|
|
1110
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1111
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1112
|
+
}).env({
|
|
998
1113
|
...process.env,
|
|
999
1114
|
GCM_INTERACTIVE: "false",
|
|
1000
1115
|
GIT_TERMINAL_PROMPT: "0",
|
|
1001
1116
|
GIT_ASKPASS: "echo"
|
|
1002
1117
|
});
|
|
1003
1118
|
const doPush = async () => {
|
|
1004
|
-
const pushArgs = [
|
|
1119
|
+
const pushArgs = [
|
|
1120
|
+
"-c",
|
|
1121
|
+
"credential.helper=",
|
|
1122
|
+
"-c",
|
|
1123
|
+
"lfs.allowincompletepush=true",
|
|
1124
|
+
"-c",
|
|
1125
|
+
"pack.windowMemory=256m",
|
|
1126
|
+
"-c",
|
|
1127
|
+
"pack.packSizeLimit=500m",
|
|
1128
|
+
"-c",
|
|
1129
|
+
"http.postBuffer=524288000",
|
|
1130
|
+
"-c",
|
|
1131
|
+
"lfs.concurrenttransfers=8",
|
|
1132
|
+
"push",
|
|
1133
|
+
"--progress",
|
|
1134
|
+
"--no-verify"
|
|
1135
|
+
];
|
|
1005
1136
|
if (remoteObj && remoteObj.refs.push) {
|
|
1006
1137
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1007
1138
|
return await tempGit.raw([...pushArgs, authUrl, branch]);
|
|
@@ -1153,7 +1284,16 @@ async function checkRemoteUpdates(sessionId) {
|
|
|
1153
1284
|
await new Promise((resolve) => {
|
|
1154
1285
|
const child = child_process.spawn(
|
|
1155
1286
|
"git",
|
|
1156
|
-
[
|
|
1287
|
+
[
|
|
1288
|
+
"-c",
|
|
1289
|
+
"credential.helper=",
|
|
1290
|
+
"-c",
|
|
1291
|
+
"core.longpaths=true",
|
|
1292
|
+
"fetch",
|
|
1293
|
+
"--prune",
|
|
1294
|
+
authUrl,
|
|
1295
|
+
`+refs/heads/*:refs/remotes/${remoteObj.name}/*`
|
|
1296
|
+
],
|
|
1157
1297
|
{
|
|
1158
1298
|
cwd: repoPath,
|
|
1159
1299
|
env: {
|
|
@@ -1201,7 +1341,14 @@ async function getBranches(sessionId) {
|
|
|
1201
1341
|
const remotes = await git.getRemotes(true);
|
|
1202
1342
|
if (remotes.length > 0) {
|
|
1203
1343
|
const remoteObj = remotes[0];
|
|
1204
|
-
const tempGit = simpleGit.simpleGit({
|
|
1344
|
+
const tempGit = simpleGit.simpleGit({
|
|
1345
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1346
|
+
unsafe: {
|
|
1347
|
+
allowUnsafeEditor: true,
|
|
1348
|
+
allowUnsafeAskPass: true,
|
|
1349
|
+
allowUnsafeCredentialHelper: true
|
|
1350
|
+
}
|
|
1351
|
+
}).env({
|
|
1205
1352
|
...process.env,
|
|
1206
1353
|
GCM_INTERACTIVE: "false",
|
|
1207
1354
|
GIT_TERMINAL_PROMPT: "0",
|
|
@@ -1286,7 +1433,14 @@ async function createBranch(sessionId, branch, baseBranch) {
|
|
|
1286
1433
|
const remoteName = remotes[0].name;
|
|
1287
1434
|
await git.addConfig(`branch.${branch}.remote`, remoteName);
|
|
1288
1435
|
await git.addConfig(`branch.${branch}.merge`, `refs/heads/${branch}`);
|
|
1289
|
-
const tempGit = simpleGit.simpleGit({
|
|
1436
|
+
const tempGit = simpleGit.simpleGit({
|
|
1437
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1438
|
+
unsafe: {
|
|
1439
|
+
allowUnsafeEditor: true,
|
|
1440
|
+
allowUnsafeAskPass: true,
|
|
1441
|
+
allowUnsafeCredentialHelper: true
|
|
1442
|
+
}
|
|
1443
|
+
}).env({
|
|
1290
1444
|
...process.env,
|
|
1291
1445
|
GCM_INTERACTIVE: "false",
|
|
1292
1446
|
GIT_TERMINAL_PROMPT: "0",
|
|
@@ -1312,7 +1466,10 @@ async function addRemote(sessionId, name, url) {
|
|
|
1312
1466
|
async function pushAllBranches(sessionId, remote) {
|
|
1313
1467
|
const remotes = await getRemotes(sessionId);
|
|
1314
1468
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1315
|
-
const tempGit = simpleGit.simpleGit({
|
|
1469
|
+
const tempGit = simpleGit.simpleGit({
|
|
1470
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1471
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1472
|
+
}).env({
|
|
1316
1473
|
...process.env,
|
|
1317
1474
|
GCM_INTERACTIVE: "false",
|
|
1318
1475
|
GIT_TERMINAL_PROMPT: "0",
|
|
@@ -1335,7 +1492,14 @@ async function getCommitHistory(sessionId) {
|
|
|
1335
1492
|
if (remotes.length > 0) {
|
|
1336
1493
|
const remoteObj = remotes[0];
|
|
1337
1494
|
const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : remoteObj.name;
|
|
1338
|
-
await git.raw([
|
|
1495
|
+
await git.raw([
|
|
1496
|
+
"-c",
|
|
1497
|
+
"credential.helper=",
|
|
1498
|
+
"fetch",
|
|
1499
|
+
"--prune",
|
|
1500
|
+
authUrl,
|
|
1501
|
+
`+refs/heads/*:refs/remotes/${remoteObj.name}/*`
|
|
1502
|
+
]);
|
|
1339
1503
|
}
|
|
1340
1504
|
} catch (e) {
|
|
1341
1505
|
}
|
|
@@ -1433,8 +1597,12 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1433
1597
|
const tempId = crypto.randomUUID();
|
|
1434
1598
|
const workspacePath = path__namespace.join(electron.app.getPath("temp"), `gitelectron_universal_${tempId}`);
|
|
1435
1599
|
await fs__namespace.mkdir(workspacePath, { recursive: true });
|
|
1436
|
-
const git = simpleGit.simpleGit({
|
|
1600
|
+
const git = simpleGit.simpleGit({
|
|
1601
|
+
baseDir: workspacePath,
|
|
1602
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1603
|
+
});
|
|
1437
1604
|
await git.init();
|
|
1605
|
+
await setupDefaultGitAttributes(workspacePath);
|
|
1438
1606
|
let authUrl = remoteUrl;
|
|
1439
1607
|
if (remoteUrl.includes("github.com")) {
|
|
1440
1608
|
let cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
|
|
@@ -1493,7 +1661,10 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
|
|
|
1493
1661
|
};
|
|
1494
1662
|
}
|
|
1495
1663
|
async function getUniversalDiff(workspacePath, filePath) {
|
|
1496
|
-
const git = simpleGit.simpleGit({
|
|
1664
|
+
const git = simpleGit.simpleGit({
|
|
1665
|
+
baseDir: workspacePath,
|
|
1666
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1667
|
+
});
|
|
1497
1668
|
try {
|
|
1498
1669
|
const diff = await git.raw(["diff", "--", filePath]);
|
|
1499
1670
|
return diff;
|
|
@@ -1502,14 +1673,25 @@ async function getUniversalDiff(workspacePath, filePath) {
|
|
|
1502
1673
|
}
|
|
1503
1674
|
}
|
|
1504
1675
|
async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
1505
|
-
const git = simpleGit.simpleGit({
|
|
1676
|
+
const git = simpleGit.simpleGit({
|
|
1677
|
+
baseDir: workspacePath,
|
|
1678
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1679
|
+
}).env({
|
|
1506
1680
|
...process.env,
|
|
1507
1681
|
GCM_INTERACTIVE: "false",
|
|
1508
1682
|
GIT_TERMINAL_PROMPT: "0",
|
|
1509
1683
|
GIT_ASKPASS: "echo"
|
|
1510
1684
|
});
|
|
1511
1685
|
try {
|
|
1512
|
-
await git.raw([
|
|
1686
|
+
await git.raw([
|
|
1687
|
+
"-c",
|
|
1688
|
+
"core.safecrlf=false",
|
|
1689
|
+
"-c",
|
|
1690
|
+
"core.autocrlf=false",
|
|
1691
|
+
"add",
|
|
1692
|
+
"--sparse",
|
|
1693
|
+
"."
|
|
1694
|
+
]);
|
|
1513
1695
|
} catch (e) {
|
|
1514
1696
|
if (e.message && e.message.includes("LF will be replaced by CRLF") && !e.message.includes("fatal:")) {
|
|
1515
1697
|
console.warn("Ignored CRLF warning in universal workspace");
|
|
@@ -1586,7 +1768,16 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1586
1768
|
await new Promise((resolve) => {
|
|
1587
1769
|
const child = child_process.spawn(
|
|
1588
1770
|
"git",
|
|
1589
|
-
[
|
|
1771
|
+
[
|
|
1772
|
+
"-c",
|
|
1773
|
+
"core.longpaths=true",
|
|
1774
|
+
"-c",
|
|
1775
|
+
"filter.lfs.required=false",
|
|
1776
|
+
"-c",
|
|
1777
|
+
"filter.lfs.smudge=",
|
|
1778
|
+
"checkout",
|
|
1779
|
+
"-f"
|
|
1780
|
+
],
|
|
1590
1781
|
{
|
|
1591
1782
|
cwd: destPath,
|
|
1592
1783
|
env: { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" }
|
|
@@ -1806,18 +1997,180 @@ async function removeGitLock(sessionId) {
|
|
|
1806
1997
|
}
|
|
1807
1998
|
}
|
|
1808
1999
|
async function masterPush(sessionId, remote, branch) {
|
|
1809
|
-
const { git } = getGitSession(sessionId);
|
|
2000
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1810
2001
|
const remotes = await getRemotes(sessionId);
|
|
1811
2002
|
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1812
2003
|
if (!remoteObj) throw new Error("No remote repository configured.");
|
|
1813
2004
|
const targetRemote = remoteObj.name;
|
|
1814
2005
|
const authUrl = remoteObj.refs?.push ? injectAuthToken(sessionId, remoteObj.refs.push) : targetRemote;
|
|
1815
|
-
|
|
2006
|
+
let originalBranch = branch;
|
|
1816
2007
|
try {
|
|
1817
|
-
await git.
|
|
2008
|
+
const branchSummary = await git.branchLocal();
|
|
2009
|
+
originalBranch = branchSummary.current || branch;
|
|
1818
2010
|
} catch (e) {
|
|
1819
2011
|
}
|
|
1820
|
-
|
|
2012
|
+
const tempBranch = `__master_push_temp_${Date.now()}`;
|
|
2013
|
+
const hiddenPointers = [];
|
|
2014
|
+
const cleanedAttrsFiles = [];
|
|
2015
|
+
const lfsOffFlags = [
|
|
2016
|
+
"-c",
|
|
2017
|
+
"filter.lfs.clean=cat",
|
|
2018
|
+
"-c",
|
|
2019
|
+
"filter.lfs.smudge=cat",
|
|
2020
|
+
"-c",
|
|
2021
|
+
"filter.lfs.process=",
|
|
2022
|
+
"-c",
|
|
2023
|
+
"filter.lfs.required=false",
|
|
2024
|
+
"-c",
|
|
2025
|
+
"lfs.allowincompletepush=true",
|
|
2026
|
+
"-c",
|
|
2027
|
+
"http.postBuffer=524288000",
|
|
2028
|
+
"-c",
|
|
2029
|
+
"credential.helper="
|
|
2030
|
+
];
|
|
2031
|
+
function runGit(args, allowFail = false) {
|
|
2032
|
+
return new Promise((resolve, reject) => {
|
|
2033
|
+
const child = child_process.spawn("git", [...lfsOffFlags, ...args], {
|
|
2034
|
+
cwd: repoPath,
|
|
2035
|
+
env: {
|
|
2036
|
+
...process.env,
|
|
2037
|
+
GIT_LFS_SKIP_SMUDGE: "1",
|
|
2038
|
+
GIT_LFS_SKIP_PUSH: "1",
|
|
2039
|
+
GCM_INTERACTIVE: "false",
|
|
2040
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
2041
|
+
GIT_ASKPASS: "echo"
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
const stdout = [];
|
|
2045
|
+
const stderr = [];
|
|
2046
|
+
child.stdout?.on("data", (d) => stdout.push(d.toString()));
|
|
2047
|
+
child.stderr?.on("data", (d) => stderr.push(d.toString()));
|
|
2048
|
+
child.on("error", (err) => allowFail ? resolve("") : reject(err));
|
|
2049
|
+
child.on("close", (code) => {
|
|
2050
|
+
if (code === 0 || allowFail) {
|
|
2051
|
+
resolve(stdout.join(""));
|
|
2052
|
+
} else {
|
|
2053
|
+
reject(new Error(stderr.join("") || `git exited with code ${code}`));
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
async function processRepo(dir) {
|
|
2059
|
+
let entries;
|
|
2060
|
+
try {
|
|
2061
|
+
entries = await fs__namespace.readdir(dir, { withFileTypes: true });
|
|
2062
|
+
} catch (e) {
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
for (const entry of entries) {
|
|
2066
|
+
if (entry.name === ".git" || entry.name === "node_modules" || entry.name.endsWith(".__lfs_ptr_bak__")) continue;
|
|
2067
|
+
const fullPath = path__namespace.join(dir, entry.name);
|
|
2068
|
+
if (entry.isDirectory()) {
|
|
2069
|
+
await processRepo(fullPath);
|
|
2070
|
+
continue;
|
|
2071
|
+
}
|
|
2072
|
+
if (!entry.isFile()) continue;
|
|
2073
|
+
try {
|
|
2074
|
+
if (entry.name === ".gitattributes") {
|
|
2075
|
+
const content = await fs__namespace.readFile(fullPath, "utf8");
|
|
2076
|
+
if (content.includes("filter=lfs") || content.includes("lfs")) {
|
|
2077
|
+
cleanedAttrsFiles.push({ filePath: fullPath, original: content });
|
|
2078
|
+
const cleaned = content.split("\n").filter((line) => !line.toLowerCase().includes("lfs")).join("\n");
|
|
2079
|
+
await fs__namespace.writeFile(fullPath, cleaned);
|
|
2080
|
+
}
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
const stat = await fs__namespace.stat(fullPath);
|
|
2084
|
+
if (stat.size < 4096 && stat.size > 20) {
|
|
2085
|
+
const buf = await fs__namespace.readFile(fullPath);
|
|
2086
|
+
const text = buf.toString("utf8");
|
|
2087
|
+
if (text.includes("version https://git-lfs.github.com/spec/v1") && text.includes("oid sha256:")) {
|
|
2088
|
+
const backupPath = fullPath + ".__lfs_ptr_bak__";
|
|
2089
|
+
await fs__namespace.rename(fullPath, backupPath);
|
|
2090
|
+
hiddenPointers.push({ original: fullPath, backup: backupPath });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
} catch (e) {
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
try {
|
|
2098
|
+
try {
|
|
2099
|
+
await git.raw(["lfs", "uninstall", "--local"]);
|
|
2100
|
+
} catch (e) {
|
|
2101
|
+
}
|
|
2102
|
+
try {
|
|
2103
|
+
await git.addConfig("filter.lfs.clean", "cat");
|
|
2104
|
+
await git.addConfig("filter.lfs.smudge", "cat");
|
|
2105
|
+
await git.addConfig("filter.lfs.process", "");
|
|
2106
|
+
await git.addConfig("filter.lfs.required", "false");
|
|
2107
|
+
} catch (e) {
|
|
2108
|
+
}
|
|
2109
|
+
await processRepo(repoPath);
|
|
2110
|
+
await runGit(["checkout", "--orphan", tempBranch]);
|
|
2111
|
+
await runGit(["add", "."]);
|
|
2112
|
+
try {
|
|
2113
|
+
const grepResult = await runGit(
|
|
2114
|
+
["grep", "--cached", "-l", "version https://git-lfs.github.com/spec/v1"],
|
|
2115
|
+
true
|
|
2116
|
+
);
|
|
2117
|
+
if (grepResult.trim()) {
|
|
2118
|
+
const lfsFiles = grepResult.trim().split("\n").filter(Boolean);
|
|
2119
|
+
for (const f of lfsFiles) {
|
|
2120
|
+
await runGit(["rm", "--cached", "--force", f.trim()], true);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
} catch (e) {
|
|
2124
|
+
}
|
|
2125
|
+
try {
|
|
2126
|
+
await runGit(["commit", "-m", "Master Override Push", "--no-verify"]);
|
|
2127
|
+
} catch (e) {
|
|
2128
|
+
}
|
|
2129
|
+
await runGit(["push", "--force", "--no-verify", authUrl, `${tempBranch}:${branch}`]);
|
|
2130
|
+
} finally {
|
|
2131
|
+
try {
|
|
2132
|
+
await runGit(["checkout", "-f", originalBranch], true);
|
|
2133
|
+
} catch (e) {
|
|
2134
|
+
try {
|
|
2135
|
+
await runGit(["checkout", "-f", branch], true);
|
|
2136
|
+
} catch (e2) {
|
|
2137
|
+
try {
|
|
2138
|
+
await runGit(["checkout", "-b", branch], true);
|
|
2139
|
+
} catch (e3) {
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
try {
|
|
2144
|
+
const branchList = await runGit(["branch", "--list", "__master_push_temp_*"], true);
|
|
2145
|
+
if (branchList.trim()) {
|
|
2146
|
+
const branches = branchList.trim().split("\n").map((b) => b.trim().replace(/^\*\s*/, "")).filter(Boolean);
|
|
2147
|
+
for (const b of branches) {
|
|
2148
|
+
await runGit(["branch", "-D", b], true);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
} catch (e) {
|
|
2152
|
+
}
|
|
2153
|
+
try {
|
|
2154
|
+
await runGit(["branch", "-D", tempBranch], true);
|
|
2155
|
+
} catch (e) {
|
|
2156
|
+
}
|
|
2157
|
+
for (const { original, backup } of hiddenPointers) {
|
|
2158
|
+
try {
|
|
2159
|
+
await fs__namespace.rename(backup, original);
|
|
2160
|
+
} catch (e) {
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
for (const { filePath, original } of cleanedAttrsFiles) {
|
|
2164
|
+
try {
|
|
2165
|
+
await fs__namespace.writeFile(filePath, original);
|
|
2166
|
+
} catch (e) {
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
try {
|
|
2170
|
+
await git.raw(["lfs", "install", "--local"]);
|
|
2171
|
+
} catch (e) {
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
1821
2174
|
}
|
|
1822
2175
|
const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
|
|
1823
2176
|
The user has encountered a Git error during a push, pull, or merge operation and you are executing as a background process to silently fix it.
|
|
@@ -1931,7 +2284,12 @@ ${errorMsg}
|
|
|
1931
2284
|
if (lowerError.includes("merge conflict") || lowerError.includes("conflict") || lowerError.includes("automatic merge failed")) {
|
|
1932
2285
|
return {
|
|
1933
2286
|
explanation: "Merge conflict detected. Forcing resolution by keeping local changes...",
|
|
1934
|
-
commandsToRun: [
|
|
2287
|
+
commandsToRun: [
|
|
2288
|
+
"git merge --abort",
|
|
2289
|
+
"git pull -X ours --no-edit",
|
|
2290
|
+
"git add .",
|
|
2291
|
+
'git commit -m "Auto-resolve merge conflict"'
|
|
2292
|
+
]
|
|
1935
2293
|
};
|
|
1936
2294
|
}
|
|
1937
2295
|
if (lowerError.includes("unmerged files") || lowerError.includes("you have unmerged paths")) {
|
|
@@ -1943,13 +2301,22 @@ ${errorMsg}
|
|
|
1943
2301
|
if (lowerError.includes("please tell me who you are")) {
|
|
1944
2302
|
return {
|
|
1945
2303
|
explanation: "Git identity not set. Setting temporary identity...",
|
|
1946
|
-
commandsToRun: [
|
|
2304
|
+
commandsToRun: [
|
|
2305
|
+
'git config user.email "gitelectron@example.com"',
|
|
2306
|
+
'git config user.name "Git Electron"'
|
|
2307
|
+
]
|
|
1947
2308
|
};
|
|
1948
2309
|
}
|
|
1949
2310
|
if (lowerError.includes("would be overwritten by merge") || lowerError.includes("local changes to the following files would be overwritten")) {
|
|
1950
2311
|
return {
|
|
1951
2312
|
explanation: "Local changes would be overwritten. Stashing, pulling, and popping...",
|
|
1952
|
-
commandsToRun: [
|
|
2313
|
+
commandsToRun: [
|
|
2314
|
+
"git stash",
|
|
2315
|
+
"git pull --no-edit",
|
|
2316
|
+
"git stash pop",
|
|
2317
|
+
"git add .",
|
|
2318
|
+
'git commit -m "Auto-resolved stashed changes"'
|
|
2319
|
+
]
|
|
1953
2320
|
};
|
|
1954
2321
|
}
|
|
1955
2322
|
throw new Error(
|
|
@@ -2028,7 +2395,10 @@ ${content}
|
|
|
2028
2395
|
if (provider === "gemini") {
|
|
2029
2396
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2030
2397
|
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
2031
|
-
const result = await model.generateContent([
|
|
2398
|
+
const result = await model.generateContent([
|
|
2399
|
+
{ text: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2400
|
+
{ text: prompt }
|
|
2401
|
+
]);
|
|
2032
2402
|
responseText = result.response.text();
|
|
2033
2403
|
}
|
|
2034
2404
|
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
@@ -2158,7 +2528,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2158
2528
|
try {
|
|
2159
2529
|
const updates = await checkRemoteUpdates(sessionId);
|
|
2160
2530
|
if (updates.behind > 0 && !isResolving) {
|
|
2161
|
-
onLog(
|
|
2531
|
+
onLog(
|
|
2532
|
+
`📥 Found ${updates.behind} new commit(s) from team members on GitHub! Syncing to your computer...`
|
|
2533
|
+
);
|
|
2162
2534
|
const result = await pullFromRemote(sessionId);
|
|
2163
2535
|
onLog(`✅ ${result.message}`);
|
|
2164
2536
|
if (globalOnResolveComplete) {
|
|
@@ -2361,7 +2733,15 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
|
2361
2733
|
for (const [file, content] of Object.entries(resolvedCode)) {
|
|
2362
2734
|
await fs__namespace.writeFile(path__namespace.join(repoPath, file), content, "utf-8");
|
|
2363
2735
|
try {
|
|
2364
|
-
await git.raw([
|
|
2736
|
+
await git.raw([
|
|
2737
|
+
"-c",
|
|
2738
|
+
"core.safecrlf=false",
|
|
2739
|
+
"-c",
|
|
2740
|
+
"core.autocrlf=false",
|
|
2741
|
+
"add",
|
|
2742
|
+
"--force",
|
|
2743
|
+
file
|
|
2744
|
+
]);
|
|
2365
2745
|
} catch (e) {
|
|
2366
2746
|
if (e.message && e.message.includes("LF will be replaced by CRLF") && !e.message.includes("fatal:")) {
|
|
2367
2747
|
console.warn("Ignored CRLF warning during AI fix apply");
|