gitxelectron 1.3.4 → 1.3.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 +97 -96
- package/bin/postinstall.js +6 -6
- package/bin/preuninstall.js +12 -12
- package/out/main/index.js +444 -41
- package/out/renderer/assets/{index-DBSFqXQe.js → index-BQtoJFsL.js} +1441 -701
- package/out/renderer/assets/index-CmvL1s5W.css +2913 -0
- package/out/renderer/index.html +2 -2
- package/package.json +2 -3
- package/out/renderer/assets/index-DnYiBoot.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 {
|
|
@@ -636,6 +638,58 @@ function getGitSession(sessionId) {
|
|
|
636
638
|
if (!session) throw new Error("Git not initialized for this window");
|
|
637
639
|
return session;
|
|
638
640
|
}
|
|
641
|
+
const DEFAULT_LFS_EXTENSIONS = [
|
|
642
|
+
"*.pt",
|
|
643
|
+
"*.ckpt",
|
|
644
|
+
"*.safetensors",
|
|
645
|
+
"*.bin",
|
|
646
|
+
"*.onnx",
|
|
647
|
+
"*.pb",
|
|
648
|
+
"*.h5",
|
|
649
|
+
"*.pkl",
|
|
650
|
+
"*.model",
|
|
651
|
+
"*.mp4",
|
|
652
|
+
"*.mov",
|
|
653
|
+
"*.avi",
|
|
654
|
+
"*.wav",
|
|
655
|
+
"*.mp3",
|
|
656
|
+
"*.uasset",
|
|
657
|
+
"*.umap",
|
|
658
|
+
"*.pak",
|
|
659
|
+
"*.dll",
|
|
660
|
+
"*.so",
|
|
661
|
+
"*.dylib",
|
|
662
|
+
"*.exe",
|
|
663
|
+
"*.zip",
|
|
664
|
+
"*.tar",
|
|
665
|
+
"*.gz",
|
|
666
|
+
"*.rar",
|
|
667
|
+
"*.7z",
|
|
668
|
+
"*.blend",
|
|
669
|
+
"*.fbx",
|
|
670
|
+
"*.obj"
|
|
671
|
+
];
|
|
672
|
+
async function setupDefaultGitAttributes(repoPath) {
|
|
673
|
+
try {
|
|
674
|
+
const attrsPath = path__namespace.join(repoPath, ".gitattributes");
|
|
675
|
+
let existing = "";
|
|
676
|
+
try {
|
|
677
|
+
existing = await fs__namespace.readFile(attrsPath, "utf8");
|
|
678
|
+
} catch (e) {
|
|
679
|
+
}
|
|
680
|
+
let newLines = [];
|
|
681
|
+
for (const ext of DEFAULT_LFS_EXTENSIONS) {
|
|
682
|
+
if (!existing.includes(ext)) {
|
|
683
|
+
newLines.push(`${ext} filter=lfs diff=lfs merge=lfs -text`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (newLines.length > 0) {
|
|
687
|
+
const appendContent = (existing && !existing.endsWith("\n") ? "\n" : "") + newLines.join("\n") + "\n";
|
|
688
|
+
await fs__namespace.appendFile(attrsPath, appendContent);
|
|
689
|
+
}
|
|
690
|
+
} catch (e) {
|
|
691
|
+
}
|
|
692
|
+
}
|
|
639
693
|
async function isGitRepo(dir) {
|
|
640
694
|
let hasGitFolder = false;
|
|
641
695
|
try {
|
|
@@ -678,7 +732,14 @@ async function isGitRepo(dir) {
|
|
|
678
732
|
}
|
|
679
733
|
async function initializeRepo(sessionId, dir) {
|
|
680
734
|
try {
|
|
681
|
-
const tempGit = simpleGit.simpleGit({
|
|
735
|
+
const tempGit = simpleGit.simpleGit({
|
|
736
|
+
baseDir: dir,
|
|
737
|
+
unsafe: {
|
|
738
|
+
allowUnsafeEditor: true,
|
|
739
|
+
allowUnsafeAskPass: true,
|
|
740
|
+
allowUnsafeCredentialHelper: true
|
|
741
|
+
}
|
|
742
|
+
});
|
|
682
743
|
await tempGit.init();
|
|
683
744
|
const branches = await tempGit.branchLocal();
|
|
684
745
|
if (branches.all.length === 0) {
|
|
@@ -693,6 +754,7 @@ async function initializeRepo(sessionId, dir) {
|
|
|
693
754
|
"Initial repository setup"
|
|
694
755
|
]);
|
|
695
756
|
}
|
|
757
|
+
await setupDefaultGitAttributes(dir);
|
|
696
758
|
initGit(sessionId, dir);
|
|
697
759
|
return true;
|
|
698
760
|
} catch (e) {
|
|
@@ -772,7 +834,9 @@ async function stageFiles(sessionId, files) {
|
|
|
772
834
|
try {
|
|
773
835
|
await git.raw(["lfs", "install"]);
|
|
774
836
|
for (const file of largeFiles) {
|
|
775
|
-
|
|
837
|
+
const ext = file.includes(".") ? "." + file.split(".").pop() : "";
|
|
838
|
+
const trackPattern = ext ? `*${ext}` : file;
|
|
839
|
+
await git.raw(["lfs", "track", trackPattern]);
|
|
776
840
|
}
|
|
777
841
|
if (!fileArray.includes(".gitattributes")) {
|
|
778
842
|
fileArray.push(".gitattributes");
|
|
@@ -786,7 +850,15 @@ async function stageFiles(sessionId, files) {
|
|
|
786
850
|
for (let i = 0; i < fileArray.length; i += chunkSize) {
|
|
787
851
|
const chunk = fileArray.slice(i, i + chunkSize);
|
|
788
852
|
try {
|
|
789
|
-
await git.raw([
|
|
853
|
+
await git.raw([
|
|
854
|
+
"-c",
|
|
855
|
+
"core.safecrlf=false",
|
|
856
|
+
"-c",
|
|
857
|
+
"core.autocrlf=false",
|
|
858
|
+
"add",
|
|
859
|
+
"--force",
|
|
860
|
+
...chunk
|
|
861
|
+
]);
|
|
790
862
|
} catch (e) {
|
|
791
863
|
if (e.message && e.message.includes("LF will be replaced by CRLF") && !e.message.includes("fatal:")) {
|
|
792
864
|
console.warn("Ignored CRLF warning during stage");
|
|
@@ -840,7 +912,10 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
840
912
|
}
|
|
841
913
|
} catch (e) {
|
|
842
914
|
if (remoteUrl.includes("github.com")) {
|
|
843
|
-
return remoteUrl.replace(
|
|
915
|
+
return remoteUrl.replace(
|
|
916
|
+
/https:\/\/(?:[^@]+@)?github\.com\//,
|
|
917
|
+
`https://x-access-token:${token}@github.com/`
|
|
918
|
+
);
|
|
844
919
|
}
|
|
845
920
|
}
|
|
846
921
|
return remoteUrl;
|
|
@@ -898,7 +973,8 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
898
973
|
return `${sec}s left`;
|
|
899
974
|
};
|
|
900
975
|
const etaStr = formatEta(etaMs);
|
|
901
|
-
if (onProgress)
|
|
976
|
+
if (onProgress)
|
|
977
|
+
onProgress(`Processing chunk ${chunkCount} (${chunk.length} files)... ETA: ${etaStr}`);
|
|
902
978
|
if (onDetailedProgress) {
|
|
903
979
|
onDetailedProgress({
|
|
904
980
|
processed: processedFiles,
|
|
@@ -909,20 +985,24 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
909
985
|
}
|
|
910
986
|
const LARGE_FILE_LIMIT = 50 * 1024 * 1024;
|
|
911
987
|
const largeFiles = [];
|
|
912
|
-
await Promise.all(
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
988
|
+
await Promise.all(
|
|
989
|
+
chunk.map(async (file) => {
|
|
990
|
+
try {
|
|
991
|
+
const stats = await fs__namespace.stat(path__namespace.join(repoPath, file));
|
|
992
|
+
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
993
|
+
largeFiles.push(file);
|
|
994
|
+
}
|
|
995
|
+
} catch (e) {
|
|
917
996
|
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
}));
|
|
997
|
+
})
|
|
998
|
+
);
|
|
921
999
|
if (largeFiles.length > 0) {
|
|
922
1000
|
try {
|
|
923
1001
|
await git.raw(["lfs", "install"]);
|
|
924
1002
|
for (const file of largeFiles) {
|
|
925
|
-
|
|
1003
|
+
const ext = file.includes(".") ? "." + file.split(".").pop() : "";
|
|
1004
|
+
const trackPattern = ext ? `*${ext}` : file;
|
|
1005
|
+
await git.raw(["lfs", "track", trackPattern]);
|
|
926
1006
|
}
|
|
927
1007
|
if (!chunk.includes(".gitattributes")) {
|
|
928
1008
|
chunk.push(".gitattributes");
|
|
@@ -934,7 +1014,14 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
934
1014
|
const tempFile = path__namespace.join(repoPath, ".git", "massive_chunk.txt");
|
|
935
1015
|
await fs__namespace.writeFile(tempFile, chunk.join("\0") + "\0", "utf-8");
|
|
936
1016
|
try {
|
|
937
|
-
await git.raw([
|
|
1017
|
+
await git.raw([
|
|
1018
|
+
"-c",
|
|
1019
|
+
"core.safecrlf=false",
|
|
1020
|
+
"add",
|
|
1021
|
+
"--force",
|
|
1022
|
+
"--pathspec-from-file=" + tempFile,
|
|
1023
|
+
"--pathspec-file-nul"
|
|
1024
|
+
]);
|
|
938
1025
|
} catch (e) {
|
|
939
1026
|
if (e.message && e.message.includes("fatal:")) {
|
|
940
1027
|
throw e;
|
|
@@ -973,7 +1060,23 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
973
1060
|
} catch (e) {
|
|
974
1061
|
}
|
|
975
1062
|
if (onProgress) onProgress("Pushing to remote...");
|
|
976
|
-
const pushArgs = [
|
|
1063
|
+
const pushArgs = [
|
|
1064
|
+
"-c",
|
|
1065
|
+
"credential.helper=",
|
|
1066
|
+
"-c",
|
|
1067
|
+
"lfs.allowincompletepush=true",
|
|
1068
|
+
"-c",
|
|
1069
|
+
"pack.windowMemory=256m",
|
|
1070
|
+
"-c",
|
|
1071
|
+
"pack.packSizeLimit=500m",
|
|
1072
|
+
"-c",
|
|
1073
|
+
"http.postBuffer=524288000",
|
|
1074
|
+
"-c",
|
|
1075
|
+
"lfs.concurrenttransfers=8",
|
|
1076
|
+
"push",
|
|
1077
|
+
"--progress",
|
|
1078
|
+
"--no-verify"
|
|
1079
|
+
];
|
|
977
1080
|
try {
|
|
978
1081
|
await git.raw([...pushArgs, authUrl, branch]);
|
|
979
1082
|
} catch (err) {
|
|
@@ -994,14 +1097,33 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
994
1097
|
async function pushToRemote(sessionId, remote, branch) {
|
|
995
1098
|
const remotes = await getRemotes(sessionId);
|
|
996
1099
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
997
|
-
const tempGit = simpleGit.simpleGit({
|
|
1100
|
+
const tempGit = simpleGit.simpleGit({
|
|
1101
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1102
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1103
|
+
}).env({
|
|
998
1104
|
...process.env,
|
|
999
1105
|
GCM_INTERACTIVE: "false",
|
|
1000
1106
|
GIT_TERMINAL_PROMPT: "0",
|
|
1001
1107
|
GIT_ASKPASS: "echo"
|
|
1002
1108
|
});
|
|
1003
1109
|
const doPush = async () => {
|
|
1004
|
-
const pushArgs = [
|
|
1110
|
+
const pushArgs = [
|
|
1111
|
+
"-c",
|
|
1112
|
+
"credential.helper=",
|
|
1113
|
+
"-c",
|
|
1114
|
+
"lfs.allowincompletepush=true",
|
|
1115
|
+
"-c",
|
|
1116
|
+
"pack.windowMemory=256m",
|
|
1117
|
+
"-c",
|
|
1118
|
+
"pack.packSizeLimit=500m",
|
|
1119
|
+
"-c",
|
|
1120
|
+
"http.postBuffer=524288000",
|
|
1121
|
+
"-c",
|
|
1122
|
+
"lfs.concurrenttransfers=8",
|
|
1123
|
+
"push",
|
|
1124
|
+
"--progress",
|
|
1125
|
+
"--no-verify"
|
|
1126
|
+
];
|
|
1005
1127
|
if (remoteObj && remoteObj.refs.push) {
|
|
1006
1128
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1007
1129
|
return await tempGit.raw([...pushArgs, authUrl, branch]);
|
|
@@ -1153,7 +1275,16 @@ async function checkRemoteUpdates(sessionId) {
|
|
|
1153
1275
|
await new Promise((resolve) => {
|
|
1154
1276
|
const child = child_process.spawn(
|
|
1155
1277
|
"git",
|
|
1156
|
-
[
|
|
1278
|
+
[
|
|
1279
|
+
"-c",
|
|
1280
|
+
"credential.helper=",
|
|
1281
|
+
"-c",
|
|
1282
|
+
"core.longpaths=true",
|
|
1283
|
+
"fetch",
|
|
1284
|
+
"--prune",
|
|
1285
|
+
authUrl,
|
|
1286
|
+
`+refs/heads/*:refs/remotes/${remoteObj.name}/*`
|
|
1287
|
+
],
|
|
1157
1288
|
{
|
|
1158
1289
|
cwd: repoPath,
|
|
1159
1290
|
env: {
|
|
@@ -1201,7 +1332,14 @@ async function getBranches(sessionId) {
|
|
|
1201
1332
|
const remotes = await git.getRemotes(true);
|
|
1202
1333
|
if (remotes.length > 0) {
|
|
1203
1334
|
const remoteObj = remotes[0];
|
|
1204
|
-
const tempGit = simpleGit.simpleGit({
|
|
1335
|
+
const tempGit = simpleGit.simpleGit({
|
|
1336
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1337
|
+
unsafe: {
|
|
1338
|
+
allowUnsafeEditor: true,
|
|
1339
|
+
allowUnsafeAskPass: true,
|
|
1340
|
+
allowUnsafeCredentialHelper: true
|
|
1341
|
+
}
|
|
1342
|
+
}).env({
|
|
1205
1343
|
...process.env,
|
|
1206
1344
|
GCM_INTERACTIVE: "false",
|
|
1207
1345
|
GIT_TERMINAL_PROMPT: "0",
|
|
@@ -1286,7 +1424,14 @@ async function createBranch(sessionId, branch, baseBranch) {
|
|
|
1286
1424
|
const remoteName = remotes[0].name;
|
|
1287
1425
|
await git.addConfig(`branch.${branch}.remote`, remoteName);
|
|
1288
1426
|
await git.addConfig(`branch.${branch}.merge`, `refs/heads/${branch}`);
|
|
1289
|
-
const tempGit = simpleGit.simpleGit({
|
|
1427
|
+
const tempGit = simpleGit.simpleGit({
|
|
1428
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1429
|
+
unsafe: {
|
|
1430
|
+
allowUnsafeEditor: true,
|
|
1431
|
+
allowUnsafeAskPass: true,
|
|
1432
|
+
allowUnsafeCredentialHelper: true
|
|
1433
|
+
}
|
|
1434
|
+
}).env({
|
|
1290
1435
|
...process.env,
|
|
1291
1436
|
GCM_INTERACTIVE: "false",
|
|
1292
1437
|
GIT_TERMINAL_PROMPT: "0",
|
|
@@ -1312,7 +1457,10 @@ async function addRemote(sessionId, name, url) {
|
|
|
1312
1457
|
async function pushAllBranches(sessionId, remote) {
|
|
1313
1458
|
const remotes = await getRemotes(sessionId);
|
|
1314
1459
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1315
|
-
const tempGit = simpleGit.simpleGit({
|
|
1460
|
+
const tempGit = simpleGit.simpleGit({
|
|
1461
|
+
baseDir: getGitSession(sessionId).repoPath,
|
|
1462
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1463
|
+
}).env({
|
|
1316
1464
|
...process.env,
|
|
1317
1465
|
GCM_INTERACTIVE: "false",
|
|
1318
1466
|
GIT_TERMINAL_PROMPT: "0",
|
|
@@ -1335,7 +1483,14 @@ async function getCommitHistory(sessionId) {
|
|
|
1335
1483
|
if (remotes.length > 0) {
|
|
1336
1484
|
const remoteObj = remotes[0];
|
|
1337
1485
|
const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : remoteObj.name;
|
|
1338
|
-
await git.raw([
|
|
1486
|
+
await git.raw([
|
|
1487
|
+
"-c",
|
|
1488
|
+
"credential.helper=",
|
|
1489
|
+
"fetch",
|
|
1490
|
+
"--prune",
|
|
1491
|
+
authUrl,
|
|
1492
|
+
`+refs/heads/*:refs/remotes/${remoteObj.name}/*`
|
|
1493
|
+
]);
|
|
1339
1494
|
}
|
|
1340
1495
|
} catch (e) {
|
|
1341
1496
|
}
|
|
@@ -1433,8 +1588,12 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1433
1588
|
const tempId = crypto.randomUUID();
|
|
1434
1589
|
const workspacePath = path__namespace.join(electron.app.getPath("temp"), `gitelectron_universal_${tempId}`);
|
|
1435
1590
|
await fs__namespace.mkdir(workspacePath, { recursive: true });
|
|
1436
|
-
const git = simpleGit.simpleGit({
|
|
1591
|
+
const git = simpleGit.simpleGit({
|
|
1592
|
+
baseDir: workspacePath,
|
|
1593
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1594
|
+
});
|
|
1437
1595
|
await git.init();
|
|
1596
|
+
await setupDefaultGitAttributes(workspacePath);
|
|
1438
1597
|
let authUrl = remoteUrl;
|
|
1439
1598
|
if (remoteUrl.includes("github.com")) {
|
|
1440
1599
|
let cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
|
|
@@ -1493,7 +1652,10 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
|
|
|
1493
1652
|
};
|
|
1494
1653
|
}
|
|
1495
1654
|
async function getUniversalDiff(workspacePath, filePath) {
|
|
1496
|
-
const git = simpleGit.simpleGit({
|
|
1655
|
+
const git = simpleGit.simpleGit({
|
|
1656
|
+
baseDir: workspacePath,
|
|
1657
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1658
|
+
});
|
|
1497
1659
|
try {
|
|
1498
1660
|
const diff = await git.raw(["diff", "--", filePath]);
|
|
1499
1661
|
return diff;
|
|
@@ -1502,14 +1664,25 @@ async function getUniversalDiff(workspacePath, filePath) {
|
|
|
1502
1664
|
}
|
|
1503
1665
|
}
|
|
1504
1666
|
async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
1505
|
-
const git = simpleGit.simpleGit({
|
|
1667
|
+
const git = simpleGit.simpleGit({
|
|
1668
|
+
baseDir: workspacePath,
|
|
1669
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1670
|
+
}).env({
|
|
1506
1671
|
...process.env,
|
|
1507
1672
|
GCM_INTERACTIVE: "false",
|
|
1508
1673
|
GIT_TERMINAL_PROMPT: "0",
|
|
1509
1674
|
GIT_ASKPASS: "echo"
|
|
1510
1675
|
});
|
|
1511
1676
|
try {
|
|
1512
|
-
await git.raw([
|
|
1677
|
+
await git.raw([
|
|
1678
|
+
"-c",
|
|
1679
|
+
"core.safecrlf=false",
|
|
1680
|
+
"-c",
|
|
1681
|
+
"core.autocrlf=false",
|
|
1682
|
+
"add",
|
|
1683
|
+
"--sparse",
|
|
1684
|
+
"."
|
|
1685
|
+
]);
|
|
1513
1686
|
} catch (e) {
|
|
1514
1687
|
if (e.message && e.message.includes("LF will be replaced by CRLF") && !e.message.includes("fatal:")) {
|
|
1515
1688
|
console.warn("Ignored CRLF warning in universal workspace");
|
|
@@ -1586,7 +1759,16 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1586
1759
|
await new Promise((resolve) => {
|
|
1587
1760
|
const child = child_process.spawn(
|
|
1588
1761
|
"git",
|
|
1589
|
-
[
|
|
1762
|
+
[
|
|
1763
|
+
"-c",
|
|
1764
|
+
"core.longpaths=true",
|
|
1765
|
+
"-c",
|
|
1766
|
+
"filter.lfs.required=false",
|
|
1767
|
+
"-c",
|
|
1768
|
+
"filter.lfs.smudge=",
|
|
1769
|
+
"checkout",
|
|
1770
|
+
"-f"
|
|
1771
|
+
],
|
|
1590
1772
|
{
|
|
1591
1773
|
cwd: destPath,
|
|
1592
1774
|
env: { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" }
|
|
@@ -1806,18 +1988,180 @@ async function removeGitLock(sessionId) {
|
|
|
1806
1988
|
}
|
|
1807
1989
|
}
|
|
1808
1990
|
async function masterPush(sessionId, remote, branch) {
|
|
1809
|
-
const { git } = getGitSession(sessionId);
|
|
1991
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1810
1992
|
const remotes = await getRemotes(sessionId);
|
|
1811
1993
|
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1812
1994
|
if (!remoteObj) throw new Error("No remote repository configured.");
|
|
1813
1995
|
const targetRemote = remoteObj.name;
|
|
1814
1996
|
const authUrl = remoteObj.refs?.push ? injectAuthToken(sessionId, remoteObj.refs.push) : targetRemote;
|
|
1815
|
-
|
|
1997
|
+
let originalBranch = branch;
|
|
1816
1998
|
try {
|
|
1817
|
-
await git.
|
|
1999
|
+
const branchSummary = await git.branchLocal();
|
|
2000
|
+
originalBranch = branchSummary.current || branch;
|
|
1818
2001
|
} catch (e) {
|
|
1819
2002
|
}
|
|
1820
|
-
|
|
2003
|
+
const tempBranch = `__master_push_temp_${Date.now()}`;
|
|
2004
|
+
const hiddenPointers = [];
|
|
2005
|
+
const cleanedAttrsFiles = [];
|
|
2006
|
+
const lfsOffFlags = [
|
|
2007
|
+
"-c",
|
|
2008
|
+
"filter.lfs.clean=cat",
|
|
2009
|
+
"-c",
|
|
2010
|
+
"filter.lfs.smudge=cat",
|
|
2011
|
+
"-c",
|
|
2012
|
+
"filter.lfs.process=",
|
|
2013
|
+
"-c",
|
|
2014
|
+
"filter.lfs.required=false",
|
|
2015
|
+
"-c",
|
|
2016
|
+
"lfs.allowincompletepush=true",
|
|
2017
|
+
"-c",
|
|
2018
|
+
"http.postBuffer=524288000",
|
|
2019
|
+
"-c",
|
|
2020
|
+
"credential.helper="
|
|
2021
|
+
];
|
|
2022
|
+
function runGit(args, allowFail = false) {
|
|
2023
|
+
return new Promise((resolve, reject) => {
|
|
2024
|
+
const child = child_process.spawn("git", [...lfsOffFlags, ...args], {
|
|
2025
|
+
cwd: repoPath,
|
|
2026
|
+
env: {
|
|
2027
|
+
...process.env,
|
|
2028
|
+
GIT_LFS_SKIP_SMUDGE: "1",
|
|
2029
|
+
GIT_LFS_SKIP_PUSH: "1",
|
|
2030
|
+
GCM_INTERACTIVE: "false",
|
|
2031
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
2032
|
+
GIT_ASKPASS: "echo"
|
|
2033
|
+
}
|
|
2034
|
+
});
|
|
2035
|
+
const stdout = [];
|
|
2036
|
+
const stderr = [];
|
|
2037
|
+
child.stdout?.on("data", (d) => stdout.push(d.toString()));
|
|
2038
|
+
child.stderr?.on("data", (d) => stderr.push(d.toString()));
|
|
2039
|
+
child.on("error", (err) => allowFail ? resolve("") : reject(err));
|
|
2040
|
+
child.on("close", (code) => {
|
|
2041
|
+
if (code === 0 || allowFail) {
|
|
2042
|
+
resolve(stdout.join(""));
|
|
2043
|
+
} else {
|
|
2044
|
+
reject(new Error(stderr.join("") || `git exited with code ${code}`));
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
async function processRepo(dir) {
|
|
2050
|
+
let entries;
|
|
2051
|
+
try {
|
|
2052
|
+
entries = await fs__namespace.readdir(dir, { withFileTypes: true });
|
|
2053
|
+
} catch (e) {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
for (const entry of entries) {
|
|
2057
|
+
if (entry.name === ".git" || entry.name === "node_modules" || entry.name.endsWith(".__lfs_ptr_bak__")) continue;
|
|
2058
|
+
const fullPath = path__namespace.join(dir, entry.name);
|
|
2059
|
+
if (entry.isDirectory()) {
|
|
2060
|
+
await processRepo(fullPath);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
if (!entry.isFile()) continue;
|
|
2064
|
+
try {
|
|
2065
|
+
if (entry.name === ".gitattributes") {
|
|
2066
|
+
const content = await fs__namespace.readFile(fullPath, "utf8");
|
|
2067
|
+
if (content.includes("filter=lfs") || content.includes("lfs")) {
|
|
2068
|
+
cleanedAttrsFiles.push({ filePath: fullPath, original: content });
|
|
2069
|
+
const cleaned = content.split("\n").filter((line) => !line.toLowerCase().includes("lfs")).join("\n");
|
|
2070
|
+
await fs__namespace.writeFile(fullPath, cleaned);
|
|
2071
|
+
}
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
const stat = await fs__namespace.stat(fullPath);
|
|
2075
|
+
if (stat.size < 4096 && stat.size > 20) {
|
|
2076
|
+
const buf = await fs__namespace.readFile(fullPath);
|
|
2077
|
+
const text = buf.toString("utf8");
|
|
2078
|
+
if (text.includes("version https://git-lfs.github.com/spec/v1") && text.includes("oid sha256:")) {
|
|
2079
|
+
const backupPath = fullPath + ".__lfs_ptr_bak__";
|
|
2080
|
+
await fs__namespace.rename(fullPath, backupPath);
|
|
2081
|
+
hiddenPointers.push({ original: fullPath, backup: backupPath });
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
} catch (e) {
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
try {
|
|
2089
|
+
try {
|
|
2090
|
+
await git.raw(["lfs", "uninstall", "--local"]);
|
|
2091
|
+
} catch (e) {
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
await git.addConfig("filter.lfs.clean", "cat");
|
|
2095
|
+
await git.addConfig("filter.lfs.smudge", "cat");
|
|
2096
|
+
await git.addConfig("filter.lfs.process", "");
|
|
2097
|
+
await git.addConfig("filter.lfs.required", "false");
|
|
2098
|
+
} catch (e) {
|
|
2099
|
+
}
|
|
2100
|
+
await processRepo(repoPath);
|
|
2101
|
+
await runGit(["checkout", "--orphan", tempBranch]);
|
|
2102
|
+
await runGit(["add", "."]);
|
|
2103
|
+
try {
|
|
2104
|
+
const grepResult = await runGit(
|
|
2105
|
+
["grep", "--cached", "-l", "version https://git-lfs.github.com/spec/v1"],
|
|
2106
|
+
true
|
|
2107
|
+
);
|
|
2108
|
+
if (grepResult.trim()) {
|
|
2109
|
+
const lfsFiles = grepResult.trim().split("\n").filter(Boolean);
|
|
2110
|
+
for (const f of lfsFiles) {
|
|
2111
|
+
await runGit(["rm", "--cached", "--force", f.trim()], true);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
} catch (e) {
|
|
2115
|
+
}
|
|
2116
|
+
try {
|
|
2117
|
+
await runGit(["commit", "-m", "Master Override Push", "--no-verify"]);
|
|
2118
|
+
} catch (e) {
|
|
2119
|
+
}
|
|
2120
|
+
await runGit(["push", "--force", "--no-verify", authUrl, `${tempBranch}:${branch}`]);
|
|
2121
|
+
} finally {
|
|
2122
|
+
try {
|
|
2123
|
+
await runGit(["checkout", "-f", originalBranch], true);
|
|
2124
|
+
} catch (e) {
|
|
2125
|
+
try {
|
|
2126
|
+
await runGit(["checkout", "-f", branch], true);
|
|
2127
|
+
} catch (e2) {
|
|
2128
|
+
try {
|
|
2129
|
+
await runGit(["checkout", "-b", branch], true);
|
|
2130
|
+
} catch (e3) {
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
try {
|
|
2135
|
+
const branchList = await runGit(["branch", "--list", "__master_push_temp_*"], true);
|
|
2136
|
+
if (branchList.trim()) {
|
|
2137
|
+
const branches = branchList.trim().split("\n").map((b) => b.trim().replace(/^\*\s*/, "")).filter(Boolean);
|
|
2138
|
+
for (const b of branches) {
|
|
2139
|
+
await runGit(["branch", "-D", b], true);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
} catch (e) {
|
|
2143
|
+
}
|
|
2144
|
+
try {
|
|
2145
|
+
await runGit(["branch", "-D", tempBranch], true);
|
|
2146
|
+
} catch (e) {
|
|
2147
|
+
}
|
|
2148
|
+
for (const { original, backup } of hiddenPointers) {
|
|
2149
|
+
try {
|
|
2150
|
+
await fs__namespace.rename(backup, original);
|
|
2151
|
+
} catch (e) {
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
for (const { filePath, original } of cleanedAttrsFiles) {
|
|
2155
|
+
try {
|
|
2156
|
+
await fs__namespace.writeFile(filePath, original);
|
|
2157
|
+
} catch (e) {
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
try {
|
|
2161
|
+
await git.raw(["lfs", "install", "--local"]);
|
|
2162
|
+
} catch (e) {
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
1821
2165
|
}
|
|
1822
2166
|
const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
|
|
1823
2167
|
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.
|
|
@@ -1920,8 +2264,54 @@ ${errorMsg}
|
|
|
1920
2264
|
lastError = e;
|
|
1921
2265
|
}
|
|
1922
2266
|
}
|
|
2267
|
+
console.warn("[AI Service] Falling back to local heuristic analysis...");
|
|
2268
|
+
const lowerError = errorMsg.toLowerCase();
|
|
2269
|
+
if (lowerError.includes("fetch first") || lowerError.includes("non-fast-forward") || lowerError.includes("updates were rejected") || lowerError.includes("remote contains work")) {
|
|
2270
|
+
return {
|
|
2271
|
+
explanation: "Diverged branch detected. Pulling remote changes to reconcile...",
|
|
2272
|
+
commandsToRun: ["git pull --no-edit"]
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
if (lowerError.includes("merge conflict") || lowerError.includes("conflict") || lowerError.includes("automatic merge failed")) {
|
|
2276
|
+
return {
|
|
2277
|
+
explanation: "Merge conflict detected. Forcing resolution by keeping local changes...",
|
|
2278
|
+
commandsToRun: [
|
|
2279
|
+
"git merge --abort",
|
|
2280
|
+
"git pull -X ours --no-edit",
|
|
2281
|
+
"git add .",
|
|
2282
|
+
'git commit -m "Auto-resolve merge conflict"'
|
|
2283
|
+
]
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
if (lowerError.includes("unmerged files") || lowerError.includes("you have unmerged paths")) {
|
|
2287
|
+
return {
|
|
2288
|
+
explanation: "Unmerged paths detected. Committing current state to proceed...",
|
|
2289
|
+
commandsToRun: ["git add .", 'git commit -m "Auto-resolve unmerged paths"']
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
if (lowerError.includes("please tell me who you are")) {
|
|
2293
|
+
return {
|
|
2294
|
+
explanation: "Git identity not set. Setting temporary identity...",
|
|
2295
|
+
commandsToRun: [
|
|
2296
|
+
'git config user.email "gitelectron@example.com"',
|
|
2297
|
+
'git config user.name "Git Electron"'
|
|
2298
|
+
]
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
if (lowerError.includes("would be overwritten by merge") || lowerError.includes("local changes to the following files would be overwritten")) {
|
|
2302
|
+
return {
|
|
2303
|
+
explanation: "Local changes would be overwritten. Stashing, pulling, and popping...",
|
|
2304
|
+
commandsToRun: [
|
|
2305
|
+
"git stash",
|
|
2306
|
+
"git pull --no-edit",
|
|
2307
|
+
"git stash pop",
|
|
2308
|
+
"git add .",
|
|
2309
|
+
'git commit -m "Auto-resolved stashed changes"'
|
|
2310
|
+
]
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
1923
2313
|
throw new Error(
|
|
1924
|
-
`AI Analysis failed after trying all provided keys. Last error: ${lastError?.message}`
|
|
2314
|
+
`AI Analysis failed after trying all provided keys and no local heuristics matched. Last error: ${lastError?.message || "Unknown"}`
|
|
1925
2315
|
);
|
|
1926
2316
|
}
|
|
1927
2317
|
const AUTO_SYNC_SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitQuickPush" app.
|
|
@@ -1996,7 +2386,10 @@ ${content}
|
|
|
1996
2386
|
if (provider === "gemini") {
|
|
1997
2387
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
1998
2388
|
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
1999
|
-
const result = await model.generateContent([
|
|
2389
|
+
const result = await model.generateContent([
|
|
2390
|
+
{ text: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2391
|
+
{ text: prompt }
|
|
2392
|
+
]);
|
|
2000
2393
|
responseText = result.response.text();
|
|
2001
2394
|
}
|
|
2002
2395
|
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
@@ -2126,7 +2519,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2126
2519
|
try {
|
|
2127
2520
|
const updates = await checkRemoteUpdates(sessionId);
|
|
2128
2521
|
if (updates.behind > 0 && !isResolving) {
|
|
2129
|
-
onLog(
|
|
2522
|
+
onLog(
|
|
2523
|
+
`📥 Found ${updates.behind} new commit(s) from team members on GitHub! Syncing to your computer...`
|
|
2524
|
+
);
|
|
2130
2525
|
const result = await pullFromRemote(sessionId);
|
|
2131
2526
|
onLog(`✅ ${result.message}`);
|
|
2132
2527
|
if (globalOnResolveComplete) {
|
|
@@ -2329,7 +2724,15 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
|
2329
2724
|
for (const [file, content] of Object.entries(resolvedCode)) {
|
|
2330
2725
|
await fs__namespace.writeFile(path__namespace.join(repoPath, file), content, "utf-8");
|
|
2331
2726
|
try {
|
|
2332
|
-
await git.raw([
|
|
2727
|
+
await git.raw([
|
|
2728
|
+
"-c",
|
|
2729
|
+
"core.safecrlf=false",
|
|
2730
|
+
"-c",
|
|
2731
|
+
"core.autocrlf=false",
|
|
2732
|
+
"add",
|
|
2733
|
+
"--force",
|
|
2734
|
+
file
|
|
2735
|
+
]);
|
|
2333
2736
|
} catch (e) {
|
|
2334
2737
|
if (e.message && e.message.includes("LF will be replaced by CRLF") && !e.message.includes("fatal:")) {
|
|
2335
2738
|
console.warn("Ignored CRLF warning during AI fix apply");
|