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/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`, { headers }),
334
- fetch(`https://api.github.com/repos/${repo.owner.login}/${repo.name}/invitations`, { headers })
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
- if (!remoteUrl.includes("github.com")) throw new Error("Not a GitHub repository");
386
- let parts = remoteUrl.split("github.com");
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
- fetch(`https://api.github.com/repos/${owner}/${repo}/collaborators`, { headers }),
405
- fetch(`https://api.github.com/repos/${owner}/${repo}/invitations`, { headers }),
406
- fetch(`https://api.github.com/repos/${owner}/${repo}/contributors`, { headers })
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({ baseDir: dir, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } });
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
- await git.raw(["lfs", "track", file]);
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(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "--force", ...chunk]);
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(/https:\/\/(?:[^@]+@)?github\.com\//, `https://x-access-token:${token}@github.com/`);
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) onProgress(`Processing chunk ${chunkCount} (${chunk.length} files)... ETA: ${etaStr}`);
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(chunk.map(async (file) => {
913
- try {
914
- const stats = await fs__namespace.stat(path__namespace.join(repoPath, file));
915
- if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
916
- largeFiles.push(file);
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
- } catch (e) {
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
- await git.raw(["lfs", "track", file]);
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(["-c", "core.safecrlf=false", "add", "--force", "--pathspec-from-file=" + tempFile, "--pathspec-file-nul"]);
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 = ["-c", "credential.helper=", "-c", "pack.windowMemory=256m", "-c", "pack.packSizeLimit=500m", "-c", "http.postBuffer=524288000", "-c", "lfs.concurrenttransfers=8", "push", "--progress", "--no-verify"];
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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 = ["-c", "credential.helper=", "-c", "pack.windowMemory=256m", "-c", "pack.packSizeLimit=500m", "-c", "http.postBuffer=524288000", "-c", "lfs.concurrenttransfers=8", "push", "--progress", "--no-verify"];
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
- ["-c", "credential.helper=", "-c", "core.longpaths=true", "fetch", "--prune", authUrl, `+refs/heads/*:refs/remotes/${remoteObj.name}/*`],
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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(["-c", "credential.helper=", "fetch", "--prune", authUrl, `+refs/heads/*:refs/remotes/${remoteObj.name}/*`]);
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({ baseDir: workspacePath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } });
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({ baseDir: workspacePath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } });
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({ baseDir: workspacePath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "--sparse", "."]);
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
- ["-c", "core.longpaths=true", "-c", "filter.lfs.required=false", "-c", "filter.lfs.smudge=", "checkout", "-f"],
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
- await git.add(".");
2006
+ let originalBranch = branch;
1816
2007
  try {
1817
- await git.commit("Master Override Push", ["--no-verify"]);
2008
+ const branchSummary = await git.branchLocal();
2009
+ originalBranch = branchSummary.current || branch;
1818
2010
  } catch (e) {
1819
2011
  }
1820
- return await git.push(authUrl, branch, ["--force"]);
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: ["git merge --abort", "git pull -X ours --no-edit", "git add .", 'git commit -m "Auto-resolve merge conflict"']
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: ['git config user.email "gitelectron@example.com"', 'git config user.name "Git Electron"']
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: ["git stash", "git pull --no-edit", "git stash pop", "git add .", 'git commit -m "Auto-resolved stashed changes"']
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([{ text: AUTO_SYNC_SYSTEM_PROMPT }, { text: prompt }]);
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(`📥 Found ${updates.behind} new commit(s) from team members on GitHub! Syncing to your computer...`);
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(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "--force", file]);
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");