gitxelectron 1.3.5 → 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/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 {
@@ -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({ baseDir: dir, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } });
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
- await git.raw(["lfs", "track", file]);
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(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "--force", ...chunk]);
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(/https:\/\/(?:[^@]+@)?github\.com\//, `https://x-access-token:${token}@github.com/`);
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) onProgress(`Processing chunk ${chunkCount} (${chunk.length} files)... ETA: ${etaStr}`);
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(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);
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
- } catch (e) {
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
- await git.raw(["lfs", "track", file]);
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(["-c", "core.safecrlf=false", "add", "--force", "--pathspec-from-file=" + tempFile, "--pathspec-file-nul"]);
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 = ["-c", "credential.helper=", "-c", "pack.windowMemory=256m", "-c", "pack.packSizeLimit=500m", "-c", "http.postBuffer=524288000", "-c", "lfs.concurrenttransfers=8", "push", "--progress", "--no-verify"];
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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 = ["-c", "credential.helper=", "-c", "pack.windowMemory=256m", "-c", "pack.packSizeLimit=500m", "-c", "http.postBuffer=524288000", "-c", "lfs.concurrenttransfers=8", "push", "--progress", "--no-verify"];
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
- ["-c", "credential.helper=", "-c", "core.longpaths=true", "fetch", "--prune", authUrl, `+refs/heads/*:refs/remotes/${remoteObj.name}/*`],
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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({ baseDir: getGitSession(sessionId).repoPath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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(["-c", "credential.helper=", "fetch", "--prune", authUrl, `+refs/heads/*:refs/remotes/${remoteObj.name}/*`]);
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({ baseDir: workspacePath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } });
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({ baseDir: workspacePath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } });
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({ baseDir: workspacePath, unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true } }).env({
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(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "--sparse", "."]);
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
- ["-c", "core.longpaths=true", "-c", "filter.lfs.required=false", "-c", "filter.lfs.smudge=", "checkout", "-f"],
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
- await git.add(".");
1997
+ let originalBranch = branch;
1816
1998
  try {
1817
- await git.commit("Master Override Push", ["--no-verify"]);
1999
+ const branchSummary = await git.branchLocal();
2000
+ originalBranch = branchSummary.current || branch;
1818
2001
  } catch (e) {
1819
2002
  }
1820
- return await git.push(authUrl, branch, ["--force"]);
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.
@@ -1931,7 +2275,12 @@ ${errorMsg}
1931
2275
  if (lowerError.includes("merge conflict") || lowerError.includes("conflict") || lowerError.includes("automatic merge failed")) {
1932
2276
  return {
1933
2277
  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"']
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
+ ]
1935
2284
  };
1936
2285
  }
1937
2286
  if (lowerError.includes("unmerged files") || lowerError.includes("you have unmerged paths")) {
@@ -1943,13 +2292,22 @@ ${errorMsg}
1943
2292
  if (lowerError.includes("please tell me who you are")) {
1944
2293
  return {
1945
2294
  explanation: "Git identity not set. Setting temporary identity...",
1946
- commandsToRun: ['git config user.email "gitelectron@example.com"', 'git config user.name "Git Electron"']
2295
+ commandsToRun: [
2296
+ 'git config user.email "gitelectron@example.com"',
2297
+ 'git config user.name "Git Electron"'
2298
+ ]
1947
2299
  };
1948
2300
  }
1949
2301
  if (lowerError.includes("would be overwritten by merge") || lowerError.includes("local changes to the following files would be overwritten")) {
1950
2302
  return {
1951
2303
  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"']
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
+ ]
1953
2311
  };
1954
2312
  }
1955
2313
  throw new Error(
@@ -2028,7 +2386,10 @@ ${content}
2028
2386
  if (provider === "gemini") {
2029
2387
  const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
2030
2388
  const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
2031
- const result = await model.generateContent([{ text: AUTO_SYNC_SYSTEM_PROMPT }, { text: prompt }]);
2389
+ const result = await model.generateContent([
2390
+ { text: AUTO_SYNC_SYSTEM_PROMPT },
2391
+ { text: prompt }
2392
+ ]);
2032
2393
  responseText = result.response.text();
2033
2394
  }
2034
2395
  responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
@@ -2158,7 +2519,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
2158
2519
  try {
2159
2520
  const updates = await checkRemoteUpdates(sessionId);
2160
2521
  if (updates.behind > 0 && !isResolving) {
2161
- onLog(`📥 Found ${updates.behind} new commit(s) from team members on GitHub! Syncing to your computer...`);
2522
+ onLog(
2523
+ `📥 Found ${updates.behind} new commit(s) from team members on GitHub! Syncing to your computer...`
2524
+ );
2162
2525
  const result = await pullFromRemote(sessionId);
2163
2526
  onLog(`✅ ${result.message}`);
2164
2527
  if (globalOnResolveComplete) {
@@ -2361,7 +2724,15 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
2361
2724
  for (const [file, content] of Object.entries(resolvedCode)) {
2362
2725
  await fs__namespace.writeFile(path__namespace.join(repoPath, file), content, "utf-8");
2363
2726
  try {
2364
- await git.raw(["-c", "core.safecrlf=false", "-c", "core.autocrlf=false", "add", "--force", file]);
2727
+ await git.raw([
2728
+ "-c",
2729
+ "core.safecrlf=false",
2730
+ "-c",
2731
+ "core.autocrlf=false",
2732
+ "add",
2733
+ "--force",
2734
+ file
2735
+ ]);
2365
2736
  } catch (e) {
2366
2737
  if (e.message && e.message.includes("LF will be replaced by CRLF") && !e.message.includes("fatal:")) {
2367
2738
  console.warn("Ignored CRLF warning during AI fix apply");